diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 0e05a43835..9c6edf68e9 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -89,8 +89,9 @@ class Gateway extends StaticModel break; case 7: return [ - GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie + GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie, GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true], + GatewayType::KBC => ['refund' => false, 'token_billing' => false], ]; case 15: return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 5be577bb6a..dff3fc2e4f 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -25,6 +25,7 @@ class GatewayType extends StaticModel const APPLE_PAY = 8; const SEPA = 9; const CREDIT = 10; + const KBC = 11; public function gateway() { @@ -66,7 +67,10 @@ class GatewayType extends StaticModel case self::SEPA: return ctrans('texts.sepa'); break; - + case self::KBC: + return ctrans('texts.kbc_cbc'); + break; + default: return 'Undefined.'; break; diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index 37723d0a01..d8c0cf2c78 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -43,6 +43,7 @@ class PaymentType extends StaticModel const GOCARDLESS = 30; const CRYPTO = 31; const MOLLIE_BANK_TRANSFER = 34; + const KBC = 35; public static function parseCardType($cardName) { diff --git a/app/PaymentDrivers/Mollie/KBC.php b/app/PaymentDrivers/Mollie/KBC.php new file mode 100644 index 0000000000..1e90725b6c --- /dev/null +++ b/app/PaymentDrivers/Mollie/KBC.php @@ -0,0 +1,199 @@ +mollie = $mollie; + + $this->mollie->init(); + } + + /** + * Show the authorization page for KBC. + * + * @param array $data + * @return View + */ + public function authorizeView(array $data): View + { + return render('gateways.mollie.kbc.authorize', $data); + } + + /** + * Handle the authorization for KBC. + * + * @param Request $request + * @return RedirectResponse + */ + public function authorizeResponse(Request $request): RedirectResponse + { + return redirect()->route('client.payment_methods.index'); + } + + /** + * Show the payment page for KBC. + * + * @param array $data + * @return Redirector|RedirectResponse + */ + public function paymentView(array $data) + { + $this->mollie->payment_hash + ->withData('gateway_type_id', GatewayType::KBC) + ->withData('client_id', $this->mollie->client->id); + + try { + $payment = $this->mollie->gateway->payments->create([ + 'method' => 'kbc', + 'amount' => [ + 'currency' => $this->mollie->client->currency()->code, + 'value' => $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee), + ], + 'description' => \sprintf('Invoices: %s', collect($data['invoices'])->pluck('invoice_number')), + 'redirectUrl' => route('client.payments.response', [ + 'company_gateway_id' => $this->mollie->company_gateway->id, + 'payment_hash' => $this->mollie->payment_hash->hash, + 'payment_method_id' => GatewayType::KBC, + ]), + 'webhookUrl' => $this->mollie->company_gateway->webhookUrl(), + 'metadata' => [ + 'client_id' => $this->mollie->client->hashed_id, + ], + ]); + + $this->mollie->payment_hash->withData('payment_id', $payment->id); + + return redirect( + $payment->getCheckoutUrl() + ); + } catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) { + return $this->processUnsuccessfulPayment($exception); + } + } + + /** + * Handle unsuccessful payment. + * + * @param Exception $exception + * @throws PaymentFailed + * @return void + */ + public function processUnsuccessfulPayment(\Exception $exception): void + { + PaymentFailureMailer::dispatch( + $this->mollie->client, + $exception->getMessage(), + $this->mollie->client->company, + $this->mollie->payment_hash->data->amount_with_fee + ); + + SystemLogger::dispatch( + $exception->getMessage(), + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_MOLLIE, + $this->mollie->client, + $this->mollie->client->company, + ); + + throw new PaymentFailed($exception->getMessage(), $exception->getCode()); + } + + /** + * Handle the payments for the KBC. + * + * @param PaymentResponseRequest $request + * @return mixed + */ + public function paymentResponse(PaymentResponseRequest $request) + { + if (! \property_exists($this->mollie->payment_hash->data, 'payment_id')) { + return $this->processUnsuccessfulPayment( + new PaymentFailed('Whoops, something went wrong. Missing required [payment_id] parameter. Please contact administrator. Reference hash: ' . $this->mollie->payment_hash->hash) + ); + } + + try { + $payment = $this->mollie->gateway->payments->get( + $this->mollie->payment_hash->data->payment_id + ); + + if ($payment->status === 'paid') { + return $this->processSuccessfulPayment($payment); + } + + if ($payment->status === 'failed') { + return $this->processUnsuccessfulPayment( + new PaymentFailed(ctrans('texts.status_failed')) + ); + } + + return $this->processUnsuccessfulPayment( + new PaymentFailed(ctrans('texts.status_voided')) + ); + } catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) { + return $this->processUnsuccessfulPayment($exception); + } + } + + /** + * Handle the successful payment for KBC. + * + * @param ResourcesPayment $payment + * @return RedirectResponse + */ + public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment): RedirectResponse + { + $data = [ + 'gateway_type_id' => GatewayType::KBC, + 'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total, + 'payment_type' => PaymentType::KBC, + 'transaction_reference' => $payment->id, + ]; + + $payment_record = $this->mollie->createPayment( + $data, + $payment->status === 'paid' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING + ); + + SystemLogger::dispatch( + ['response' => $payment, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_MOLLIE, + $this->mollie->client, + $this->mollie->client->company, + ); + + return redirect()->route('client.payments.show', ['payment' => $this->mollie->encodePrimaryKey($payment_record->id)]); + } +} diff --git a/app/PaymentDrivers/MolliePaymentDriver.php b/app/PaymentDrivers/MolliePaymentDriver.php index 18b808cb66..f9e2644668 100644 --- a/app/PaymentDrivers/MolliePaymentDriver.php +++ b/app/PaymentDrivers/MolliePaymentDriver.php @@ -26,6 +26,7 @@ use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\Mollie\BankTransfer; use App\PaymentDrivers\Mollie\CreditCard; +use App\PaymentDrivers\Mollie\KBC; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Validator; use Mollie\Api\Exceptions\ApiException; @@ -66,6 +67,7 @@ class MolliePaymentDriver extends BaseDriver public static $methods = [ GatewayType::CREDIT_CARD => CreditCard::class, GatewayType::BANK_TRANSFER => BankTransfer::class, + GatewayType::KBC => KBC::class, ]; const SYSTEM_LOG_TYPE = SystemLog::TYPE_MOLLIE; @@ -87,6 +89,7 @@ class MolliePaymentDriver extends BaseDriver $types[] = GatewayType::CREDIT_CARD; $types[] = GatewayType::BANK_TRANSFER; + $types[] = GatewayType::KBC; return $types; } diff --git a/database/migrations/2021_09_24_211504_add_kbc_to_payment_types.php b/database/migrations/2021_09_24_211504_add_kbc_to_payment_types.php new file mode 100644 index 0000000000..9962e7802c --- /dev/null +++ b/database/migrations/2021_09_24_211504_add_kbc_to_payment_types.php @@ -0,0 +1,26 @@ +id = 35; + $type->name = 'KBC/CBC'; + $type->gateway_type_id = GatewayType::KBC; + + $type->save(); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index bb078c1f04..a7de19c420 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4314,6 +4314,7 @@ $LANG = array( 'generic_gateway_error' => 'Gateway configuration error. Please check your credentials.', 'my_documents' => 'My documents', 'payment_method_cannot_be_preauthorized' => 'This payment method cannot be preauthorized.', + 'kbc_cbc' => 'KBC/CBC', ); return $LANG; diff --git a/resources/views/portal/ninja2020/gateways/mollie/kbc/authorize.blade.php b/resources/views/portal/ninja2020/gateways/mollie/kbc/authorize.blade.php new file mode 100644 index 0000000000..d6b3b3c310 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/mollie/kbc/authorize.blade.php @@ -0,0 +1,8 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'KBC/CBC', 'card_title' => +'KBC/CBC']) + +@section('gateway_content') + @component('portal.ninja2020.components.general.card-element-single') + {{ __('texts.payment_method_cannot_be_preauthorized') }} + @endcomponent +@endsection diff --git a/tests/Browser/ClientPortal/Gateways/Mollie/KBCTest.php b/tests/Browser/ClientPortal/Gateways/Mollie/KBCTest.php new file mode 100644 index 0000000000..a8be7b6cbf --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Mollie/KBCTest.php @@ -0,0 +1,89 @@ +driver->manage()->deleteAllCookies(); + } + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', '1bd651fb213ca0c9d66ae3c336dc77e8')->restore(); + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testSuccessfulPayment(): void + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Undefined.') + ->waitForText('Test profile') + ->press('CBC') + ->radio('final_state', 'paid') + ->press('Continue') + ->waitForText('Details of the payment') + ->assertSee('Completed'); + }); + } + + public function testFailedPayment(): void + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Undefined.') + ->waitForText('Test profile') + ->press('CBC') + ->radio('final_state', 'failed') + ->press('Continue') + ->waitForText('Failed.'); + }); + } + + public function testCancelledTest(): void + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Undefined.') + ->waitForText('Test profile') + ->press('CBC') + ->radio('final_state', 'canceled') + ->press('Continue') + ->waitForText('Cancelled.'); + }); + } +} \ No newline at end of file