Auth.net ACH
This commit is contained in:
parent
d30441c36f
commit
5e0e4487eb
|
|
@ -124,8 +124,8 @@ class Gateway extends StaticModel
|
|||
switch ($this->id) {
|
||||
case 1:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true,'webhooks' => ['net.authorize.payment.void.created']],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true,'webhooks' => ['net.authorize.payment.void.created']],
|
||||
]; //Authorize.net
|
||||
case 3:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]]; //eWay
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ class AuthorizeACH implements LivewireMethodInterface
|
|||
return render('gateways.authorize.ach.pay', $data);
|
||||
}
|
||||
|
||||
public function tokenBilling($cgt, $payment_hash)
|
||||
{
|
||||
$cc = new AuthorizeCreditCard($this->authorize);
|
||||
return $cc->tokenBilling($cgt, $payment_hash);
|
||||
}
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ class AuthorizeCreditCard implements LivewireMethodInterface
|
|||
|
||||
// if ($response != null && $response->getMessages()->getResultCode() == 'Ok') {
|
||||
if ($response != null && $response->getMessages() != null) {
|
||||
$this->storePayment($payment_hash, $data);
|
||||
$this->storePayment($payment_hash, $data, $gateway_type = $cgt->gateway_type_id);
|
||||
|
||||
$vars = [
|
||||
'invoices' => $payment_hash->invoices(),
|
||||
|
|
@ -213,7 +213,7 @@ class AuthorizeCreditCard implements LivewireMethodInterface
|
|||
return $this->processFailedResponse($data, $request);
|
||||
}
|
||||
|
||||
private function storePayment($payment_hash, $data)
|
||||
private function storePayment($payment_hash, $data, $gateway_type)
|
||||
{
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
|
||||
|
|
@ -221,8 +221,8 @@ class AuthorizeCreditCard implements LivewireMethodInterface
|
|||
|
||||
$payment_record = [];
|
||||
$payment_record['amount'] = $amount;
|
||||
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||
$payment_record['payment_type'] = $gateway_type == GatewayType::CREDIT_CARD ? PaymentType::CREDIT_CARD_OTHER : PaymentType::ACH;
|
||||
$payment_record['gateway_type_id'] = $gateway_type;
|
||||
$payment_record['transaction_reference'] = $response->getTransId();
|
||||
|
||||
$payment = $this->authorize->createPayment($payment_record);
|
||||
|
|
|
|||
|
|
@ -149,16 +149,16 @@ class AuthorizePaymentMethod
|
|||
|
||||
$data['token'] = $payment_profile->getPaymentProfile()->getCustomerPaymentProfileId();
|
||||
$data['payment_method_id'] = $this->payment_method_id;
|
||||
$data['payment_meta'] = $this->buildPaymentMethod($payment_profile, true);
|
||||
$data['payment_meta'] = $this->buildPaymentMethod($payment_profile);
|
||||
|
||||
$additional['gateway_customer_reference'] = $gateway_customer_reference;
|
||||
|
||||
return $this->authorize->storeGatewayToken($data, $additional);
|
||||
}
|
||||
|
||||
public function buildPaymentMethod($payment_profile, $is_ach = false)
|
||||
public function buildPaymentMethod($payment_profile)
|
||||
{
|
||||
if ($is_ach) {
|
||||
if ($this->payment_method_id == GatewayType::BANK_TRANSFER) {
|
||||
$brand = sprintf($payment_profile->getPaymentProfile()->getPayment()->getBankAccount()->getBankName());
|
||||
$last4 = (string) $payment_profile->getPaymentProfile()->getPayment()->getBankAccount()->getAccountNumber();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -48,12 +48,10 @@ class RefundTransaction
|
|||
|
||||
$transaction_details = $this->authorize_transaction->getTransactionDetails($payment->transaction_reference);
|
||||
|
||||
$creditCard = $transaction_details->getTransaction()->getPayment()->getCreditCard();
|
||||
$creditCardNumber = $creditCard->getCardNumber();
|
||||
$creditCardExpiry = $creditCard->getExpirationDate();
|
||||
$transaction_status = $transaction_details->getTransaction()->getTransactionStatus();
|
||||
$transaction = $transaction_details->getTransaction();
|
||||
$payment_details = $transaction->getPayment();
|
||||
|
||||
$transaction_type = $transaction_status == 'capturedPendingSettlement' ? 'voidTransaction' : 'refundTransaction';
|
||||
$transaction_status = $transaction->getTransactionStatus();
|
||||
|
||||
$transaction_type = match($transaction_status){
|
||||
'capturedPendingSettlement' => 'voidTransaction',
|
||||
|
|
@ -63,10 +61,10 @@ class RefundTransaction
|
|||
};
|
||||
|
||||
if ($transaction_type == 'voidTransaction') {
|
||||
$amount = $transaction_details->getTransaction()->getAuthAmount();
|
||||
$amount = $transaction->getAuthAmount();
|
||||
}
|
||||
elseif ($transaction_type == 'voidHeldTransaction') {
|
||||
$amount = $transaction_details->getTransaction()->getAuthAmount();
|
||||
$amount = $transaction->getAuthAmount();
|
||||
return $this->declineHeldTransaction($payment, $amount);
|
||||
}
|
||||
|
||||
|
|
@ -75,20 +73,33 @@ class RefundTransaction
|
|||
// Set the transaction's refId
|
||||
$refId = 'ref'.time();
|
||||
|
||||
$creditCard = new CreditCardType();
|
||||
$creditCard->setCardNumber($creditCardNumber);
|
||||
$creditCard->setExpirationDate($creditCardExpiry);
|
||||
$paymentOne = new PaymentType();
|
||||
$paymentOne->setCreditCard($creditCard);
|
||||
|
||||
//create a transaction
|
||||
$transactionRequest = new TransactionRequestType();
|
||||
$transactionRequest->setTransactionType($transaction_type);
|
||||
$transactionRequest->setAmount($amount);
|
||||
// $transactionRequest->setProfile($customerProfile);
|
||||
$transactionRequest->setPayment($paymentOne);
|
||||
$transactionRequest->setRefTransId($payment->transaction_reference);
|
||||
|
||||
// Set payment info based on type
|
||||
if ($payment_details->getCreditCard()) {
|
||||
$creditCard = new CreditCardType();
|
||||
$creditCard->setCardNumber($payment_details->getCreditCard()->getCardNumber());
|
||||
$creditCard->setExpirationDate($payment_details->getCreditCard()->getExpirationDate());
|
||||
$paymentOne = new PaymentType();
|
||||
$paymentOne->setCreditCard($creditCard);
|
||||
$transactionRequest->setPayment($paymentOne);
|
||||
} elseif ($payment_details->getBankAccount()) {
|
||||
$bankAccount = new \net\authorize\api\contract\v1\BankAccountType();
|
||||
$bankAccount->setRoutingNumber($payment_details->getBankAccount()->getRoutingNumber());
|
||||
$bankAccount->setAccountNumber($payment_details->getBankAccount()->getAccountNumber());
|
||||
$bankAccount->setAccountType($payment_details->getBankAccount()->getAccountType());
|
||||
$bankAccount->setNameOnAccount($payment_details->getBankAccount()->getNameOnAccount());
|
||||
$bankAccount->setBankName($payment_details->getBankAccount()->getBankName());
|
||||
$bankAccount->setEcheckType('WEB');
|
||||
$paymentOne = new PaymentType();
|
||||
$paymentOne->setBankAccount($bankAccount);
|
||||
$transactionRequest->setPayment($paymentOne);
|
||||
}
|
||||
|
||||
$solution = new \net\authorize\api\contract\v1\SolutionType();
|
||||
$solution->setId($this->authorize->company_gateway->getConfigField('testMode') ? 'AAA100303' : 'AAA172036');
|
||||
$transactionRequest->setSolution($solution);
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ use App\Models\SystemLog;
|
|||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Jobs\Mail\PaymentFailedMailer;
|
||||
use App\PaymentDrivers\Authorize\AuthorizeACH;
|
||||
use net\authorize\api\constants\ANetEnvironment;
|
||||
use App\PaymentDrivers\Authorize\AuthorizeCustomer;
|
||||
use App\PaymentDrivers\Authorize\RefundTransaction;
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\PaymentDrivers\Authorize\AuthorizeCreditCard;
|
||||
use App\PaymentDrivers\Authorize\AuthorizePaymentMethod;
|
||||
use net\authorize\api\contract\v1\GetMerchantDetailsRequest;
|
||||
|
|
@ -140,6 +142,7 @@ class AuthorizePaymentDriver extends BaseDriver
|
|||
{
|
||||
$this->init();
|
||||
|
||||
//Universal token billing.
|
||||
$this->setPaymentMethod($cgt->gateway_type_id);
|
||||
|
||||
return $this->payment_method->tokenBilling($cgt, $payment_hash);
|
||||
|
|
@ -217,4 +220,116 @@ class AuthorizePaymentDriver extends BaseDriver
|
|||
{
|
||||
return $this->init()->getPublicClientKey() ? 'ok' : 'error';
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request)
|
||||
{
|
||||
|
||||
$payload = file_get_contents('php://input');
|
||||
$headers = getallheaders();
|
||||
|
||||
$signatureKey = $this->company_gateway->getConfigField('signatureKey');
|
||||
|
||||
function isValidSignature($payload, $headers, $signatureKey)
|
||||
{
|
||||
// Normalize headers to uppercase for consistent lookup
|
||||
$normalizedHeaders = array_change_key_case($headers, CASE_UPPER);
|
||||
|
||||
if (!isset($normalizedHeaders['X-ANET-SIGNATURE'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$receivedSignature = $normalizedHeaders['X-ANET-SIGNATURE'];
|
||||
|
||||
// Remove 'sha512=' prefix if it exists
|
||||
$receivedHash = str_replace('sha512=', '', $receivedSignature);
|
||||
|
||||
// Make sure signatureKey is a valid hex string and convert to binary
|
||||
if (!ctype_xdigit($signatureKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate HMAC exactly as Authorize.net does
|
||||
$expectedHash = strtoupper(hash_hmac('sha512', $payload, $signatureKey));
|
||||
|
||||
return hash_equals($receivedHash, $expectedHash);
|
||||
}
|
||||
|
||||
if (!isValidSignature($payload, $headers, $signatureKey)) {
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
$data = json_decode($payload, true);
|
||||
|
||||
// Check event type
|
||||
$eventType = $data['eventType'] ?? null;
|
||||
$transactionId = $data['payload']['id'] ?? 'unknown';
|
||||
|
||||
switch ($eventType) {
|
||||
case 'net.authorize.payment.void.created':
|
||||
$this->voidPayment($data);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Other webhook event types can be handled here
|
||||
nlog("ℹ️ Unhandled event type: $eventType");
|
||||
break;
|
||||
}
|
||||
|
||||
return response()->noContent();
|
||||
|
||||
}
|
||||
|
||||
// array (
|
||||
// 'notificationId' => '2ebb25fa-a814-4c53-8e1c-013423214f00',
|
||||
// 'eventType' => 'net.authorize.payment.void.created',
|
||||
// 'eventDate' => '2025-05-14T04:09:10.2193293Z',
|
||||
// 'webhookId' => '95c72ffd-635d-43a7-97b6-8096078cb11a',
|
||||
// 'payload' =>
|
||||
// array (
|
||||
// 'responseCode' => 1,
|
||||
// 'avsResponse' => 'P',
|
||||
// 'authAmount' => 13.85,
|
||||
// 'merchantReferenceId' => 'ref1747192172',
|
||||
// 'invoiceNumber' => '0082',
|
||||
// 'entityName' => 'transaction',
|
||||
// 'id' => '80040995616',
|
||||
// ),
|
||||
// )
|
||||
private function voidPayment($data)
|
||||
{
|
||||
|
||||
$payment = Payment::withTrashed()
|
||||
->where('company_id', $this->company_gateway->company_id)
|
||||
->where('transaction_reference', $data['payload']['id'])
|
||||
->first();
|
||||
|
||||
if($payment){
|
||||
|
||||
if($payment->status_id != Payment::STATUS_COMPLETED)
|
||||
return;
|
||||
|
||||
$payment->service()->deletePayment();
|
||||
$payment->status_id = Payment::STATUS_FAILED;
|
||||
$payment->save();
|
||||
|
||||
$payment_hash = PaymentHash::query()->where('payment_id', $payment->id)->first();
|
||||
|
||||
if ($payment_hash) {
|
||||
$error = ctrans('texts.client_payment_failure_body', [
|
||||
'invoice' => implode(',', $payment->invoices->pluck('number')->toArray()),
|
||||
'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total, ]);
|
||||
} else {
|
||||
$error = 'Payment for '.$payment->client->present()->name()." for {$payment->amount} failed";
|
||||
}
|
||||
|
||||
|
||||
PaymentFailedMailer::dispatch(
|
||||
$payment_hash,
|
||||
$payment->client->company,
|
||||
$payment->client,
|
||||
$error
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
|
||||
if($g = \App\Models\Gateway::where('key', '3b6621f970ab18887c4f6dca78d3f8bb')->first()) {
|
||||
$g->fields = json_encode(array_merge(json_decode($g->fields, true), ['signatureKey' => '']));
|
||||
$g->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
|
@ -26,7 +26,7 @@ class PaymentLibrariesSeeder extends Seeder
|
|||
Model::unguard();
|
||||
|
||||
$gateways = [
|
||||
['id' => 1, 'name' => 'Authorize.Net', 'provider' => 'Authorize', 'sort_order' => 5, 'key' => '3b6621f970ab18887c4f6dca78d3f8bb', 'fields' => '{"apiLoginId":"","transactionKey":"","testMode":false,"developerMode":false,"liveEndpoint":"https:\/\/api2.authorize.net\/xml\/v1\/request.api","developerEndpoint":"https:\/\/apitest.authorize.net\/xml\/v1\/request.api"}
|
||||
['id' => 1, 'name' => 'Authorize.Net', 'provider' => 'Authorize', 'sort_order' => 5, 'key' => '3b6621f970ab18887c4f6dca78d3f8bb', 'fields' => '{"apiLoginId":"","transactionKey":"", "signatureKey":"","testMode":false,"developerMode":false,"liveEndpoint":"https:\/\/api2.authorize.net\/xml\/v1\/request.api","developerEndpoint":"https:\/\/apitest.authorize.net\/xml\/v1\/request.api"}
|
||||
'],
|
||||
['id' => 2, 'name' => 'CardSave', 'provider' => 'CardSave', 'key' => '46c5c1fed2c43acf4f379bae9c8b9f76', 'fields' => '{"merchantId":"","password":""}
|
||||
'],
|
||||
|
|
|
|||
Loading…
Reference in New Issue