Saving a token to capture future payments

You can handle many types of purchase using simple one-off payments. However, you might want to let customers store their card details so they can make future payments more quickly.
Also, recurring payments (such as subscriptions and instalments) are easier to handle using stored card details because you can take payment from them automatically without any action from the customer.

The Smart Router API lets you create Customer objects to store various details about customers, including payment details from one or more cards. Once you have created a Customer, you can generate a customer token from it which you can then use in place of a card token to handle payments. However, unlike a card token, a customer token can be reused repeatedly.

Below is a sequence diagram that shows the flow for creating a customer token. The following sections explain the steps in more detail.

Step 1: Create a customer

The first step is to create a new Customer object on the server side. The object has numerous fields to store data about your customer, including contact details and card details. Although you can create a new Customer without supplying any data, we strongly recommend that you add all the data you have available to the object. This data can help you locate a Customer when you search in the Dashboard and Smart Router can also use it to improve routing performance and decrease the risk of fraudulent payments. See the Customer section of the API reference for details of all the object's data fields.

The code sample below shows how to create a new Customer object. Note that once the object is created, you should store its id property in your database or otherwise keep track of it. You will need the id value to retrieve the object in the future. Note that the currency field used in the example is optional but it will be set to the currency of the first payment the customer makes if you choose not to set it during creation. Once currency
has been set for a Customer, its value cannot be changed.

See the page about setting up your environment to learn how to install the required library for your language and create the client object to access the API.

curl -X POST https://api.processout.com/customers \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d first_name=John \
    -d last_name=Smith \
    -d currency=USD
var customer = client.newCustomer().create({
    first_name: "John",
    last_name:  "Smith",
    currency:   "USD"
}).then(function(customer) {
    // Continue
}, function(err) {
    // An error occured
});
customer = client.new_customer().create({
    "first_name": "John",
    "last_name":  "Smith",
    "currency":   "USD"
})
customer = client.customer.create(
    first_name: "John",
    last_name:  "Smith",
    currency:   "USD"
)
$customer = $client->newCustomer()->create(array(
    "first_name" => "John",
    "last_name"  => "Smith",
    "currency"   => "USD"
));
cust, err := client.NewCustomer().Create(processout.CustomerCreateParameters{
    Customer: &processout.Customer{
        FirstName: processout.String("John"),
        LastName:  processout.String("Smith"),
        Currency:  processout.String("USD"),
    },
})

Step 2: Create a customer token

You can use a Customer object and a card token together to create a customer token. You can use this in place of a card token to capture payments.

There are 2 different approaches you can use to create the customer token from the card token:

  • Without verification: With this approach, you supply an existing card token as a parameter when you create the customer token on the server. The API does not check the validity of the card details before creating the token.
  • With verification: Here, you do not supply a card token on the server side but instead use an extra verification step with a card token on the client side. This checks that the card details are valid before creating the customer token.

The following code samples show the difference between these approaches:

Without verification

curl -X POST https://api.processout.com/customers/cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks/tokens \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d source=card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU
client.newToken().create({
    customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    source:      "card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU"
}).then(function(token) {
    // Continue
}, function(err) {
    // An error occured
});
token = client.new_token().create({
    "customer_id": "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    "source":      "card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU"
})
token = client.token.create(
    customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    source:      "card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU"
)
$token = $client->newToken()->create(array(
    "customer_id" => "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    "source"      => "card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU"
));
token, err := client.NewToken().Create(&processout.TokenCreateParameters{
    Token: &processout.Token{
        CustomerID: processout.String("cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj"),
    },
    Source: "card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU",
})

With verification

curl -X POST https://api.processout.com/customers/cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks/tokens \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d verify=true
client.newToken().create({
    customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    verify:      true
}).then(function(token) {
    // Continue
}, function(err) {
    // An error occured
});
token = client.new_token().create({
    "customer_id": "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    "verify":      True
})
token = client.token.create(
    customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    verify:      true
)
$token = $client->newToken()->create(array(
    "customer_id" => "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    "verify"      => true
));
token, err := client.NewToken().Create(&processout.TokenCreateParameters{
    Token: &processout.Token{
        CustomerID: processout.String("cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj"),
    },
    Verify: true,
})

If you create the token object without verification then you can immediately use the string in its id field to capture payment in the same way you would with a card token string. If you do request verification then you must follow an extra step on the client side, as described below.

Step 3: Verify the token on the client side

The customer token object has a verification_status field whose possible values are success, pending, failed, not-requested and unknown. When you create a token without verification, this will be set to pending which means that you cannot use it for payment yet.

You must pass the id values from both the Customer object and the customer Token object to the client to perform the verification, as shown in the code sample below. You will also need a payment source token on the client side, such as a card token. For example, you could display a form to let the customer enter the details of a new card to associate with the token.

📘

When you are using mobile SDKs, please ensure that the customer token has a valid invoice_return_urldeep link assigned. It enables the SDKs to pass control back to your application. Deep links for Android must follow the your.application.id://processout/return format.

Note that in some cases, the Payment Service Provider (PSP) will not respond synchronously to the verification request. When this happens, you can still get confirmation that the card was verified using webhooks. See the API reference section about customer token events for more information.

📘

For mobile, the 3DS2 flow is native. See the page about mobile app payments to learn more about the threeDSService object shown in the code sample.

// Make sure the card ID, customer ID and token ID are available
// in the same scope as the makeCardToken() call
client.makeCardToken(
    "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", 
    "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj", 
    "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy", 
    {
        // Use this if you want to offer the customer a preferred scheme
        // to pay on (for example, if the customer's card supports
        // co-schemes such as carte bancaire)
        preferred_scheme: "carte bancaire"
    }, function(customerTokenID) {
        var field   = document.createElement("input");
        field.type  = "hidden";
        field.name  = "customer_token_id";
        field.value = customerTokenID;

        // Re-enable the button
        document.getElementById("paymentBtn").disabled = false;

        // Add the customer_token_id input so that it gets sent back to the server.
        // Finally, submit the form.
        formElement.appendChild(field);
        formElement.submit();
    }, function(err) {
        document.getElementById("errors").innerHTML = err.message;
    });
// Make sure the card ID, customer ID and token ID are available
// in the same scope as the assignCustomerToken() call.

let request = POAssignCustomerTokenRequest(
    customerId: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    tokenId: "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
    source: "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ",
    preferredScheme: "carte bancaire" // Optional
)

let customerToken = try await ProcessOut.shared
    .customerTokens
    .assignCustomerToken(request: request, threeDSService: threeDSService)
// Make sure the card ID, customer ID and token ID are available
// in the same scope as the assignCustomerToken() call.

val request = POAssignCustomerTokenRequest(
    customerId = "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
    tokenId = "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
    source = "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ",
    preferredScheme = "carte bancaire"
)

ProcessOut.instance.customerTokens.assignCustomerToken(
    request = request,
    threeDSService = threeDSService
)

// Subscribe to collect the result in the coroutine’s scope.
ProcessOut.instance.customerTokens.assignCustomerTokenResult
    .collect { result ->
        // Handle the result.
    }

When the PSP has verified the customer token successfully, you can start using it to capture payments. Note that for Cardholder Initiated Transactions (CITs), you should authorize payment on the client before capturing to comply with Strong Customer Authentication (SCA) laws.

Listing and deleting customers' tokens

You can let the customer set up a selection of cards or other payment methods to choose from. The code sample below shows how to obtain a list of all tokens for a particular customer and also how to delete an unwanted token from the list (which you might need to do when a card expires, say).

curl -X GET https://api.processout.com/customers/cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks/tokens \
     -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
customer.fetchTokens().then(
    function(tokens) {
        // And let's say our customer wants to remove its first token
        tokens[0].delete(); 

    }, function(err) {
        // The customer's tokens could not be fetched
    });
tokens = customer.fetch_tokens()

# And let's say our customer wants to remove its first token
tokens[0].delete()
tokens = customer.fetch_tokens()

# And let's say our customer wants to remove its first token
tokens.first.delete
$tokens = $customer->fetchTokens();

// And let's say our customer wants to remove its first token
$tokens[0]->delete();
tok, _ := cust.FetchTokens()

// And let's say our customer wants to remove its first token
tok.Get().Delete()