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
Card
object with a new customer profile. When you callCreateCard
, thesource_id
is thepayment_id
from 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
storeCard
method, as shown in the following example, that passes thetoken
andcustomerId
objects 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
tokenize
method with the following updates toverificationDetails
. Setintent
toSTORE
,customerInitiated
totrue
, andsellerKeyedIn
tofalse
.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
handlePaymentMethodSubmission
method with a new method calledhandleStoreCardSubmission
, pass theevent
,card
, andcustomerId
arguments to the method, and modify the followingtry
statement 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.addEventListener
event listener, declare a newcustomerId
object and call thehandleStoreCardSubmission
method 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
verificationDetails
andcardId
of the stored card.The
Card.tokenize()
method passes the following properties in averificationDetails
object: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
billingContact
so that you get more accurate decline rate performance from 3DS authentication.Modify the CreatePayment endpoint by renaming the
createPaymentWithCardOnFile
function and passing in thetoken
andcustomerId
.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
CreatePayment
method in your application, create a new method calledstoreCard
, pass thepaymentId
andcustomerId
objects as arguments, and add the requisite properties and thestoreCardResponse
as 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
tokenize
method, update theverificationDetails
object by settingintent
toCHARGE_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.addEventListener
event listener method, change thehandlePaymentMethodSubmission
method tohandleChargeAndStoreCardSubmission
, pass theevent
,card
, andcustomerId
objects 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.