From c0287085b5c59f91b53da300f246cfc1a473a757 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 20 Jul 2021 21:26:24 +1000 Subject: [PATCH 01/60] Scaffolding Paytrace --- app/Console/Commands/CreateAccount.php | 1 + app/Models/CompanyGateway.php | 8 +- app/Models/SystemLog.php | 2 +- app/PaymentDrivers/PayTrace/CreditCard.php | 121 +++++++++++++ app/PaymentDrivers/PaytracePaymentDriver.php | 123 +++++++++++++ ...95537_activate_paytrace_payment_driver.php | 29 ++++ database/seeders/PaymentLibrariesSeeder.php | 4 +- .../gateways/paytrace/authorize.blade.php | 162 ++++++++++++++++++ .../ninja2020/gateways/paytrace/pay.blade.php | 0 .../livewire/simple-bootstrap.blade.php | 29 ++++ 10 files changed, 474 insertions(+), 5 deletions(-) create mode 100644 app/PaymentDrivers/PayTrace/CreditCard.php create mode 100644 app/PaymentDrivers/PaytracePaymentDriver.php create mode 100644 database/migrations/2021_07_20_095537_activate_paytrace_payment_driver.php create mode 100644 resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php create mode 100644 resources/views/vendor/livewire/simple-bootstrap.blade.php diff --git a/app/Console/Commands/CreateAccount.php b/app/Console/Commands/CreateAccount.php index 689ba262b0b5..b210981e16ba 100644 --- a/app/Console/Commands/CreateAccount.php +++ b/app/Console/Commands/CreateAccount.php @@ -91,6 +91,7 @@ class CreateAccount extends Command $account = Account::factory()->create(); $company = Company::factory()->create([ 'account_id' => $account->id, + 'domain' => config('ninja.app_url'), ]); $account->default_company_id = $company->id; diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index b9105b566e9e..1204b8e0ea1a 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -69,16 +69,20 @@ class CompanyGateway extends BaseModel // const TYPE_CUSTOM = 306; // const TYPE_BRAINTREE = 307; // const TYPE_WEPAY = 309; + // const TYPE_PAYFAST = 310; + // const TYPE_PAYTRACE = 311; public $gateway_consts = [ '38f2c48af60c7dd69e04248cbb24c36e' => 300, 'd14dd26a37cecc30fdd65700bfb55b23' => 301, + 'd14dd26a47cecc30fdd65700bfb67b34' => 301, '3758e7f7c6f4cecf0f4f348b9a00f456' => 304, '3b6621f970ab18887c4f6dca78d3f8bb' => 305, '54faab2ab6e3223dbe848b1686490baa' => 306, - 'd14dd26a47cecc30fdd65700bfb67b34' => 301, - '8fdeed552015b3c7b44ed6c8ebd9e992' => 309, 'f7ec488676d310683fb51802d076d713' => 307, + '8fdeed552015b3c7b44ed6c8ebd9e992' => 309, + 'd6814fc83f45d2935e7777071e629ef9' => 310, + 'bbd736b3254b0aabed6ad7fda1298c88' => 311, ]; protected $touches = []; diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index 7614c01ecccc..5e933edfe8a1 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -68,7 +68,7 @@ class SystemLog extends Model const TYPE_BRAINTREE = 307; const TYPE_WEPAY = 309; const TYPE_PAYFAST = 310; - + const TYPE_PAYTRACE = 311; const TYPE_QUOTA_EXCEEDED = 400; const TYPE_UPSTREAM_FAILURE = 401; diff --git a/app/PaymentDrivers/PayTrace/CreditCard.php b/app/PaymentDrivers/PayTrace/CreditCard.php new file mode 100644 index 000000000000..58937195e553 --- /dev/null +++ b/app/PaymentDrivers/PayTrace/CreditCard.php @@ -0,0 +1,121 @@ +paytrace_driver = $paytrace_driver; + } + + public function authorizeView($data) + { + + $data['client_key'] = $this->paytrace_driver->getAuthToken(); + + return render('gateways.paytrace.authorize', $data); + } + + + public function authorizeResponse($request) + { + $data = $request->all(); + + return response()->json([], 200); + + } + + public function paymentView($data) + { + + + return render('gateways.paytrace.pay', $data); + + } + + + public function paymentResponse(Request $request) + { + $response_array = $request->all(); + + + + // if($response_array['payment_status'] == 'COMPLETE') { + + // $this->payfast->logSuccessfulGatewayResponse(['response' => $response_array, 'data' => $this->paytrace_driver->payment_hash], SystemLog::TYPE_PAYFAST); + + // return $this->processSuccessfulPayment($response_array); + // } + // else { + // $this->processUnsuccessfulPayment($response_array); + // } + } + + private function processSuccessfulPayment($response_array) + { + + + + // $payment = $this->paytrace_driver->createPayment($payment_record, Payment::STATUS_COMPLETED); + + + } + + private function processUnsuccessfulPayment($server_response) + { + // PaymentFailureMailer::dispatch($this->paytrace_driver->client, $server_response->cancellation_reason, $this->paytrace_driver->client->company, $server_response->amount); + + // PaymentFailureMailer::dispatch( + // $this->paytrace_driver->client, + // $server_response, + // $this->paytrace_driver->client->company, + // $server_response['amount_gross'] + // ); + + // $message = [ + // 'server_response' => $server_response, + // 'data' => $this->paytrace_driver->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); + } + +} \ No newline at end of file diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php new file mode 100644 index 000000000000..94049366d250 --- /dev/null +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -0,0 +1,123 @@ + CreditCard::class, //maps GatewayType => Implementation class + ]; + + const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYTRACE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model + + public function init() + { + return $this; /* This is where you boot the gateway with your auth credentials*/ + } + + /* Returns an array of gateway types for the payment gateway */ + public function gatewayTypes(): array + { + $types = []; + + $types[] = GatewayType::CREDIT_CARD; + + return $types; + } + + /* Sets the payment method initialized */ + public function setPaymentMethod($payment_method_id) + { + $class = self::$methods[$payment_method_id]; + $this->payment_method = new $class($this); + return $this; + } + + public function authorizeView(array $data) + { + return $this->payment_method->authorizeView($data); //this is your custom implementation from here + } + + public function authorizeResponse($request) + { + return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here + } + + public function processPaymentView(array $data) + { + return $this->payment_method->paymentView($data); //this is your custom implementation from here + } + + public function processPaymentResponse($request) + { + return $this->payment_method->paymentResponse($request); //this is your custom implementation from here + } + + public function refund(Payment $payment, $amount, $return_client_response = false) + { + return $this->payment_method->yourRefundImplementationHere(); //this is your custom implementation from here + } + + public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) + { + return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here + } + + public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) + { + } + + /*Helpers*/ + + public function getAuthToken() + { + + $url = 'https://api.paytrace.com/oauth/token'; + $data = [ + 'grant_type' => 'password', + 'username' => $this->company_gateway->getConfigField('username'), + 'password' => $this->company_gateway->getConfigField('password') + ]; + + $response = CurlUtils::post($url, $data, $headers = false); + + if($response) + { + $auth_data = json_decode($response); + + return $auth_data->access_token; + } + + return false; + } +} diff --git a/database/migrations/2021_07_20_095537_activate_paytrace_payment_driver.php b/database/migrations/2021_07_20_095537_activate_paytrace_payment_driver.php new file mode 100644 index 000000000000..5d9743c35714 --- /dev/null +++ b/database/migrations/2021_07_20_095537_activate_paytrace_payment_driver.php @@ -0,0 +1,29 @@ +where('id', 46)->update(['visible' => true, 'provider' => 'Paytrace']); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php index b9ff0f84146f..e6d31f414fe4 100644 --- a/database/seeders/PaymentLibrariesSeeder.php +++ b/database/seeders/PaymentLibrariesSeeder.php @@ -70,7 +70,7 @@ class PaymentLibrariesSeeder extends Seeder ['id' => 43, 'name' => 'Fasapay', 'provider' => 'Fasapay', 'key' => '1b2cef0e8c800204a29f33953aaf3360', 'fields' => ''], ['id' => 44, 'name' => 'Komoju', 'provider' => 'Komoju', 'key' => '7ea2d40ecb1eb69ef8c3d03e5019028a', 'fields' => '{"apiKey":"","accountId":"","paymentMethod":"credit_card","testMode":false,"locale":"en"}'], ['id' => 45, 'name' => 'Paysafecard', 'provider' => 'Paysafecard', 'key' => '70ab90cd6c5c1ab13208b3cef51c0894', 'fields' => '{"username":"","password":"","testMode":false}'], - ['id' => 46, 'name' => 'Paytrace', 'provider' => 'Paytrace_CreditCard', 'key' => 'bbd736b3254b0aabed6ad7fda1298c88', 'fields' => '{"username":"","password":"","testMode":false,"endpoint":"https:\/\/paytrace.com\/api\/default.pay"}'], + ['id' => 46, 'name' => 'Paytrace', 'provider' => 'Paytrace', 'key' => 'bbd736b3254b0aabed6ad7fda1298c88', 'fields' => '{"username":"","password":"","testMode":false,"endpoint":"https:\/\/paytrace.com\/api\/default.pay"}'], ['id' => 47, 'name' => 'Secure Trading', 'provider' => 'SecureTrading', 'key' => '231cb401487b9f15babe04b1ac4f7a27', 'fields' => '{"siteReference":"","username":"","password":"","applyThreeDSecure":false,"accountType":"ECOM"}'], ['id' => 48, 'name' => 'SecPay', 'provider' => 'SecPay', 'key' => 'bad8699d581d9fa040e59c0bb721a76c', 'fields' => '{"mid":"","vpnPswd":"","remotePswd":"","usageType":"","confirmEmail":"","testStatus":"true","mailCustomer":"true","additionalOptions":""}'], ['id' => 49, 'name' => 'WePay', 'provider' => 'WePay', 'is_offsite' => false, 'sort_order' => 3, 'key' => '8fdeed552015b3c7b44ed6c8ebd9e992', 'fields' => '{"accountId":"","accessToken":"","type":"goods","testMode":false,"feePayer":"payee"}'], @@ -96,7 +96,7 @@ class PaymentLibrariesSeeder extends Seeder Gateway::query()->update(['visible' => 0]); - Gateway::whereIn('id', [1,15,20,39,55,50])->update(['visible' => 1]); + Gateway::whereIn('id', [1,15,20,39,46,55,50])->update(['visible' => 1]); if (Ninja::isHosted()) { Gateway::whereIn('id', [20])->update(['visible' => 0]); diff --git a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php new file mode 100644 index 000000000000..061901fb3dd2 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php @@ -0,0 +1,162 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')]) + +@section('gateway_head') +@endsection + +@section('gateway_content') + + @if(!Request::isSecure()) +

{{ ctrans('texts.https_required') }}

+ @endif + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+@endsection + +@section('gateway_footer') + + + + + +@endsection diff --git a/resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php b/resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/resources/views/vendor/livewire/simple-bootstrap.blade.php b/resources/views/vendor/livewire/simple-bootstrap.blade.php new file mode 100644 index 000000000000..dbc374434911 --- /dev/null +++ b/resources/views/vendor/livewire/simple-bootstrap.blade.php @@ -0,0 +1,29 @@ +
+ @if ($paginator->hasPages()) + + @endif +
From df7d20fa76d819eaaf88a5765cc504cd4d53b1a6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Jul 2021 07:53:11 +1000 Subject: [PATCH 02/60] Scaffolding Paytrace --- .../gateways/paytrace/authorize.blade.php | 99 ++----------------- 1 file changed, 7 insertions(+), 92 deletions(-) diff --git a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php index 061901fb3dd2..794117356a92 100644 --- a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php @@ -39,103 +39,12 @@ // Minimal Protect.js setup call PTPayment.setup({ - styles: - { - 'code': { - 'font_color':'#5D99CA', - 'border_color':'#EF9F6D', - 'border_style':'dotted', - 'font_size':'13pt', - 'input_border_radius':'10px', - 'input_border_width':'2px', - 'input_font':'serif, cursive, fantasy', - 'input_font_weight':'700', - 'input_margin':'5px 0px 5px 20px', - 'input_padding':'0px 5px 0px 5px', - 'label_color':'#5D99CA', - 'label_size':'16px', - 'label_width':'150px', - 'label_font':'sans-serif, arial, serif', - 'label_font_weight':'bold', - 'label_margin':'5px 0px 0px 20px', - 'label_padding':'2px 5px 2px 5px', - 'label_border_style':'dotted', - 'label_border_color':'#EF9F6D', - 'label_border_radius':'10px', - 'label_border_width':'2px', - 'background_color':'white', - 'height':'25px', - 'width':'110px', - 'padding_bottom':'2px' - }, - 'cc': { - 'font_color':'#5D99CA', - 'border_color':'#EF9F6D', - 'border_style':'solid', - 'font_size':'13pt', - 'input_border_radius':'20px', - 'input_border_width':'2px', - 'input_font':'Times New Roman, arial, fantasy', - 'input_font_weight':'400', - 'input_margin':'5px 0px 5px 0px', - 'input_padding':'0px 5px 0px 5px', - 'label_color':'#5D99CA', - 'label_size':'16px', - 'label_width':'150px', - 'label_font':'Times New Roman, sans-serif, serif', - 'label_font_weight':'light', - 'label_margin':'5px 0px 0px 0px', - 'label_padding':'0px 5px 0px 5px', - 'label_border_style':'solid', - 'label_border_color':'#EF9F6D', - 'label_border_radius':'20px', - 'label_border_width':'2px', - 'background_color':'white', - 'height':'25px', - 'width':'320px', - 'padding_bottom':'0px' - }, - 'exp': { - 'font_color':'#5D99CA', - 'border_color':'#EF9F6D', - 'border_style':'dashed', - 'font_size':'12pt', - 'input_border_radius':'0px', - 'input_border_width':'2px', - 'input_font':'arial, cursive, fantasy', - 'input_font_weight':'400', - 'input_margin':'5px 0px 5px 0px', - 'input_padding':'0px 5px 0px 5px', - 'label_color':'#5D99CA', - 'label_size':'16px', - 'label_width':'150px', - 'label_font':'arial, fantasy, serif', - 'label_font_weight':'normal', - 'label_margin':'5px 0px 0px 0px', - 'label_padding':'2px 5px 2px 5px', - 'label_border_style':'dashed', - 'label_border_color':'#EF9F6D', - 'label_border_radius':'0px', - 'label_border_width':'2px', - 'background_color':'white', - 'height':'25px', - 'width':'85px', - 'padding_bottom':'2px', - 'type':'dropdown' - }, - 'body': { - 'background_color':'white' - } - }, + authorization: { clientKey: "{!! $client_key !!}" } }).then(function(instance){ //use instance object to process and tokenize sensitive data payment fields. PTPayment.theme('above the line'); - }); - - - // this can be any event we chose. We will use the submit event and stop any default event handling and prevent event handling bubbling. document.getElementById("ProtectForm").addEventListener("submit",function(e){ e.preventDefault(); @@ -157,6 +66,12 @@ PTPayment.validate(function(validationErrors) { } });// end of PTPayment.validate });// end of add event listener submit + + }); + + + + @endsection From 75ac7ec48cbc19f612d5f74be6a8773c0d7e291b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Jul 2021 09:21:32 +1000 Subject: [PATCH 03/60] minor fixes --- app/Console/Commands/CreateAccount.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/CreateAccount.php b/app/Console/Commands/CreateAccount.php index b210981e16ba..b6c51e992858 100644 --- a/app/Console/Commands/CreateAccount.php +++ b/app/Console/Commands/CreateAccount.php @@ -91,7 +91,8 @@ class CreateAccount extends Command $account = Account::factory()->create(); $company = Company::factory()->create([ 'account_id' => $account->id, - 'domain' => config('ninja.app_url'), + 'portal_domain' => config('ninja.app_url'), + 'portal_mode' => 'domain', ]); $account->default_company_id = $company->id; From 0d52d57d411f69a1c6d17e40173face4f0529eb5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Jul 2021 10:43:39 +1000 Subject: [PATCH 04/60] Paytrace --- app/PaymentDrivers/PaytracePaymentDriver.php | 12 +- .../gateways/paytrace/authorize.blade.php | 128 ++++++++++++++---- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php index 94049366d250..8e95e6f4f701 100644 --- a/app/PaymentDrivers/PaytracePaymentDriver.php +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -115,7 +115,17 @@ class PaytracePaymentDriver extends BaseDriver { $auth_data = json_decode($response); - return $auth_data->access_token; + $headers = []; + $headers[] = 'Content-type: application/json'; + $headers[] = 'Authorization: Bearer '.$auth_data->access_token; + + $response = CurlUtils::post('https://api.paytrace.com/v1/payment_fields/token/create', [], $headers); + + $response = json_decode($response); + + if($response) + return $response->clientKey; + } return false; diff --git a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php index 794117356a92..ac3968e7808a 100644 --- a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php @@ -10,6 +10,7 @@ @endif +
@@ -17,7 +18,11 @@
- + + + +

+
@else diff --git a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php index 2d8ce5482e6b..c84c6052171a 100644 --- a/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/paytrace/authorize.blade.php @@ -1,145 +1,37 @@ -@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')]) +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' +=> ctrans('texts.payment_type_credit_card')]) @section('gateway_head') + + + + @endsection @section('gateway_content') - - @if(!Request::isSecure()) -

{{ ctrans('texts.https_required') }}

- @endif +
+ @csrf + + + + +
-
- @csrf -
+ @component('portal.ninja2020.components.general.card-element-single') +
+
+
+ @endcomponent -
- -
- - - - - -
- -
- -
+ @component('portal.ninja2020.gateways.includes.pay_now') + {{ ctrans('texts.add_payment_method') }} + @endcomponent @endsection @section('gateway_footer') - - - - - + + @endsection diff --git a/resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php b/resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php index 4535639e43d4..d7e752fea647 100644 --- a/resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/paytrace/pay.blade.php @@ -1,7 +1,11 @@ -@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => ctrans('texts.payment_type_credit_card')]) +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' +=> ctrans('texts.payment_type_credit_card')]) @section('gateway_head') - + + + + @endsection @section('gateway_content') @@ -10,12 +14,12 @@ - - - - - - + + + + + + @@ -26,182 +30,35 @@ @include('portal.ninja2020.gateways.includes.payment_details') @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) - @if(count($tokens) > 0) - @foreach($tokens as $token) + @if (count($tokens) > 0) + @foreach ($tokens as $token) @endforeach @endisset - + @endcomponent @include('portal.ninja2020.gateways.includes.save_card') -
+ @component('portal.ninja2020.components.general.card-element-single') +
+
+
+ @endcomponent -
- -
- @include('portal.ninja2020.gateways.includes.pay_now', ['type' => 'submit']) - + @include('portal.ninja2020.gateways.includes.pay_now') @endsection @section('gateway_footer') - - - - + + @endsection diff --git a/tests/Browser/ClientPortal/Gateways/PayTrace/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/PayTrace/CreditCardTest.php new file mode 100644 index 000000000000..3809a8a048f8 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/PayTrace/CreditCardTest.php @@ -0,0 +1,62 @@ +driver->manage()->deleteAllCookies(); + } + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', 'bbd736b3254b0aabed6ad7fda1298c88')->restore(); + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPayingWithNewCreditCard() + { + $this->markTestSkipped('Credit card not supported.'); + + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('CC', '4012000098765439') + ->select('EXP_MM', '12') + ->select('EXP_YY', '30') + ->type('SEC', '999'); + }) + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } +} diff --git a/webpack.mix.js b/webpack.mix.js index a5eafbedebe1..86fa1cea6d3f 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -81,6 +81,10 @@ mix.js("resources/js/app.js", "public/js") .js( "resources/js/clients/payment_methods/wepay-bank-account.js", "public/js/clients/payment_methods/wepay-bank-account.js" + ) + .js( + "resources/js/clients/payments/paytrace-credit-card.js", + "public/js/clients/payments/paytrace-credit-card.js" ); mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css'); From 1e2e55c9e481932c76ece5479cacf8967de94604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Jul 2021 15:13:38 +0200 Subject: [PATCH 27/60] Credit card 3ds processing --- .../Gateways/Mollie3dsController.php | 5 ++- .../Gateways/Mollie/Mollie3dsRequest.php | 35 ++++++++++++++++++- app/Models/PaymentHash.php | 2 +- app/PaymentDrivers/Mollie/CreditCard.php | 21 +++++++---- app/PaymentDrivers/MolliePaymentDriver.php | 16 +++++++++ 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Gateways/Mollie3dsController.php b/app/Http/Controllers/Gateways/Mollie3dsController.php index 7f6a0d9121dd..0e3d8f8c113f 100644 --- a/app/Http/Controllers/Gateways/Mollie3dsController.php +++ b/app/Http/Controllers/Gateways/Mollie3dsController.php @@ -14,11 +14,14 @@ namespace App\Http\Controllers\Gateways; use App\Http\Controllers\Controller; use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest; +use App\Models\PaymentHash; class Mollie3dsController extends Controller { public function index(Mollie3dsRequest $request) { - dd($request->all()); + return $request->getCompanyGateway() + ->driver($request->getClient()) + ->process3dsConfirmation($request); } } diff --git a/app/Http/Requests/Gateways/Mollie/Mollie3dsRequest.php b/app/Http/Requests/Gateways/Mollie/Mollie3dsRequest.php index 85415e6e8dc9..8c06791cd957 100644 --- a/app/Http/Requests/Gateways/Mollie/Mollie3dsRequest.php +++ b/app/Http/Requests/Gateways/Mollie/Mollie3dsRequest.php @@ -12,10 +12,18 @@ namespace App\Http\Requests\Gateways\Mollie; +use App\Models\Client; +use App\Models\ClientGatewayToken; +use App\Models\Company; +use App\Models\CompanyGateway; +use App\Models\PaymentHash; +use App\Utils\Traits\MakesHash; use Illuminate\Foundation\Http\FormRequest; class Mollie3dsRequest extends FormRequest { + use MakesHash; + /** * Determine if the user is authorized to make this request. * @@ -23,7 +31,7 @@ class Mollie3dsRequest extends FormRequest */ public function authorize() { - return false; + return true; } /** @@ -37,4 +45,29 @@ class Mollie3dsRequest extends FormRequest // ]; } + + public function getCompany(): ?Company + { + return Company::where('company_key', $this->company_key)->first(); + } + + public function getCompanyGateway(): ?CompanyGateway + { + return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id)); + } + + public function getPaymentHash(): ?PaymentHash + { + return PaymentHash::where('hash', $this->hash)->first(); + } + + public function getClient(): ?Client + { + return Client::find($this->getPaymentHash()->data->client_id); + } + + public function getPaymentId(): ?string + { + return $this->getPaymentHash()->data->payment_id; + } } diff --git a/app/Models/PaymentHash.php b/app/Models/PaymentHash.php index 0839120f5a9a..c2ea7232b651 100644 --- a/app/Models/PaymentHash.php +++ b/app/Models/PaymentHash.php @@ -41,7 +41,7 @@ class PaymentHash extends Model return $this->belongsTo(Invoice::class, 'fee_invoice_id', 'id'); } - public function withData(string $property, $value): PaymentHash + public function withData(string $property, $value): self { $this->data = array_merge((array) $this->data, [$property => $value]); $this->save(); diff --git a/app/PaymentDrivers/Mollie/CreditCard.php b/app/PaymentDrivers/Mollie/CreditCard.php index c7495ca6b52a..38886e65d722 100644 --- a/app/PaymentDrivers/Mollie/CreditCard.php +++ b/app/PaymentDrivers/Mollie/CreditCard.php @@ -50,21 +50,26 @@ class CreditCard */ public function paymentResponse(PaymentResponseRequest $request) { - $this->mollie->payment_hash->withData('gateway_type_id', GatewayType::CREDIT_CARD); + // TODO: Unit tests. + $amount = number_format((float) $this->mollie->payment_hash->data->amount_with_fee, 2, '.', ''); + + $this->mollie->payment_hash + ->withData('gateway_type_id', GatewayType::CREDIT_CARD) + ->withData('client_id', $this->mollie->client->id); try { $payment = $this->mollie->gateway->payments->create([ 'amount' => [ 'currency' => $this->mollie->client->currency()->code, - 'value' => Number::formatValue($this->mollie->payment_hash->data->amount_with_fee, $this->mollie->client->currency()), + 'value' => $amount, ], 'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash), - 'redirectUrl' => 'https://webshop.example.org/order/12345/', - 'webhookUrl' => route('mollie.3ds_redirect', [ + 'redirectUrl' => route('mollie.3ds_redirect', [ 'company_key' => $this->mollie->client->company->company_key, 'company_gateway_id' => $this->mollie->company_gateway->hashed_id, 'hash' => $this->mollie->payment_hash->hash, ]), + 'webhookUrl' => 'https://invoiceninja.com', 'cardToken' => $request->token, ]); @@ -78,6 +83,8 @@ class CreditCard } if ($payment->status === 'open') { + $this->mollie->payment_hash->withData('payment_id', $payment->id); + return redirect($payment->getCheckoutUrl()); } } catch (\Exception $e) { @@ -87,10 +94,8 @@ class CreditCard } } - protected function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment) + public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment) { - // Check if storing credit card is enabled - $payment_hash = $this->mollie->payment_hash; $data = [ @@ -131,5 +136,7 @@ class CreditCard $this->mollie->client, $this->mollie->client->company, ); + + throw new PaymentFailed($e->getMessage(), $e->getCode()); } } diff --git a/app/PaymentDrivers/MolliePaymentDriver.php b/app/PaymentDrivers/MolliePaymentDriver.php index 6cdcb8b97676..88d7883ac359 100644 --- a/app/PaymentDrivers/MolliePaymentDriver.php +++ b/app/PaymentDrivers/MolliePaymentDriver.php @@ -12,6 +12,7 @@ namespace App\PaymentDrivers; +use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest; use App\Http\Requests\Payments\PaymentWebhookRequest; use App\Models\ClientGatewayToken; use App\Models\GatewayType; @@ -122,4 +123,19 @@ class MolliePaymentDriver extends BaseDriver public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) { } + + public function process3dsConfirmation(Mollie3dsRequest $request) + { + $this->init(); + + $this->setPaymentHash($request->getPaymentHash()); + + try { + $payment = $this->gateway->payments->get($request->getPaymentId()); + + return (new CreditCard($this))->processSuccessfulPayment($payment); + } catch(\Mollie\Api\Exceptions\ApiException $e) { + return (new CreditCard($this))->processUnsuccessfulPayment($e); + } + } } From d50c629476f7911c1ea1d26dc062bf95df7db0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Jul 2021 15:26:01 +0200 Subject: [PATCH 28/60] Show a message about preauthorizing credit card --- .../gateways/mollie/credit_card/pay.blade.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php index bd57d1a5d8bc..d6a4d7db4f56 100644 --- a/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php @@ -28,6 +28,31 @@ ctrans('texts.credit_card')]) @include('portal.ninja2020.gateways.includes.payment_details') + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) + @if(count($tokens) > 0) + @foreach($tokens as $token) + + @endforeach + @endif + + + @endcomponent + @component('portal.ninja2020.components.general.card-element-single')
@endcomponent + @component('portal.ninja2020.components.general.card-element-single') + If you want to save the card for future purchases, please click on the + Payment methods page and authorize the credit card manually. + + + After that, come back to this page and select your payment method. + @endcomponent + @include('portal.ninja2020.gateways.includes.pay_now') @endsection From 202cc9d670e7b7e0491bb5a9fffb88dab6a393f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 29 Jul 2021 16:15:27 +0200 Subject: [PATCH 29/60] wip --- app/PaymentDrivers/Mollie/CreditCard.php | 38 ++++++++++++++++++- .../mollie/credit_card/authorize.blade.php | 37 ++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php diff --git a/app/PaymentDrivers/Mollie/CreditCard.php b/app/PaymentDrivers/Mollie/CreditCard.php index 38886e65d722..959ecdc9c1c2 100644 --- a/app/PaymentDrivers/Mollie/CreditCard.php +++ b/app/PaymentDrivers/Mollie/CreditCard.php @@ -11,7 +11,7 @@ use App\Models\Payment; use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\MolliePaymentDriver; -use App\Utils\Number; +use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\View\Factory; use Illuminate\View\View; @@ -50,6 +50,8 @@ class CreditCard */ public function paymentResponse(PaymentResponseRequest $request) { + dd($this->mollie->gateway->mandates->listForId('cst_6S77wEkuQT')); + // TODO: Unit tests. $amount = number_format((float) $this->mollie->payment_hash->data->amount_with_fee, 2, '.', ''); @@ -58,7 +60,16 @@ class CreditCard ->withData('client_id', $this->mollie->client->id); try { + $customer = $this->mollie->gateway->customers->create([ + 'name' => $this->mollie->client->name, + 'metadata' => [ + 'id' => $this->mollie->client->hashed_id, + ], + ]); + $payment = $this->mollie->gateway->payments->create([ + 'customerId' => $customer->id, + 'sequenceType' => 'first', 'amount' => [ 'currency' => $this->mollie->client->currency()->code, 'value' => $amount, @@ -79,7 +90,7 @@ class CreditCard SystemLog::TYPE_MOLLIE ); - return $this->processSuccessfulPayment($payment); + return $this->processSuccessfulPayment($payment); } if ($payment->status === 'open') { @@ -139,4 +150,27 @@ class CreditCard throw new PaymentFailed($e->getMessage(), $e->getCode()); } + + /** + * Show authorization page. + * + * @param array $data + * @return Factory|View + */ + public function authorizeView(array $data) + { + return render('gateways.mollie.credit_card.authorize', $data); + } + + public function authorizeResponse($request) + { + $customer = $this->mollie->gateway->customers->create([ + 'name' => $this->mollie->client->name, + 'metadata' => [ + 'id' => $this->mollie->client->hashed_id, + ], + ]); + + // Save $customer->id to database.. + } } diff --git a/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php new file mode 100644 index 000000000000..5e3ae5d75ef3 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php @@ -0,0 +1,37 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => +ctrans('texts.credit_card')]) + +@section('gateway_head') + +@endsection + +@section('gateway_content') +
+ @csrf + + {{-- --}} + +
+ + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')]) + {{ ctrans('texts.credit_card') }} + @endcomponent + + @component('portal.ninja2020.components.general.card-element-single') + Click the "Add Payment Method" button to complete test payment. + @endcomponent + + @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card']) + {{ ctrans('texts.add_payment_method') }} + @endcomponent +@endsection + +@section('gateway_footer') + +@endsection From e3062785471c1355e3f416c29107e9c11fa080f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 30 Jul 2021 14:09:29 +0200 Subject: [PATCH 30/60] Update authentication page --- app/PaymentDrivers/Mollie/CreditCard.php | 21 ++++++------- .../mollie/credit_card/authorize.blade.php | 31 +------------------ 2 files changed, 10 insertions(+), 42 deletions(-) diff --git a/app/PaymentDrivers/Mollie/CreditCard.php b/app/PaymentDrivers/Mollie/CreditCard.php index 959ecdc9c1c2..d67b6f249bf9 100644 --- a/app/PaymentDrivers/Mollie/CreditCard.php +++ b/app/PaymentDrivers/Mollie/CreditCard.php @@ -11,8 +11,8 @@ use App\Models\Payment; use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\MolliePaymentDriver; -use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\View\Factory; +use Illuminate\Http\RedirectResponse; use Illuminate\View\View; class CreditCard @@ -50,8 +50,6 @@ class CreditCard */ public function paymentResponse(PaymentResponseRequest $request) { - dd($this->mollie->gateway->mandates->listForId('cst_6S77wEkuQT')); - // TODO: Unit tests. $amount = number_format((float) $this->mollie->payment_hash->data->amount_with_fee, 2, '.', ''); @@ -162,15 +160,14 @@ class CreditCard return render('gateways.mollie.credit_card.authorize', $data); } - public function authorizeResponse($request) + /** + * Handle authorization response. + * + * @param mixed $request + * @return RedirectResponse + */ + public function authorizeResponse($request): RedirectResponse { - $customer = $this->mollie->gateway->customers->create([ - 'name' => $this->mollie->client->name, - 'metadata' => [ - 'id' => $this->mollie->client->hashed_id, - ], - ]); - - // Save $customer->id to database.. + return redirect()->route('client.payment_methods.index'); } } diff --git a/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php index 5e3ae5d75ef3..395a8d68bd11 100644 --- a/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/mollie/credit_card/authorize.blade.php @@ -1,37 +1,8 @@ @extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')]) -@section('gateway_head') - -@endsection - @section('gateway_content') -
- @csrf - - {{-- --}} - -
- - @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')]) - {{ ctrans('texts.credit_card') }} - @endcomponent - @component('portal.ninja2020.components.general.card-element-single') - Click the "Add Payment Method" button to complete test payment. - @endcomponent - - @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card']) - {{ ctrans('texts.add_payment_method') }} + {{ __('texts.payment_method_cannot_be_authorized_first') }} @endcomponent @endsection - -@section('gateway_footer') - -@endsection From 8af3cfe737c9028147bb8a42dd66eeeb79266a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 30 Jul 2021 14:36:14 +0200 Subject: [PATCH 31/60] Pay with credit card and save for future use --- .../Payments/PaymentResponseRequest.php | 5 ++ app/PaymentDrivers/Mollie/CreditCard.php | 77 +++++++++++++------ .../gateways/mollie/credit_card/pay.blade.php | 36 ++++----- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php b/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php index d3980f4b89af..3a917f339e2f 100644 --- a/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php +++ b/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php @@ -37,6 +37,11 @@ class PaymentResponseRequest extends FormRequest return PaymentHash::whereRaw('BINARY `hash`= ?', [$input['payment_hash']])->first(); } + public function shouldStoreToken(): bool + { + return (bool) $this->store_card; + } + public function prepareForValidation() { if ($this->has('store_card')) { diff --git a/app/PaymentDrivers/Mollie/CreditCard.php b/app/PaymentDrivers/Mollie/CreditCard.php index d67b6f249bf9..22806fb44e71 100644 --- a/app/PaymentDrivers/Mollie/CreditCard.php +++ b/app/PaymentDrivers/Mollie/CreditCard.php @@ -31,9 +31,9 @@ class CreditCard /** * Show the page for credit card payments. - * - * @param array $data - * @return Factory|View + * + * @param array $data + * @return Factory|View */ public function paymentView(array $data) { @@ -44,9 +44,9 @@ class CreditCard /** * Create a payment object. - * - * @param PaymentResponseRequest $request - * @return mixed + * + * @param PaymentResponseRequest $request + * @return mixed */ public function paymentResponse(PaymentResponseRequest $request) { @@ -58,16 +58,7 @@ class CreditCard ->withData('client_id', $this->mollie->client->id); try { - $customer = $this->mollie->gateway->customers->create([ - 'name' => $this->mollie->client->name, - 'metadata' => [ - 'id' => $this->mollie->client->hashed_id, - ], - ]); - - $payment = $this->mollie->gateway->payments->create([ - 'customerId' => $customer->id, - 'sequenceType' => 'first', + $data = [ 'amount' => [ 'currency' => $this->mollie->client->currency()->code, 'value' => $amount, @@ -80,7 +71,26 @@ class CreditCard ]), 'webhookUrl' => 'https://invoiceninja.com', 'cardToken' => $request->token, - ]); + ]; + + if ($request->shouldStoreToken()) { + $customer = $this->mollie->gateway->customers->create([ + 'name' => $this->mollie->client->name, + 'email' => $this->mollie->client->present()->email(), + 'metadata' => [ + 'id' => $this->mollie->client->hashed_id, + ], + ]); + + $data['customerId'] = $customer->id; + $data['sequenceType'] = 'first'; + + $this->mollie->payment_hash + ->withData('mollieCustomerId', $customer->id) + ->withData('shouldStoreToken', true); + } + + $payment = $this->mollie->gateway->payments->create($data); if ($payment->status === 'paid') { $this->mollie->logSuccessfulGatewayResponse( @@ -107,6 +117,27 @@ class CreditCard { $payment_hash = $this->mollie->payment_hash; + if ($payment_hash->data->shouldStoreToken) { + try { + $mandates = \iterator_to_array($this->mollie->gateway->mandates->listForId($payment_hash->data->mollieCustomerId)); + } catch (\Mollie\Api\Exceptions\ApiException $e) { + return $this->processUnsuccessfulPayment($e); + } + + $payment_meta = new \stdClass; + $payment_meta->exp_month = (string) $mandates[0]->details->cardExpiryDate; + $payment_meta->exp_year = (string) ''; + $payment_meta->brand = (string) $mandates[0]->details->cardLabel; + $payment_meta->last4 = (string) $mandates[0]->details->cardNumber; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $this->mollie->storeGatewayToken([ + 'token' => $mandates[0]->id, + 'payment_method_id' => GatewayType::CREDIT_CARD, + 'payment_meta' => $payment_meta, + ]); + } + $data = [ 'gateway_type_id' => GatewayType::CREDIT_CARD, 'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total, @@ -151,9 +182,9 @@ class CreditCard /** * Show authorization page. - * - * @param array $data - * @return Factory|View + * + * @param array $data + * @return Factory|View */ public function authorizeView(array $data) { @@ -162,9 +193,9 @@ class CreditCard /** * Handle authorization response. - * - * @param mixed $request - * @return RedirectResponse + * + * @param mixed $request + * @return RedirectResponse */ public function authorizeResponse($request): RedirectResponse { diff --git a/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php index d6a4d7db4f56..a99e55c6dfca 100644 --- a/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php @@ -29,26 +29,19 @@ ctrans('texts.credit_card')]) @include('portal.ninja2020.gateways.includes.payment_details') @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) - @if(count($tokens) > 0) - @foreach($tokens as $token) + @if (count($tokens) > 0) + @foreach ($tokens as $token) @endforeach @endif @endcomponent @@ -83,13 +76,7 @@ ctrans('texts.credit_card')]) @endcomponent - @component('portal.ninja2020.components.general.card-element-single') - If you want to save the card for future purchases, please click on the - Payment methods page and authorize the credit card manually. - - - After that, come back to this page and select your payment method. - @endcomponent + @include('portal.ninja2020.gateways.includes.save_card') @include('portal.ninja2020.gateways.includes.pay_now') @endsection @@ -193,6 +180,15 @@ ctrans('texts.credit_card')]) return; } + let tokenBillingCheckbox = document.querySelector( + 'input[name="token-billing-checkbox"]:checked' + ); + + if (tokenBillingCheckbox) { + document.querySelector('input[name="store_card"]').value = + tokenBillingCheckbox.value; + } + document.querySelector('input[name=token]').value = token; document.getElementById('server-response').submit(); }); From 541a1a825fd4eb3463b36fe31ce7ca2202740249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 30 Jul 2021 16:04:26 +0200 Subject: [PATCH 32/60] Pay with saved credit card --- app/PaymentDrivers/Mollie/CreditCard.php | 42 +++++++++++++++++-- .../gateways/mollie/credit_card/pay.blade.php | 28 ++++++++++++- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/app/PaymentDrivers/Mollie/CreditCard.php b/app/PaymentDrivers/Mollie/CreditCard.php index 22806fb44e71..c223d1392277 100644 --- a/app/PaymentDrivers/Mollie/CreditCard.php +++ b/app/PaymentDrivers/Mollie/CreditCard.php @@ -6,6 +6,7 @@ use App\Exceptions\PaymentFailed; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Jobs\Mail\PaymentFailureMailer; use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; use App\Models\GatewayType; use App\Models\Payment; use App\Models\PaymentType; @@ -57,6 +58,41 @@ class CreditCard ->withData('gateway_type_id', GatewayType::CREDIT_CARD) ->withData('client_id', $this->mollie->client->id); + if (!empty($request->token)) { + try { + $cgt = ClientGatewayToken::where('token', $request->token)->firstOrFail(); + + $payment = $this->mollie->gateway->payments->create([ + 'amount' => [ + 'currency' => $this->mollie->client->currency()->code, + 'value' => $amount, + ], + 'mandateId' => $request->token, + 'customerId' => $cgt->gateway_customer_reference, + 'sequenceType' => 'recurring', + 'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash), + 'webhookUrl' => 'https://invoiceninja.com', + ]); + + if ($payment->status === 'paid') { + $this->mollie->logSuccessfulGatewayResponse( + ['response' => $payment, 'data' => $this->mollie->payment_hash], + SystemLog::TYPE_MOLLIE + ); + + return $this->processSuccessfulPayment($payment); + } + + if ($payment->status === 'open') { + $this->mollie->payment_hash->withData('payment_id', $payment->id); + + return redirect($payment->getCheckoutUrl()); + } + } catch (\Exception $e) { + return $this->processUnsuccessfulPayment($e); + } + } + try { $data = [ 'amount' => [ @@ -70,7 +106,7 @@ class CreditCard 'hash' => $this->mollie->payment_hash->hash, ]), 'webhookUrl' => 'https://invoiceninja.com', - 'cardToken' => $request->token, + 'cardToken' => $request->gateway_response, ]; if ($request->shouldStoreToken()) { @@ -117,7 +153,7 @@ class CreditCard { $payment_hash = $this->mollie->payment_hash; - if ($payment_hash->data->shouldStoreToken) { + if (property_exists($payment_hash->data, 'shouldStoreToken') && $payment_hash->data->shouldStoreToken) { try { $mandates = \iterator_to_array($this->mollie->gateway->mandates->listForId($payment_hash->data->mollieCustomerId)); } catch (\Mollie\Api\Exceptions\ApiException $e) { @@ -135,7 +171,7 @@ class CreditCard 'token' => $mandates[0]->id, 'payment_method_id' => GatewayType::CREDIT_CARD, 'payment_meta' => $payment_meta, - ]); + ], ['gateway_customer_reference' => $payment_hash->data->mollieCustomerId]); } $data = [ diff --git a/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php index a99e55c6dfca..2e89246c3532 100644 --- a/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/mollie/credit_card/pay.blade.php @@ -47,7 +47,7 @@ ctrans('texts.credit_card')]) @endcomponent @component('portal.ninja2020.components.general.card-element-single') -
+