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 000000000000..3b0c3f6053c8
--- /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 add65701a903..df9553087b8c 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 1d73902f7168..a6b6aef31eab 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 a5eafbedebe1..28be7be1c3b4 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');