Pay with credit card and save for future use

This commit is contained in:
Benjamin Beganović 2021-07-30 14:36:14 +02:00
parent e306278547
commit 8af3cfe737
3 changed files with 75 additions and 43 deletions

View File

@ -37,6 +37,11 @@ class PaymentResponseRequest extends FormRequest
return PaymentHash::whereRaw('BINARY `hash`= ?', [$input['payment_hash']])->first(); return PaymentHash::whereRaw('BINARY `hash`= ?', [$input['payment_hash']])->first();
} }
public function shouldStoreToken(): bool
{
return (bool) $this->store_card;
}
public function prepareForValidation() public function prepareForValidation()
{ {
if ($this->has('store_card')) { if ($this->has('store_card')) {

View File

@ -31,9 +31,9 @@ class CreditCard
/** /**
* Show the page for credit card payments. * Show the page for credit card payments.
* *
* @param array $data * @param array $data
* @return Factory|View * @return Factory|View
*/ */
public function paymentView(array $data) public function paymentView(array $data)
{ {
@ -44,9 +44,9 @@ class CreditCard
/** /**
* Create a payment object. * Create a payment object.
* *
* @param PaymentResponseRequest $request * @param PaymentResponseRequest $request
* @return mixed * @return mixed
*/ */
public function paymentResponse(PaymentResponseRequest $request) public function paymentResponse(PaymentResponseRequest $request)
{ {
@ -58,16 +58,7 @@ class CreditCard
->withData('client_id', $this->mollie->client->id); ->withData('client_id', $this->mollie->client->id);
try { try {
$customer = $this->mollie->gateway->customers->create([ $data = [
'name' => $this->mollie->client->name,
'metadata' => [
'id' => $this->mollie->client->hashed_id,
],
]);
$payment = $this->mollie->gateway->payments->create([
'customerId' => $customer->id,
'sequenceType' => 'first',
'amount' => [ 'amount' => [
'currency' => $this->mollie->client->currency()->code, 'currency' => $this->mollie->client->currency()->code,
'value' => $amount, 'value' => $amount,
@ -80,7 +71,26 @@ class CreditCard
]), ]),
'webhookUrl' => 'https://invoiceninja.com', 'webhookUrl' => 'https://invoiceninja.com',
'cardToken' => $request->token, 'cardToken' => $request->token,
]); ];
if ($request->shouldStoreToken()) {
$customer = $this->mollie->gateway->customers->create([
'name' => $this->mollie->client->name,
'email' => $this->mollie->client->present()->email(),
'metadata' => [
'id' => $this->mollie->client->hashed_id,
],
]);
$data['customerId'] = $customer->id;
$data['sequenceType'] = 'first';
$this->mollie->payment_hash
->withData('mollieCustomerId', $customer->id)
->withData('shouldStoreToken', true);
}
$payment = $this->mollie->gateway->payments->create($data);
if ($payment->status === 'paid') { if ($payment->status === 'paid') {
$this->mollie->logSuccessfulGatewayResponse( $this->mollie->logSuccessfulGatewayResponse(
@ -107,6 +117,27 @@ class CreditCard
{ {
$payment_hash = $this->mollie->payment_hash; $payment_hash = $this->mollie->payment_hash;
if ($payment_hash->data->shouldStoreToken) {
try {
$mandates = \iterator_to_array($this->mollie->gateway->mandates->listForId($payment_hash->data->mollieCustomerId));
} catch (\Mollie\Api\Exceptions\ApiException $e) {
return $this->processUnsuccessfulPayment($e);
}
$payment_meta = new \stdClass;
$payment_meta->exp_month = (string) $mandates[0]->details->cardExpiryDate;
$payment_meta->exp_year = (string) '';
$payment_meta->brand = (string) $mandates[0]->details->cardLabel;
$payment_meta->last4 = (string) $mandates[0]->details->cardNumber;
$payment_meta->type = GatewayType::CREDIT_CARD;
$this->mollie->storeGatewayToken([
'token' => $mandates[0]->id,
'payment_method_id' => GatewayType::CREDIT_CARD,
'payment_meta' => $payment_meta,
]);
}
$data = [ $data = [
'gateway_type_id' => GatewayType::CREDIT_CARD, 'gateway_type_id' => GatewayType::CREDIT_CARD,
'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total, 'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total,
@ -151,9 +182,9 @@ class CreditCard
/** /**
* Show authorization page. * Show authorization page.
* *
* @param array $data * @param array $data
* @return Factory|View * @return Factory|View
*/ */
public function authorizeView(array $data) public function authorizeView(array $data)
{ {
@ -162,9 +193,9 @@ class CreditCard
/** /**
* Handle authorization response. * Handle authorization response.
* *
* @param mixed $request * @param mixed $request
* @return RedirectResponse * @return RedirectResponse
*/ */
public function authorizeResponse($request): RedirectResponse public function authorizeResponse($request): RedirectResponse
{ {

View File

@ -29,26 +29,19 @@ ctrans('texts.credit_card')])
@include('portal.ninja2020.gateways.includes.payment_details') @include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
@if(count($tokens) > 0) @if (count($tokens) > 0)
@foreach($tokens as $token) @foreach ($tokens as $token)
<label class="mr-4"> <label class="mr-4">
<input <input type="radio" data-token="{{ $token->token }}" name="payment-type"
type="radio" class="form-radio cursor-pointer toggle-payment-with-token" />
data-token="{{ $token->token }}"
name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token"/>
<span class="ml-1 cursor-pointer">**** {{ optional($token->meta)->last4 }}</span> <span class="ml-1 cursor-pointer">**** {{ optional($token->meta)->last4 }}</span>
</label> </label>
@endforeach @endforeach
@endif @endif
<label> <label>
<input <input type="radio" id="toggle-payment-with-credit-card" class="form-radio cursor-pointer" name="payment-type"
type="radio" checked />
id="toggle-payment-with-credit-card"
class="form-radio cursor-pointer"
name="payment-type"
checked/>
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span> <span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
</label> </label>
@endcomponent @endcomponent
@ -83,13 +76,7 @@ ctrans('texts.credit_card')])
</div> </div>
@endcomponent @endcomponent
@component('portal.ninja2020.components.general.card-element-single') @include('portal.ninja2020.gateways.includes.save_card')
<span class="text-sm text-gray-900">If you want to save the card for future purchases, please click on the
<a href="{{ route('client.payment_methods.index') }}" class="underline text-primary">Payment methods</a> page and authorize the credit card manually.
</span>
<span class="text-sm text-gray-900">After that, come back to this page and select your payment method.</span>
@endcomponent
@include('portal.ninja2020.gateways.includes.pay_now') @include('portal.ninja2020.gateways.includes.pay_now')
@endsection @endsection
@ -193,6 +180,15 @@ ctrans('texts.credit_card')])
return; return;
} }
let tokenBillingCheckbox = document.querySelector(
'input[name="token-billing-checkbox"]:checked'
);
if (tokenBillingCheckbox) {
document.querySelector('input[name="store_card"]').value =
tokenBillingCheckbox.value;
}
document.querySelector('input[name=token]').value = token; document.querySelector('input[name=token]').value = token;
document.getElementById('server-response').submit(); document.getElementById('server-response').submit();
}); });