From ecdbd8a5849ba4ece448744944e53029d55c5bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 15:52:32 +0100 Subject: [PATCH 01/10] Scaffold InstantBankPay --- .../GoCardless/InstantBankPay.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/PaymentDrivers/GoCardless/InstantBankPay.php diff --git a/app/PaymentDrivers/GoCardless/InstantBankPay.php b/app/PaymentDrivers/GoCardless/InstantBankPay.php new file mode 100644 index 000000000..0d7ceb967 --- /dev/null +++ b/app/PaymentDrivers/GoCardless/InstantBankPay.php @@ -0,0 +1,18 @@ + Date: Thu, 11 Nov 2021 15:53:05 +0100 Subject: [PATCH 02/10] Define INSTANT_BANK_PAY constant --- app/Models/GatewayType.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index d6c51bc4e..fb4d60248 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -37,6 +37,7 @@ class GatewayType extends StaticModel const DIRECT_DEBIT = 18; const ACSS = 19; const BECS = 20; + const INSTANT_BANK_PAY = 21; public function gateway() { From d45c5a1e41390c15074325e52f8d70e4af3e2ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 15:54:16 +0100 Subject: [PATCH 03/10] Add INSTANT_BANK_PAY to Gateway --- app/Models/Gateway.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 1f60070b0..68b56001a 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -159,7 +159,8 @@ class Gateway extends StaticModel return [ GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']], // GoCardless GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']], - GatewayType::SEPA => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']] + GatewayType::SEPA => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']], + GatewayType::INSTANT_BANK_PAY => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']], ]; break; case 58: From 085bcbe3c32321f16899d498d153bd2a76b7b79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 15:57:01 +0100 Subject: [PATCH 04/10] Add InstantBankPay to GoCardlessPaymentDriver --- app/PaymentDrivers/GoCardlessPaymentDriver.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index db2dd56d1..948df3c67 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -40,6 +40,7 @@ class GoCardlessPaymentDriver extends BaseDriver GatewayType::BANK_TRANSFER => \App\PaymentDrivers\GoCardless\ACH::class, GatewayType::DIRECT_DEBIT => \App\PaymentDrivers\GoCardless\DirectDebit::class, GatewayType::SEPA => \App\PaymentDrivers\GoCardless\SEPA::class, + GatewayType::INSTANT_BANK_PAY => \App\PaymentDrivers\GoCardless\InstantBankPay::class, ]; const SYSTEM_LOG_TYPE = SystemLog::TYPE_GOCARDLESS; @@ -77,6 +78,8 @@ class GoCardlessPaymentDriver extends BaseDriver $types[] = GatewayType::SEPA; } + $types[] = GatewayType::INSTANT_BANK_PAY; + return $types; } From 619803f2be9e8bc22dc103736c53f0899e2c9722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 15:57:41 +0100 Subject: [PATCH 05/10] Initialize GoCardlessPro\Client --- app/PaymentDrivers/GoCardless/InstantBankPay.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/PaymentDrivers/GoCardless/InstantBankPay.php b/app/PaymentDrivers/GoCardless/InstantBankPay.php index 0d7ceb967..824121f5f 100644 --- a/app/PaymentDrivers/GoCardless/InstantBankPay.php +++ b/app/PaymentDrivers/GoCardless/InstantBankPay.php @@ -5,9 +5,19 @@ namespace App\PaymentDrivers\GoCardless; use Illuminate\Http\Request; use App\PaymentDrivers\Common\MethodInterface; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\PaymentDrivers\GoCardlessPaymentDriver; class InstantBankPay implements MethodInterface { + protected GoCardlessPaymentDriver $go_cardless; + + public function __construct(GoCardlessPaymentDriver $go_cardless) + { + $this->go_cardless = $go_cardless; + + $this->go_cardless->init(); + } + public function authorizeView(array $data) { } public function authorizeResponse(Request $request) { } From a844553cf320c4d0892a7ccc77f9616b38bdc061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 17:49:31 +0100 Subject: [PATCH 06/10] Add INSTANT_BANK_PAY To PaymentType --- app/Models/PaymentType.php | 1 + ...11_11_163121_add_instant_bank_transfer.php | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 database/migrations/2021_11_11_163121_add_instant_bank_transfer.php diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index 4370b3217..3a28863de 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -53,6 +53,7 @@ class PaymentType extends StaticModel const DIRECT_DEBIT = 42; const BECS = 43; const ACSS = 44; + const INSTANT_BANK_PAY = 45; public static function parseCardType($cardName) { diff --git a/database/migrations/2021_11_11_163121_add_instant_bank_transfer.php b/database/migrations/2021_11_11_163121_add_instant_bank_transfer.php new file mode 100644 index 000000000..15a2ba3ff --- /dev/null +++ b/database/migrations/2021_11_11_163121_add_instant_bank_transfer.php @@ -0,0 +1,26 @@ +id = PaymentType::INSTANT_BANK_PAY; + $type->name = 'Instant Bank Pay'; + $type->gateway_type_id = GatewayType::INSTANT_BANK_PAY; + + $type->save(); + } +} From 0e5b5393d355e2a3bf8fdc8d4745f3226176c426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 17:51:04 +0100 Subject: [PATCH 07/10] Payments --- .../Gateways/GoCardlessController.php | 29 ++++ .../Gateways/GoCardless/IbpRequest.php | 67 ++++++++ .../GoCardless/InstantBankPay.php | 145 +++++++++++++++++- .../GoCardlessPaymentDriver.php | 4 +- routes/web.php | 1 + 5 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/Gateways/GoCardlessController.php create mode 100644 app/Http/Requests/Gateways/GoCardless/IbpRequest.php diff --git a/app/Http/Controllers/Gateways/GoCardlessController.php b/app/Http/Controllers/Gateways/GoCardlessController.php new file mode 100644 index 000000000..fce5d5eea --- /dev/null +++ b/app/Http/Controllers/Gateways/GoCardlessController.php @@ -0,0 +1,29 @@ +getCompanyGateway() + ->driver($request->getClient()) + ->setPaymentMethod(GatewayType::INSTANT_BANK_PAY) + ->processPaymentResponse($request); + } +} diff --git a/app/Http/Requests/Gateways/GoCardless/IbpRequest.php b/app/Http/Requests/Gateways/GoCardless/IbpRequest.php new file mode 100644 index 000000000..8caeaf64c --- /dev/null +++ b/app/Http/Requests/Gateways/GoCardless/IbpRequest.php @@ -0,0 +1,67 @@ +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)->firstOrFail(); + } + + public function getClient(): ?Client + { + return Client::find($this->getPaymentHash()->data->client_id); + } +} diff --git a/app/PaymentDrivers/GoCardless/InstantBankPay.php b/app/PaymentDrivers/GoCardless/InstantBankPay.php index 824121f5f..8ff808af9 100644 --- a/app/PaymentDrivers/GoCardless/InstantBankPay.php +++ b/app/PaymentDrivers/GoCardless/InstantBankPay.php @@ -2,9 +2,16 @@ namespace App\PaymentDrivers\GoCardless; +use App\Exceptions\PaymentFailed; use Illuminate\Http\Request; use App\PaymentDrivers\Common\MethodInterface; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Jobs\Mail\PaymentFailureMailer; +use App\Jobs\Util\SystemLogger; +use App\Models\GatewayType; +use App\Models\Payment; +use App\Models\PaymentType; +use App\Models\SystemLog; use App\PaymentDrivers\GoCardlessPaymentDriver; class InstantBankPay implements MethodInterface @@ -18,11 +25,141 @@ class InstantBankPay implements MethodInterface $this->go_cardless->init(); } - public function authorizeView(array $data) { } + public function authorizeView(array $data) + { + } - public function authorizeResponse(Request $request) { } + public function authorizeResponse(Request $request) + { + } - public function paymentView(array $data) { } + public function paymentView(array $data) + { + try { + $billing_request = $this->go_cardless->gateway->billingRequests()->create([ + 'params' => [ + 'payment_request' => [ + 'description' => ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number'), + 'amount' => (string) $data['amount_with_fee'] * 100, + 'currency' => $this->go_cardless->client->getCurrencyCode(), + ], + ] + ]); - public function paymentResponse(PaymentResponseRequest $request) { } + $billing_request_flow = $this->go_cardless->gateway->billingRequestFlows()->create([ + 'params' => [ + 'redirect_uri' => route('gocardless.ibp_redirect', [ + 'company_key' => $this->go_cardless->company_gateway->company->company_key, + 'company_gateway_id' => $this->go_cardless->company_gateway->hashed_id, + 'hash' => $this->go_cardless->payment_hash->hash, + ]), + 'links' => [ + 'billing_request' => $billing_request->id, + ] + ], + ]); + + $this->go_cardless->payment_hash + ->withData('client_id', $this->go_cardless->client->id) + ->withData('billing_request', $billing_request->id) + ->withData('billing_request_flow', $billing_request_flow->id); + + return redirect( + $billing_request_flow->authorisation_url + ); + } catch (\Exception $exception) { + throw $exception; + } + } + + public function paymentResponse($request) + { + $this->go_cardless->setPaymentHash( + $request->getPaymentHash() + ); + + try { + $billing_request = $this->go_cardless->gateway->billingRequests()->get( + $this->go_cardless->payment_hash->data->billing_request + ); + + $payment = $this->go_cardless->gateway->payments()->get( + $billing_request->payment_request->links->payment + ); + + if ($billing_request->status === 'fulfilled') { + return $this->processSuccessfulPayment($payment); + } + + return $this->processUnsuccessfulPayment($payment); + } catch (\Exception $exception) { + throw new PaymentFailed( + $exception->getMessage(), + $exception->getCode() + ); + } + } + + /** + * Handle pending payments for Instant Bank Transfer. + * + * @param ResourcesPayment $payment + * @param array $data + * @return RedirectResponse + */ + public function processSuccessfulPayment(\GoCardlessPro\Resources\Payment $payment, array $data = []) + { + $data = [ + 'payment_method' => $payment->links->mandate, + 'payment_type' => PaymentType::INSTANT_BANK_PAY, + 'amount' => $this->go_cardless->payment_hash->data->amount_with_fee, + 'transaction_reference' => $payment->id, + 'gateway_type_id' => GatewayType::INSTANT_BANK_PAY, + ]; + + $payment = $this->go_cardless->createPayment($data, Payment::STATUS_COMPLETED); + + SystemLogger::dispatch( + ['response' => $payment, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_GOCARDLESS, + $this->go_cardless->client, + $this->go_cardless->client->company, + ); + + return redirect()->route('client.payments.show', ['payment' => $this->go_cardless->encodePrimaryKey($payment->id)]); + } + + /** + * Process unsuccessful payments for Direct Debit. + * + * @param ResourcesPayment $payment + * @return never + */ + public function processUnsuccessfulPayment(\GoCardlessPro\Resources\Payment $payment) + { + PaymentFailureMailer::dispatch($this->go_cardless->client, $payment->status, $this->go_cardless->client->company, $this->go_cardless->payment_hash->data->amount_with_fee); + + PaymentFailureMailer::dispatch( + $this->go_cardless->client, + $payment, + $this->go_cardless->client->company, + $payment->amount + ); + + $message = [ + 'server_response' => $payment, + 'data' => $this->go_cardless->payment_hash->data, + ]; + + SystemLogger::dispatch( + $message, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_GOCARDLESS, + $this->go_cardless->client, + $this->go_cardless->client->company, + ); + } } diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 948df3c67..c7d890e63 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -78,7 +78,9 @@ class GoCardlessPaymentDriver extends BaseDriver $types[] = GatewayType::SEPA; } - $types[] = GatewayType::INSTANT_BANK_PAY; + if ($this->client->currency()->code === 'GBP') { + $types[] = GatewayType::INSTANT_BANK_PAY; + } return $types; } diff --git a/routes/web.php b/routes/web.php index 8c3a8e85d..b8180d7f1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -43,3 +43,4 @@ Route::get('stripe/completed', 'StripeConnectController@completed')->name('strip Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Checkout3dsController@index')->name('checkout.3ds_redirect'); Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Mollie3dsController@index')->name('mollie.3ds_redirect'); +Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\GoCardlessController@ibpRedirect')->name('gocardless.ibp_redirect'); From a7c7f58bb56e239aac73e83eaa2612f2b1190253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 17:52:05 +0100 Subject: [PATCH 08/10] Authorization --- .../GoCardless/InstantBankPay.php | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/app/PaymentDrivers/GoCardless/InstantBankPay.php b/app/PaymentDrivers/GoCardless/InstantBankPay.php index 8ff808af9..d65944f0c 100644 --- a/app/PaymentDrivers/GoCardless/InstantBankPay.php +++ b/app/PaymentDrivers/GoCardless/InstantBankPay.php @@ -13,6 +13,8 @@ use App\Models\Payment; use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\GoCardlessPaymentDriver; +use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Http\RedirectResponse; class InstantBankPay implements MethodInterface { @@ -25,12 +27,28 @@ class InstantBankPay implements MethodInterface $this->go_cardless->init(); } - public function authorizeView(array $data) + /** + * Authorization page for Instant Bank Pay. + * + * @param array $data + * @return RedirectResponse + * @throws BindingResolutionException + */ + public function authorizeView(array $data): RedirectResponse { + return redirect()->back(); } - public function authorizeResponse(Request $request) + /** + * Handle authorization for Instant Bank Pay. + * + * @param array $data + * @return RedirectResponse + * @throws BindingResolutionException + */ + public function authorizeResponse(Request $request): RedirectResponse { + return redirect()->back(); } public function paymentView(array $data) @@ -94,7 +112,7 @@ class InstantBankPay implements MethodInterface return $this->processUnsuccessfulPayment($payment); } catch (\Exception $exception) { throw new PaymentFailed( - $exception->getMessage(), + $exception->getMessage(), $exception->getCode() ); } @@ -131,7 +149,7 @@ class InstantBankPay implements MethodInterface return redirect()->route('client.payments.show', ['payment' => $this->go_cardless->encodePrimaryKey($payment->id)]); } - /** + /** * Process unsuccessful payments for Direct Debit. * * @param ResourcesPayment $payment From fd08ebc6cb5d19d8a9eee59708c821fc1b32ff0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 17:52:24 +0100 Subject: [PATCH 09/10] Apply styles --- .../GoCardless/InstantBankPay.php | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app/PaymentDrivers/GoCardless/InstantBankPay.php b/app/PaymentDrivers/GoCardless/InstantBankPay.php index d65944f0c..3ba3cdc8d 100644 --- a/app/PaymentDrivers/GoCardless/InstantBankPay.php +++ b/app/PaymentDrivers/GoCardless/InstantBankPay.php @@ -3,18 +3,17 @@ namespace App\PaymentDrivers\GoCardless; use App\Exceptions\PaymentFailed; -use Illuminate\Http\Request; -use App\PaymentDrivers\Common\MethodInterface; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Jobs\Mail\PaymentFailureMailer; use App\Jobs\Util\SystemLogger; use App\Models\GatewayType; use App\Models\Payment; use App\Models\PaymentType; use App\Models\SystemLog; +use App\PaymentDrivers\Common\MethodInterface; use App\PaymentDrivers\GoCardlessPaymentDriver; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class InstantBankPay implements MethodInterface { @@ -29,10 +28,10 @@ class InstantBankPay implements MethodInterface /** * Authorization page for Instant Bank Pay. - * - * @param array $data - * @return RedirectResponse - * @throws BindingResolutionException + * + * @param array $data + * @return RedirectResponse + * @throws BindingResolutionException */ public function authorizeView(array $data): RedirectResponse { @@ -41,10 +40,10 @@ class InstantBankPay implements MethodInterface /** * Handle authorization for Instant Bank Pay. - * - * @param array $data - * @return RedirectResponse - * @throws BindingResolutionException + * + * @param array $data + * @return RedirectResponse + * @throws BindingResolutionException */ public function authorizeResponse(Request $request): RedirectResponse { From 14b92b3cabe2557488a4d061568cc8e8587913f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Nov 2021 17:55:22 +0100 Subject: [PATCH 10/10] Translation --- app/Models/GatewayType.php | 2 ++ resources/lang/en/texts.php | 1 + 2 files changed, 3 insertions(+) diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index fb4d60248..23262cd9e 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -90,6 +90,8 @@ class GatewayType extends StaticModel return ctrans('texts.acss'); case self::DIRECT_DEBIT: return ctrans('texts.payment_type_direct_debit'); + case self::INSTANT_BANK_PAY: + return ctrans('texts.payment_type_instant_bank_pay'); default: return 'Undefined.'; break; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 14af04fc3..3afda95c0 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4339,6 +4339,7 @@ $LANG = array( 'browser_pay' => 'Google Pay, Apple Pay, Microsoft Pay', 'no_available_methods' => 'We can\'t find any credit cards on your device. Read more about this.', 'gocardless_mandate_not_ready' => 'Payment mandate is not ready. Please try again later.', + 'payment_type_instant_bank_pay' => 'Instant Bank Pay', ); return $LANG;