200 lines
6.6 KiB
JavaScript
200 lines
6.6 KiB
JavaScript
/**
|
|
* 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 AuthorizeACH {
|
|
constructor(publicKey, loginId) {
|
|
this.form = document.getElementById('server_response');
|
|
this.submitButton = document.getElementById('card_button');
|
|
this.errorDiv = document.getElementById('errors');
|
|
this.publicKey = publicKey;
|
|
this.loginId = loginId;
|
|
// Input fields
|
|
this.accountHolderName = document.getElementById('account_holder_name');
|
|
this.routingNumber = document.getElementById('routing_number');
|
|
this.accountNumber = document.getElementById('account_number');
|
|
this.acceptTerms = document.getElementById('accept-terms');
|
|
|
|
// Validation state
|
|
this.isValid = {
|
|
accountHolderName: false,
|
|
routingNumber: false,
|
|
accountNumber: false,
|
|
acceptTerms: false
|
|
};
|
|
|
|
this.setupEventListeners();
|
|
this.updateSubmitButton(); // Initial state
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Monitor account holder name
|
|
this.accountHolderName.addEventListener('input', () => {
|
|
this.validateAccountHolderName();
|
|
this.updateSubmitButton();
|
|
});
|
|
|
|
// Monitor routing number
|
|
this.routingNumber.addEventListener('input', () => {
|
|
this.validateRoutingNumber();
|
|
this.updateSubmitButton();
|
|
});
|
|
|
|
// Monitor account number
|
|
this.accountNumber.addEventListener('input', () => {
|
|
this.validateAccountNumber();
|
|
this.updateSubmitButton();
|
|
});
|
|
|
|
this.acceptTerms.addEventListener('change', () => {
|
|
this.validateAcceptTerms();
|
|
this.updateSubmitButton();
|
|
});
|
|
|
|
// Submit button handler
|
|
this.submitButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if (this.isFormValid()) {
|
|
this.handleSubmit();
|
|
}
|
|
});
|
|
}
|
|
|
|
validateAcceptTerms() {
|
|
this.isValid.acceptTerms = this.acceptTerms.checked;
|
|
if (!this.isValid.acceptTerms) {
|
|
this.acceptTerms.classList.add('border-red-500');
|
|
} else {
|
|
this.acceptTerms.classList.remove('border-red-500');
|
|
}
|
|
}
|
|
|
|
validateAccountHolderName() {
|
|
const name = this.accountHolderName.value.trim();
|
|
this.isValid.accountHolderName = name.length > 0 && name.length <= 22;
|
|
|
|
if (!this.isValid.accountHolderName) {
|
|
this.accountHolderName.classList.add('border-red-500');
|
|
} else {
|
|
this.accountHolderName.classList.remove('border-red-500');
|
|
}
|
|
}
|
|
|
|
validateRoutingNumber() {
|
|
const routing = this.routingNumber.value.replace(/\D/g, '');
|
|
this.isValid.routingNumber = routing.length === 9;
|
|
|
|
if (!this.isValid.routingNumber) {
|
|
this.routingNumber.classList.add('border-red-500');
|
|
} else {
|
|
this.routingNumber.classList.remove('border-red-500');
|
|
}
|
|
}
|
|
|
|
validateAccountNumber() {
|
|
const account = this.accountNumber.value.replace(/\D/g, '');
|
|
this.isValid.accountNumber = account.length >= 1 && account.length <= 17;
|
|
|
|
if (!this.isValid.accountNumber) {
|
|
this.accountNumber.classList.add('border-red-500');
|
|
} else {
|
|
this.accountNumber.classList.remove('border-red-500');
|
|
}
|
|
}
|
|
|
|
isFormValid() {
|
|
return Object.values(this.isValid).every(Boolean);
|
|
}
|
|
|
|
updateSubmitButton() {
|
|
const isValid = this.isFormValid();
|
|
this.submitButton.disabled = !isValid;
|
|
|
|
// Visual feedback
|
|
if (isValid) {
|
|
this.submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
this.submitButton.classList.add('hover:bg-primary-dark');
|
|
} else {
|
|
this.submitButton.classList.add('opacity-50', 'cursor-not-allowed');
|
|
this.submitButton.classList.remove('hover:bg-primary-dark');
|
|
}
|
|
}
|
|
|
|
handleSubmit() {
|
|
if (!this.isFormValid()) {
|
|
return;
|
|
}
|
|
|
|
// Disable the submit button and show loading state
|
|
this.submitButton.disabled = true;
|
|
this.submitButton.querySelector('svg')?.classList.remove('hidden');
|
|
this.submitButton.querySelector('span')?.classList.add('hidden');
|
|
|
|
// Get the selected account type
|
|
const accountType = document.querySelector('input[name="account_type"]:checked').value ?? 'checking';
|
|
|
|
var authData = {};
|
|
authData.clientKey = this.publicKey;
|
|
authData.apiLoginID = this.loginId;
|
|
|
|
// Prepare the data for Authorize.net
|
|
var bankData = {};
|
|
bankData.accountType = accountType;
|
|
bankData.routingNumber = this.routingNumber.value;
|
|
bankData.accountNumber = this.accountNumber.value;
|
|
bankData.nameOnAccount = this.accountHolderName.value;
|
|
|
|
var secureData = {};
|
|
secureData.authData = authData;
|
|
secureData.bankData = bankData;
|
|
|
|
// Initialize Accept.js
|
|
Accept.dispatchData(secureData, this.handleResponse.bind(this));
|
|
}
|
|
|
|
handleResponse(response) {
|
|
if (response.messages.resultCode === 'Error') {
|
|
let errorMessage = '';
|
|
for (let i = 0; i < response.messages.message.length; i++) {
|
|
errorMessage += `${response.messages.message[i].code}: ${response.messages.message[i].text}\n`;
|
|
}
|
|
|
|
this.errorDiv.textContent = errorMessage;
|
|
this.errorDiv.hidden = false;
|
|
|
|
// Re-enable the submit button
|
|
this.submitButton.disabled = false;
|
|
this.submitButton.querySelector('svg')?.classList.add('hidden');
|
|
this.submitButton.querySelector('span')?.classList.remove('hidden');
|
|
|
|
return;
|
|
}
|
|
|
|
// On success, update the hidden form fields
|
|
document.getElementById('dataDescriptor').value = response.opaqueData.dataDescriptor;
|
|
document.getElementById('dataValue').value = response.opaqueData.dataValue;
|
|
|
|
// Submit the form
|
|
this.form.submit();
|
|
}
|
|
}
|
|
|
|
const publicKey = document.querySelector(
|
|
'meta[name="authorize-public-key"]'
|
|
).content;
|
|
|
|
const loginId = document.querySelector(
|
|
'meta[name="authorize-login-id"]'
|
|
).content;
|
|
|
|
// Initialize when the DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new AuthorizeACH(publicKey, loginId);
|
|
});
|