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
You have integrated the payment form into your web application.
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
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.
In index.html, add the new line after
applicationId: "REPLACE_WITH_APPLICATION_ID",
.Replace
REPLACE_WITH_APPLICATION_ID
andREPLACE_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
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
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:
The Customer.cards
object holds a collection of Card objects.
Step 4: Call the verifyBuyer method
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:
Add the
verifyBuyer
function call.Move the
fetch
API call into the callback argument ofverifyBuyer
.
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
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');
});
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
});
}
});