Updates for payfast token billing
This commit is contained in:
parent
3a3664e8c6
commit
0f16f8e98c
|
|
@ -138,7 +138,7 @@ class AuthorizeCreditCard implements LivewireMethodInterface
|
|||
{
|
||||
$client_gateway_token = ClientGatewayToken::query()
|
||||
->where('id', $this->decodePrimaryKey($request->token))
|
||||
->where('company_id', auth()->guard('contact')->user()->client->company->id)
|
||||
->where('company_id', auth()->guard('contact')->user()->client->company_id)
|
||||
->first();
|
||||
|
||||
if (! $client_gateway_token) {
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@ class CreditCard implements LivewireMethodInterface
|
|||
*/
|
||||
public function paymentResponse(Request $request)
|
||||
{
|
||||
|
||||
if($request->token){
|
||||
return $this->processTokenPayment($request->token, $request->payment_hash);
|
||||
}
|
||||
|
||||
$response_array = $request->all();
|
||||
|
||||
nlog($request->all());
|
||||
|
|
@ -216,6 +221,27 @@ class CreditCard implements LivewireMethodInterface
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private function processTokenPayment(string $token, string $payment_hash)
|
||||
{
|
||||
|
||||
$client_gateway_token = \App\Models\ClientGatewayToken::query()
|
||||
->where('token', $token)
|
||||
->where('company_id', auth()->guard('contact')->user()->client->company_id)
|
||||
->first();
|
||||
|
||||
if (! $client_gateway_token) {
|
||||
throw new \App\Exceptions\PaymentFailed(ctrans('texts.payment_token_not_found'), 401);
|
||||
}
|
||||
|
||||
$payment_hash = \App\Models\PaymentHash::with('fee_invoice')->where('hash', $payment_hash)->firstOrFail();
|
||||
|
||||
$payment = $this->payfast->tokenBilling($client_gateway_token, $payment_hash);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
|
||||
|
||||
}
|
||||
|
||||
private function processSuccessfulPayment($response_array)
|
||||
{
|
||||
$payment_record = [];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\PayFast;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Payment;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\CompanyGateway;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentCompletedWebhook implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public array $data, public string $company_key, public int $company_gateway_id){}
|
||||
// 'm_payment_id' => 'aobgUGfYHQXCdFdXYyfXiEolPOOYIdbb',
|
||||
// 'pf_payment_id' => '2579',
|
||||
// 'payment_status' => 'COMPLETE',
|
||||
// 'item_name' => 'Invoices: ["0081"]',
|
||||
// 'item_description' => 'Credit Card Pre Authorization',
|
||||
// 'amount_gross' => '1481.55',
|
||||
// 'amount_fee' => '-68.75',
|
||||
// 'amount_net' => '1412.80',
|
||||
// 'custom_str1' => NULL,
|
||||
// 'custom_str2' => NULL,
|
||||
// 'custom_str3' => NULL,
|
||||
// 'custom_str4' => NULL,
|
||||
// 'custom_str5' => NULL,
|
||||
// 'custom_int1' => NULL,
|
||||
// 'custom_int2' => NULL,
|
||||
// 'custom_int3' => NULL,
|
||||
// 'custom_int4' => NULL,
|
||||
// 'custom_int5' => NULL,
|
||||
// 'name_first' => NULL,
|
||||
// 'name_last' => NULL,
|
||||
// 'email_address' => NULL,
|
||||
// 'merchant_id' => '10023100',
|
||||
// 'token' => '8e1bf463-0c75-4f9c-836b-9bd02de14fc4',
|
||||
// 'billing_date' => '2025-06-16',
|
||||
// 'signature' => 'acfddcf33967679bcc743532dfef9a89',
|
||||
// 'q' => '/payment_notification_webhook/M2zB4QN6EabKLGV319vzqXFy0J2Xvxer/4w9aAOdvMR/7LDdwRb1YK',
|
||||
|
||||
public function handle()
|
||||
{
|
||||
nlog("PaymentCompletedWebhook");
|
||||
nlog(now()->format('Y-m-d H:i:s'));
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
$company = Company::query()->where('company_key', $this->company_key)->first();
|
||||
|
||||
$p = Payment::query()
|
||||
->where('company_id', $company->id)
|
||||
->where('transaction_reference', $this->data['pf_payment_id'])
|
||||
->first();
|
||||
|
||||
if($p){
|
||||
nlog("payment found returning");
|
||||
return;
|
||||
}
|
||||
|
||||
nlog("yolo");
|
||||
$payment_hash = PaymentHash::where('hash', $this->data['m_payment_id'])->first();
|
||||
|
||||
$company_gateway = CompanyGateway::query()->where('company_id', $company->id)->where('id', $this->company_gateway_id)->first();
|
||||
$driver = $company_gateway->driver($payment_hash->fee_invoice->client)->init();
|
||||
$driver->setPaymentHash($payment_hash);
|
||||
|
||||
$payment_record = [];
|
||||
$payment_record['amount'] = $this->data['amount_gross'];
|
||||
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||
$payment_record['transaction_reference'] = $this->data['pf_payment_id'];
|
||||
$payment_record['idempotency_key'] = $this->data['pf_payment_id'].$payment_hash->hash;
|
||||
|
||||
$payment = $driver->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,15 @@
|
|||
|
||||
namespace App\PaymentDrivers\PayFast;
|
||||
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\PaymentDrivers\PayFastPaymentDriver;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
class Token
|
||||
{
|
||||
|
|
@ -29,98 +34,93 @@ class Token
|
|||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$amount = round(($amount * pow(10, $this->payfast->client->currency()->precision)), 0);
|
||||
$amount = (int)round(($amount * pow(10, $this->payfast->client->currency()->precision)), 0);
|
||||
|
||||
$header = [
|
||||
'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
'version' => 'v1',
|
||||
'timestamp' => now()->format('c'),
|
||||
];
|
||||
|
||||
$body = [
|
||||
'amount' => $amount,
|
||||
'item_name' => 'purchase',
|
||||
'item_description' => ctrans('texts.invoices').': '.collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||
'm_payment_id' => $payment_hash->hash,
|
||||
];
|
||||
|
||||
$header['signature'] = $this->payfast->generateTokenSignature(array_merge($body, $header));
|
||||
|
||||
// nlog($header['signature']);
|
||||
|
||||
$result = $this->send($header, $body, $cgt->token);
|
||||
}
|
||||
|
||||
protected function generate_parameter_string($api_data, $sort_data_before_merge = true, $skip_empty_values = true)
|
||||
{
|
||||
// if sorting is required the passphrase should be added in before sort.
|
||||
if (! empty($this->payfast->company_gateway->getConfigField('passphrase')) && $sort_data_before_merge) {
|
||||
$api_data['passphrase'] = $this->payfast->company_gateway->getConfigField('passphrase');
|
||||
}
|
||||
|
||||
if ($sort_data_before_merge) {
|
||||
ksort($api_data);
|
||||
}
|
||||
|
||||
// concatenate the array key value pairs.
|
||||
$parameter_string = '';
|
||||
foreach ($api_data as $key => $val) {
|
||||
if ($skip_empty_values && empty($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('signature' !== $key) {
|
||||
$val = urlencode($val);
|
||||
$parameter_string .= "$key=$val&";
|
||||
}
|
||||
}
|
||||
// when not sorting passphrase should be added to the end before md5
|
||||
if ($sort_data_before_merge) {
|
||||
$parameter_string = rtrim($parameter_string, '&');
|
||||
} elseif (! empty($this->pass_phrase)) {
|
||||
$parameter_string .= 'passphrase='.urlencode($this->payfast->company_gateway->getConfigField('passphrase'));
|
||||
} else {
|
||||
$parameter_string = rtrim($parameter_string, '&');
|
||||
}
|
||||
|
||||
// nlog($parameter_string);
|
||||
|
||||
return $parameter_string;
|
||||
}
|
||||
|
||||
private function genSig($data)
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
ksort($data);
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (! empty($data[$key])) {
|
||||
$fields[$key] = $data[$key];
|
||||
}
|
||||
}
|
||||
|
||||
nlog(http_build_query($fields));
|
||||
|
||||
return md5(http_build_query($fields));
|
||||
}
|
||||
|
||||
private function send($headers, $body, $token)
|
||||
{
|
||||
$client = new \GuzzleHttp\Client(
|
||||
try {
|
||||
$payfast = new \PayFast\PayFastApi(
|
||||
[
|
||||
'headers' => $headers,
|
||||
'merchantId' => (string)$this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
'merchantKey' => $this->payfast->company_gateway->getConfigField('merchantKey'),
|
||||
'passPhrase' => $this->payfast->company_gateway->getConfigField('passphrase'),
|
||||
'testMode' => $this->payfast->company_gateway->getConfigField('testMode')
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $client->post("https://api.payfast.co.za/subscriptions/{$token}/adhoc?testing=true", [
|
||||
RequestOptions::JSON => ['body' => $body], RequestOptions::ALLOW_REDIRECTS => false,
|
||||
]);
|
||||
$data = [
|
||||
'amount' => $amount,
|
||||
'item_name' => ctrans('texts.invoices').': '.collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||
'm_payment_id' => $payment_hash->hash,
|
||||
];
|
||||
|
||||
return json_decode($response->getBody(), true);
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
$response = $payfast->subscriptions->adhoc($cgt->token, $data);
|
||||
|
||||
nlog("TokenBilling");
|
||||
nlog($response);
|
||||
nlog(now()->format('Y-m-d H:i:s'));
|
||||
|
||||
if($response['code'] == 200 && $response['status'] == 'success') {
|
||||
return $this->processSuccessfulPayment($response);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment($response, $payment_hash);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo 'There was an exception: '.$e->getMessage();
|
||||
return $this->processUnsuccessfulPayment($e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Array
|
||||
// (
|
||||
// [code] => 200
|
||||
// [status] => success
|
||||
// [data] => Array
|
||||
// (
|
||||
// [response] => true
|
||||
// [message] => Transaction was successful (00)
|
||||
// [pf_payment_id] => 2577761
|
||||
// )
|
||||
|
||||
// )
|
||||
|
||||
private function processSuccessfulPayment(array $response)
|
||||
{
|
||||
|
||||
$payment_record = [];
|
||||
$payment_record['amount'] = array_sum(array_column($this->payfast->payment_hash->invoices(), 'amount')) + $this->payfast->payment_hash->fee_total;
|
||||
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||
$payment_record['transaction_reference'] = $response['data']['pf_payment_id'];
|
||||
$payment_record['idempotency_key'] = $response['data']['pf_payment_id'].$this->payfast->payment_hash->hash;
|
||||
$payment = $this->payfast->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
private function processUnsuccessfulPayment($response)
|
||||
{
|
||||
$error_message = $response['data']['message'];
|
||||
$error_code = $response['code'];
|
||||
|
||||
$this->payfast->sendFailureMail($error_message);
|
||||
|
||||
$message = [
|
||||
'server_response' => $response,
|
||||
'data' => $this->payfast->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_PAYFAST,
|
||||
$this->payfast->client,
|
||||
$this->payfast->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed('Failed to process the payment.', 500);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,18 @@
|
|||
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\PayFast\CreditCard;
|
||||
use App\PaymentDrivers\PayFast\Token;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\PaymentDrivers\PayFast\Token;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\PaymentDrivers\PayFast\CreditCard;
|
||||
use App\PaymentDrivers\PayFast\PaymentCompletedWebhook;
|
||||
use App\Http\Requests\Payments\PaymentNotificationWebhookRequest;
|
||||
|
||||
class PayFastPaymentDriver extends BaseDriver
|
||||
{
|
||||
|
|
@ -69,19 +71,6 @@ class PayFastPaymentDriver extends BaseDriver
|
|||
|
||||
public function init()
|
||||
{
|
||||
// try {
|
||||
// $this->payfast = new \Payfast\PayFastPayment(
|
||||
// [
|
||||
// 'merchantId' => $this->company_gateway->getConfigField('merchantId'),
|
||||
// 'merchantKey' => $this->company_gateway->getConfigField('merchantKey'),
|
||||
// 'passPhrase' => $this->company_gateway->getConfigField('passphrase'),
|
||||
// 'testMode' => $this->company_gateway->getConfigField('testMode'),
|
||||
// ]
|
||||
// );
|
||||
// } catch (\Exception $e) {
|
||||
// nlog('##PAYFAST## There was an exception: '.$e->getMessage());
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -196,12 +185,17 @@ class PayFastPaymentDriver extends BaseDriver
|
|||
return md5(http_build_query($fields));
|
||||
}
|
||||
|
||||
public function processWebhookRequest(Request $request, Payment $payment = null)
|
||||
public function processWebhookRequest(PaymentNotificationWebhookRequest $request, Payment $payment = null)
|
||||
{
|
||||
$data = $request->all();
|
||||
// nlog("payfast");
|
||||
// nlog($data);
|
||||
|
||||
if(array_key_exists('pf_payment_id', $data) && strlen($data['pf_payment_id']) > 1) {
|
||||
PaymentCompletedWebhook::dispatch($data, $request->company_key, $this->company_gateway->id)->delay(10);
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_key_exists('m_payment_id', $data)) {
|
||||
$hash = Cache::get($data['m_payment_id']);
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
"hyvor/php-json-exporter": "^0.0.3",
|
||||
"imdhemy/laravel-purchases": "^1.7",
|
||||
"intervention/image": "^2.5",
|
||||
"invoiceninja/admin-api": "dev-main",
|
||||
"invoiceninja/einvoice": "dev-main",
|
||||
"invoiceninja/inspector": "^3.0",
|
||||
"invoiceninja/ubl_invoice": "^2",
|
||||
|
|
@ -89,6 +90,7 @@
|
|||
"nelexa/zip": "^4.0",
|
||||
"nordigen/nordigen-php": "^1.1",
|
||||
"nwidart/laravel-modules": "^11.0",
|
||||
"payfast/payfast-php-sdk": "^1.1",
|
||||
"phpoffice/phpspreadsheet": "^2.2",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"predis/predis": "^2",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4361272676d998d08fb1926198065b39",
|
||||
"content-hash": "afc109ee881bc826259c15922048972d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adrienrn/php-mimetyper",
|
||||
|
|
@ -4667,6 +4667,65 @@
|
|||
],
|
||||
"time": "2022-05-21T17:30:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "invoiceninja/admin-api",
|
||||
"version": "dev-main",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../admin-api",
|
||||
"reference": "4d95a2318a4dc41cdea95793d85ffe5589ed9117"
|
||||
},
|
||||
"require": {
|
||||
"afosto/yaac": "^1.5",
|
||||
"asm/php-ansible": "dev-main",
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-simplexml": "*",
|
||||
"illuminate/database": "^11",
|
||||
"illuminate/support": "^11",
|
||||
"imdhemy/laravel-purchases": "^1.7",
|
||||
"php": "^8.2|^8.3|^8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"larastan/larastan": "^3.0",
|
||||
"orchestra/testbench": "^9.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpunit/phpunit": "^11.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"InvoiceNinja\\AdminApi\\Providers\\AdminApiServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"InvoiceNinja\\AdminApi\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"InvoiceNinja\\AdminApi\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"license": [
|
||||
"Elastic"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Bomba",
|
||||
"email": "turbo124@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "API endpoints for the admin interface",
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "invoiceninja/einvoice",
|
||||
"version": "dev-main",
|
||||
|
|
@ -9088,6 +9147,59 @@
|
|||
},
|
||||
"time": "2024-09-04T12:51:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "payfast/payfast-php-sdk",
|
||||
"version": "v1.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Payfast/payfast-php-sdk.git",
|
||||
"reference": "015efcd2df3e580e023dae6e16c943328d38bb78"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Payfast/payfast-php-sdk/zipball/015efcd2df3e580e023dae6e16c943328d38bb78",
|
||||
"reference": "015efcd2df3e580e023dae6e16c943328d38bb78",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": ">=6.0.0",
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PayFast\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Payfast",
|
||||
"email": "support@payfast.help"
|
||||
}
|
||||
],
|
||||
"description": "Payfast PHP Library",
|
||||
"keywords": [
|
||||
"api",
|
||||
"onsite",
|
||||
"payfast",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Payfast/payfast-php-sdk/issues",
|
||||
"source": "https://github.com/Payfast/payfast-php-sdk/tree/v1.1.6"
|
||||
},
|
||||
"time": "2024-02-28T09:54:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-http/client-common",
|
||||
"version": "2.7.2",
|
||||
|
|
@ -9883,16 +9995,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.43",
|
||||
"version": "3.0.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "709ec107af3cb2f385b9617be72af8cf62441d02"
|
||||
"reference": "1d0b5e7e1434678411787c5a0535e68907cf82d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02",
|
||||
"reference": "709ec107af3cb2f385b9617be72af8cf62441d02",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/1d0b5e7e1434678411787c5a0535e68907cf82d9",
|
||||
"reference": "1d0b5e7e1434678411787c5a0535e68907cf82d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -9973,7 +10085,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.43"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -9989,7 +10101,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-14T21:12:59+00:00"
|
||||
"time": "2025-06-15T09:59:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
|
|
@ -21396,6 +21508,7 @@
|
|||
"asm/php-ansible": 20,
|
||||
"beganovich/snappdf": 20,
|
||||
"horstoeko/orderx": 20,
|
||||
"invoiceninja/admin-api": 20,
|
||||
"invoiceninja/einvoice": 20,
|
||||
"socialiteproviders/apple": 20
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
var h=Object.defineProperty;var w=(a,e,n)=>e in a?h(a,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):a[e]=n;var u=(a,e,n)=>(w(a,typeof e!="symbol"?e+"":e,n),n);import{i as y,w as p}from"./wait-8f4ae121.js";/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/class b{constructor(){u(this,"startTimer",e=>{const n=new Date().getTime()+e*1e3;document.getElementById("countdown").innerHTML="10:00 min";const c=()=>{const r=new Date().getTime(),t=n-r;if(document.getElementsByClassName("btc-value")[0].innerHTML.includes("Refreshing"))return;if(t<0){refreshBTCPrice();return}const s=Math.floor(t%(1e3*60*60)/(1e3*60)),i=Math.floor(t%(1e3*60)/1e3),d=String(s).padStart(2,"0"),m=String(i).padStart(2,"0");document.getElementById("countdown").innerHTML=d+":"+m+" min"};clearInterval(window.countdownInterval),window.countdownInterval=setInterval(c,1e3)});this.copyToClipboard=this.copyToClipboard.bind(this),this.refreshBTCPrice=this.refreshBTCPrice.bind(this),this.fetchAndDisplayQRCode=this.fetchAndDisplayQRCode.bind(this),this.startTimer=this.startTimer.bind(this)}copyToClipboard(e,n,c){const r=c?n.nextElementSibling:n,t=r.src,o=document.createElement("input"),s=document.getElementById(e),{value:i,innerText:d}=s||{},m=i||d;o.value=m,document.body.appendChild(o),o.select(),document.execCommand("copy"),document.body.removeChild(o),r.src="data:image/svg+xml;base64,"+btoa(`
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.04706 14C4.04706 8.55609 8.46025 4.1429 13.9042 4.1429C19.3482 4.1429 23.7613 8.55609 23.7613 14C23.7613 19.444 19.3482 23.8572 13.9042 23.8572C8.46025 23.8572 4.04706 19.444 4.04706 14Z" stroke="#000" stroke-width="2.19048" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.52325 14L12.809 17.2858L18.2852 11.8096" stroke="#000" stroke-width="2.19048" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
`),setTimeout(()=>{r.src=t},5e3)}async fetchAndDisplayQRCode(e=null){try{const n=document.querySelector('meta[name="btc_address"]').content,r=encodeURIComponent(`bitcoin:${n}?amount=${e||"{{$btc_amount}}"}`),t=await fetch(`/api/v1/get-blockonomics-qr-code?qr_string=${r}`);if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const o=await t.text();document.getElementById("qrcode-container").innerHTML=o}catch(n){console.error("Error fetching QR code:",n),document.getElementById("qrcode-container").textContent="Error loading QR code"}}async refreshBTCPrice(){const e=document.querySelector(".icon-refresh");e.classList.add("rotating"),document.getElementsByClassName("btc-value")[0].innerHTML="Refreshing...";const n=async()=>{try{const c=document.querySelector('meta[name="currency"]').content,r=await fetch(`/api/v1/get-btc-price?currency=${c}`);if(!r.ok)throw new Error("Network response was not ok");return(await r.json()).price}catch(c){console.error("There was a problem with the BTC price fetch operation:",c)}};try{const c=await n();if(c){const r=document.querySelector('meta[name="currency"]').content;document.getElementsByClassName("btc-value")[0].innerHTML="1 BTC = "+(c||"N/A")+" "+r+", updates in <span id='countdown'></span>";const t=(document.querySelector('meta[name="amount"]').content/c).toFixed(10);document.querySelector('input[name="btc_price"]').value=c,document.querySelector('input[name="btc_amount"]').value=t,document.getElementById("btc-amount").textContent=t;const o=document.querySelector('meta[name="btc_address"]').content,s=document.getElementById("qr-code-link"),i=document.getElementById("open-in-wallet-link");s.href=`bitcoin:${o}?amount=${t}`,i.href=`bitcoin:${o}?amount=${t}`,await this.fetchAndDisplayQRCode(t),this.startTimer(600)}}finally{e.classList.remove("rotating")}}handle(){window.copyToClipboard=this.copyToClipboard,window.refreshBTCPrice=this.refreshBTCPrice,window.fetchAndDisplayQRCode=this.fetchAndDisplayQRCode,window.startTimer=this.startTimer;const e=()=>{const c=`wss://www.blockonomics.co/payment/${document.querySelector('meta[name="btc_address"]').content}`,r=new WebSocket(c);r.onmessage=function(t){const o=JSON.parse(t.data);console.log("Payment status:",o.status);const s=o.status===0,i=o.status===1,d=o.status===2;(s||i||d)&&(document.querySelector('input[name="txid"]').value=o.txid||"",document.getElementById("server-response").submit())}};startTimer(600),e(),fetchAndDisplayQRCode()}}function l(){new b().handle(),window.bootBlockonomics=l}y()?l():p("#blockonomics-payment").then(()=>l());
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
var h=Object.defineProperty;var y=(a,t,e)=>t in a?h(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var l=(a,t,e)=>(y(a,typeof t!="symbol"?t+"":t,e),e);import{i as w,w as p}from"./wait-8f4ae121.js";/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/class f{constructor(){l(this,"startTimer",t=>{const e=new Date().getTime()+t*1e3;document.getElementById("countdown").innerHTML="10:00 min";const o=()=>{const c=new Date().getTime(),n=e-c;if(document.getElementsByClassName("btc-value")[0].innerHTML.includes("Refreshing"))return;if(n<0){refreshBTCPrice();return}const s=Math.floor(n%(1e3*60*60)/(1e3*60)),i=Math.floor(n%(1e3*60)/1e3),d=String(s).padStart(2,"0"),m=String(i).padStart(2,"0");document.getElementById("countdown").innerHTML=d+":"+m+" min"};clearInterval(window.countdownInterval),window.countdownInterval=setInterval(o,1e3)});this.copyToClipboard=this.copyToClipboard.bind(this),this.refreshBTCPrice=this.refreshBTCPrice.bind(this),this.fetchAndDisplayQRCode=this.fetchAndDisplayQRCode.bind(this),this.startTimer=this.startTimer.bind(this)}copyToClipboard(t,e,o){const c=o?e.nextElementSibling:e,n=c.src,r=document.createElement("input"),s=document.getElementById(t),{value:i,innerText:d}=s||{},m=i||d;r.value=m,document.body.appendChild(r),r.select(),document.execCommand("copy"),document.body.removeChild(r),c.src="data:image/svg+xml;base64,"+btoa(`
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.04706 14C4.04706 8.55609 8.46025 4.1429 13.9042 4.1429C19.3482 4.1429 23.7613 8.55609 23.7613 14C23.7613 19.444 19.3482 23.8572 13.9042 23.8572C8.46025 23.8572 4.04706 19.444 4.04706 14Z" stroke="#000" stroke-width="2.19048" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.52325 14L12.809 17.2858L18.2852 11.8096" stroke="#000" stroke-width="2.19048" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
`),setTimeout(()=>{c.src=n},5e3)}async fetchAndDisplayQRCode(t=null){try{const e=document.querySelector('meta[name="btc_address"]').content,c=encodeURIComponent(`bitcoin:${e}?amount=${t||"{{$btc_amount}}"}`),n=await fetch(`/api/v1/get-blockonomics-qr-code?qr_string=${c}`);if(!n.ok)throw new Error(`HTTP error! status: ${n.status}`);const r=await n.text();document.getElementById("qrcode-container").innerHTML=r}catch(e){console.error("Error fetching QR code:",e),document.getElementById("qrcode-container").textContent="Error loading QR code"}}async refreshBTCPrice(){const t=document.querySelector(".icon-refresh");t.classList.add("rotating"),document.getElementsByClassName("btc-value")[0].innerHTML="Refreshing...";const e=async()=>{try{const o=document.querySelector('meta[name="currency"]').content,c=await fetch(`/api/v1/get-btc-price?currency=${o}`);if(!c.ok)throw new Error("Network response was not ok");return(await c.json()).price}catch(o){console.error("There was a problem with the BTC price fetch operation:",o)}};try{const o=await e();if(o){const c=document.querySelector('meta[name="currency"]').content;document.getElementsByClassName("btc-value")[0].innerHTML="1 BTC = "+(o||"N/A")+" "+c+", updates in <span id='countdown'></span>";const n=(document.querySelector('meta[name="amount"]').content/o).toFixed(10);document.querySelector('input[name="btc_price"]').value=o,document.querySelector('input[name="btc_amount"]').value=n,document.getElementById("btc-amount").textContent=n;const r=document.querySelector('meta[name="btc_address"]').content,s=document.getElementById("qr-code-link"),i=document.getElementById("open-in-wallet-link");s.href=`bitcoin:${r}?amount=${n}`,i.href=`bitcoin:${r}?amount=${n}`,await this.fetchAndDisplayQRCode(n),this.startTimer(600)}}finally{t.classList.remove("rotating")}}handle(){window.copyToClipboard=this.copyToClipboard,window.refreshBTCPrice=this.refreshBTCPrice,window.fetchAndDisplayQRCode=this.fetchAndDisplayQRCode,window.startTimer=this.startTimer;const t=()=>{const e=document.querySelector('meta[name="btc_address"]').content,o=`wss://www.blockonomics.co/payment/${e}`,c=new WebSocket(o);c.onmessage=function(n){const r=JSON.parse(n.data),{status:s,txid:i,value:d}=r||{};console.log("Payment status:",s),(s===0||s===1||s===2)&&(document.querySelector('input[name="txid"]').value=i||"",document.querySelector('input[name="status"]').value=s||"",document.querySelector('input[name="btc_amount"]').value=d||"",document.querySelector('input[name="btc_address"]').value=e||"",document.getElementById("server-response").submit())}};startTimer(600),t(),fetchAndDisplayQRCode()}}function u(){new f().handle(),window.bootBlockonomics=u}w()?u():p("#blockonomics-payment").then(()=>u());
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
"src": "resources/js/clients/payments/authorize-credit-card-payment.js"
|
||||
},
|
||||
"resources/js/clients/payments/blockonomics.js": {
|
||||
"file": "assets/blockonomics-56ba6746.js",
|
||||
"file": "assets/blockonomics-c3966bec.js",
|
||||
"imports": [
|
||||
"_wait-8f4ae121.js"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
@section('gateway_content')
|
||||
<form action="{{ $payment_endpoint_url }}" method="post" id="server_response">
|
||||
@csrf
|
||||
<input type="hidden" name="merchant_id" value="{{ $merchant_id }}">
|
||||
<input type="hidden" name="merchant_key" value="{{ $merchant_key }}">
|
||||
<input type="hidden" name="return_url" value="{{ $return_url }}">
|
||||
|
|
@ -20,6 +21,12 @@
|
|||
<input type="hidden" name="passphrase" value="{{ $passphrase }}">
|
||||
<input type="hidden" name="signature" value="{{ $signature }}">
|
||||
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||
<input type="hidden" name="payment_method_id" value="1">
|
||||
<input type="hidden" name="gateway_response" id="gateway_response">
|
||||
<input type="hidden" name="token" id="token">
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')])
|
||||
|
|
@ -29,28 +36,34 @@
|
|||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
<ul class="list-none space-y-2">
|
||||
@if(count($tokens) > 0)
|
||||
@foreach($tokens as $token)
|
||||
<label class="mr-4">
|
||||
<li class="py-2 hover:bg-gray-100 rounded transition-colors duration-150">
|
||||
<label class="flex items-center cursor-pointer px-2">
|
||||
<input
|
||||
type="radio"
|
||||
data-token="{{ $token->token }}"
|
||||
name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token"/>
|
||||
<span class="ml-1 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
|
||||
class="form-radio text-indigo-600 rounded-full cursor-pointer toggle-payment-with-token"/>
|
||||
<span class="ml-2 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
|
||||
</label>
|
||||
</li>
|
||||
@endforeach
|
||||
@endisset
|
||||
|
||||
<label>
|
||||
<li class="py-2 hover:bg-gray-100 rounded transition-colors duration-150">
|
||||
<label class="flex items-center cursor-pointer px-2">
|
||||
<input
|
||||
type="radio"
|
||||
id="toggle-payment-with-credit-card"
|
||||
class="form-radio cursor-pointer"
|
||||
class="form-radio text-indigo-600 rounded-full cursor-pointer"
|
||||
name="payment-type"
|
||||
checked/>
|
||||
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
|
||||
<span class="ml-2 cursor-pointer">{{ __('texts.new_card') }}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
@endcomponent
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
|
@ -62,11 +75,49 @@
|
|||
|
||||
@section('gateway_footer')
|
||||
<script>
|
||||
|
||||
|
||||
document.getElementById('pay-now').addEventListener('click', function() {
|
||||
document.getElementById('server_response').submit();
|
||||
// Add click listeners to all token radio buttons
|
||||
Array.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||
.forEach((element) => {
|
||||
element.addEventListener('click', (e) => {
|
||||
const sourceInput = document.querySelector('input[name=payment-type]');
|
||||
if (sourceInput) {
|
||||
sourceInput.value = e.target.dataset.token;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle the pay now button click
|
||||
const payNowButton = document.getElementById('pay-now');
|
||||
if (payNowButton) {
|
||||
payNowButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
payNowButton.disabled = true;
|
||||
payNowButton.querySelector('#pay-now svg').classList.remove('hidden');
|
||||
payNowButton.querySelector('#pay-now span').classList.add('hidden');
|
||||
|
||||
const form = document.getElementById('server_response');
|
||||
const selectedToken = document.querySelector('input[name="payment-type"]:checked');
|
||||
|
||||
if (selectedToken && selectedToken?.dataset?.token) {
|
||||
form.action = "{{ route('client.payments.response') }}";
|
||||
document.querySelector('input[name=token]').value = selectedToken.value;
|
||||
} else {
|
||||
const endpointUrl = document.getElementById('payment_endpoint_url');
|
||||
if (endpointUrl) {
|
||||
form.action = endpointUrl.value;
|
||||
}
|
||||
}
|
||||
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-select the first payment option if it exists
|
||||
const first = document.querySelector('input[name="payment-type"]');
|
||||
if (first) {
|
||||
first.click();
|
||||
}
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<meta name="client-postal-code" content="{{ $contact->client->postal_code }}">
|
||||
|
||||
<form action="{{ $payment_endpoint_url }}" method="post" id="server_response">
|
||||
@csrf
|
||||
<input type="hidden" name="merchant_id" value="{{ $merchant_id }}">
|
||||
<input type="hidden" name="merchant_key" value="{{ $merchant_key }}">
|
||||
<input type="hidden" name="return_url" value="{{ $return_url }}">
|
||||
|
|
@ -16,6 +17,12 @@
|
|||
<input type="hidden" name="passphrase" value="{{ $passphrase }}">
|
||||
<input type="hidden" name="signature" value="{{ $signature }}">
|
||||
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||
<input type="hidden" name="payment_method_id" value="1">
|
||||
<input type="hidden" name="gateway_response" id="gateway_response">
|
||||
<input type="hidden" name="token" id="token">
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')])
|
||||
|
|
@ -25,28 +32,34 @@
|
|||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
<ul class="list-none space-y-2">
|
||||
@if(count($tokens) > 0)
|
||||
@foreach($tokens as $token)
|
||||
<label class="mr-4">
|
||||
<li class="py-2 hover:bg-gray-100 rounded transition-colors duration-150">
|
||||
<label class="flex items-center cursor-pointer px-2">
|
||||
<input
|
||||
type="radio"
|
||||
data-token="{{ $token->token }}"
|
||||
name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token"/>
|
||||
<span class="ml-1 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
|
||||
class="form-radio text-indigo-600 rounded-full cursor-pointer toggle-payment-with-token"/>
|
||||
<span class="ml-2 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
|
||||
</label>
|
||||
</li>
|
||||
@endforeach
|
||||
@endisset
|
||||
|
||||
<label>
|
||||
<li class="py-2 hover:bg-gray-100 rounded transition-colors duration-150">
|
||||
<label class="flex items-center cursor-pointer px-2">
|
||||
<input
|
||||
type="radio"
|
||||
id="toggle-payment-with-credit-card"
|
||||
class="form-radio cursor-pointer"
|
||||
class="form-radio text-indigo-600 rounded-full cursor-pointer"
|
||||
name="payment-type"
|
||||
checked/>
|
||||
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
|
||||
<span class="ml-2 cursor-pointer">{{ __('texts.new_card') }}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
@endcomponent
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
|
@ -57,9 +70,50 @@
|
|||
</div>
|
||||
|
||||
@script
|
||||
<script>
|
||||
document.getElementById('pay-now').addEventListener('click', function() {
|
||||
document.getElementById('server_response').submit();
|
||||
<script defer>
|
||||
// Add click listeners to all token radio buttons
|
||||
Array.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||
.forEach((element) => {
|
||||
element.addEventListener('click', (e) => {
|
||||
const sourceInput = document.querySelector('input[name=payment-type]');
|
||||
if (sourceInput) {
|
||||
sourceInput.value = e.target.dataset.token;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle the pay now button click
|
||||
const payNowButton = document.getElementById('pay-now');
|
||||
if (payNowButton) {
|
||||
payNowButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
payNowButton.disabled = true;
|
||||
payNowButton.querySelector('#pay-now svg').classList.remove('hidden');
|
||||
payNowButton.querySelector('#pay-now span').classList.add('hidden');
|
||||
|
||||
const form = document.getElementById('server_response');
|
||||
const selectedToken = document.querySelector('input[name="payment-type"]:checked');
|
||||
|
||||
if (selectedToken && selectedToken?.dataset?.token) {
|
||||
form.action = "{{ route('client.payments.response') }}";
|
||||
document.querySelector('input[name=token]').value = selectedToken.value;
|
||||
} else {
|
||||
const endpointUrl = document.getElementById('payment_endpoint_url');
|
||||
if (endpointUrl) {
|
||||
form.action = endpointUrl.value;
|
||||
}
|
||||
}
|
||||
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-select the first payment option if it exists
|
||||
const first = document.querySelector('input[name="payment-type"]');
|
||||
if (first) {
|
||||
first.click();
|
||||
}
|
||||
|
||||
</script>
|
||||
@endscript
|
||||
Loading…
Reference in New Issue