diff --git a/app/Http/Controllers/Gateways/GoCardlessController.php b/app/Http/Controllers/Gateways/GoCardlessController.php new file mode 100644 index 000000000000..fce5d5eea7ee --- /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 000000000000..8caeaf64ca2b --- /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/Models/Gateway.php b/app/Models/Gateway.php index 1f60070b0acb..68b56001a84c 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: diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index d6c51bc4e7f1..23262cd9eb0b 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() { @@ -89,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/app/Models/PaymentType.php b/app/Models/PaymentType.php index 4370b321715c..3a28863def71 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/app/PaymentDrivers/GoCardless/InstantBankPay.php b/app/PaymentDrivers/GoCardless/InstantBankPay.php new file mode 100644 index 000000000000..3ba3cdc8dcd3 --- /dev/null +++ b/app/PaymentDrivers/GoCardless/InstantBankPay.php @@ -0,0 +1,182 @@ +go_cardless = $go_cardless; + + $this->go_cardless->init(); + } + + /** + * Authorization page for Instant Bank Pay. + * + * @param array $data + * @return RedirectResponse + * @throws BindingResolutionException + */ + public function authorizeView(array $data): RedirectResponse + { + return redirect()->back(); + } + + /** + * 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) + { + 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(), + ], + ] + ]); + + $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 db2dd56d155b..c7d890e637d0 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,10 @@ class GoCardlessPaymentDriver extends BaseDriver $types[] = GatewayType::SEPA; } + if ($this->client->currency()->code === 'GBP') { + $types[] = GatewayType::INSTANT_BANK_PAY; + } + return $types; } 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 000000000000..15a2ba3ff61f --- /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(); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 14af04fc3f09..3afda95c0172 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; diff --git a/routes/web.php b/routes/web.php index 8c3a8e85d0d3..b8180d7f1583 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');