Payment Form: Cookbook

Verify the Buyer in a Card-on-File Charge

Payment Form

Strong customer authentication (SCA) is required in some geographical areas and when used, can reduce fraudulent payments. SCA is generally friction-free for the buyer, but a card-issuing bank may require additional authentication for some payments. In those cases, the buyer must verify their identiy with the bank using an additional secure dialog.

Before you start
Permalink Get a link to this section

  • Your web application posts a nonce to your backend via HTTPS POST.

  • If you look up the merchant's location by using the Locations API and you are using OAuth, you will need the MERCHANT_PROFILE_READ permission.


The SCA flow must not be initiated for a card on file transaction where the buyer is not present. In this scenario, do not call the verifyBuyer function. You can still charge the card but be prepared to handle the CARD_DECLINED_VERIFICATION_REQUIRED error.

Step 1: Initialize SqPaymentForm with a location ID
Permalink Get a link to this section

The payment form must be initialized with the location ID of the seller location. Square needs to know the seller location so it can determine if the seller is in scope for SCA. An exception is thrown if your code calls the verifyBuyer function without initializing payment form with a location ID.

The following example adds locationId: "REPLACE_WITH_LOCATION_ID", to the SqPaymentForm JavaScript initialization block.


When testing the SCA flow in v2 Sandbox, be sure to initialize SqPaymentForm with a UK location ID.

Add this code your project after applicationId: "REPLACE_WITH_APPLICATION_ID",

     // Create and initialize a payment form object
     const paymentForm = new SqPaymentForm({
       // Initialize the payment form elements
       // ... 
       //TODO: Replace with your sandbox location ID
       locationId: "REPLACE_WITH_LOCATION_ID",
       // ...

Step 2: Declare verification details object
Permalink Get a link to this section

Declare an object that includes the intent of the payment request, and an instance of SqContact. The SqContact.givenName field value is requred.

Did you know?

billingContact fields are optional - with the exception of the required givenName field. However, to reduce the chance that a buyer will be challenged, the fields should be set with as much billing contact information as your app can provide.

      const verificationDetails = { 
        intent: 'CHARGE', 
        amount: '1.00', 
        currencyCode: 'USD', 
        billingContact: {
          givenName: 'Jane',
          familyName: 'Doe'

Step 3: Get a customer card on file ID
Permalink Get a link to this section

Did you know?

You can use a sandbox test value to simulate a customer card on file while developing your app.

In production, your backend should be responsible for getting a customer card ID and returning that ID to your client payment page.

You can also get a Customer with the following cURL operation.

curl{customer_id} \
  -X GET \
  -H 'Authorization: Bearer ACCESS_TOKEN' \

The object holds a collection of Card objects.

Step 4: Call the verifyBuyer method
Permalink Get a link to this section

Call verifyBuyer and pass the verification details declared in step 3, the id of a sandbox card on file, and an anonymous function to handle the result.

         function(err, verificationResult) {
          if (err == null) {
            //TODO Replace with code from Step 5

Did you know?

To force a verification challenge in the sandbox, set the card ID to ccof:customer-card-id-requires-verification

Step 5: Update client-side JavaScript to post token
Permalink Get a link to this section

If the buyer is verified, the verificationResult.token variable contains a buyer verification token to be provided to your backend process that creates the charge.

The following code uses the fetch API to post a nonce. You will modify the code to add token: verificationResult.token to the request body.

In this step, replace //TODO Replace with code from Step 5 in Step 4 logic with with the following logic:

      fetch('process-payment', {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        body: JSON.stringify({
          source: 'ccof:customer-card-id-ok',
          token: verificationResult.token  //ADD this line
      .catch(err => {
        alert('Network error: ' + err);
      .then(response => {
        if (!response.ok) {
          return response.text().then(errorInfo => Promise.reject(errorInfo));
        return response.text();
      .then(data => {
        alert('Payment complete successfully!\nCheck browser developer console for more details');
      .catch(err => {
        alert('Payment failed to complete!\nCheck browser developer console for more details');