From 14ea63ced7d88bde8e606b927396865ef5f9e5f0 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 11 Oct 2021 18:05:47 +0200 Subject: [PATCH 01/25] Added models --- app/Models/Gateway.php | 4 ++-- app/Models/GatewayType.php | 6 ++++++ app/Models/PaymentType.php | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index f1c55fbae701..df3575c9f3ff 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -112,8 +112,8 @@ class Gateway extends StaticModel GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], - GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], - ]; + GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], + GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; case 39: return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 31b7083326b6..4eadceb1d5e4 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -32,6 +32,8 @@ class GatewayType extends StaticModel const GIROPAY = 15; const PRZELEWY24 = 16; const EPS = 17; + const BACS = 19; + const BECS = 20; public function gateway() { @@ -78,6 +80,10 @@ class GatewayType extends StaticModel return ctrans('texts.giropay'); case self::EPS: return ctrans('texts.eps'); + case self::BACS: + return ctrans('text.bacs'); + case self::BECS: + return ctrans('tets.becs'); default: return 'Undefined.'; break; diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index f0f6d8c91b3c..4558df44cfe3 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -50,6 +50,8 @@ class PaymentType extends StaticModel const GIROPAY = 39; const PRZELEWY24 = 40; const EPS = 41; + const BACS = 43; + const BECS = 44; public static function parseCardType($cardName) { From b268d68a577703b076df94dcd8fec5f263844df6 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 11 Oct 2021 18:06:53 +0200 Subject: [PATCH 02/25] Added language strings --- resources/lang/en/texts.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 0c6451c1ec9a..bc0b940b8328 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4327,10 +4327,12 @@ $LANG = array( 'giropay' => 'GiroPay', 'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.', 'eps' => 'EPS', + 'bacs' => 'Bacs Direct Debit', + 'becs' => 'BECS Direct Debit', 'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.', 'clone_to_expense' => 'Clone to expense', 'checkout' => 'Checkout', - + ); return $LANG; From e5d56f70b51b5c27faf6992a9fb963da744cf4be Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 11 Oct 2021 18:15:34 +0200 Subject: [PATCH 03/25] Added base classes --- app/PaymentDrivers/Stripe/BACS.php | 163 +++++++++++++++++++++ app/PaymentDrivers/Stripe/BECS.php | 163 +++++++++++++++++++++ app/PaymentDrivers/StripePaymentDriver.php | 22 +++ 3 files changed, 348 insertions(+) create mode 100644 app/PaymentDrivers/Stripe/BACS.php create mode 100644 app/PaymentDrivers/Stripe/BECS.php diff --git a/app/PaymentDrivers/Stripe/BACS.php b/app/PaymentDrivers/Stripe/BACS.php new file mode 100644 index 000000000000..160653b43e7b --- /dev/null +++ b/app/PaymentDrivers/Stripe/BACS.php @@ -0,0 +1,163 @@ +stripe = $stripe; + } + + public function authorizeView($data) + { + return render('gateways.stripe.bacs.authorize', $data); + } + + public function paymentView(array $data) { + $data['gateway'] = $this->stripe; + $data['payment_method_id'] = GatewayType::BACS; + $data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()); + $data['client'] = $this->stripe->client; + $data['customer'] = $this->stripe->findOrCreateCustomer()->id; + $data['country'] = $this->stripe->client->country->iso_3166_2; + $data['payment_hash'] = $this->stripe->payment_hash->hash; + + $intent = \Stripe\PaymentIntent::create([ + 'amount' => $data['stripe_amount'], + 'currency' => 'eur', + 'payment_method_types' => ['bacs_debit'], + 'customer' => $this->stripe->findOrCreateCustomer(), + 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), + + ]); + + $data['pi_client_secret'] = $intent->client_secret; + + $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]); + $this->stripe->payment_hash->save(); + + return render('gateways.stripe.bacs.pay', $data); + } + + public function paymentResponse(PaymentResponseRequest $request) + { + + $gateway_response = json_decode($request->gateway_response); + + $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all()); + $this->stripe->payment_hash->save(); + + if (property_exists($gateway_response, 'status') && $gateway_response->status == 'processing') { + + $this->stripe->init(); + $this->storePaymentMethod($gateway_response); + + return $this->processSuccessfulPayment($gateway_response->id); + } + + return $this->processUnsuccessfulPayment(); + + } + + public function processSuccessfulPayment(string $payment_intent) + { + $this->stripe->init(); + + $data = [ + 'payment_method' => $payment_intent, + 'payment_type' => PaymentType::BACS, + 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), + 'transaction_reference' => $payment_intent, + 'gateway_type_id' => GatewayType::BACS, + ]; + + $this->stripe->createPayment($data, Payment::STATUS_PENDING); + + SystemLogger::dispatch( + ['response' => $this->stripe->payment_hash->data, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + return redirect()->route('client.payments.index'); + } + + public function processUnsuccessfulPayment() + { + $server_response = $this->stripe->payment_hash->data; + + PaymentFailureMailer::dispatch( + $this->stripe->client, + $server_response, + $this->stripe->client->company, + $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()) + ); + + $message = [ + 'server_response' => $server_response, + 'data' => $this->stripe->payment_hash->data, + ]; + + SystemLogger::dispatch( + $message, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + throw new PaymentFailed('Failed to process the payment.', 500); + } + + + private function storePaymentMethod($intent) + { + try { + + $method = $this->stripe->getStripePaymentMethod($intent->payment_method); + + $payment_meta = new \stdClass; + $payment_meta->brand = (string) \sprintf('%s (%s)', $method->sepa_debit->bank_code, ctrans('texts.bacs')); + $payment_meta->last4 = (string) $method->sepa_debit->last4; + $payment_meta->state = 'authorized'; + $payment_meta->type = GatewayType::BACS; + + $data = [ + 'payment_meta' => $payment_meta, + 'token' => $intent->payment_method, + 'payment_method_id' => GatewayType::BACS, + ]; + + $this->stripe->storeGatewayToken($data, ['gateway_customer_reference' => $method->customer]); + } catch (\Exception $e) { + return $this->stripe->processInternallyFailedPayment($this->stripe, $e); + } + } +} diff --git a/app/PaymentDrivers/Stripe/BECS.php b/app/PaymentDrivers/Stripe/BECS.php new file mode 100644 index 000000000000..178749565297 --- /dev/null +++ b/app/PaymentDrivers/Stripe/BECS.php @@ -0,0 +1,163 @@ +stripe = $stripe; + } + + public function authorizeView($data) + { + return render('gateways.stripe.becs.authorize', $data); + } + + public function paymentView(array $data) { + $data['gateway'] = $this->stripe; + $data['payment_method_id'] = GatewayType::BECS; + $data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()); + $data['client'] = $this->stripe->client; + $data['customer'] = $this->stripe->findOrCreateCustomer()->id; + $data['country'] = $this->stripe->client->country->iso_3166_2; + $data['payment_hash'] = $this->stripe->payment_hash->hash; + + $intent = \Stripe\PaymentIntent::create([ + 'amount' => $data['stripe_amount'], + 'currency' => 'eur', + 'payment_method_types' => ['au_becs_debit'], + 'customer' => $this->stripe->findOrCreateCustomer(), + 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), + + ]); + + $data['pi_client_secret'] = $intent->client_secret; + + $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]); + $this->stripe->payment_hash->save(); + + return render('gateways.stripe.becs.pay', $data); + } + + public function paymentResponse(PaymentResponseRequest $request) + { + + $gateway_response = json_decode($request->gateway_response); + + $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all()); + $this->stripe->payment_hash->save(); + + if (property_exists($gateway_response, 'status') && $gateway_response->status == 'processing') { + + $this->stripe->init(); + $this->storePaymentMethod($gateway_response); + + return $this->processSuccessfulPayment($gateway_response->id); + } + + return $this->processUnsuccessfulPayment(); + + } + + public function processSuccessfulPayment(string $payment_intent) + { + $this->stripe->init(); + + $data = [ + 'payment_method' => $payment_intent, + 'payment_type' => PaymentType::BECS, + 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), + 'transaction_reference' => $payment_intent, + 'gateway_type_id' => GatewayType::BECS, + ]; + + $this->stripe->createPayment($data, Payment::STATUS_PENDING); + + SystemLogger::dispatch( + ['response' => $this->stripe->payment_hash->data, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + return redirect()->route('client.payments.index'); + } + + public function processUnsuccessfulPayment() + { + $server_response = $this->stripe->payment_hash->data; + + PaymentFailureMailer::dispatch( + $this->stripe->client, + $server_response, + $this->stripe->client->company, + $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()) + ); + + $message = [ + 'server_response' => $server_response, + 'data' => $this->stripe->payment_hash->data, + ]; + + SystemLogger::dispatch( + $message, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + throw new PaymentFailed('Failed to process the payment.', 500); + } + + + private function storePaymentMethod($intent) + { + try { + + $method = $this->stripe->getStripePaymentMethod($intent->payment_method); + + $payment_meta = new \stdClass; + $payment_meta->brand = (string) \sprintf('%s (%s)', $method->sepa_debit->bank_code, ctrans('texts.becs')); + $payment_meta->last4 = (string) $method->sepa_debit->last4; + $payment_meta->state = 'authorized'; + $payment_meta->type = GatewayType::BECS; + + $data = [ + 'payment_meta' => $payment_meta, + 'token' => $intent->payment_method, + 'payment_method_id' => GatewayType::BECS, + ]; + + $this->stripe->storeGatewayToken($data, ['gateway_customer_reference' => $method->customer]); + } catch (\Exception $e) { + return $this->stripe->processInternallyFailedPayment($this->stripe, $e); + } + } +} diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index b70ff0715dce..3aaf0f158074 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -38,6 +38,8 @@ use App\PaymentDrivers\Stripe\GIROPAY; use App\PaymentDrivers\Stripe\iDeal; use App\PaymentDrivers\Stripe\EPS; use App\PaymentDrivers\Stripe\Bancontact; +use App\PaymentDrivers\Stripe\BECS; +use App\PaymentDrivers\Stripe\BACS; use App\PaymentDrivers\Stripe\UpdatePaymentMethods; use App\PaymentDrivers\Stripe\Utilities; use App\Utils\Traits\MakesHash; @@ -87,6 +89,8 @@ class StripePaymentDriver extends BaseDriver GatewayType::IDEAL => iDeal::class, GatewayType::EPS => EPS::class, GatewayType::BANCONTACT => Bancontact::class, + GatewayType::BACS => BACS::class, + GatewayType::BECS => BECS::class, ]; const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; @@ -200,6 +204,20 @@ class StripePaymentDriver extends BaseDriver && in_array($this->client->country->iso_3166_3, ["BEL"])) $types[] = GatewayType::BANCONTACT; + if ($this->client + && $this->client->currency() + && ($this->client->currency()->code == 'GBR') + && isset($this->client->country) + && in_array($this->client->country->iso_3166_3, ["GBP", "DEU"])) + $types[] = GatewayType::BACS; + + if ($this->client + && $this->client->currency() + && ($this->client->currency()->code == 'AUD') + && isset($this->client->country) + && in_array($this->client->country->iso_3166_3, ["AUS", "DEU"])) + $types[] = GatewayType::BECS; + return $types; } @@ -235,6 +253,10 @@ class StripePaymentDriver extends BaseDriver return 'gateways.stripe.eps'; case GatewayType::BANCONTACT: return 'gateways.stripe.bancontact'; + case GatewayType::BACS: + return 'gateways.stripe.bacs'; + case GatewayType::BECS: + return 'gateways.stripe.becs'; default: break; } From 4df070ce8a4f1807020ec48a95780347a0526869 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 11 Oct 2021 18:22:40 +0200 Subject: [PATCH 04/25] Added views --- .../gateways/stripe/bacs/authorize.blade.php | 84 +++++++++++++++++++ .../gateways/stripe/bacs/bacs_debit.blade.php | 29 +++++++ .../gateways/stripe/bacs/pay.blade.php | 29 +++++++ .../gateways/stripe/becs/authorize.blade.php | 84 +++++++++++++++++++ .../gateways/stripe/becs/becs_debit.blade.php | 29 +++++++ .../gateways/stripe/becs/pay.blade.php | 29 +++++++ 6 files changed, 284 insertions(+) create mode 100644 resources/views/portal/ninja2020/gateways/stripe/bacs/authorize.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/bacs/bacs_debit.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/bacs/pay.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/becs/authorize.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/becs/pay.blade.php diff --git a/resources/views/portal/ninja2020/gateways/stripe/bacs/authorize.blade.php b/resources/views/portal/ninja2020/gateways/stripe/bacs/authorize.blade.php new file mode 100644 index 000000000000..78f859b75c30 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/bacs/authorize.blade.php @@ -0,0 +1,84 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'SEPA', 'card_title' => 'SEPA-Lastschrift']) + +@section('gateway_head') + @if($gateway->company_gateway->getConfigField('account_id')) + + + @else + + @endif +@endsection + +@section('gateway_content') + @if(session()->has('sepa_error')) +
+

{{ session('sepa_error') }}

+
+ @endif + +
+ @csrf + + + + + + + +
+ + + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_type')]) + + + {{ __('texts.individual_account') }} + + + + {{ __('texts.company_account') }} + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.country')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.currency')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.routing_number')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_number')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element-single') + + + @endcomponent + + @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'save-button']) + {{ ctrans('texts.add_payment_method') }} + @endcomponent +@endsection + +@section('gateway_footer') + + +@endsection diff --git a/resources/views/portal/ninja2020/gateways/stripe/bacs/bacs_debit.blade.php b/resources/views/portal/ninja2020/gateways/stripe/bacs/bacs_debit.blade.php new file mode 100644 index 000000000000..f6a6b23d9aab --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/bacs/bacs_debit.blade.php @@ -0,0 +1,29 @@ +
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.name')]) + +
+ @csrf + + + + + + + + + +
+ + +
+
+ @endcomponent +
diff --git a/resources/views/portal/ninja2020/gateways/stripe/bacs/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/bacs/pay.blade.php new file mode 100644 index 000000000000..979946e63d7a --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/bacs/pay.blade.php @@ -0,0 +1,29 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'BACS', 'card_title' => 'BACS']) + +@section('gateway_head') + + + + + + +@endsection + +@section('gateway_content') + + + @include('portal.ninja2020.gateways.includes.payment_details') + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')]) + {{ ctrans('texts.bacs') }} ({{ ctrans('texts.bank_transfer') }}) + @endcomponent + + @include('portal.ninja2020.gateways.stripe.bacs.bacs_debit') + @include('portal.ninja2020.gateways.includes.save_card') + @include('portal.ninja2020.gateways.includes.pay_now') +@endsection + +@push('footer') + + +@endpush diff --git a/resources/views/portal/ninja2020/gateways/stripe/becs/authorize.blade.php b/resources/views/portal/ninja2020/gateways/stripe/becs/authorize.blade.php new file mode 100644 index 000000000000..78f859b75c30 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/becs/authorize.blade.php @@ -0,0 +1,84 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'SEPA', 'card_title' => 'SEPA-Lastschrift']) + +@section('gateway_head') + @if($gateway->company_gateway->getConfigField('account_id')) + + + @else + + @endif +@endsection + +@section('gateway_content') + @if(session()->has('sepa_error')) +
+

{{ session('sepa_error') }}

+
+ @endif + +
+ @csrf + + + + + + + +
+ + + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_type')]) + + + {{ __('texts.individual_account') }} + + + + {{ __('texts.company_account') }} + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.country')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.currency')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.routing_number')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_number')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element-single') + + + @endcomponent + + @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'save-button']) + {{ ctrans('texts.add_payment_method') }} + @endcomponent +@endsection + +@section('gateway_footer') + + +@endsection diff --git a/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php b/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php new file mode 100644 index 000000000000..523f93dea8bc --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php @@ -0,0 +1,29 @@ +
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.name')]) + +
+ @csrf + + + + + + + + + +
+ + +
+
+ @endcomponent +
diff --git a/resources/views/portal/ninja2020/gateways/stripe/becs/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/becs/pay.blade.php new file mode 100644 index 000000000000..207391521acd --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/becs/pay.blade.php @@ -0,0 +1,29 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'BECS', 'card_title' => 'BECS']) + +@section('gateway_head') + + + + + + +@endsection + +@section('gateway_content') + + + @include('portal.ninja2020.gateways.includes.payment_details') + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')]) + {{ ctrans('texts.becs') }} ({{ ctrans('texts.bank_transfer') }}) + @endcomponent + + @include('portal.ninja2020.gateways.stripe.becs.becs_debit') + @include('portal.ninja2020.gateways.includes.save_card') + @include('portal.ninja2020.gateways.includes.pay_now') +@endsection + +@push('footer') + + +@endpush From 2dc8a51dd6dcb0bfb866801e2c72c8dc056758b5 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Tue, 12 Oct 2021 16:03:21 +0200 Subject: [PATCH 05/25] Added language strings --- resources/lang/en/texts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index bc0b940b8328..57f9c076ebc2 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4327,8 +4327,8 @@ $LANG = array( 'giropay' => 'GiroPay', 'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.', 'eps' => 'EPS', - 'bacs' => 'Bacs Direct Debit', 'becs' => 'BECS Direct Debit', + 'becs_mandate' => 'By providing your bank account details, you agree to this Direct Debit Request and the Direct Debit Request service agreement, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.', 'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.', 'clone_to_expense' => 'Clone to expense', 'checkout' => 'Checkout', From aba570a2e5df5738f84f4b49dbe7c8e9fbf5e87f Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Tue, 12 Oct 2021 16:14:57 +0200 Subject: [PATCH 06/25] Added js for becs --- public/js/clients/payments/stripe-becs.js | 1 + .../payments/stripe-becs.js.LICENSE.txt | 9 ++ resources/js/clients/payments/stripe-becs.js | 136 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 public/js/clients/payments/stripe-becs.js create mode 100644 public/js/clients/payments/stripe-becs.js.LICENSE.txt create mode 100644 resources/js/clients/payments/stripe-becs.js diff --git a/public/js/clients/payments/stripe-becs.js b/public/js/clients/payments/stripe-becs.js new file mode 100644 index 000000000000..817b06574349 --- /dev/null +++ b/public/js/clients/payments/stripe-becs.js @@ -0,0 +1 @@ +!function(n){var o={};function r(e){if(o[e])return o[e].exports;var t=o[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=n,r.c=o,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/",r(r.s=28)}({28:function(e,t,n){e.exports=n("guV3")},guV3:function(e,t){var n;function o(e,t){for(var n=0;n svg").classList.add("hidden"),document.querySelector("#pay-now > span").classList.remove("hidden")}}]),u),c=null!==(n=null===(c=document.querySelector('meta[name="stripe-publishable-key"]'))||void 0===c?void 0:c.content)&&void 0!==n?n:"",i=null!==(n=null===(n=document.querySelector('meta[name="stripe-account-id"]'))||void 0===n?void 0:n.content)&&void 0!==n?n:"";function u(e,t){var n=this;!function(e){if(!(e instanceof u))throw new TypeError("Cannot call a class as a function")}(this),r(this,"setupStripe",function(){n.stripe=Stripe(n.key),n.stripeConnect&&(n.stripe.stripeAccount=i);const e=n.stripe.elements();var t={style:{base:{color:"#32325d",fontSize:"16px","::placeholder":{color:"#aab7c4"},":-webkit-autofill":{color:"#32325d"}},invalid:{color:"#fa755a",iconColor:"#fa755a",":-webkit-autofill":{color:"#fa755a"}}},disabled:!1,hideIcon:!1,iconStyle:"default"};return n.auBankAccount=e.create("auBankAccount",t),n.auBankAccount.mount("#becs-iban"),n}),r(this,"handle",function(){document.getElementById("pay-now").addEventListener("click",e=>{let t=document.getElementById("errors");return""===document.getElementById("becs-name").value?(document.getElementById("becs-name").focus(),t.textContent="Name required.",void(t.hidden=!1)):""===document.getElementById("becs-email-address").value?(document.getElementById("becs-email-address").focus(),t.textContent="Email required.",void(t.hidden=!1)):document.getElementById("becs-mandate-acceptance").checked?(document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),void this.stripe.confirmAuBecsDebitPayment(document.querySelector("meta[name=pi-client-secret").content,{payment_method:{au_becs_debit:this.auBankAccount,billing_details:{name:document.getElementById("becs-name").value,email:document.getElementById("becs-email-address").value}}}).then(e=>e.error?this.handleFailure(e.error.message):this.handleSuccess(e))):(document.getElementById("becs-mandate-acceptance").focus(),t.textContent="Accept Terms",t.hidden=!1,void console.log("Terms"))})}),this.key=e,this.errors=document.getElementById("errors"),this.stripeConnect=t}new a(c,i).setupStripe().handle()}}); diff --git a/public/js/clients/payments/stripe-becs.js.LICENSE.txt b/public/js/clients/payments/stripe-becs.js.LICENSE.txt new file mode 100644 index 000000000000..585c6ab0e4fc --- /dev/null +++ b/public/js/clients/payments/stripe-becs.js.LICENSE.txt @@ -0,0 +1,9 @@ +/** + * 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://opensource.org/licenses/AAL + */ diff --git a/resources/js/clients/payments/stripe-becs.js b/resources/js/clients/payments/stripe-becs.js new file mode 100644 index 000000000000..5aaa85ee3933 --- /dev/null +++ b/resources/js/clients/payments/stripe-becs.js @@ -0,0 +1,136 @@ +/** + * 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://opensource.org/licenses/AAL + */ + +class ProcessBECS { + constructor(key, stripeConnect) { + this.key = key; + this.errors = document.getElementById('errors'); + this.stripeConnect = stripeConnect; + } + + setupStripe = () => { + this.stripe = Stripe(this.key); + + if(this.stripeConnect) + this.stripe.stripeAccount = stripeConnect; + const elements = this.stripe.elements(); + const style = { + base: { + color: '#32325d', + fontSize: '16px', + '::placeholder': { + color: '#aab7c4' + }, + ':-webkit-autofill': { + color: '#32325d', + }, + }, + invalid: { + color: '#fa755a', + iconColor: '#fa755a', + ':-webkit-autofill': { + color: '#fa755a', + }, + } + }; + + const options = { + style: style, + disabled: false, + hideIcon: false, + iconStyle: "default", // or "solid" + }; + this.auBankAccount = elements.create("auBankAccount", options); + this.auBankAccount.mount("#becs-iban"); + return this; + }; + + handle = () => { + document.getElementById('pay-now').addEventListener('click', (e) => { + + let errors = document.getElementById('errors'); + + if (document.getElementById('becs-name').value === "") { + document.getElementById('becs-name').focus(); + errors.textContent = "Name required."; + errors.hidden = false; + return; + } + + if (document.getElementById('becs-email-address').value === "") { + document.getElementById('becs-email-address').focus(); + errors.textContent = "Email required."; + errors.hidden = false; + return ; + } + + + if (!document.getElementById('becs-mandate-acceptance').checked) { + document.getElementById('becs-mandate-acceptance').focus(); + errors.textContent = "Accept Terms"; + errors.hidden = false; + console.log("Terms"); + return ; + } + + document.getElementById('pay-now').disabled = true; + document.querySelector('#pay-now > svg').classList.remove('hidden'); + document.querySelector('#pay-now > span').classList.add('hidden'); + + this.stripe.confirmAuBecsDebitPayment( + document.querySelector('meta[name=pi-client-secret').content, + { + payment_method: { + au_becs_debit: this.auBankAccount, + billing_details: { + name: document.getElementById("becs-name").value, + email: document.getElementById("becs-email-address").value, + }, + }, + } + ).then((result) => { + if (result.error) { + return this.handleFailure(result.error.message); + } + + return this.handleSuccess(result); + }); + }); + }; + + handleSuccess(result) { + document.querySelector( + 'input[name="gateway_response"]' + ).value = JSON.stringify(result.paymentIntent); + + document.getElementById('server-response').submit(); + } + + handleFailure(message) { + let errors = document.getElementById('errors'); + + errors.textContent = ''; + errors.textContent = message; + errors.hidden = false; + + document.getElementById('pay-now').disabled = false; + document.querySelector('#pay-now > svg').classList.add('hidden'); + document.querySelector('#pay-now > span').classList.remove('hidden'); + } +} + +const publishableKey = document.querySelector( + 'meta[name="stripe-publishable-key"]' +)?.content ?? ''; + +const stripeConnect = + document.querySelector('meta[name="stripe-account-id"]')?.content ?? ''; + +new ProcessBECS(publishableKey, stripeConnect).setupStripe().handle(); From 175552b77f274f4dabf09eb6632c758da8b88846 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Tue, 12 Oct 2021 16:15:09 +0200 Subject: [PATCH 07/25] Adapted base files --- app/PaymentDrivers/Stripe/BECS.php | 1 + .../ninja2020/gateways/stripe/becs/becs_debit.blade.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/PaymentDrivers/Stripe/BECS.php b/app/PaymentDrivers/Stripe/BECS.php index 178749565297..126bf0e07283 100644 --- a/app/PaymentDrivers/Stripe/BECS.php +++ b/app/PaymentDrivers/Stripe/BECS.php @@ -49,6 +49,7 @@ class BECS 'amount' => $data['stripe_amount'], 'currency' => 'eur', 'payment_method_types' => ['au_becs_debit'], + 'setup_future_usage' => 'off_session', 'customer' => $this->stripe->findOrCreateCustomer(), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), diff --git a/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php b/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php index 523f93dea8bc..cd41c59d29e6 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/becs/becs_debit.blade.php @@ -10,10 +10,10 @@