1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00

Merge pull request #6720 from beganovich/v5-mollie-bancontact

Mollie: Bancontact
This commit is contained in:
Benjamin Beganović 2021-09-26 21:12:54 +02:00 committed by GitHub
commit 02f1c881bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 363 additions and 1 deletions

View File

@ -89,9 +89,10 @@ 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],
GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false],
];
case 15:
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal

View File

@ -26,6 +26,7 @@ class GatewayType extends StaticModel
const SEPA = 9;
const CREDIT = 10;
const KBC = 11;
const BANCONTACT = 12;
public function gateway()
{
@ -70,6 +71,9 @@ class GatewayType extends StaticModel
case self::KBC:
return ctrans('texts.kbc_cbc');
break;
case self::BANCONTACT:
return ctrans('texts.bancontact');
break;
default:
return 'Undefined.';

View File

@ -44,6 +44,7 @@ class PaymentType extends StaticModel
const CRYPTO = 31;
const MOLLIE_BANK_TRANSFER = 34;
const KBC = 35;
const BANCONTACT = 36;
public static function parseCardType($cardName)
{

View File

@ -0,0 +1,216 @@
<?php
/**
* 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://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Mollie;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\Request;
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\MolliePaymentDriver;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class Bancontact implements MethodInterface
{
protected MolliePaymentDriver $mollie;
public function __construct(MolliePaymentDriver $mollie)
{
$this->mollie = $mollie;
$this->mollie->init();
}
/**
* Show the authorization page for Bancontact.
*
* @param array $data
* @return View
*/
public function authorizeView(array $data): View
{
return render('gateways.mollie.bancontact.authorize', $data);
}
/**
* Handle the authorization for Bancontact.
*
* @param Request $request
* @return RedirectResponse
*/
public function authorizeResponse(Request $request): RedirectResponse
{
return redirect()->route('client.payment_methods.index');
}
/**
* Show the payment page for Bancontact.
*
* @param array $data
* @return Redirector|RedirectResponse
*/
public function paymentView(array $data)
{
$this->mollie->payment_hash
->withData('gateway_type_id', GatewayType::BANCONTACT)
->withData('client_id', $this->mollie->client->id);
try {
$payment = $this->mollie->gateway->payments->create([
'method' => 'bancontact',
'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::BANCONTACT,
]),
'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 === 'open') {
return $this->processOpenPayment($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 Bancontact.
*
* @param string $status
* @param ResourcesPayment $payment
* @return RedirectResponse
*/
public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment, string $status = 'paid'): RedirectResponse
{
$data = [
'gateway_type_id' => GatewayType::BANCONTACT,
'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total,
'payment_type' => PaymentType::BANCONTACT,
'transaction_reference' => $payment->id,
];
$payment_record = $this->mollie->createPayment(
$data,
$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)]);
}
/**
* Handle 'open' payment status for Bancontact.
*
* @param ResourcesPayment $payment
* @return RedirectResponse
*/
public function processOpenPayment(\Mollie\Api\Resources\Payment $payment): RedirectResponse
{
return $this->processSuccessfulPayment($payment, 'open');
}
}

View File

@ -24,6 +24,7 @@ use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Mollie\Bancontact;
use App\PaymentDrivers\Mollie\BankTransfer;
use App\PaymentDrivers\Mollie\CreditCard;
use App\PaymentDrivers\Mollie\KBC;
@ -66,6 +67,7 @@ class MolliePaymentDriver extends BaseDriver
*/
public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class,
GatewayType::BANCONTACT => Bancontact::class,
GatewayType::BANK_TRANSFER => BankTransfer::class,
GatewayType::KBC => KBC::class,
];
@ -88,6 +90,7 @@ class MolliePaymentDriver extends BaseDriver
$types = [];
$types[] = GatewayType::CREDIT_CARD;
$types[] = GatewayType::BANCONTACT;
$types[] = GatewayType::BANK_TRANSFER;
$types[] = GatewayType::KBC;

View File

@ -0,0 +1,26 @@
<?php
use App\Models\GatewayType;
use App\Models\PaymentType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddBancontactToPaymentTypes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$type = new PaymentType();
$type->id = 35;
$type->name = 'Bancontact';
$type->gateway_type_id = GatewayType::BANCONTACT;
$type->save();
}
}

View File

@ -4315,6 +4315,7 @@ $LANG = array(
'my_documents' => 'My documents',
'payment_method_cannot_be_preauthorized' => 'This payment method cannot be preauthorized.',
'kbc_cbc' => 'KBC/CBC',
'bancontact' => 'Bancontact',
);
return $LANG;

View File

@ -0,0 +1,8 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Bancontact', 'card_title' =>
'Bancontact'])
@section('gateway_content')
@component('portal.ninja2020.components.general.card-element-single')
{{ __('texts.payment_method_cannot_be_preauthorized') }}
@endcomponent
@endsection

View File

@ -0,0 +1,102 @@
<?php
/**
* 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://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Browser\ClientPortal\Gateways\Mollie;
use App\Models\CompanyGateway;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Tests\Browser\Pages\ClientPortal\Login;
class BancontactTest extends DuskTestCase
{
protected function setUp(): void
{
parent::setUp();
foreach (static::$browsers as $browser) {
$browser->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('Bancontact')
->waitForText('Test profile')
->radio('final_state', 'paid')
->press('Continue')
->waitForText('Details of the payment')
->assertSee('Completed');
});
}
public function testOpenPayments(): void
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.invoices.index')
->click('@pay-now')
->press('Pay Now')
->clickLink('Bancontact')
->waitForText('Test profile')
->radio('final_state', 'open')
->press('Continue')
->waitForText('Details of the payment')
->assertSee('Pending');
});
}
public function testFailedPayment(): void
{
$this->browse(function (Browser $browser) {
$browser
->visitRoute('client.invoices.index')
->click('@pay-now')
->press('Pay Now')
->clickLink('Bancontact')
->waitForText('Test profile')
->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('Bancontact')
->waitForText('Test profile')
->radio('final_state', 'canceled')
->press('Continue')
->waitForText('Cancelled.');
});
}
}