Applies to: Web Payments SDK | Payments API | Cards API | Customers API
Learn how to use the Web Payments SDK to charge a card and store card details for future online payments with a card on file.
After configuring your application with the Web Payments SDK to accept card-not-present payments, you can use the Web Payments SDK to store card details with Square as a card on file and charge the card on file for future online payments.
During payment tokenization, Square checks the tokenize request to determine whether buyer verification is needed based on the buyer's information. After receiving the payment token and verifying the buyer, the application performs one of the following card-on-file workflows:
Store card details - The application includes the payment token in a Cards API call to store card details as a card on file with CreateCard.
Charge a card on file - The application includes the payment token in a Payments API call to process a payment with CreatePayment.
Charge a card and store card details - The application charges the card and stores the card details as a card on file with a single request call. The application includes the payment token in a Payments API call to process a payment and then creates a card on file and stores the card details as a
Cardobject with a new customer profile. When you callCreateCard, thesource_idis thepayment_idfrom the Payments API payment response object. This workflow is ideal for scenarios where buyers want to save their card details while making a payment.
To explore example implementations of the card-on-file workflows, see Web Payments SDK Quickstart or set up the quickstart repository and choose from the available examples.
The following are additional code examples of each card-on-file workflow setup:
Update your application to support the Web Payments SDK card payment flow by following the instructions in Take a Card Payment or Add the Web Payments SDK to the Web Client.
Choose a workflow when you set up the application. Follow the example code by setting up the quickstart repository.
The "store card details" workflow involves a storeCard method to store card details, while the "charge a card on file" workflow modifies a CreatePayment method to take the payment token and the customer ID. If your application needs to charge a card for a payment and store the card details from that same payment, use the "charge a card and store its details" workflow where the application combines both tasks in a single request call.
Add a helper
storeCardmethod, as shown in the following example, that passes thetokenandcustomerIdobjects as arguments to your server. The server can then make the call to the Square CreateCard endpoint.async function storeCard(token, customerId) { const body = JSON.stringify({ locationId, sourceId: token, customerId, idempotencyKey: window.crypto.randomUUID(), }); const paymentResponse = await fetch('/card', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); }Modify the
tokenizemethod with the following updates toverificationDetails. SetintenttoSTORE,customerInitiatedtotrue, andsellerKeyedIntofalse.async function tokenize(paymentMethod) { const verificationDetails = { billingContact: { givenName: 'John', familyName: 'Doe', email: '[email protected]', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, intent: 'STORE', customerInitiated: true, sellerKeyedIn: false, }; const tokenResult = await paymentMethod.tokenize(verificationDetails); if (tokenResult.status === 'OK') { return tokenResult.token; } else { throw new Error( `Tokenization errors: ${JSON.stringify(tokenResult.errors)}`, ); } }Replace the
handlePaymentMethodSubmissionmethod with a new method calledhandleStoreCardSubmission, pass theevent,card, andcustomerIdarguments to the method, and modify the followingtrystatement declaration from the code example:async function handleStoreCardSubmission(event, card, customerId) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card); const storeCardResults = await storeCard( token, customerId, ); displayResults('SUCCESS'); console.debug('Store Card Success', storeCardResults); } catch (e) { cardButton.disabled = false; displayResults('FAILURE'); console.error('Store Card Failure', e); } }In the
cardButton.addEventListenerevent listener, declare a newcustomerIdobject and call thehandleStoreCardSubmissionmethod as indicated in the following code example:const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const textInput = document.getElementById('customer-input'); if (!textInput.reportValidity()) { return; } const customerId = textInput.value; handleStoreCardSubmission(event, card, customerId); });
In your application, initialize a Card object.
Call Card.tokenize() with the
verificationDetailsandcardIdof the stored card.The
Card.tokenize()method passes the following properties in averificationDetailsobject:amount- The amount of the card payment to be charged.billingContact- The buyer's contact information for billing.intent- The transactional intent of the payment.sellerKeyedIn- Indicates that the seller keyed in payment details on behalf of the customer. This is used to flag a payment as Mail Order / Telephone Order (MOTO).customerInitiated- Indicates whether the customer initiated the payment.currencyCode- The three-letter ISO 4217 currency code.
Important
Provide as much buyer information as possible for
billingContactso that you get more accurate decline rate performance from 3DS authentication.Modify the CreatePayment endpoint by renaming the
createPaymentWithCardOnFilefunction and passing in thetokenandcustomerId.The following code example demonstrates the charge card-on-file setup.
async function createPaymentWithCardOnFile( token, customerId, ) { const body = JSON.stringify({ locationId, sourceId: token, customerId, idempotencyKey: window.crypto.randomUUID(), }); const paymentResponse = await fetch('/payment', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } // status is either SUCCESS or FAILURE; function displayPaymentResults(status) { const statusContainer = document.getElementById( 'payment-status-container', ); if (status === 'SUCCESS') { statusContainer.classList.remove('is-failure'); statusContainer.classList.add('is-success'); } else { statusContainer.classList.remove('is-success'); statusContainer.classList.add('is-failure'); } statusContainer.style.visibility = 'visible'; } async function tokenize(paymentMethod, sourceId) { const verificationDetails = { amount: '1.00', billingContact: { givenName: 'John', familyName: 'Doe', email: '[email protected]', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, currencyCode: 'GBP', intent: 'CHARGE', customerInitiated: true, sellerKeyedIn: false, }; const tokenResult = await paymentMethod.tokenize(verificationDetails, sourceId); if (tokenResult.status === 'OK') { return tokenResult.token; } else { let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; if (tokenResult.errors) { errorMessage += ` and errors: ${JSON.stringify( tokenResult.errors, )}`; } throw new Error(errorMessage); } } document.addEventListener('DOMContentLoaded', async function () { if (!window.Square) { throw new Error('Square.js failed to load properly'); } let payments; try { payments = window.Square.payments(appId, locationId); } catch { const statusContainer = document.getElementById( 'payment-status-container', ); statusContainer.className = 'missing-credentials'; statusContainer.style.visibility = 'visible'; return; } let card; try { card = await payments.card(); } catch (e) { console.error('Initializing Card failed', e); return; } async function handleChargeCardOnFileSubmission( event, card, cardId, customerId, ) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card, cardId); const paymentResults = await createPaymentWithCardOnFile( token, customerId ); displayPaymentResults('SUCCESS'); console.debug('Payment Success', paymentResults); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); console.error(e.message); } } const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const customerTextInput = document.getElementById('customer-input'); const cardTextInput = document.getElementById('card-input'); if ( !customerTextInput.reportValidity() || !cardTextInput.reportValidity() ) { return; } const cardId = cardTextInput.value; const customerId = customerTextInput.value; handleChargeCardOnFileSubmission(event, card, cardId, customerId); }); });
After the
CreatePaymentmethod in your application, create a new method calledstoreCard, pass thepaymentIdandcustomerIdobjects as arguments, and add the requisite properties and thestoreCardResponseas shown in the following example:async function storeCard(paymentId, customerId) { const body = JSON.stringify({ locationId, sourceId: paymentId, customerId, idempotencyKey: window.crypto.randomUUID(), }); const storeCardResponse = await fetch('/card', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (storeCardResponse.ok) { return storeCardResponse.json(); } const errorBody = await storeCardResponse.text(); throw new Error(errorBody); }In the
tokenizemethod, update theverificationDetailsobject by settingintenttoCHARGE_AND_STORE.async function tokenize(paymentMethod) { const verificationDetails = { amount: '1.00', billingContact: { givenName: 'John', familyName: 'Doe', email: '[email protected]', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, currencyCode: 'GBP', intent: 'CHARGE_AND_STORE', customerInitiated: true, sellerKeyedIn: false, };In the
document.addEventListenerevent listener method, change thehandlePaymentMethodSubmissionmethod tohandleChargeAndStoreCardSubmission, pass theevent,card, andcustomerIdobjects as arguments, and modify the rest of the code as shown in the following example:async function handleChargeAndStoreCardSubmission(event, card, customerId) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card); const paymentResults = await createPayment(token); console.debug('Payment Success', paymentResults); const storeCardResults = await storeCard( paymentResults.payment.id, customerId, ); console.debug('Store Card Success', storeCardResults); displayPaymentResults('SUCCESS'); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); console.error(e.message); } } const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const textInput = document.getElementById('customer-input'); if (!textInput.reportValidity()) { return; } const customerId = textInput.value; await handleChargeAndStoreCardSubmission(event, card, customerId); });
If your application doesn't yet have a Content Security Policy configured, see Add a Content Security Policy.