Payment Form: Cookbook

Verify the Buyer in a Card-on-File Charge

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 might require additional authentication for some payments. In those cases, buyers must verify their identity with the bank using additional secure authentication.

Before you start Permalink Get a link to this section

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

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

Important

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 whether the seller is in scope for SCA. An exception is thrown if your code calls the verifyBuyer function without initializing the payment form with a location ID.

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

Important

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

  1. In index.html, add the new line after applicationId: "REPLACE_WITH_APPLICATION_ID",.

  2. Replace REPLACE_WITH_APPLICATION_ID and REPLACE_WITH_LOCATION_ID with your application ID and location ID. If needed, you can get these values from the Developer Dashboard.

     // Create and initialize a payment form object
     const paymentForm = new SqPaymentForm({
       // Initialize the payment form elements
       
       //TODO: Replace with your sandbox application ID
       applicationId: "REPLACE_WITH_APPLICATION_ID",
       locationId: "REPLACE_WITH_LOCATION_ID",  //ADD this line
       // ...
     });

Step 2: Declare a 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 required.

Did you know?

billingContact fields are optional, with the exception of the required givenName field. However, to reduce the chance that a buyer is challenged, the fields should be set with as much billing contact information as your application 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 application.

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:

Retrieve Customer
  • 1
  • 2
  • 3
  • 4
curl https://connect.squareupsandbox.com/v2/customers/CUSTOMER_ID \
  -H 'Square-Version: 2021-03-17' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json'

The Customer.cards 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 2, the ID of a Sandbox card on file, and an anonymous function to handle the result.

In this step, you modify the existing cardNonceResponseReceived callback:

  1. Add the verifyBuyer function call.

  2. Move the fetch API call into the callback argument of verifyBuyer.

    paymentForm.verifyBuyer(
        'ccof:customer-card-id-ok', 
         verificationDetails, 
         function(err, verificationResult) {
          if (err == null) {
            //TODO: Move existing Fetch API call here
          }
    }); 

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 a token Permalink Get a link to this section

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

The following code shows the fetch API used to post a nonce. Edit the request body, as shown in the following code block:

      fetch('process-payment', {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({  //UPDATE this `body` object to use the following code
          nonce: 'ccof:customer-card-id-ok', 
          idempotency_key: idempotency_key,
          location_id: paymentForm.options.locationId,          
          token: verificationResult.token 
        })
      })
      .catch(err => {
        alert('Network error: ' + err);
      })
      .then(response => {
        if (!response.ok) {
          return response.json().then(
            errorInfo => Promise.reject(errorInfo)); 
        }
        return response.json(); 
      })
      .then(data => {
        console.log(data); 
        alert('Payment complete successfully!\nCheck browser developer console for more details');
      })
      .catch(err => {
        console.error(err);
        alert('Payment failed to complete!\nCheck browser developer console for more details');
      });

Step 6: Create a payment Permalink Get a link to this section

Locate your server-side code that listens for the POST from the client and gets the posted values to create the payment.

Add verificationToken: requestParams.token, to the request body, as shown in the following code:

app.post('/process-payment', async (req, res) => {
  const requestParams = req.body;

  // Charge the customer's card
  const paymentsApi = client.paymentsApi;
  const requestBody = {
    sourceId: requestParams.nonce,
    amountMoney: {
      amount: 100, // $1.00 charge
      currency: 'USD'
    },
    locationId: requestParams.location_id,
    idempotencyKey: requestParams.idempotency_key,
    verificationToken: requestParams.token  // ADD this line
  };

  try {
    const response = await paymentsApi.createPayment(requestBody);
    res.status(200).json({
      'title': 'Payment Successful',
      'result': response.result
    });
  } catch(error) {
    let errorResult = null;
    if (error instanceof ApiError) {
      errorResult = error.errors;
    } else {
      errorResult = error;
    }
    res.status(500).json({
      'title': 'Payment Failure',
      'result': errorResult
    });
  }
});