Instant Payments - Card-On-File

Overview

For External Cards-On-File, Synctera supports Instant Payments. An instant payment can be either:

  • a PULL from an external card to fund an account on the Synctera platform or
  • a PUSH or a payout from an account on the Synctera platform to an external card

For more details on supported scenarios, see External Cards.

Prerequisites

To create an external card-on-file payment, you must first:

  • Create a Customer
  • Create an Account for the customer
  • Have External Cards enabled with the help of your Synctera implementation representative
  • Tokenize card
  • Add card-on-file using card token
  • Enable 3-D Secure (3DS) authentication (PULL payments only)

Enable 3-D Secure (3DS) Authentication

For additional security, Synctera External Cards supports 3-D Secure (3DS) authentication, a globally accepted authentication solution designed to increase security, reduce fraud and reduce chargebacks for e-commerce payments. 3DS operates at the per transaction level in real time at time of payment. Additional information about 3DS can be found here. Synctera's 3DS provider is Cardinal Commerce, facilitated through TabaPay.

3DS is only required for PULL transactions.

Implementation

An implemention of External Cards 3DS authentication consists of frontend and backend components.

The backend is responsible for interacting with Synctera's three 3DS endpoints: Initialize 3DS, Lookup 3DS and Authenticate 3DS.

The frontend is responsible for performing Device Data Collection (DDC) and (if necessary) presenting the cardholder with the 3DS Challenge.

Device Data Collection (DDC): A process which collects and sends details about the cardholder's physical device being used to complete the payment. It is sometimes the case that DDC is all that is required for a successful authentication. This is called a frictionless authentication.

3DS Challenge: If DDC is not enough to authenticate the cardholder, the card issuer may require a challenge, also known as a step up. This is a dynamic prompt which the cardholder must interact with to complete successfully in order to help prove they are the true card owner. The content and structure of the challenge varies and is determined by the issuer. For example, the cardholder may be required to provide a one time password (OTP) obtained through a separate channel.

Frontend Implementation

This guide contains information about how to implement and execute the necessary 3DS frontend components, including code snippets for a browser implementation: BROWSER passed for device_channel of Lookup 3DS (step 3).

For a mobile app (SDK) implementation, please refer to our vendor's documentation for how to obtain and implement the 3DS SDK, provided by JFrog. Your Synctera implementation and onboarding will provide you with JFrog credentials.

In order to perform the frontend processes, the Cardinal script must be loaded into the browser document according to environment:

Code snippet:

// Ensure any previous Cardinal instance is removed from the document.
// After being used once, the Cardinal library cannot be reused.
delete window.Cardinal;
​
const headElement = document.getElementsByTagName('head')[0];
const songbirdScriptElement = document.createElement('script');
songbirdScriptElement.async = true;
songbirdScriptElement.type = 'text/javascript';
songbirdScriptElement.onload = () => {
  if (window.Cardinal) {
    // SUCCESS
  } else {
    // FAILURE
  }
};
songbirdScriptElement.onerror = () => undefined; // FAILURE
songbirdScriptElement.src = SONGBIRD_LIBRARY_URL;
​
setTimeout(() => undefined, 5000); // FAILURE

If you would like debug information output by the Cardinal library, you can add this:

window.Cardinal.configure({
  logging: { level: 'on' },
});

Flow

The entire flow of a 3DS authentication looks like this:

  1. (backend) Initialize the 3DS authentication with Initialize 3DS
  2. (frontend) Perform Device Data Collection (DDC)
  3. (backend) Call Lookup 3DS
  4. (frontend) Depending on Lookup 3DS results, present 3DS Challenge to cardholder
  5. (backend) Validate 3DS Challenge results with Authenticate 3DS
  6. (backend) Attach successful 3DS authentication to External Card transaction

Note that results of step 3 dictate whether step 4 and step 5 are necessary, or if you may proceed directly to step 6.

Continue reading for complete details about each step.

1. Initialize 3DS

To begin a 3DS authentication, first call Initialize 3DS, specifying the external_card_id, amount, and currency.

If successful, device_data_collection_jwt and device_data_collection_url will be returned (used in step 2), and id, used in all subsequent 3DS calls. The JWT will expire in 2 hours, so DDC must be used before then.

Example request:

curl \
  $baseurl/v0/external_cards/initialize_3ds \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "external_card_id": "{EXTERNAL_CARD_ID}",
    "amount": 1500,
    "currency": "USD"
  }'

Example response:

{
  "device_data_collection_jwt": "{DDC_JWT}",
  "device_data_collection_url": "{DDC_URL}",
  "id": "{3DS_ID}"
}

2. Device Data Collection (DDC)

Using {DDC_JWT}, obtained from the previous step, perform DDC with the Cardinal library. This is a background process that does not require any user interaction. Only proceed to the next step once the process completes successfully.

Code snippet:

// listen for DDC Process completion
window.Cardinal.on('payments.setupComplete', (setupCompleteData) => {
  // finished - ready to proceed
});
​
// initialize Cardinal DDC process
window.Cardinal.setup('init', { jwt: '{DDC_JWT}' });
​
setTimeout(() => undefined, 5000); // FAILURE

For additional information, please refer to our vendor's documentation.

3. Lookup 3DS

Upon completion of DDC, the next step is to call Lookup 3DS. device_channel must be set according to your 3DS frontend implementation: SDK for mobile app, or BROWSER for mobile or desktop internet browser. If the device channel is BROWSER, you can optionally provide device_details. This is a set of device data (collected separately by you) to be used as a fallback in case there is an issue with DDC.

For authentication_indicator, select the options that best reflect the type of transaction being performed. For transaction_mode, select the correct device type.

📘

To detect the transaction_mode, you can use UAParser.js:

import UAParser from 'ua-parser-js';
const ua = new UAParser();
const { type: deviceType } = ua.getDevice();
const transactionMode = deviceType === 'mobile'
  ? "MOBILE_DEVICE"
  : deviceType === 'tablet'
    ? "TABLET_DEVICE"
    : "COMPUTER_DEVICE";

status from the response indicates the outcome:

  • SUCCESS: The cardholder was successfully authenticated - proceed to step 6
  • FAILED: The cardholder failed authentication
  • CHALLENGE_REQUIRED: A challenge is required to complete authentication - proceed to step 4
  • NOT_ENROLLED: The card provider does not support 3DS, so authentication cannot be completed
  • UNKNOWN: An indiscriminate error occured with the 3DS authentication and it cannot be completed

If CHALLENGE_REQUIRED status is returned, processor_transaction_id, challenge_url and challenge_payload will also be returned, which are needed to perform the 3DS challenge in the next step.

Example request:

curl \
  $baseurl/v0/external_cards/lookup_3ds \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "id": "{3DS_ID}",
    "authentication_indicator": "PAYMENT",
    "transaction_mode": "COMPUTER_DEVICE",
    "device_channel": "BROWSER"
  }'

Example response (success):

{
  "id": "{3DS_ID}",
  "status": "SUCCESS"
}

Example response (challenge):

{
  "challenge_payload": "{CHALLENGE_PAYLOAD}",
  "challenge_url": "{CHALLENGE_URL}",
  "id": "{3DS_ID}",
  "processor_transaction_id": "{PROCESSOR_TRANSACTION_ID}",
  "status": "CHALLENGE_REQUIRED"
}

4. 3DS Challenge

To trigger the challenge, {CHALLENGE_URL}, {CHALLENGE_PAYLOAD} and {PROCESSOR_TRANSACTION_ID} are required, obtained from the previous step. Once triggered, a modal window will be displayed containing the challenge for the user to complete. They have 10 minutes to complete the challenge before timing out.

Use an event listener to handle the various challenge results upon completion:

ResultDescription
SUCCESSThe challenge was completed successfully.
NOACTIONThere was no service level error, but authentication was not applicable. If challengeJwt is returned, you may proceed to step 5, but note that it may still result in a failure.
FAILUREThe user failed the challenge.
CANCELThe challenge was canceled by the user.
ERRORAn error was encountered while completing the challenge.
TIMEOUTThe challenged timed out.

Any result except SUCCESS or NOACTION should be treated as a failure.

Assuming a successful outcome, retain challengeJwt for performing Authenticate 3DS (step 5). This must be performed within 2 hours or else the JWT will expire.

Code snippet:

// event listener, triggered once the challenged is complete/cancelled (or error)
window.Cardinal.on('payments.validated', (data, challengeJwt) => {
  switch (data.ActionCode) {
    case 'SUCCESS':
      return challengeJwt // SUCCESS
    case 'NOACTION':
      return challengeJwt || undefined // SUCCESS OR FAILURE
    case 'FAILURE':
      return // FAILURE
    case 'CANCEL':
      return // FAILURE
    case 'ERROR':
      return // FAILURE
    case 'TIMEOUT':
      return // FAILURE
    default:
      return // FAILURE
  }
  return undefined;
});
​
// trigger the challenge
window.Cardinal.continue(
  'cca',
  { AcsUrl: "{CHALLENGE_URL}", Payload: "{CHALLENGE_PAYLOAD}" },
  {
    OrderDetails: { TransactionId: "{PROCESSOR_TRANSACTION_ID}" },
  },
);

5. Authenticate 3DS

Assuming a positive result from the challenge, the last thing to do before the 3DS authentication is complete is to call Authenticate 3DS. You must provide challenge_jwt, obtained from the challenge.

Example request:

curl \
  $baseurl/v0/external_cards/authenticate_3ds \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "id": "{3DS_ID}",
    "challenge_jwt": "{CHALLENGE_JWT}"
  }'

Example response:

  {
    "id": "{3DS_ID}",
    "status": "SUCCESS"
  }'

6. Attach to External Card Transaction

Finally, once you have a successful 3DS authentication, you must provide the id_3ds in the transaction request. A successful authentication must be used within 90 days before expiring.

Example request:

curl \
  $baseurl/v0/external_cards/transfers \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "external_card_id": "{EXTERNAL_CARD_ID}",
    "originating_account_id": "{ACCOUNT_ID}",
    "currency": "USD",
    "type": "PULL",
    "amount": 1500,
    "3ds_id": "{3DS_ID}",
  }'

Sandbox Testing - 3DS

For testing purposes, refer to this list of test PANs from our vendor that can be used to test various 3DS scenarios in the sandbox environment. See External Cards guide for information about how to create an External Card.

Create External Card-On-File Payment

Once an External Card has been tokenized and added on file, it may be used to perform transactions. An account_id must be provided, which refers to the Synctera Account that the funds will flow into or out from, depending on the type of transaction.

The merchant object contains merchant descriptor information that will be shown on financial statements and transaction details. If not provided, default information, defined during onboarding, is used.

Example request:

curl \
  $baseurl/v0/external_cards/transfers \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "external_card_id": "{EXTERNAL_CARD_ID}",
    "originating_account_id": "{ACCOUNT_ID}",
    "currency": "USD",
    "type": "PULL",
    "amount": 2500,
    "merchant": {
        "name": "Jane Taylor’s Pub",
        "address": {
            "address_line_1": "4455 Vine Street",
            "city": "Lake Forest",
            "state": "IL",
            "postal_code": "60045",
            "country_code": "US"
        },
        "email": "[email protected]"
    }
  }'

Example response:

{
  "account_id": "{ACCOUNT_ID}",
  "amount": 2500,
  "country_code": "US",
  "created_time": "2023-01-18T17:37:59.449877-05:00",
  "currency": "USD",
  "customer_id": "{CUSTOMER_ID}",
  "external_card_id": "{EXTERNAL_CARD_ID}",
  "id": "{EXTERNAL_CARD_TRANSFER_ID}",
  "last_modified_time": "2023-01-18T17:37:59.449877-05:00",
  "merchant": {
      "name": "Jane Taylor’s Pub",
      "address": {
          "address_line_1": "4455 Vine Street",
          "city": "Lake Forest",
          "state": "IL",
          "postal_code": "60045",
          "country_code": "US"
      },
      "email": "[email protected]"
  },
  "status": "SUCCEEDED",
  "transaction_id": "{TRANSACTION_ID}",
  "type": "PULL"
}

The status field of the response indicates the outcome of the transaction:

  • SUCCEEDED: The transaction was successful and funds are available - terminal status
  • DECLINED: The transaction could not be completed due to a specific rule, e.g. low balance or velocity control - terminal status
  • CANCELED: The transaction could not be completed due to error, e.g. upstream processing error - terminal status
  • UNKNOWN: The transaction status is indeterminate - non-terminal status
  • PENDING: The transaction has been initialized - non-terminal status

For the non-terminal status, you can subscribe to the EXTERNAL_CARD_TRANSFER.UPDATED webhook to be notified of status change, or simply Get External Card Transfer at a later time. Once a transaction is in a terminal status, it will not change.

transaction_id can be used to look up the transaction using the Transactions API.

3DS Response:

If you receive the following response, it means 3-D Secure is required for this transaction and three_ds_id must be provided. See the previous section for more information. Note that this is only applicable to PULL transactions.

{
  "code": "EXTERNAL_CARD_TRANSFER_3DS_REQUIRED",
  "detail": "3-D Secure authorization required for this external card transfer",
  "status": 422,
  "title": "Rule Violation",
  "type": "https://dev.synctera.com/errors/rule-violation"
}

Me-to-You PUSH Transactions

In the typical case, the originating Account and the External Card are assumed to be owned by the same Person (me-to-me transaction). However, it's also possible to initiate a (PUSH only) transaction to an External Card owned by Person who is not the originating Account owner (me-to-you transaction). In this scenario, originating_customer_id is supplied with the Person who owns the originating Account.
Note that an approval is required for this use case as it requires an MSB licence - see here.

Example request:

curl \
  $baseurl/v0/external_cards/transfers \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "external_card_id": "{EXTERNAL_CARD_ID}",
    "originating_customer_id": "{CUSTOMER_ID}": 
    "originating_account_id": "{ACCOUNT_ID}",
    "currency": "USD",
    "type": "PULL",
    "amount": 1000,
  }'

Reverse External Card Transaction

Reversals can only be applied to PULL transactions. The full or partial amount may be reversed. However, only one partial reversal may be applied to a single transaction.

Example request:

curl \
  $baseurl/v0/external_cards/transfers/{EXTERNAL_CARD_TRANSFER_ID}/reversals \
  -H "Authorization: Bearer $apikey" \
  -H 'Content-Type: application/json' \
  -d '
  {
    "currency": "USD",
    "amount": 2500
  }'

Example response:

{
  "account_id": "{ACCOUNT_ID}",
  "amount": 2500,
  "country_code": "US",
  "created_time": "2023-01-18T17:53:12.52114-05:00",
  "currency": "USD",
  "customer_id": "{CUSTOMER_ID}",
  "external_card_id": "{EXTERNAL_CARD_ID}",
  "id": "{EXTERNAL_CARD_TRANSFER_REVERSAL_ID}",
  "last_modified_time": "2023-01-18T17:53:12.52114-05:00",
  "merchant": {
      "name": "Jane Taylor’s Pub",
      "address": {
          "address_line_1": "4455 Vine Street",
          "city": "Lake Forest",
          "state": "IL",
          "postal_code": "60045",
          "country_code": "US"
      },
      "email": "[email protected]"
  },
  "status": "SUCCEEDED",
  "transaction_id": "{TRANSACTION_ID}",
  "type": "PULL_REVERSAL"
}

Sandbox Testing

The following card PANs can be used in sandbox environment for testing various scenarios:

NetworkPANPush EnabledPull EnabledType
Visa4005519200000004YYDebit
Visa4217651111111119YNDebit
Visa4111111111111111YYCredit
Mastercard2223000048400011NYDebit
Mastercard5105105105105100NNPrePaid

⚠️

Be aware that while testing in the sandbox environment, amount values 1, 1100, 2, 1200, 3, 1300, 4 and 1400 are special values reserved for generating upstream processor errors. They can be used in any Create External Card Transfer request to force an error to occur.