diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php
index feddf40625de..2c74e118500a 100644
--- a/app/Models/Gateway.php
+++ b/app/Models/Gateway.php
@@ -63,6 +63,8 @@ class Gateway extends StaticModel
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
} elseif ($this->id == 20 || $this->id == 56) {
$link = 'https://dashboard.stripe.com/account/apikeys';
+ } elseif ($this->id == 59) {
+ $link = 'https://www.forte.net/';
}
return $link;
@@ -170,6 +172,12 @@ class Gateway extends StaticModel
GatewayType::HOSTED_PAGE => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']] // Razorpay
];
break;
+ case 59:
+ return [
+ GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], // Forte
+ GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
+ ];
+ break;
default:
return [];
break;
diff --git a/app/PaymentDrivers/Forte/ACH.php b/app/PaymentDrivers/Forte/ACH.php
new file mode 100644
index 000000000000..4795f956d6d0
--- /dev/null
+++ b/app/PaymentDrivers/Forte/ACH.php
@@ -0,0 +1,150 @@
+forte = $forte;
+
+ $this->forte_base_uri = "https://sandbox.forte.net/api/v3/";
+ if($this->forte->company_gateway->getConfigField('testMode') == false){
+ $this->forte_base_uri = "https://api.forte.net/v3/";
+ }
+ $this->forte_api_access_id = $this->forte->company_gateway->getConfigField('apiAccessId');
+ $this->forte_secure_key = $this->forte->company_gateway->getConfigField('secureKey');
+ $this->forte_auth_organization_id = $this->forte->company_gateway->getConfigField('authOrganizationId');
+ $this->forte_organization_id = $this->forte->company_gateway->getConfigField('organizationId');
+ $this->forte_location_id = $this->forte->company_gateway->getConfigField('locationId');
+ }
+
+ public function authorizeView(array $data)
+ {
+ return render('gateways.forte.ach.authorize', $data);
+ }
+
+ public function authorizeResponse(Request $request)
+ {
+
+ $payment_meta = new \stdClass;
+ $payment_meta->brand = (string)ctrans('texts.ach');
+ $payment_meta->last4 = (string) $request->last_4;
+ $payment_meta->exp_year = '-';
+ $payment_meta->type = GatewayType::BANK_TRANSFER;
+
+ $data = [
+ 'payment_meta' => $payment_meta,
+ 'token' => $request->one_time_token,
+ 'payment_method_id' => $request->gateway_type_id,
+ ];
+
+ $this->forte->storeGatewayToken($data);
+
+ return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
+ }
+
+ public function paymentView(array $data)
+ {
+ $this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
+ $this->forte->payment_hash->save();
+
+ $data['gateway'] = $this;
+ return render('gateways.forte.ach.pay', $data);
+ }
+
+ public function paymentResponse($request)
+ {
+ $payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
+
+ try {
+ $curl = curl_init();
+ curl_setopt_array($curl, array(
+ CURLOPT_URL => $this->forte_base_uri.'organizations/'.$this->forte_organization_id.'/locations/'.$this->forte_location_id.'/transactions',
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_ENCODING => '',
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_TIMEOUT => 0,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_POSTFIELDS =>'{
+ "action":"sale",
+ "authorization_amount": '.$payment_hash->data->total->amount_with_fee.',
+ "echeck":{
+ "sec_code":"PPD",
+ },
+ "billing_address":{
+ "first_name": "'.$this->forte->client->name.'",
+ "last_name": "'.$this->forte->client->name.'"
+ },
+ "echeck":{
+ "one_time_token":"'.$request->payment_token.'"
+ }
+ }',
+ CURLOPT_HTTPHEADER => array(
+ 'X-Forte-Auth-Organization-Id: '.$this->forte_organization_id,
+ 'Content-Type: application/json',
+ 'Authorization: Basic '.base64_encode($this->forte_api_access_id.':'.$this->forte_secure_key),
+ 'Cookie: visid_incap_621087=u18+3REYR/iISgzZxOF5s2ODW2IAAAAAQUIPAAAAAADuGqKgECQLS81FcSDrmhGe; nlbi_621087=YHngadhJ2VU+yr7/R1efXgAAAAD3mQyhqmnLls8PRu4iN58G; incap_ses_1136_621087=CVdrXUdhIlm9WJNDieLDD4QVXGIAAAAAvBwvkUcwhM0+OwvdPm2stg=='
+ ),
+ ));
+
+ $response = curl_exec($curl);
+ $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+ curl_close($curl);
+
+ $response=json_decode($response);
+ } catch (\Throwable $th) {
+ throw $th;
+ }
+
+ if ($httpcode>299) {
+ $error = Validator::make([], []);
+ $error->getMessageBag()->add('gateway_error', $response->response->response_desc);
+ return redirect('client/invoices')->withErrors($error);
+ }
+
+ $data = [
+ 'payment_method' => $request->payment_method_id,
+ 'payment_type' => PaymentType::ACH,
+ 'amount' => $payment_hash->data->amount_with_fee,
+ 'transaction_reference' => $response->transaction_id,
+ 'gateway_type_id' => GatewayType::BANK_TRANSFER,
+ ];
+
+ $payment=$this->forte->createPayment($data, Payment::STATUS_COMPLETED);
+ return redirect('client/invoices')->withSuccess('Invoice paid.');
+ }
+}
diff --git a/app/PaymentDrivers/Forte/CreditCard.php b/app/PaymentDrivers/Forte/CreditCard.php
new file mode 100644
index 000000000000..b9882b3a4349
--- /dev/null
+++ b/app/PaymentDrivers/Forte/CreditCard.php
@@ -0,0 +1,160 @@
+forte = $forte;
+
+ $this->forte_base_uri = "https://sandbox.forte.net/api/v3/";
+ if($this->forte->company_gateway->getConfigField('testMode') == false){
+ $this->forte_base_uri = "https://api.forte.net/v3/";
+ }
+ $this->forte_api_access_id = $this->forte->company_gateway->getConfigField('apiAccessId');
+ $this->forte_secure_key = $this->forte->company_gateway->getConfigField('secureKey');
+ $this->forte_auth_organization_id = $this->forte->company_gateway->getConfigField('authOrganizationId');
+ $this->forte_organization_id = $this->forte->company_gateway->getConfigField('organizationId');
+ $this->forte_location_id = $this->forte->company_gateway->getConfigField('locationId');
+ }
+
+ public function authorizeView(array $data)
+ {
+ return render('gateways.forte.credit_card.authorize', $data);
+ }
+
+ public function authorizeResponse($request)
+ {
+ $payment_meta = new \stdClass;
+ $payment_meta->exp_month = (string) $request->expire_month;
+ $payment_meta->exp_year = (string) $request->expire_year;
+ $payment_meta->brand = (string) $request->card_type;
+ $payment_meta->last4 = (string) $request->last_4;
+ $payment_meta->type = GatewayType::CREDIT_CARD;
+
+ $data = [
+ 'payment_meta' => $payment_meta,
+ 'token' => $request->one_time_token,
+ 'payment_method_id' => $request->payment_method_id,
+ ];
+
+ $this->forte->storeGatewayToken($data);
+
+ return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
+ }
+
+ public function paymentView(array $data)
+ {
+ $this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
+ $this->forte->payment_hash->save();
+
+ $data['gateway'] = $this;
+ return render('gateways.forte.credit_card.pay', $data);
+ }
+
+ public function paymentResponse(PaymentResponseRequest $request)
+ {
+ $payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
+ $amount_with_fee = $payment_hash->data->total->amount_with_fee;
+ $invoice_totals = $payment_hash->data->total->invoice_totals;
+ $fee_total = 0;
+ print_r($payment_hash->data->total);
+ for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
+ $calculated_fee = ( 3 * $i) / 100;
+ $calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
+ if ($calculated_amount_with_fee == $amount_with_fee) {
+ $fee_total = round($calculated_fee / 100,2);
+ $amount_with_fee = $calculated_amount_with_fee;
+ break;
+ }
+ }
+
+ try {
+ $curl = curl_init();
+
+ curl_setopt_array($curl, array(
+ CURLOPT_URL => $this->forte_base_uri.'organizations/'.$this->forte_organization_id.'/locations/'.$this->forte_location_id.'/transactions',
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_ENCODING => '',
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_TIMEOUT => 0,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_POSTFIELDS =>'{
+ "action":"sale",
+ "authorization_amount":'.$amount_with_fee.',
+ "service_fee_amount":'.$fee_total.',
+ "billing_address":{
+ "first_name":"'.$this->forte->client->name.'",
+ "last_name":"'.$this->forte->client->name.'"
+ },
+ "card":{
+ "one_time_token":"'.$request->payment_token.'"
+ }
+ }',
+ CURLOPT_HTTPHEADER => array(
+ 'Content-Type: application/json',
+ 'X-Forte-Auth-Organization-Id: '.$this->forte_organization_id,
+ 'Authorization: Basic '.base64_encode($this->forte_api_access_id.':'.$this->forte_secure_key)
+ ),
+ ));
+
+ $response = curl_exec($curl);
+ $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+ curl_close($curl);
+
+ $response=json_decode($response);
+ } catch (\Throwable $th) {
+ throw $th;
+ }
+ if ($httpcode>299) {
+ $error = Validator::make([], []);
+ $error->getMessageBag()->add('gateway_error', $response->response->response_desc);
+ return redirect('client/invoices')->withErrors($error);
+ }
+
+ $data = [
+ 'payment_method' => $request->payment_method_id,
+ 'payment_type' => PaymentType::parseCardType(strtolower($request->card_brand)) ?: PaymentType::CREDIT_CARD_OTHER,
+ 'amount' => $payment_hash->data->amount_with_fee,
+ 'transaction_reference' => $response->transaction_id,
+ 'gateway_type_id' => GatewayType::CREDIT_CARD,
+ ];
+ $payment=$this->forte->createPayment($data, Payment::STATUS_COMPLETED);
+ return redirect('client/invoices')->withSuccess('Invoice paid.');
+ }
+}
diff --git a/app/PaymentDrivers/FortePaymentDriver.php b/app/PaymentDrivers/FortePaymentDriver.php
new file mode 100644
index 000000000000..e79df1dd96dd
--- /dev/null
+++ b/app/PaymentDrivers/FortePaymentDriver.php
@@ -0,0 +1,90 @@
+ CreditCard::class,
+ GatewayType::BANK_TRANSFER => ACH::class,
+ ];
+
+ /**
+ * Returns the gateway types.
+ */
+ public function gatewayTypes(): array
+ {
+ $types = [];
+
+ $types[] = GatewayType::CREDIT_CARD;
+ $types[] = GatewayType::BANK_TRANSFER;
+
+ return $types;
+ }
+
+ const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
+
+ public function setPaymentMethod($payment_method_id)
+ {
+ $class = self::$methods[$payment_method_id];
+ $this->payment_method = new $class($this);
+ return $this;
+ }
+
+ public function authorizeView(array $data)
+ {
+ return $this->payment_method->authorizeView($data); //this is your custom implementation from here
+ }
+
+ public function authorizeResponse($request)
+ {
+ return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
+ }
+
+ public function processPaymentView(array $data)
+ {
+ return $this->payment_method->paymentView($data); //this is your custom implementation from here
+ }
+
+ public function processPaymentResponse($request)
+ {
+ return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
+ }
+
+ // public function refund(Payment $payment, $amount, $return_client_response = false)
+ // {
+ // return $this->payment_method->yourRefundImplementationHere(); //this is your custom implementation from here
+ // }
+
+ // public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
+ // {
+ // return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
+ // }
+}
diff --git a/database/migrations/2022_04_14_121548_forte_payment_gateway.php b/database/migrations/2022_04_14_121548_forte_payment_gateway.php
new file mode 100644
index 000000000000..93268ed7ad38
--- /dev/null
+++ b/database/migrations/2022_04_14_121548_forte_payment_gateway.php
@@ -0,0 +1,50 @@
+testMode = false;
+ $fields->apiLoginId = "";
+ $fields->apiAccessId = "";
+ $fields->secureKey = "";
+ $fields->authOrganizationId = "";
+ $fields->organizationId = "";
+ $fields->locationId = "";
+
+ $forte = new Gateway;
+ $forte->id = 59;
+ $forte->name = 'Forte';
+ $forte->key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs';
+ $forte->provider = 'Forte';
+ $forte->is_offsite = true;
+ $forte->fields = \json_encode($fields);
+ $forte->visible = 1;
+ $forte->site_url = 'https://www.forte.net/';
+ $forte->default_gateway_type_id = GatewayType::CREDIT_CARD;
+ $forte->save();
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ //
+ }
+}
diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php
index 219665df6991..b3a6041e3638 100644
--- a/database/seeders/PaymentLibrariesSeeder.php
+++ b/database/seeders/PaymentLibrariesSeeder.php
@@ -98,7 +98,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
- Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58])->update(['visible' => 1]);
+ Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58,59])->update(['visible' => 1]);
if (Ninja::isHosted()) {
Gateway::whereIn('id', [20])->update(['visible' => 0]);
diff --git a/public/js/clients/payments/forte-ach-payment.js b/public/js/clients/payments/forte-ach-payment.js
new file mode 100644
index 000000000000..837aeac0b641
--- /dev/null
+++ b/public/js/clients/payments/forte-ach-payment.js
@@ -0,0 +1,81 @@
+/******/ (() => { // webpackBootstrap
+var __webpack_exports__ = {};
+/*!************************************************************!*\
+ !*** ./resources/js/clients/payments/forte-ach-payment.js ***!
+ \************************************************************/
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/**
+ * 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://opensource.org/licenses/AAL
+ */
+var ForteAuthorizeACH = function ForteAuthorizeACH(apiLoginId) {
+ var _this = this;
+
+ _classCallCheck(this, ForteAuthorizeACH);
+
+ _defineProperty(this, "handleAuthorization", function () {
+ var account_number = document.getElementById('account-number').value;
+ var routing_number = document.getElementById('routing-number').value;
+ var data = {
+ api_login_id: _this.apiLoginId,
+ account_number: account_number,
+ routing_number: routing_number,
+ account_type: 'checking'
+ };
+ var payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ document.getElementById('pay-now').disabled = true;
+ document.querySelector('#pay-now > svg').classList.remove('hidden');
+ document.querySelector('#pay-now > span').classList.add('hidden');
+ } // console.log(data);
+
+
+ forte.createToken(data).success(_this.successResponseHandler).error(_this.failedResponseHandler);
+ return false;
+ });
+
+ _defineProperty(this, "successResponseHandler", function (response) {
+ document.getElementById('payment_token').value = response.onetime_token;
+ document.getElementById('server_response').submit();
+ return false;
+ });
+
+ _defineProperty(this, "failedResponseHandler", function (response) {
+ var errors = '
- ' + response.response_description + '
';
+ document.getElementById('forte_errors').innerHTML = errors;
+ document.getElementById('pay-now').disabled = false;
+ document.querySelector('#pay-now > svg').classList.add('hidden');
+ document.querySelector('#pay-now > span').classList.remove('hidden');
+ return false;
+ });
+
+ _defineProperty(this, "handle", function () {
+ var payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ payNowButton.addEventListener('click', function (e) {
+ _this.handleAuthorization();
+ });
+ }
+
+ return _this;
+ });
+
+ this.apiLoginId = apiLoginId;
+};
+
+var apiLoginId = document.querySelector('meta[name="forte-api-login-id"]').content;
+/** @handle */
+
+new ForteAuthorizeACH(apiLoginId).handle();
+/******/ })()
+;
\ No newline at end of file
diff --git a/public/js/clients/payments/forte-card-js.min.js b/public/js/clients/payments/forte-card-js.min.js
new file mode 100644
index 000000000000..833dd5ee4bed
--- /dev/null
+++ b/public/js/clients/payments/forte-card-js.min.js
@@ -0,0 +1,669 @@
+(() => {
+ function t(t) {
+ (this.elem = jQuery(t)),
+ (this.captureName =
+ !!this.elem.data('capture-name') &&
+ this.elem.data('capture-name')),
+ (this.iconColour =
+ !!this.elem.data('icon-colour') &&
+ this.elem.data('icon-colour')),
+ (this.stripe =
+ !!this.elem.data('stripe') && this.elem.data('stripe')),
+ this.stripe && (this.captureName = !1),
+ this.initCardNumberInput(),
+ this.initNameInput(),
+ this.initExpiryMonthInput(),
+ this.initExpiryYearInput(),
+ this.initCvcInput(),
+ this.elem.empty(),
+ this.setupCardNumberInput(),
+ this.setupNameInput(),
+ this.setupExpiryInput(),
+ this.setupCvcInput(),
+ this.iconColour && this.setIconColour(this.iconColour),
+ this.refreshCreditCardTypeIcon();
+ }
+ !(function (e) {
+ var r = {
+ init: function () {
+ return this.data('cardjs', new t(this)), this;
+ },
+ cardNumber: function () {
+ return this.data('cardjs').getCardNumber();
+ },
+ cardType: function () {
+ return this.data('cardjs').getCardType();
+ },
+ name: function () {
+ return this.data('cardjs').getName();
+ },
+ expiryMonth: function () {
+ return this.data('cardjs').getExpiryMonth();
+ },
+ expiryYear: function () {
+ return this.data('cardjs').getExpiryYear();
+ },
+ cvc: function () {
+ return this.data('cardjs').getCvc();
+ },
+ };
+ e.fn.CardJs = function (t) {
+ return r[t]
+ ? r[t].apply(this, Array.prototype.slice.call(arguments, 1))
+ : 'object' != typeof t && t
+ ? void e.error(
+ 'Method ' + t + ' does not exist on jQuery.CardJs'
+ )
+ : r.init.apply(this, arguments);
+ };
+ })(jQuery),
+ $(function () {
+ $('.card-js').each(function (t, e) {
+ $(e).CardJs();
+ });
+ }),
+ (t.prototype.constructor = t),
+ (t.KEYS = {
+ 0: 48,
+ 9: 57,
+ NUMPAD_0: 96,
+ NUMPAD_9: 105,
+ DELETE: 46,
+ BACKSPACE: 8,
+ ARROW_LEFT: 37,
+ ARROW_RIGHT: 39,
+ ARROW_UP: 38,
+ ARROW_DOWN: 40,
+ HOME: 36,
+ END: 35,
+ TAB: 9,
+ A: 65,
+ X: 88,
+ C: 67,
+ V: 86,
+ }),
+ (t.CREDIT_CARD_NUMBER_DEFAULT_MASK = 'XXXX XXXX XXXX XXXX'),
+ (t.CREDIT_CARD_NUMBER_VISA_MASK = 'XXXX XXXX XXXX XXXX'),
+ (t.CREDIT_CARD_NUMBER_MASTERCARD_MASK = 'XXXX XXXX XXXX XXXX'),
+ (t.CREDIT_CARD_NUMBER_DISCOVER_MASK = 'XXXX XXXX XXXX XXXX'),
+ (t.CREDIT_CARD_NUMBER_JCB_MASK = 'XXXX XXXX XXXX XXXX'),
+ (t.CREDIT_CARD_NUMBER_AMEX_MASK = 'XXXX XXXXXX XXXXX'),
+ (t.CREDIT_CARD_NUMBER_DINERS_MASK = 'XXXX XXXX XXXX XX'),
+ (t.prototype.creditCardNumberMask = t.CREDIT_CARD_NUMBER_DEFAULT_MASK),
+ (t.CREDIT_CARD_NUMBER_PLACEHOLDER = 'Card number'),
+ (t.NAME_PLACEHOLDER = 'Name on card'),
+ (t.EXPIRY_MASK = 'XX / XXXX'),
+ (t.EXPIRY_PLACEHOLDER = 'MM / YYYY'),
+ (t.EXPIRY_USE_DROPDOWNS = !1),
+ (t.EXPIRY_NUMBER_OF_YEARS = 10),
+ (t.CVC_MASK_3 = 'XXX'),
+ (t.CVC_MASK_4 = 'XXXX'),
+ (t.CVC_PLACEHOLDER = 'CVC'),
+ (t.CREDIT_CARD_SVG =
+ ''),
+ (t.LOCK_SVG =
+ ''),
+ (t.CALENDAR_SVG =
+ ''),
+ (t.USER_SVG =
+ ''),
+ (t.MAIL_SVG =
+ ''),
+ (t.INFORMATION_SVG =
+ ''),
+ (t.keyCodeFromEvent = function (t) {
+ return t.which || t.keyCode;
+ }),
+ (t.keyIsCommandFromEvent = function (t) {
+ return t.ctrlKey || t.metaKey;
+ }),
+ (t.keyIsNumber = function (e) {
+ return t.keyIsTopNumber(e) || t.keyIsKeypadNumber(e);
+ }),
+ (t.keyIsTopNumber = function (e) {
+ var r = t.keyCodeFromEvent(e);
+ return r >= t.KEYS[0] && r <= t.KEYS[9];
+ }),
+ (t.keyIsKeypadNumber = function (e) {
+ var r = t.keyCodeFromEvent(e);
+ return r >= t.KEYS.NUMPAD_0 && r <= t.KEYS.NUMPAD_9;
+ }),
+ (t.keyIsDelete = function (e) {
+ return t.keyCodeFromEvent(e) == t.KEYS.DELETE;
+ }),
+ (t.keyIsBackspace = function (e) {
+ return t.keyCodeFromEvent(e) == t.KEYS.BACKSPACE;
+ }),
+ (t.keyIsDeletion = function (e) {
+ return t.keyIsDelete(e) || t.keyIsBackspace(e);
+ }),
+ (t.keyIsArrow = function (e) {
+ var r = t.keyCodeFromEvent(e);
+ return r >= t.KEYS.ARROW_LEFT && r <= t.KEYS.ARROW_DOWN;
+ }),
+ (t.keyIsNavigation = function (e) {
+ var r = t.keyCodeFromEvent(e);
+ return r == t.KEYS.HOME || r == t.KEYS.END;
+ }),
+ (t.keyIsKeyboardCommand = function (e) {
+ var r = t.keyCodeFromEvent(e);
+ return (
+ t.keyIsCommandFromEvent(e) &&
+ (r == t.KEYS.A ||
+ r == t.KEYS.X ||
+ r == t.KEYS.C ||
+ r == t.KEYS.V)
+ );
+ }),
+ (t.keyIsTab = function (e) {
+ return t.keyCodeFromEvent(e) == t.KEYS.TAB;
+ }),
+ (t.copyAllElementAttributes = function (t, e) {
+ $.each(t[0].attributes, function (t, r) {
+ e.attr(r.nodeName, r.nodeValue);
+ });
+ }),
+ (t.numbersOnlyString = function (t) {
+ for (var e = '', r = 0; r < t.length; r++) {
+ var n = t.charAt(r);
+ !isNaN(parseInt(n)) && (e += n);
+ }
+ return e;
+ }),
+ (t.applyFormatMask = function (t, e) {
+ for (var r = '', n = 0, i = 0; i < e.length; i++) {
+ var a = e[i];
+ if ('X' == a) {
+ if (!t.charAt(n)) break;
+ (r += t.charAt(n)), n++;
+ } else r += a;
+ }
+ return r;
+ }),
+ (t.cardTypeFromNumber = function (t) {
+ if (((e = new RegExp('^30[0-5]')), null != t.match(e)))
+ return 'Diners - Carte Blanche';
+ if (((e = new RegExp('^(30[6-9]|36|38)')), null != t.match(e)))
+ return 'Diners';
+ if (((e = new RegExp('^35(2[89]|[3-8][0-9])')), null != t.match(e)))
+ return 'JCB';
+ if (((e = new RegExp('^3[47]')), null != t.match(e))) return 'AMEX';
+ if (
+ ((e = new RegExp('^(4026|417500|4508|4844|491(3|7))')),
+ null != t.match(e))
+ )
+ return 'Visa Electron';
+ var e = new RegExp('^4');
+ return null != t.match(e)
+ ? 'Visa'
+ : ((e = new RegExp('^5[1-5]')),
+ null != t.match(e)
+ ? 'Mastercard'
+ : ((e = new RegExp(
+ '^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)'
+ )),
+ null != t.match(e) ? 'Discover' : ''));
+ }),
+ (t.caretStartPosition = function (t) {
+ return 'number' == typeof t.selectionStart && t.selectionStart;
+ }),
+ (t.caretEndPosition = function (t) {
+ return 'number' == typeof t.selectionEnd && t.selectionEnd;
+ }),
+ (t.setCaretPosition = function (t, e) {
+ if (null != t)
+ if (t.createTextRange) {
+ var r = t.createTextRange();
+ r.move('character', e), r.select();
+ } else
+ t.selectionStart
+ ? (t.focus(), t.setSelectionRange(e, e))
+ : t.focus();
+ }),
+ (t.normaliseCaretPosition = function (t, e) {
+ var r = 0;
+ if (0 > e || e > t.length) return 0;
+ for (var n = 0; n < t.length; n++) {
+ if (n == e) return r;
+ 'X' == t[n] && r++;
+ }
+ return r;
+ }),
+ (t.denormaliseCaretPosition = function (t, e) {
+ var r = 0;
+ if (0 > e || e > t.length) return 0;
+ for (var n = 0; n < t.length; n++) {
+ if (r == e) return n;
+ 'X' == t[n] && r++;
+ }
+ return t.length;
+ }),
+ (t.filterNumberOnlyKey = function (e) {
+ var r = t.keyIsNumber(e),
+ n = t.keyIsDeletion(e),
+ i = t.keyIsArrow(e),
+ a = t.keyIsNavigation(e),
+ s = t.keyIsKeyboardCommand(e),
+ p = t.keyIsTab(e);
+ r || n || i || a || s || p || e.preventDefault();
+ }),
+ (t.digitFromKeyCode = function (e) {
+ return e >= t.KEYS[0] && e <= t.KEYS[9]
+ ? e - t.KEYS[0]
+ : e >= t.KEYS.NUMPAD_0 && e <= t.KEYS.NUMPAD_9
+ ? e - t.KEYS.NUMPAD_0
+ : null;
+ }),
+ (t.handleMaskedNumberInputKey = function (e, r) {
+ t.filterNumberOnlyKey(e);
+ var n = e.which || e.keyCode,
+ i = e.target,
+ a = t.caretStartPosition(i),
+ s = t.caretEndPosition(i),
+ p = t.normaliseCaretPosition(r, a),
+ c = t.normaliseCaretPosition(r, s),
+ o = a,
+ u = t.keyIsNumber(e),
+ h = t.keyIsDelete(e),
+ d = t.keyIsBackspace(e);
+ if (u || h || d) {
+ e.preventDefault();
+ var l = $(i).val(),
+ y = t.numbersOnlyString(l),
+ m = t.digitFromKeyCode(n),
+ C = c > p;
+ C && (y = y.slice(0, p) + y.slice(c)),
+ a != r.length &&
+ (u &&
+ l.length <= r.length &&
+ ((y = y.slice(0, p) + m + y.slice(p)),
+ (o = Math.max(
+ t.denormaliseCaretPosition(r, p + 1),
+ t.denormaliseCaretPosition(r, p + 2) - 1
+ ))),
+ h && (y = y.slice(0, p) + y.slice(p + 1))),
+ 0 != a &&
+ d &&
+ !C &&
+ ((y = y.slice(0, p - 1) + y.slice(p)),
+ (o = t.denormaliseCaretPosition(r, p - 1))),
+ $(i).val(t.applyFormatMask(y, r)),
+ t.setCaretPosition(i, o);
+ }
+ }),
+ (t.handleCreditCardNumberKey = function (e, r) {
+ t.handleMaskedNumberInputKey(e, r);
+ }),
+ (t.handleCreditCardNumberChange = function (t) {}),
+ (t.handleExpiryKey = function (e) {
+ t.handleMaskedNumberInputKey(e, t.EXPIRY_MASK);
+ }),
+ (t.prototype.getCardNumber = function () {
+ return this.cardNumberInput.val();
+ }),
+ (t.prototype.getCardType = function () {
+ return t.cardTypeFromNumber(this.getCardNumber());
+ }),
+ (t.prototype.getName = function () {
+ return this.nameInput.val();
+ }),
+ (t.prototype.getExpiryMonth = function () {
+ return this.expiryMonthInput.val();
+ }),
+ (t.prototype.getExpiryYear = function () {
+ return this.expiryYearInput.val();
+ }),
+ (t.prototype.getCvc = function () {
+ return this.cvcInput.val();
+ }),
+ (t.prototype.setIconColour = function (t) {
+ this.elem.find('.icon .svg').css({ fill: t });
+ }),
+ (t.prototype.setIconColour = function (t) {
+ this.elem.find('.icon .svg').css({ fill: t });
+ }),
+ (t.prototype.refreshCreditCardTypeIcon = function () {
+ this.setCardTypeIconFromNumber(
+ t.numbersOnlyString(this.cardNumberInput.val())
+ );
+ }),
+ (t.prototype.clearCardTypeIcon = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .removeClass('show');
+ }),
+ (t.prototype.setCardTypeIconAsVisa = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .attr('class', 'card-type-icon show visa');
+ }),
+ (t.prototype.setCardTypeIconAsMasterCard = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .attr('class', 'card-type-icon show master-card');
+ }),
+ (t.prototype.setCardTypeIconAsAmericanExpress = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .attr('class', 'card-type-icon show american-express');
+ }),
+ (t.prototype.setCardTypeIconAsDiscover = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .attr('class', 'card-type-icon show discover');
+ }),
+ (t.prototype.setCardTypeIconAsDiners = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .attr('class', 'card-type-icon show diners');
+ }),
+ (t.prototype.setCardTypeIconAsJcb = function () {
+ this.elem
+ .find('.card-number-wrapper .card-type-icon')
+ .attr('class', 'card-type-icon show jcb');
+ }),
+ (t.prototype.setCardTypeIconFromNumber = function (e) {
+ switch (t.cardTypeFromNumber(e)) {
+ case 'Visa Electron':
+ case 'Visa':
+ this.setCardTypeAsVisa();
+ break;
+ case 'Mastercard':
+ this.setCardTypeAsMasterCard();
+ break;
+ case 'AMEX':
+ this.setCardTypeAsAmericanExpress();
+ break;
+ case 'Discover':
+ this.setCardTypeAsDiscover();
+ break;
+ case 'Diners - Carte Blanche':
+ case 'Diners':
+ this.setCardTypeAsDiners();
+ break;
+ case 'JCB':
+ this.setCardTypeAsJcb();
+ break;
+ default:
+ this.clearCardType();
+ }
+ }),
+ (t.prototype.setCardMask = function (t) {
+ (this.creditCardNumberMask = t),
+ this.cardNumberInput.attr('maxlength', t.length);
+ }),
+ (t.prototype.setCvc3 = function () {
+ this.cvcInput.attr('maxlength', t.CVC_MASK_3.length);
+ }),
+ (t.prototype.setCvc4 = function () {
+ this.cvcInput.attr('maxlength', t.CVC_MASK_4.length);
+ }),
+ (t.prototype.clearCardType = function () {
+ this.clearCardTypeIcon(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_DEFAULT_MASK),
+ this.setCvc3();
+ }),
+ (t.prototype.setCardTypeAsVisa = function () {
+ this.setCardTypeIconAsVisa(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_VISA_MASK),
+ this.setCvc3();
+ }),
+ (t.prototype.setCardTypeAsMasterCard = function () {
+ this.setCardTypeIconAsMasterCard(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_MASTERCARD_MASK),
+ this.setCvc3();
+ }),
+ (t.prototype.setCardTypeAsAmericanExpress = function () {
+ this.setCardTypeIconAsAmericanExpress(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_AMEX_MASK),
+ this.setCvc4();
+ }),
+ (t.prototype.setCardTypeAsDiscover = function () {
+ this.setCardTypeIconAsDiscover(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_DISCOVER_MASK),
+ this.setCvc3();
+ }),
+ (t.prototype.setCardTypeAsDiners = function () {
+ this.setCardTypeIconAsDiners(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_DINERS_MASK),
+ this.setCvc3();
+ }),
+ (t.prototype.setCardTypeAsJcb = function () {
+ this.setCardTypeIconAsJcb(),
+ this.setCardMask(t.CREDIT_CARD_NUMBER_JCB_MASK),
+ this.setCvc3();
+ }),
+ (t.prototype.initCardNumberInput = function () {
+ var e = this;
+ (this.cardNumberInput = this.elem.find('.card-number')),
+ this.cardNumberInput[0]
+ ? this.cardNumberInput.detach()
+ : (this.cardNumberInput = $(
+ ""
+ )),
+ this.cardNumberInput.attr('type', 'tel'),
+ this.cardNumberInput.attr('placeholder') ||
+ this.cardNumberInput.attr(
+ 'placeholder',
+ t.CREDIT_CARD_NUMBER_PLACEHOLDER
+ ),
+ this.cardNumberInput.attr(
+ 'maxlength',
+ this.creditCardNumberMask.length
+ ),
+ this.cardNumberInput.attr('x-autocompletetype', 'cc-number'),
+ this.cardNumberInput.attr('autocompletetype', 'cc-number'),
+ this.cardNumberInput.attr('autocorrect', 'off'),
+ this.cardNumberInput.attr('spellcheck', 'off'),
+ this.cardNumberInput.attr('autocapitalize', 'off'),
+ this.cardNumberInput.keydown(function (r) {
+ t.handleCreditCardNumberKey(r, e.creditCardNumberMask);
+ }),
+ this.cardNumberInput.keyup(function (t) {
+ e.refreshCreditCardTypeIcon();
+ }),
+ this.cardNumberInput.change(t.handleCreditCardNumberChange);
+ }),
+ (t.prototype.initNameInput = function () {
+ (this.nameInput = this.elem.find('.name')),
+ this.nameInput[0]
+ ? ((this.captureName = !0), this.nameInput.detach())
+ : (this.nameInput = $("")),
+ this.nameInput.attr('placeholder') ||
+ this.nameInput.attr('placeholder', t.NAME_PLACEHOLDER);
+ }),
+ (t.prototype.initExpiryMonthInput = function () {
+ (this.expiryMonthInput = this.elem.find('.expiry-month')),
+ this.expiryMonthInput[0]
+ ? this.expiryMonthInput.detach()
+ : (this.expiryMonthInput = $(
+ ""
+ ));
+ }),
+ (t.prototype.initExpiryYearInput = function () {
+ (this.expiryYearInput = this.elem.find('.expiry-year')),
+ this.expiryYearInput[0]
+ ? this.expiryYearInput.detach()
+ : (this.expiryYearInput = $(
+ ""
+ ));
+ }),
+ (t.prototype.initCvcInput = function () {
+ (this.cvcInput = this.elem.find('.cvc')),
+ this.cvcInput[0]
+ ? this.cvcInput.detach()
+ : (this.cvcInput = $("")),
+ this.cvcInput.attr('type', 'tel'),
+ this.cvcInput.attr('placeholder') ||
+ this.cvcInput.attr('placeholder', t.CVC_PLACEHOLDER),
+ this.cvcInput.attr('maxlength', t.CVC_MASK_3.length),
+ this.cvcInput.attr('x-autocompletetype', 'cc-csc'),
+ this.cvcInput.attr('autocompletetype', 'cc-csc'),
+ this.cvcInput.attr('autocorrect', 'off'),
+ this.cvcInput.attr('spellcheck', 'off'),
+ this.cvcInput.attr('autocapitalize', 'off'),
+ this.cvcInput.keydown(t.filterNumberOnlyKey);
+ }),
+ (t.prototype.setupCardNumberInput = function () {
+ this.stripe && this.cardNumberInput.attr('data-stripe', 'number'),
+ this.elem.append("");
+ var e = this.elem.find('.card-number-wrapper');
+ e.append(this.cardNumberInput),
+ e.append(""),
+ e.append(""),
+ e.find('.icon').append(t.CREDIT_CARD_SVG);
+ }),
+ (t.prototype.setupNameInput = function () {
+ if (this.captureName) {
+ this.elem.append("");
+ var e = this.elem.find('.name-wrapper');
+ e.append(this.nameInput),
+ e.append(""),
+ e.find('.icon').append(t.USER_SVG);
+ }
+ }),
+ (t.prototype.setupExpiryInput = function () {
+ this.elem.append(
+ ""
+ );
+ var e,
+ r = this.elem.find('.expiry-wrapper');
+ if (this.EXPIRY_USE_DROPDOWNS) {
+ e = $('');
+ var n = $(
+ ""
+ ),
+ i = this.expiryMonthInput;
+ t.copyAllElementAttributes(i, n),
+ this.expiryMonthInput.remove(),
+ (this.expiryMonthInput = n);
+ for (
+ var a = $(
+ ""
+ ),
+ s = parseInt(
+ new Date().getFullYear().toString().substring(2, 4)
+ ),
+ p = 0;
+ p < t.EXPIRY_NUMBER_OF_YEARS;
+ p++
+ )
+ a.append("'),
+ (s = (s + 1) % 100);
+ var c = this.expiryYearInput;
+ t.copyAllElementAttributes(c, a),
+ this.expiryYearInput.remove(),
+ (this.expiryYearInput = a),
+ e.append(this.expiryMonthInput),
+ e.append(this.expiryYearInput);
+ } else {
+ (e = $('')),
+ (this.expiryMonthInput = $(
+ ""
+ )),
+ (this.expiryYearInput = $(
+ ""
+ )),
+ this.stripe &&
+ (this.expiryMonthInput.attr('data-stripe', 'exp-month'),
+ this.expiryYearInput.attr('data-stripe', 'exp-year')),
+ (this.expiryMonthYearInput = $("")),
+ this.expiryMonthYearInput.attr('type', 'tel'),
+ this.expiryMonthYearInput.attr('placeholder') ||
+ this.expiryMonthYearInput.attr(
+ 'placeholder',
+ t.EXPIRY_PLACEHOLDER
+ ),
+ this.expiryMonthYearInput.attr(
+ 'maxlength',
+ t.EXPIRY_MASK.length
+ ),
+ this.expiryMonthYearInput.attr(
+ 'x-autocompletetype',
+ 'cc-exp'
+ ),
+ this.expiryMonthYearInput.attr(
+ 'autocompletetype',
+ 'cc-exp'
+ ),
+ this.expiryMonthYearInput.attr('autocorrect', 'off'),
+ this.expiryMonthYearInput.attr('spellcheck', 'off'),
+ this.expiryMonthYearInput.attr('autocapitalize', 'off');
+ var o = this;
+ this.expiryMonthYearInput.keydown(function (e) {
+ t.handleExpiryKey(e);
+ var r = o.expiryMonthYearInput.val();
+ 1 == r.length &&
+ parseInt(r) > 1 &&
+ t.keyIsNumber(e) &&
+ o.expiryMonthYearInput.val(
+ t.applyFormatMask('0' + r, t.EXPIRY_MASK)
+ ),
+ o.EXPIRY_USE_DROPDOWNS ||
+ null == o.expiryMonthYearInput ||
+ (o.expiryMonthInput.val(o.expiryMonth()),
+ o.expiryYearInput.val(
+ 9 == r.length ? r.substr(5, 4) : null
+ ));
+ }),
+ this.expiryMonthYearInput.blur(function (t) {
+ o.refreshExpiryMonthValidation();
+ }),
+ e.append(this.expiryMonthYearInput),
+ e.append(this.expiryMonthInput),
+ e.append(this.expiryYearInput);
+ }
+ r.append(e),
+ r.append(""),
+ r.find('.icon').append(t.CALENDAR_SVG);
+ }),
+ (t.prototype.setupCvcInput = function () {
+ this.stripe && this.cvcInput.attr('data-stripe', 'cvc'),
+ this.elem.append(
+ ""
+ );
+ var e = this.elem.find('.cvc-wrapper');
+ e.append(this.cvcInput),
+ e.append(""),
+ e.find('.icon').append(t.LOCK_SVG);
+ }),
+ (t.prototype.expiryMonth = function () {
+ if (
+ !this.EXPIRY_USE_DROPDOWNS &&
+ null != this.expiryMonthYearInput
+ ) {
+ var t = this.expiryMonthYearInput.val();
+ return t.length >= 2 ? parseInt(t.substr(0, 2)) : null;
+ }
+ return null;
+ }),
+ (t.isValidMonth = function (t) {
+ return t >= 1 && 12 >= t;
+ }),
+ (t.isExpiryValid = function (e, r) {
+ var n = new Date(),
+ i = n.getMonth() + 1,
+ a = '' + n.getFullYear();
+ return (
+ 2 == ('' + r).length && (r = a.substring(0, 2) + '' + r),
+ (i = parseInt(i)),
+ (a = parseInt(a)),
+ (e = parseInt(e)),
+ (r = parseInt(r)),
+ t.isValidMonth(e) && (r > a || (r == a && e >= i))
+ );
+ }),
+ (t.prototype.refreshExpiryMonthValidation = function () {
+ t.isExpiryValid(this.getExpiryMonth(), this.getExpiryYear())
+ ? this.setExpiryMonthAsValid()
+ : this.setExpiryMonthAsInvalid();
+ }),
+ (t.prototype.setExpiryMonthAsValid = function () {
+ this.EXPIRY_USE_DROPDOWNS ||
+ this.expiryMonthYearInput.parent().removeClass('has-error');
+ }),
+ (t.prototype.setExpiryMonthAsInvalid = function () {
+ this.EXPIRY_USE_DROPDOWNS ||
+ this.expiryMonthYearInput.parent().addClass('has-error');
+ });
+})();
diff --git a/public/js/clients/payments/forte-credit-card-payment.js b/public/js/clients/payments/forte-credit-card-payment.js
new file mode 100644
index 000000000000..2c181f59171d
--- /dev/null
+++ b/public/js/clients/payments/forte-credit-card-payment.js
@@ -0,0 +1,82 @@
+/******/ (() => { // webpackBootstrap
+var __webpack_exports__ = {};
+/*!********************************************************************!*\
+ !*** ./resources/js/clients/payments/forte-credit-card-payment.js ***!
+ \********************************************************************/
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/**
+ * 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://opensource.org/licenses/AAL
+ */
+var ForteAuthorizeCard = function ForteAuthorizeCard(apiLoginId) {
+ var _this = this;
+
+ _classCallCheck(this, ForteAuthorizeCard);
+
+ _defineProperty(this, "handleAuthorization", function () {
+ var myCard = $('#my-card');
+ var data = {
+ api_login_id: _this.apiLoginId,
+ card_number: myCard.CardJs('cardNumber').replace(/[^\d]/g, ''),
+ expire_year: myCard.CardJs('expiryYear').replace(/[^\d]/g, ''),
+ expire_month: myCard.CardJs('expiryMonth').replace(/[^\d]/g, ''),
+ cvv: document.getElementById('cvv').value.replace(/[^\d]/g, '')
+ };
+ var payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ document.getElementById('pay-now').disabled = true;
+ document.querySelector('#pay-now > svg').classList.remove('hidden');
+ document.querySelector('#pay-now > span').classList.add('hidden');
+ }
+
+ forte.createToken(data).success(_this.successResponseHandler).error(_this.failedResponseHandler);
+ return false;
+ });
+
+ _defineProperty(this, "successResponseHandler", function (response) {
+ document.getElementById('payment_token').value = response.onetime_token;
+ document.getElementById('card_brand').value = response.card_type;
+ document.getElementById('server_response').submit();
+ return false;
+ });
+
+ _defineProperty(this, "failedResponseHandler", function (response) {
+ var errors = '- ' + response.response_description + '
';
+ document.getElementById('forte_errors').innerHTML = errors;
+ document.getElementById('pay-now').disabled = false;
+ document.querySelector('#pay-now > svg').classList.add('hidden');
+ document.querySelector('#pay-now > span').classList.remove('hidden');
+ return false;
+ });
+
+ _defineProperty(this, "handle", function () {
+ var payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ payNowButton.addEventListener('click', function (e) {
+ _this.handleAuthorization();
+ });
+ }
+
+ return _this;
+ });
+
+ this.apiLoginId = apiLoginId;
+ this.cardHolderName = document.getElementById('cardholder_name');
+};
+
+var apiLoginId = document.querySelector('meta[name="forte-api-login-id"]').content;
+/** @handle */
+
+new ForteAuthorizeCard(apiLoginId).handle();
+/******/ })()
+;
\ No newline at end of file
diff --git a/resources/js/clients/payments/forte-ach-payment.js b/resources/js/clients/payments/forte-ach-payment.js
new file mode 100644
index 000000000000..90bf9f675377
--- /dev/null
+++ b/resources/js/clients/payments/forte-ach-payment.js
@@ -0,0 +1,81 @@
+/**
+ * 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://opensource.org/licenses/AAL
+ */
+
+class ForteAuthorizeACH {
+ constructor(apiLoginId) {
+ this.apiLoginId = apiLoginId;
+ }
+
+ handleAuthorization = () => {
+ var account_number = document.getElementById('account-number').value;
+ var routing_number = document.getElementById('routing-number').value;
+
+ var data = {
+ api_login_id: this.apiLoginId,
+ account_number: account_number,
+ routing_number: routing_number,
+ account_type: 'checking',
+ };
+
+ let payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ document.getElementById('pay-now').disabled = true;
+ document.querySelector('#pay-now > svg').classList.remove('hidden');
+ document.querySelector('#pay-now > span').classList.add('hidden');
+ }
+ // console.log(data);
+ forte
+ .createToken(data)
+ .success(this.successResponseHandler)
+ .error(this.failedResponseHandler);
+ return false;
+ };
+
+ successResponseHandler = (response) => {
+ document.getElementById('payment_token').value = response.onetime_token;
+
+ document.getElementById('server_response').submit();
+
+ return false;
+ };
+
+ failedResponseHandler = (response) => {
+ var errors =
+ '- ' +
+ response.response_description +
+ '
';
+ document.getElementById('forte_errors').innerHTML = errors;
+ document.getElementById('pay-now').disabled = false;
+ document.querySelector('#pay-now > svg').classList.add('hidden');
+ document.querySelector('#pay-now > span').classList.remove('hidden');
+
+ return false;
+ };
+
+ handle = () => {
+ let payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ payNowButton.addEventListener('click', (e) => {
+ this.handleAuthorization();
+ });
+ }
+
+ return this;
+ };
+}
+
+const apiLoginId = document.querySelector(
+ 'meta[name="forte-api-login-id"]'
+).content;
+
+/** @handle */
+new ForteAuthorizeACH(apiLoginId).handle();
diff --git a/resources/js/clients/payments/forte-credit-card-payment.js b/resources/js/clients/payments/forte-credit-card-payment.js
new file mode 100644
index 000000000000..0139356a4daa
--- /dev/null
+++ b/resources/js/clients/payments/forte-credit-card-payment.js
@@ -0,0 +1,83 @@
+/**
+ * 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://opensource.org/licenses/AAL
+ */
+
+class ForteAuthorizeCard {
+ constructor(apiLoginId) {
+ this.apiLoginId = apiLoginId;
+ this.cardHolderName = document.getElementById('cardholder_name');
+ }
+
+ handleAuthorization = () => {
+ var myCard = $('#my-card');
+
+ var data = {
+ api_login_id: this.apiLoginId,
+ card_number: myCard.CardJs('cardNumber').replace(/[^\d]/g, ''),
+ expire_year: myCard.CardJs('expiryYear').replace(/[^\d]/g, ''),
+ expire_month: myCard.CardJs('expiryMonth').replace(/[^\d]/g, ''),
+ cvv: document.getElementById('cvv').value.replace(/[^\d]/g, ''),
+ };
+
+ let payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ document.getElementById('pay-now').disabled = true;
+ document.querySelector('#pay-now > svg').classList.remove('hidden');
+ document.querySelector('#pay-now > span').classList.add('hidden');
+ }
+
+ forte
+ .createToken(data)
+ .success(this.successResponseHandler)
+ .error(this.failedResponseHandler);
+ return false;
+ };
+
+ successResponseHandler = (response) => {
+ document.getElementById('payment_token').value = response.onetime_token;
+ document.getElementById('card_brand').value = response.card_type;
+
+ document.getElementById('server_response').submit();
+
+ return false;
+ };
+
+ failedResponseHandler = (response) => {
+ var errors =
+ '- ' +
+ response.response_description +
+ '
';
+ document.getElementById('forte_errors').innerHTML = errors;
+ document.getElementById('pay-now').disabled = false;
+ document.querySelector('#pay-now > svg').classList.add('hidden');
+ document.querySelector('#pay-now > span').classList.remove('hidden');
+
+ return false;
+ };
+
+ handle = () => {
+ let payNowButton = document.getElementById('pay-now');
+
+ if (payNowButton) {
+ payNowButton.addEventListener('click', (e) => {
+ this.handleAuthorization();
+ });
+ }
+
+ return this;
+ };
+}
+
+const apiLoginId = document.querySelector(
+ 'meta[name="forte-api-login-id"]'
+).content;
+
+/** @handle */
+new ForteAuthorizeCard(apiLoginId).handle();
diff --git a/resources/views/portal/ninja2020/gateways/forte/ach/authorize.blade.php b/resources/views/portal/ninja2020/gateways/forte/ach/authorize.blade.php
new file mode 100644
index 000000000000..6929c8b3ea8b
--- /dev/null
+++ b/resources/views/portal/ninja2020/gateways/forte/ach/authorize.blade.php
@@ -0,0 +1,131 @@
+@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Bank Details', 'card_title' => 'Bank Details'])
+
+@section('gateway_head')
+ @if($gateway->getConfigField('testMode'))
+
+ @else
+
+ @endif
+@endsection
+
+@section('gateway_content')
+ @if(session()->has('ach_error'))
+
+
{{ session('ach_error') }}
+
+ @endif
+ @if(Session::has('error'))
+ {{ Session::get('error') }}
+ @endif
+
+ @if ($errors->any())
+
+
+ @foreach ($errors->all() as $error)
+ - {{ $error }}
+ @endforeach
+
+
+ @endif
+
+
+
+@endsection
+
+@section('gateway_footer')
+
+@endsection
diff --git a/resources/views/portal/ninja2020/gateways/forte/ach/pay.blade.php b/resources/views/portal/ninja2020/gateways/forte/ach/pay.blade.php
new file mode 100644
index 000000000000..5dbb93fbd246
--- /dev/null
+++ b/resources/views/portal/ninja2020/gateways/forte/ach/pay.blade.php
@@ -0,0 +1,53 @@
+@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Bank Transfer', 'card_title' => 'Bank Transfer'])
+
+@section('gateway_head')
+
+@endsection
+
+@section('gateway_content')
+
+
+
+
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
+ Bank Transfer
+ @endcomponent
+
+ @include('portal.ninja2020.gateways.includes.payment_details')
+
+ @component('portal.ninja2020.components.general.card-element', ['title' => 'Pay with Bank Transfer'])
+
+
+
+
+
+
+
+ @endcomponent
+
+ @include('portal.ninja2020.gateways.includes.pay_now')
+
+@endsection
+
+@section('gateway_footer')
+ @if($gateway->forte->company_gateway->getConfigField('testMode'))
+
+ @else
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/portal/ninja2020/gateways/forte/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/forte/credit_card/authorize.blade.php
new file mode 100644
index 000000000000..70d356f8e2f2
--- /dev/null
+++ b/resources/views/portal/ninja2020/gateways/forte/credit_card/authorize.blade.php
@@ -0,0 +1,122 @@
+@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
+
+@section('gateway_head')
+
+
+
+
+
+
+
+
+ @if($gateway->getConfigField('testMode'))
+
+ @else
+
+ @endif
+@endsection
+
+@section('gateway_content')
+
+@endsection
+
+@section('gateway_footer')
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/portal/ninja2020/gateways/forte/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/forte/credit_card/pay.blade.php
new file mode 100644
index 000000000000..1cceb137f39c
--- /dev/null
+++ b/resources/views/portal/ninja2020/gateways/forte/credit_card/pay.blade.php
@@ -0,0 +1,51 @@
+@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => ctrans('texts.payment_type_credit_card')])
+
+@section('gateway_head')
+
+
+
+
+
+@endsection
+
+@section('gateway_content')
+
+
+
+
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
+ {{ ctrans('texts.credit_card') }}
+ @endcomponent
+
+ @include('portal.ninja2020.gateways.includes.payment_details')
+
+ @component('portal.ninja2020.components.general.card-element', ['title' => 'Pay with Credit Card'])
+ @include('portal.ninja2020.gateways.forte.includes.credit_card')
+ @endcomponent
+
+ @include('portal.ninja2020.gateways.includes.pay_now')
+
+@endsection
+
+@section('gateway_footer')
+ @if($gateway->forte->company_gateway->getConfigField('testMode'))
+
+ @else
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/portal/ninja2020/gateways/forte/includes/credit_card.blade.php b/resources/views/portal/ninja2020/gateways/forte/includes/credit_card.blade.php
new file mode 100644
index 000000000000..5183e72986ed
--- /dev/null
+++ b/resources/views/portal/ninja2020/gateways/forte/includes/credit_card.blade.php
@@ -0,0 +1,12 @@
+
diff --git a/webpack.mix.js b/webpack.mix.js
index 926ac4b4000a..2ed78e49227c 100644
--- a/webpack.mix.js
+++ b/webpack.mix.js
@@ -10,6 +10,14 @@ mix.js("resources/js/app.js", "public/js")
"resources/js/clients/payments/authorize-credit-card-payment.js",
"public/js/clients/payments/authorize-credit-card-payment.js"
)
+ .js(
+ "resources/js/clients/payments/forte-credit-card-payment.js",
+ "public/js/clients/payments/forte-credit-card-payment.js"
+ )
+ .js(
+ "resources/js/clients/payments/forte-ach-payment.js",
+ "public/js/clients/payments/forte-ach-payment.js"
+ )
.js(
"resources/js/clients/payments/stripe-ach.js",
"public/js/clients/payments/stripe-ach.js"