Skip to content

Commit bed2c2d

Browse files
authored
Add recurring ACH example (#381)
1 parent dcaa7d7 commit bed2c2d

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

public/examples/ach-recurring.html

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<link href="/app.css" rel="stylesheet" />
5+
<script
6+
type="text/javascript"
7+
src="https://sandbox.web.squarecdn.com/v1/square.js"
8+
></script>
9+
<script>
10+
const appId = '{APPLICATION_ID}';
11+
const locationId = '{LOCATION_ID}';
12+
13+
async function initializeACH(payments) {
14+
const ach = await payments.ach();
15+
// Note: ACH does not have an .attach(...) method
16+
// the ACH auth flow is triggered by .tokenize(...)
17+
return ach;
18+
}
19+
20+
async function createPayment(token) {
21+
const body = JSON.stringify({
22+
locationId,
23+
sourceId: token,
24+
idempotencyKey: window.crypto.randomUUID(),
25+
});
26+
27+
const paymentResponse = await fetch('/payment', {
28+
method: 'POST',
29+
headers: {
30+
'Content-Type': 'application/json',
31+
},
32+
body,
33+
});
34+
35+
if (paymentResponse.ok) {
36+
return paymentResponse.json();
37+
}
38+
39+
const errorBody = await paymentResponse.text();
40+
throw new Error(errorBody);
41+
}
42+
43+
async function tokenize(paymentMethod, options = {}) {
44+
paymentMethod.addEventListener(
45+
'ontokenization',
46+
async function (event) {
47+
const { tokenResult, error } = event.detail;
48+
if (error !== undefined) {
49+
let errorMessage = `Tokenization failed with error: ${error}`;
50+
throw new Error(errorMessage);
51+
}
52+
if (tokenResult.status === 'OK') {
53+
const paymentResults = await createPayment(tokenResult.token);
54+
displayPaymentResults('SUCCESS');
55+
56+
console.debug('Payment Success', paymentResults);
57+
} else {
58+
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
59+
if (tokenResult.errors) {
60+
errorMessage += ` and errors: ${JSON.stringify(
61+
tokenResult.errors,
62+
)}`;
63+
}
64+
throw new Error(errorMessage);
65+
}
66+
},
67+
);
68+
await paymentMethod.tokenize(options);
69+
}
70+
71+
// status is either SUCCESS or FAILURE;
72+
function displayPaymentResults(status) {
73+
const statusContainer = document.getElementById(
74+
'payment-status-container',
75+
);
76+
if (status === 'SUCCESS') {
77+
statusContainer.classList.remove('is-failure');
78+
statusContainer.classList.add('is-success');
79+
} else {
80+
statusContainer.classList.remove('is-success');
81+
statusContainer.classList.add('is-failure');
82+
}
83+
84+
statusContainer.style.visibility = 'visible';
85+
}
86+
87+
function getACHOptions(form) {
88+
return {
89+
intent: 'RECURRING_CHARGE',
90+
bankAccountId: '{BANK_ACCOUNT_ID}',
91+
startDate: '2028-12-31',
92+
frequency: {
93+
yearly: {
94+
occurrence: 1,
95+
months: ['JANUARY'],
96+
days: {
97+
daysOfMonth: [15, 31],
98+
},
99+
},
100+
},
101+
amount: '5.00',
102+
currency: 'USD',
103+
};
104+
}
105+
106+
document.addEventListener('DOMContentLoaded', async function () {
107+
if (!window.Square) {
108+
throw new Error('Square.js failed to load properly');
109+
}
110+
111+
let payments;
112+
try {
113+
payments = window.Square.payments(appId, locationId);
114+
} catch {
115+
const statusContainer = document.getElementById(
116+
'payment-status-container',
117+
);
118+
statusContainer.className = 'missing-credentials';
119+
statusContainer.style.visibility = 'visible';
120+
return;
121+
}
122+
123+
let ach;
124+
try {
125+
ach = await initializeACH(payments);
126+
} catch (e) {
127+
console.error('Initializing ACH failed', e);
128+
return;
129+
}
130+
131+
if (ach) {
132+
async function handlePaymentMethodSubmission(
133+
event,
134+
paymentMethod,
135+
options,
136+
) {
137+
event.preventDefault();
138+
139+
try {
140+
achButton.disabled = true;
141+
await tokenize(paymentMethod, options);
142+
} catch (e) {
143+
achButton.disabled = false;
144+
displayPaymentResults('FAILURE');
145+
console.error(e.message);
146+
}
147+
}
148+
149+
const achButton = document.getElementById('ach-button');
150+
achButton.addEventListener('click', async function (event) {
151+
const paymentForm = document.getElementById('payment-form');
152+
const achOptions = getACHOptions(paymentForm);
153+
await handlePaymentMethodSubmission(event, ach, achOptions);
154+
});
155+
}
156+
});
157+
</script>
158+
</head>
159+
<body>
160+
<form id="payment-form">
161+
<button id="ach-button" type="button">Pay with Bank Account</button>
162+
</form>
163+
<div id="payment-status-container"></div>
164+
</body>
165+
</html>

public/examples/ach.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@
172172
name="givenName"
173173
spellcheck="false"
174174
/>
175-
176175
<input
177176
type="text"
178177
autocomplete="family-name"

public/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ <h3>Examples</h3>
6666
<li>
6767
<a href="/examples/ach.html">ACH Bank Transfer</a>
6868
</li>
69+
<li>
70+
<a href="/examples/ach-recurring.html"
71+
>ACH Recurring Bank Transfer</a
72+
>
73+
</li>
6974
<li>
7075
<a href="/examples/gift-card.html">Gift Card Payments</a>
7176
</li>

0 commit comments

Comments
 (0)