From f33a76f96d220195d83758e6491a19e7a1d18b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 15:46:45 +0200 Subject: [PATCH 01/13] Refactor Eway authorize page --- .../gateways/eway/authorize.blade.php | 348 ++++++++++++++++-- 1 file changed, 316 insertions(+), 32 deletions(-) diff --git a/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php b/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php index 19fced5099..848e0adb94 100644 --- a/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php @@ -2,6 +2,7 @@ ctrans('texts.credit_card')]) @section('gateway_head') + @endsection @section('gateway_content') @@ -9,49 +10,332 @@ ctrans('texts.credit_card')]) method="post" id="server-response"> @csrf - + - - @if (!Request::isSecure()) -

{{ ctrans('texts.https_required') }}

- @endif - - - - - @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')]) - {{ ctrans('texts.credit_card') }} - @endcomponent - -
- - @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card']) - {{ ctrans('texts.add_payment_method') }} - @endcomponent + + @if (!Request::isSecure()) +

{{ ctrans('texts.https_required') }}

+ @endif + + + + @component('portal.ninja2020.components.general.card-element-single') +
+ @endcomponent + + @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card', 'disabled' => true]) + {{ ctrans('texts.add_payment_method') }} + @endcomponent @endsection @section('gateway_footer') - - + -@include('portal.ninja2020.gateways.eway.includes.credit_card') + + if (document.getElementById('authorize-card')) { + document.getElementById('authorize-card').disabled = false; + } + + document.querySelector("input[name=securefieldcode]").value = event.secureFieldCode; + } + + handleErrors(errors) { + let _errors = errors.split(' '); + let shouldShowGenericError = false; + let message = ''; + + _errors.forEach((error) => { + message = message.concat(this.errorCodes.get(error) + '
'); + }) + + document.getElementById('errors').innerHTML = message; + document.getElementById('errors').hidden = false; + } + + handleAuthorization(event) { + event.target.parentElement.disabled = true; + + document.getElementById('server-response').submit(); + } + + initialize() { + this.eWAY = eWAY.setupSecureField(this.groupFieldConfig, (event) => this.securePanelCallback(event)) + } + + handle() { + this.initialize(); + + document + .getElementById('authorize-card') + ?.addEventListener('click', (e) => this.handleAuthorization(e)) + } + } + + new EwayRapid().handle() + @endsection From b91449f7274c0f2f698f1f972bb06adab4eb1840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 15:47:01 +0200 Subject: [PATCH 02/13] Add option to pass `disabled` to `pay_now` component --- .../views/portal/ninja2020/gateways/includes/pay_now.blade.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/views/portal/ninja2020/gateways/includes/pay_now.blade.php b/resources/views/portal/ninja2020/gateways/includes/pay_now.blade.php index ea005a0cb8..74f8189f81 100644 --- a/resources/views/portal/ninja2020/gateways/includes/pay_now.blade.php +++ b/resources/views/portal/ninja2020/gateways/includes/pay_now.blade.php @@ -4,7 +4,8 @@ type="{{ $type ?? 'button' }}" id="{{ $id ?? 'pay-now' }}" @isset($data) @foreach($data as $prop => $value) data-{{ $prop }}="{{ $value }}" @endforeach @endisset - class="button button-primary bg-primary {{ $class ?? '' }}"> + class="button button-primary bg-primary {{ $class ?? '' }}" + {{ isset($disabled) && $disabled === true ? 'disabled' : '' }}> + + + + + @endsection @section('gateway_content') @@ -226,7 +230,7 @@ ctrans('texts.credit_card')]) styles: "margin-top: 15px;", label: { fieldColSpan: 4, - text: "Card Name:", + text: document.querySelector('meta[name=translation-card-name]')?.content, styles: "", }, field: { @@ -241,7 +245,7 @@ ctrans('texts.credit_card')]) styles: "margin-top: 15px;", label: { fieldColSpan: 4, - text: "Expiry:", + text: document.querySelector('meta[name=translation-expiry_date]')?.content, styles: "", }, field: { @@ -260,7 +264,7 @@ ctrans('texts.credit_card')]) styles: "margin-top: 15px;", label: { fieldColSpan: 4, - text: "Card Number:", + text: document.querySelector('meta[name=translation-card_number]')?.content, styles: "", }, field: { @@ -274,7 +278,7 @@ ctrans('texts.credit_card')]) styles: "margin-top: 15px;", label: { fieldColSpan: 4, - text: "CVV Number:", + text: document.querySelector('meta[name=translation-cvv]')?.content, styles: "", }, field: { From 393c218c4c12be249cecdbdaccbb9b95a9dc6bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:12:23 +0200 Subject: [PATCH 04/13] Refactor Eway payment page --- .../ninja2020/gateways/eway/pay.blade.php | 418 ++++++++++++++++-- 1 file changed, 375 insertions(+), 43 deletions(-) diff --git a/resources/views/portal/ninja2020/gateways/eway/pay.blade.php b/resources/views/portal/ninja2020/gateways/eway/pay.blade.php index c45af17bb4..1d73902f71 100644 --- a/resources/views/portal/ninja2020/gateways/eway/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/eway/pay.blade.php @@ -1,6 +1,12 @@ -@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')]) +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => +ctrans('texts.credit_card')]) @section('gateway_head') + + + + + @endsection @section('gateway_content') @@ -24,72 +30,398 @@ @include('portal.ninja2020.gateways.includes.payment_details') @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) - @if(count($tokens) > 0) - @foreach($tokens as $token) + @if (count($tokens) > 0) + @foreach ($tokens as $token) @endforeach @endisset @endcomponent -
- - + @component('portal.ninja2020.components.general.card-element-single') +
+ @endcomponent + @include('portal.ninja2020.gateways.includes.save_card') -
- - - @include('portal.ninja2020.gateways.includes.pay_now') + @include('portal.ninja2020.gateways.includes.pay_now', ['disabled' => true]) @endsection @section('gateway_footer') + - @include('portal.ninja2020.gateways.eway.includes.credit_card') - + + document.querySelector("input[name=securefieldcode]").value = event.secureFieldCode; + } + handleErrors(errors) { + let _errors = errors.split(' '); + let shouldShowGenericError = false; + let message = ''; + + _errors.forEach((error) => { + message = message.concat(this.errorCodes.get(error) + '
'); + }) + + document.getElementById('errors').innerHTML = message; + document.getElementById('errors').hidden = false; + } + + completeAuthorization(event) { + event.target.parentElement.disabled = true; + + document.getElementById('server-response').submit(); + } + + completePaymentUsingToken(event) { + event.target.parentElement.disabled = true; + + document.getElementById('server-response').submit(); + } + + completePaymentWithoutToken(event) { + event.target.parentElement.disabled = true; + + let tokenBillingCheckbox = document.querySelector( + 'input[name="token-billing-checkbox"]:checked' + ); + + if (tokenBillingCheckbox) { + document.querySelector('input[name="store_card"]').value = + tokenBillingCheckbox.value; + } + + document.getElementById('server-response').submit(); + } + + initialize() { + this.eWAY = eWAY.setupSecureField(this.groupFieldConfig, (event) => this.securePanelCallback(event)) + } + + handle() { + this.initialize(); + + document + .getElementById('authorize-card') + ?.addEventListener('click', (e) => this.completeAuthorization(e)) + + Array + .from(document.getElementsByClassName('toggle-payment-with-token')) + .forEach((element) => element.addEventListener('click', (element) => { + document.getElementById('eway-secure-panel').classList.add('hidden'); + document.getElementById('save-card--container').style.display = 'none'; + document.querySelector('input[name=token]').value = element.target.dataset.token; + document.getElementById('pay-now').disabled = false; + })); + + document + .getElementById('toggle-payment-with-credit-card') + .addEventListener('click', (element) => { + document.getElementById('eway-secure-panel').classList.remove('hidden'); + document.getElementById('save-card--container').style.display = 'grid'; + document.querySelector('input[name=token]').value = ""; + document.getElementById('pay-now').disabled = true; + }); + + document + .getElementById('pay-now') + ?.addEventListener('click', (e) => { + let tokenInput = document.querySelector('input[name=token]'); + + if (tokenInput.value) { + return this.completePaymentUsingToken(e); + } + + return this.completePaymentWithoutToken(e); + }); + } + } + + new EwayRapid().handle() + @endsection From 0a7a0566154e5db1d1dde8d0cbccdbbd131249fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:16:23 +0200 Subject: [PATCH 05/13] Extract scripts to separate file --- .../js/clients/payments/eway-credit-card.js | 510 ++++++++++++++++++ .../gateways/eway/authorize.blade.php | 307 +---------- .../ninja2020/gateways/eway/pay.blade.php | 367 +------------ webpack.mix.js | 4 + 4 files changed, 516 insertions(+), 672 deletions(-) create mode 100644 resources/js/clients/payments/eway-credit-card.js diff --git a/resources/js/clients/payments/eway-credit-card.js b/resources/js/clients/payments/eway-credit-card.js new file mode 100644 index 0000000000..3b0c3f6053 --- /dev/null +++ b/resources/js/clients/payments/eway-credit-card.js @@ -0,0 +1,510 @@ +/** + * 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 + */ + +class EwayRapid { + constructor() { + this.cardStyles = + 'padding: 2px; border: 1px solid #AAA; border-radius: 3px; height: 34px; width: 100%;'; + + this.errorCodes = new Map(); + + this.errorCodes.set('V6000', 'Validation error'); + this.errorCodes.set('V6001', 'Invalid CustomerIP'); + this.errorCodes.set('V6002', 'Invalid DeviceID'); + this.errorCodes.set('V6003', 'Invalid Request PartnerID'); + this.errorCodes.set('V6004', 'Invalid Request Method'); + this.errorCodes.set( + 'V6010', + 'Invalid TransactionType, account not certified for eCome only MOTO or Recurring available' + ); + this.errorCodes.set('V6011', 'Invalid Payment TotalAmount'); + this.errorCodes.set('V6012', 'Invalid Payment InvoiceDescription'); + this.errorCodes.set('V6013', 'Invalid Payment InvoiceNumber'); + this.errorCodes.set('V6014', 'Invalid Payment InvoiceReference'); + this.errorCodes.set('V6015', 'Invalid Payment CurrencyCode'); + this.errorCodes.set('V6016', 'Payment Required'); + this.errorCodes.set('V6017', 'Payment CurrencyCode Required'); + this.errorCodes.set('V6018', 'Unknown Payment CurrencyCode'); + this.errorCodes.set( + 'V6019', + 'Cardholder identity authentication required' + ); + this.errorCodes.set('V6020', 'Cardholder Input Required'); + this.errorCodes.set('V6021', 'EWAY_CARDHOLDERNAME Required'); + this.errorCodes.set('V6022', 'EWAY_CARDNUMBER Required'); + this.errorCodes.set('V6023', 'EWAY_CARDCVN Required'); + this.errorCodes.set( + 'V6024', + 'Cardholder Identity Authentication One Time Password Not Active Yet' + ); + this.errorCodes.set('V6025', 'PIN Required'); + this.errorCodes.set('V6033', 'Invalid Expiry Date'); + this.errorCodes.set('V6034', 'Invalid Issue Number'); + this.errorCodes.set('V6035', 'Invalid Valid From Date'); + this.errorCodes.set('V6039', 'Invalid Network Token Status'); + this.errorCodes.set('V6040', 'Invalid TokenCustomerID'); + this.errorCodes.set('V6041', 'Customer Required'); + this.errorCodes.set('V6042', 'Customer FirstName Required'); + this.errorCodes.set('V6043', 'Customer LastName Required'); + this.errorCodes.set('V6044', 'Customer CountryCode Required'); + this.errorCodes.set('V6045', 'Customer Title Required'); + this.errorCodes.set('V6046', 'TokenCustomerID Required'); + this.errorCodes.set('V6047', 'RedirectURL Required'); + this.errorCodes.set( + 'V6048', + 'CheckoutURL Required when CheckoutPayment specified' + ); + this.errorCodes.set('V6049', 'nvalid Checkout URL'); + this.errorCodes.set('V6051', 'Invalid Customer FirstName'); + this.errorCodes.set('V6052', 'Invalid Customer LastName'); + this.errorCodes.set('V6053', 'Invalid Customer CountryCode'); + this.errorCodes.set('V6058', 'Invalid Customer Title'); + this.errorCodes.set('V6059', 'Invalid RedirectURL'); + this.errorCodes.set('V6060', 'Invalid TokenCustomerID'); + this.errorCodes.set('V6061', 'Invalid Customer Reference'); + this.errorCodes.set('V6062', 'Invalid Customer CompanyName'); + this.errorCodes.set('V6063', 'Invalid Customer JobDescription'); + this.errorCodes.set('V6064', 'Invalid Customer Street1'); + this.errorCodes.set('V6065', 'Invalid Customer Street2'); + this.errorCodes.set('V6066', 'Invalid Customer City'); + this.errorCodes.set('V6067', 'Invalid Customer State'); + this.errorCodes.set('V6068', 'Invalid Customer PostalCode'); + this.errorCodes.set('V6069', 'Invalid Customer Email'); + this.errorCodes.set('V6070', 'Invalid Customer Phone'); + this.errorCodes.set('V6071', 'Invalid Customer Mobile'); + this.errorCodes.set('V6072', 'Invalid Customer Comments'); + this.errorCodes.set('V6073', 'Invalid Customer Fax'); + this.errorCodes.set('V6074', 'Invalid Customer URL'); + this.errorCodes.set('V6075', 'Invalid ShippingAddress FirstName'); + this.errorCodes.set('V6076', 'Invalid ShippingAddress LastName'); + this.errorCodes.set('V6077', 'Invalid ShippingAddress Street1'); + this.errorCodes.set('V6078', 'Invalid ShippingAddress Street2'); + this.errorCodes.set('V6079', 'Invalid ShippingAddress City'); + this.errorCodes.set('V6080', 'Invalid ShippingAddress State'); + this.errorCodes.set('V6081', 'Invalid ShippingAddress PostalCode'); + this.errorCodes.set('V6082', 'Invalid ShippingAddress Email'); + this.errorCodes.set('V6083', 'Invalid ShippingAddress Phone'); + this.errorCodes.set('V6084', 'Invalid ShippingAddress Country'); + this.errorCodes.set('V6085', 'Invalid ShippingAddress ShippingMethod'); + this.errorCodes.set('V6086', 'Invalid ShippingAddress Fax'); + this.errorCodes.set('V6091', 'Unknown Customer CountryCode'); + this.errorCodes.set('V6092', 'Unknown ShippingAddress CountryCode'); + this.errorCodes.set('V6093', 'Insufficient Address Information'); + this.errorCodes.set('V6100', 'Invalid EWAY_CARDNAME'); + this.errorCodes.set('V6101', 'Invalid EWAY_CARDEXPIRYMONTH'); + this.errorCodes.set('V6102', 'Invalid EWAY_CARDEXPIRYYEAR'); + this.errorCodes.set('V6103', 'Invalid EWAY_CARDSTARTMONTH'); + this.errorCodes.set('V6104', 'Invalid EWAY_CARDSTARTYEAR'); + this.errorCodes.set('V6105', 'Invalid EWAY_CARDISSUENUMBER'); + this.errorCodes.set('V6106', 'Invalid EWAY_CARDCVN'); + this.errorCodes.set('V6107', 'Invalid EWAY_ACCESSCODE'); + this.errorCodes.set('V6108', 'Invalid CustomerHostAddress'); + this.errorCodes.set('V6109', 'Invalid UserAgent'); + this.errorCodes.set('V6110', 'Invalid EWAY_CARDNUMBER'); + this.errorCodes.set( + 'V6111', + 'Unauthorised API Access, Account Not PCI Certified' + ); + this.errorCodes.set( + 'V6112', + 'Redundant card details other than expiry year and month' + ); + this.errorCodes.set('V6113', 'Invalid transaction for refund'); + this.errorCodes.set('V6114', 'Gateway validation error'); + this.errorCodes.set( + 'V6115', + 'Invalid DirectRefundRequest, Transaction ID' + ); + this.errorCodes.set( + 'V6116', + 'Invalid card data on original TransactionID' + ); + this.errorCodes.set( + 'V6117', + 'Invalid CreateAccessCodeSharedRequest, FooterText' + ); + this.errorCodes.set( + 'V6118', + 'Invalid CreateAccessCodeSharedRequest, HeaderText' + ); + this.errorCodes.set( + 'V6119', + 'Invalid CreateAccessCodeSharedRequest, Language' + ); + this.errorCodes.set( + 'V6120', + 'Invalid CreateAccessCodeSharedRequest, LogoUrl' + ); + this.errorCodes.set( + 'V6121', + 'Invalid TransactionSearch, Filter Match Type' + ); + this.errorCodes.set( + 'V6122', + 'Invalid TransactionSearch, Non numeric Transaction ID' + ); + this.errorCodes.set( + 'V6123', + 'Invalid TransactionSearch,no TransactionID or AccessCode specified' + ); + this.errorCodes.set( + 'V6124', + 'Invalid Line Items. The line items have been provided however the totals do not match the TotalAmount field' + ); + this.errorCodes.set('V6125', 'Selected Payment Type not enabled'); + this.errorCodes.set( + 'V6126', + 'Invalid encrypted card number, decryption failed' + ); + this.errorCodes.set( + 'V6127', + 'Invalid encrypted cvn, decryption failed' + ); + this.errorCodes.set('V6128', 'Invalid Method for Payment Type'); + this.errorCodes.set( + 'V6129', + 'Transaction has not been authorised for Capture/Cancellation' + ); + this.errorCodes.set('V6130', 'Generic customer information error'); + this.errorCodes.set('V6131', 'Generic shipping information error'); + this.errorCodes.set( + 'V6132', + 'Transaction has already been completed or voided, operation not permitted' + ); + this.errorCodes.set('V6133', 'Checkout not available for Payment Type'); + this.errorCodes.set( + 'V6134', + 'Invalid Auth Transaction ID for Capture/Void' + ); + this.errorCodes.set('V6135', 'PayPal Error Processing Refund'); + this.errorCodes.set( + 'V6136', + 'Original transaction does not exist or state is incorrect' + ); + this.errorCodes.set('V6140', 'Merchant account is suspended'); + this.errorCodes.set( + 'V6141', + 'Invalid PayPal account details or API signature' + ); + this.errorCodes.set('V6142', 'Authorise not available for Bank/Branch'); + this.errorCodes.set('V6143', 'Invalid Public Key'); + this.errorCodes.set( + 'V6144', + 'Method not available with Public API Key Authentication' + ); + this.errorCodes.set( + 'V6145', + 'Credit Card not allow if Token Customer ID is provided with Public API Key Authentication' + ); + this.errorCodes.set( + 'V6146', + 'Client Side Encryption Key Missing or Invalid' + ); + this.errorCodes.set( + 'V6147', + 'Unable to Create One Time Code for Secure Field' + ); + this.errorCodes.set('V6148', 'Secure Field has Expired'); + this.errorCodes.set('V6149', 'Invalid Secure Field One Time Code'); + this.errorCodes.set('V6150', 'Invalid Refund Amount'); + this.errorCodes.set( + 'V6151', + 'Refund amount greater than original transaction' + ); + this.errorCodes.set( + 'V6152', + 'Original transaction already refunded for total amount' + ); + this.errorCodes.set('V6153', 'Card type not support by merchant'); + this.errorCodes.set('V6154', 'Insufficent Funds Available For Refund'); + this.errorCodes.set('V6155', 'Missing one or more fields in request'); + this.errorCodes.set('V6160', 'Encryption Method Not Supported'); + this.errorCodes.set( + 'V6161', + 'Encryption failed, missing or invalid key' + ); + this.errorCodes.set( + 'V6165', + 'Invalid Click-to-Pay (Visa Checkout) data or decryption failed' + ); + this.errorCodes.set( + 'V6170', + 'Invalid TransactionSearch, Invoice Number is not unique' + ); + this.errorCodes.set( + 'V6171', + 'Invalid TransactionSearch, Invoice Number not found' + ); + this.errorCodes.set('V6220', 'Three domain secure XID invalid'); + this.errorCodes.set('V6221', 'Three domain secure ECI invalid'); + this.errorCodes.set('V6222', 'Three domain secure AVV invalid'); + this.errorCodes.set('V6223', 'Three domain secure XID is required'); + this.errorCodes.set('V6224', 'Three Domain Secure ECI is required'); + this.errorCodes.set('V6225', 'Three Domain Secure AVV is required'); + this.errorCodes.set( + 'V6226', + 'Three Domain Secure AuthStatus is required' + ); + this.errorCodes.set('V6227', 'Three Domain Secure AuthStatus invalid'); + this.errorCodes.set('V6228', 'Three domain secure Version is required'); + this.errorCodes.set( + 'V6230', + 'Three domain secure Directory Server Txn ID invalid' + ); + this.errorCodes.set( + 'V6231', + 'Three domain secure Directory Server Txn ID is required' + ); + this.errorCodes.set('V6232', 'Three domain secure Version is invalid'); + this.errorCodes.set('V6501', 'Invalid Amex InstallementPlan'); + this.errorCodes.set( + 'V6502', + 'Invalid Number Of Installements for Amex. Valid values are from 0 to 99 inclusive' + ); + this.errorCodes.set('V6503', 'Merchant Amex ID required'); + this.errorCodes.set('V6504', 'Invalid Merchant Amex ID'); + this.errorCodes.set('V6505', 'Merchant Terminal ID required'); + this.errorCodes.set('V6506', 'Merchant category code required'); + this.errorCodes.set('V6507', 'Invalid merchant category code'); + this.errorCodes.set('V6508', 'Amex 3D ECI required'); + this.errorCodes.set('V6509', 'Invalid Amex 3D ECI'); + this.errorCodes.set('V6510', 'Invalid Amex 3D verification value'); + this.errorCodes.set('V6511', 'Invalid merchant location data'); + this.errorCodes.set('V6512', 'Invalid merchant street address'); + this.errorCodes.set('V6513', 'Invalid merchant city'); + this.errorCodes.set('V6514', 'Invalid merchant country'); + this.errorCodes.set('V6515', 'Invalid merchant phone'); + this.errorCodes.set('V6516', 'Invalid merchant postcode'); + this.errorCodes.set('V6517', 'Amex connection error'); + this.errorCodes.set( + 'V6518', + 'Amex EC Card Details API returned invalid data' + ); + this.errorCodes.set( + 'V6520', + 'Invalid or missing Amex Point Of Sale Data' + ); + this.errorCodes.set( + 'V6521', + 'Invalid or missing Amex transaction date time' + ); + this.errorCodes.set( + 'V6522', + 'Invalid or missing Amex Original transaction date time' + ); + this.errorCodes.set( + 'V6530', + 'Credit Card Number in non Credit Card Field' + ); + } + + get groupFieldConfig() { + return { + publicApiKey: document.querySelector('meta[name=public-api-key]') + ?.content, + fieldDivId: 'eway-secure-panel', + fieldType: 'group', + styles: '', + layout: { + fonts: ['Lobster'], + rows: [ + { + styles: '', + cells: [ + { + colSpan: 12, + styles: 'margin-top: 15px;', + label: { + fieldColSpan: 4, + text: document.querySelector( + 'meta[name=translation-card-name]' + )?.content, + styles: '', + }, + field: { + fieldColSpan: 8, + fieldType: 'name', + styles: this.cardStyles, + divStyles: 'padding-left: 10px;', + }, + }, + { + colSpan: 12, + styles: 'margin-top: 15px;', + label: { + fieldColSpan: 4, + text: document.querySelector( + 'meta[name=translation-expiry_date]' + )?.content, + styles: '', + }, + field: { + fieldColSpan: 8, + fieldType: 'expirytext', + styles: this.cardStyles, + divStyles: 'padding-left: 10px;', + }, + }, + ], + }, + { + styles: '', + cells: [ + { + colSpan: 12, + styles: 'margin-top: 15px;', + label: { + fieldColSpan: 4, + text: document.querySelector( + 'meta[name=translation-card_number]' + )?.content, + styles: '', + }, + field: { + fieldColSpan: 8, + fieldType: 'card', + styles: this.cardStyles, + }, + }, + { + colSpan: 12, + styles: 'margin-top: 15px;', + label: { + fieldColSpan: 4, + text: document.querySelector( + 'meta[name=translation-cvv]' + )?.content, + styles: '', + }, + field: { + fieldColSpan: 8, + fieldType: 'cvn', + styles: this.cardStyles, + }, + }, + ], + }, + ], + }, + }; + } + + securePanelCallback(event) { + document.getElementById('errors').hidden = true; + + if (event.errors) { + return this.handleErrors(event.errors); + } + + if (document.getElementById('authorize-card')) { + document.getElementById('authorize-card').disabled = false; + } + + if (document.getElementById('pay-now')) { + document.getElementById('pay-now').disabled = false; + } + + document.querySelector('input[name=securefieldcode]').value = + event.secureFieldCode; + } + + handleErrors(errors) { + let _errors = errors.split(' '); + let shouldShowGenericError = false; + let message = ''; + + _errors.forEach((error) => { + message = message.concat(this.errorCodes.get(error) + '
'); + }); + + document.getElementById('errors').innerHTML = message; + document.getElementById('errors').hidden = false; + } + + completeAuthorization(event) { + event.target.parentElement.disabled = true; + + document.getElementById('server-response').submit(); + } + + completePaymentUsingToken(event) { + event.target.parentElement.disabled = true; + + document.getElementById('server-response').submit(); + } + + completePaymentWithoutToken(event) { + event.target.parentElement.disabled = true; + + let tokenBillingCheckbox = document.querySelector( + 'input[name="token-billing-checkbox"]:checked' + ); + + if (tokenBillingCheckbox) { + document.querySelector('input[name="store_card"]').value = + tokenBillingCheckbox.value; + } + + document.getElementById('server-response').submit(); + } + + initialize() { + this.eWAY = eWAY.setupSecureField(this.groupFieldConfig, (event) => + this.securePanelCallback(event) + ); + } + + handle() { + this.initialize(); + + document + .getElementById('authorize-card') + ?.addEventListener('click', (e) => this.completeAuthorization(e)); + + Array.from( + document.getElementsByClassName('toggle-payment-with-token') + ).forEach((element) => + element.addEventListener('click', (element) => { + document + .getElementById('eway-secure-panel') + .classList.add('hidden'); + document.getElementById('save-card--container').style.display = + 'none'; + document.querySelector('input[name=token]').value = + element.target.dataset.token; + document.getElementById('pay-now').disabled = false; + }) + ); + + document + .getElementById('toggle-payment-with-credit-card') + .addEventListener('click', (element) => { + document + .getElementById('eway-secure-panel') + .classList.remove('hidden'); + document.getElementById('save-card--container').style.display = + 'grid'; + document.querySelector('input[name=token]').value = ''; + document.getElementById('pay-now').disabled = true; + }); + + document.getElementById('pay-now')?.addEventListener('click', (e) => { + let tokenInput = document.querySelector('input[name=token]'); + + if (tokenInput.value) { + return this.completePaymentUsingToken(e); + } + + return this.completePaymentWithoutToken(e); + }); + } +} + +new EwayRapid().handle(); diff --git a/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php b/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php index add65701a9..df9553087b 100644 --- a/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/eway/authorize.blade.php @@ -36,310 +36,5 @@ ctrans('texts.credit_card')]) @section('gateway_footer') - - + @endsection diff --git a/resources/views/portal/ninja2020/gateways/eway/pay.blade.php b/resources/views/portal/ninja2020/gateways/eway/pay.blade.php index 1d73902f71..a6b6aef31e 100644 --- a/resources/views/portal/ninja2020/gateways/eway/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/eway/pay.blade.php @@ -58,370 +58,5 @@ ctrans('texts.credit_card')]) @section('gateway_footer') - - + @endsection diff --git a/webpack.mix.js b/webpack.mix.js index a5eafbedeb..28be7be1c3 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -81,6 +81,10 @@ mix.js("resources/js/app.js", "public/js") .js( "resources/js/clients/payment_methods/wepay-bank-account.js", "public/js/clients/payment_methods/wepay-bank-account.js" + ) + .js( + "resources/js/clients/payments/eway-credit-card.js", + "public/js/clients/payments/eway-credit-card.js" ); mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css'); From e8fb4d4444eeddc69bc31ccc2ebe0f2deb30501a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:16:31 +0200 Subject: [PATCH 06/13] Production builds of assets --- public/js/clients/payments/eway-credit-card.js | 2 ++ .../js/clients/payments/eway-credit-card.js.LICENSE.txt | 9 +++++++++ public/mix-manifest.json | 1 + 3 files changed, 12 insertions(+) create mode 100644 public/js/clients/payments/eway-credit-card.js create mode 100644 public/js/clients/payments/eway-credit-card.js.LICENSE.txt diff --git a/public/js/clients/payments/eway-credit-card.js b/public/js/clients/payments/eway-credit-card.js new file mode 100644 index 0000000000..861a0ce085 --- /dev/null +++ b/public/js/clients/payments/eway-credit-card.js @@ -0,0 +1,2 @@ +/*! For license information please see eway-credit-card.js.LICENSE.txt */ +!function(e){var r={};function t(s){if(r[s])return r[s].exports;var o=r[s]={i:s,l:!1,exports:{}};return e[s].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=r,t.d=function(e,r,s){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:s})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(t.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(s,o,function(r){return e[r]}.bind(null,o));return s},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="/",t(t.s=21)}({21:function(e,r,t){e.exports=t("7VQy")},"7VQy":function(e,r){function t(e,r){for(var t=0;t")})),document.getElementById("errors").innerHTML=s,document.getElementById("errors").hidden=!1}},{key:"completeAuthorization",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server-response").submit()}},{key:"completePaymentUsingToken",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server-response").submit()}},{key:"completePaymentWithoutToken",value:function(e){e.target.parentElement.disabled=!0;var r=document.querySelector('input[name="token-billing-checkbox"]:checked');r&&(document.querySelector('input[name="store_card"]').value=r.value),document.getElementById("server-response").submit()}},{key:"initialize",value:function(){var e=this;this.eWAY=eWAY.setupSecureField(this.groupFieldConfig,(function(r){return e.securePanelCallback(r)}))}},{key:"handle",value:function(){var e,r,t=this;this.initialize(),null===(e=document.getElementById("authorize-card"))||void 0===e||e.addEventListener("click",(function(e){return t.completeAuthorization(e)})),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("eway-secure-panel").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token,document.getElementById("pay-now").disabled=!1}))})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",(function(e){document.getElementById("eway-secure-panel").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",document.getElementById("pay-now").disabled=!0})),null===(r=document.getElementById("pay-now"))||void 0===r||r.addEventListener("click",(function(e){return document.querySelector("input[name=token]").value?t.completePaymentUsingToken(e):t.completePaymentWithoutToken(e)}))}}])&&t(r.prototype,s),o&&t(r,o),e}())).handle()}}); \ No newline at end of file diff --git a/public/js/clients/payments/eway-credit-card.js.LICENSE.txt b/public/js/clients/payments/eway-credit-card.js.LICENSE.txt new file mode 100644 index 0000000000..6b30888ddb --- /dev/null +++ b/public/js/clients/payments/eway-credit-card.js.LICENSE.txt @@ -0,0 +1,9 @@ +/** + * 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 + */ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index b2050020b4..5d00d93b11 100755 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -11,6 +11,7 @@ "/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=c35db3cbb65806ab6a8a", "/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20", "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=065e5450233cc5b47020", + "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=95d6bedbb7ec7d942e13", "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7", "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17", "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=f1719b79a2bb274d3f64", From 5d12c331caf8e167d41e015ce4b5a62cb0415d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:18:26 +0200 Subject: [PATCH 07/13] Scaffold CreditCardTest --- .../Gateways/Eway/CreditCardTest.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php new file mode 100644 index 0000000000..c038aa926a --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -0,0 +1,35 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } +} From 1d1ca46aac80771ced3ec3f960e05ba7ce9b0a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:23:37 +0200 Subject: [PATCH 08/13] Credit card: Authorizing test --- .../Gateways/Eway/CreditCardTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php index c038aa926a..63aaaa22ce 100644 --- a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -32,4 +32,23 @@ class CreditCardTest extends DuskTestCase ->auth(); }); } + + public function testAddingCreditCardStandalone() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('EWAY_CARDNAME', 'Invoice Ninja') + ->type('EWAY_CARDNUMBER', '4111 1111 1111 1111') + ->type('EWAY_CARDEXPIRY', '04/22') + ->type('EWAY_CARDCVN', '100'); + }) + ->press('Add Payment Method') + ->waitForText('**** 1111'); + }); + } } From c108a5bcfc168435e58fff4f0d76fcab92d1c02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:24:26 +0200 Subject: [PATCH 09/13] Credit card: Removing test --- .../ClientPortal/Gateways/Eway/CreditCardTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php index 63aaaa22ce..6114ee5aa5 100644 --- a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -51,4 +51,17 @@ class CreditCardTest extends DuskTestCase ->waitForText('**** 1111'); }); } + + public function testRemoveCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } } From 79306ec4beeb4ce34c63f9ee67c7af65514eff3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:25:05 +0200 Subject: [PATCH 10/13] Credit card: Pay with new --- .../Gateways/Eway/CreditCardTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php index 6114ee5aa5..0f3bbeb997 100644 --- a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -33,6 +33,26 @@ class CreditCardTest extends DuskTestCase }); } + public function testPaymentWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('EWAY_CARDNAME', 'Invoice Ninja') + ->type('EWAY_CARDNUMBER', '4111 1111 1111 1111') + ->type('EWAY_CARDEXPIRY', '04/22') + ->type('EWAY_CARDCVN', '100'); + }) + ->click('#pay-now') + ->waitForText('Details of the payment', 60); + }); + } + public function testAddingCreditCardStandalone() { $this->browse(function (Browser $browser) { From f6ee61d9855fe5fdbad48d3bb69c3b60b4f6aed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:25:42 +0200 Subject: [PATCH 11/13] Credit card: Pay with new card and save for future use --- .../Gateways/Eway/CreditCardTest.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php index 0f3bbeb997..d4a9c17737 100644 --- a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -53,6 +53,30 @@ class CreditCardTest extends DuskTestCase }); } + public function testPayWithNewCardAndSaveForFutureUse() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('EWAY_CARDNAME', 'Invoice Ninja') + ->type('EWAY_CARDNUMBER', '4111 1111 1111 1111') + ->type('EWAY_CARDEXPIRY', '04/22') + ->type('EWAY_CARDCVN', '100'); + }) + ->radio('#proxy_is_default', true) + ->click('#pay-now') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('1111'); + }); + } + public function testAddingCreditCardStandalone() { $this->browse(function (Browser $browser) { From fc2094b9518b92592b4d92f053c6487838bc2cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:26:00 +0200 Subject: [PATCH 12/13] Credit card: Pay with saved card --- .../ClientPortal/Gateways/Eway/CreditCardTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php index d4a9c17737..95357d6ea0 100644 --- a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -77,6 +77,20 @@ class CreditCardTest extends DuskTestCase }); } + public function testPayWithSavedCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->click('#pay-now') + ->waitForText('Details of the payment', 60); + }); + } + public function testAddingCreditCardStandalone() { $this->browse(function (Browser $browser) { From ec9d2cc292ce280cb1866680fd9c157540547c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Sun, 15 Aug 2021 16:26:18 +0200 Subject: [PATCH 13/13] Update order of tests --- .../Gateways/Eway/CreditCardTest.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php index 95357d6ea0..528422ba07 100644 --- a/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php @@ -91,6 +91,19 @@ class CreditCardTest extends DuskTestCase }); } + public function testRemoveCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } + public function testAddingCreditCardStandalone() { $this->browse(function (Browser $browser) { @@ -109,17 +122,4 @@ class CreditCardTest extends DuskTestCase ->waitForText('**** 1111'); }); } - - public function testRemoveCreditCard() - { - $this->browse(function (Browser $browser) { - $browser - ->visitRoute('client.payment_methods.index') - ->clickLink('View') - ->press('Remove Payment Method') - ->waitForText('Confirmation') - ->click('@confirm-payment-removal') - ->assertSee('Payment method has been successfully removed.'); - }); - } }