Tokenizing Alternative Payment methods
You can create a customer token to save the details of an Alternative Payment Method (APM) token just as you can with a card token. This lets you reuse the details for multiple payments, including Merchant Initiated Transactions (MITs). See the page about saving a token to capture future payments to learn more about the uses of customer tokens. The process of creating a customer token is very similar for cards and APMs but the differences are explained below.
The example form below demonstrates how the APM flow looks from a customer's point of view. The main difference between an APM and a card is that the customer's details are received asynchronously for an APM, using a redirect to the APM's payment page. The Smart Router API uses some extra JavaScript code in the browser to handle redirection back to your web page or mobile app after payment. See the page about Alternative Payment Methods for a full description of the asynchronous payment flow.
Step 1: Creating a customer
This step is similar to the equivalent step for saving a card token. The main difference is that you add a return_url
field to the Customer
object when you create it. This tells our client-side API where to redirect the customer after selecting the APM.
For mobile apps, the return_url
contains a deep link for the screen you want to return to after the APM payment page closes. You must provide the return_url
to let our code pass control back to your app.
For a web page, the return_url
links to the page you used to launch the APM payment. Although it is not mandatory as it is for mobile apps, we strongly recommend that you supply it to ensure the user gets back to your page.
Some browsers such as Samsung Internet Browser and Facebook iOS browsers (for example, Messenger and Instagram) do not support redirection back to your web page after the customer interacts with an APM.
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 \
-d return_url="https://www.super-merchant.com/return" \
# or, if you're integrating on mobile:
-d return_url="your.application.id://processout/return"
var customer = client.newCustomer().create({
first_name: "John",
last_name: "Smith",
currency: "USD",
return_url: "https://www.super-merchant.com/return",
// Or, if you're integrating on mobile:
return_url: "your.application.id://processout/return"
}).then(function(customer) {
//
}, function(err) {
// An error occured
});
customer = client.new_customer().create({
"first_name": "John",
"last_name": "Smith",
"currency": "USD",
"return_url": "https://www.super-merchant.com/return",
# Or, if you're integrating on mobile:
"return_url": "your.application.id://processout/return"
})
customer = client.customer.create(
first_name: "John",
last_name: "Smith",
currency: "USD",
return_url: "https://www.super-merchant.com/return",
# Or, if you're integrating on mobile:
return_url: "your.application.id://processout/return"
)
$customer = $client->newCustomer()->create(array(
"first_name" => "John",
"last_name" => "Smith",
"currency" => "USD",
"return_url" => "https://www.super-merchant.com/return",
// Or, if you're integrating on mobile:
"return_url" => "your.application.id://processout/return",
));
cust, err := client.NewCustomer().Create(processout.CustomerCreateParameters{
Customer: &processout.Customer{
FirstName: processout.String("John"),
LastName: processout.String("Smith"),
Currency: processout.String("USD"),
ReturnURL: processout.String("https://www.super-merchant.com/return"),
// Or, if you're integrating on mobile:
ReturnURL: processout.String("your.application.id://processout/return"),
},
})
Step 2: Creating a blank customer token
You must now use the id
of the new Customer
object to generate a customer token that will eventually reference the customer's APM details. Again, this step is similar to the equivalent step for cards. The differences are that you must supply the return_url
(as you do when you create the Customer
object) and also that the new token is intially "blank", with no payment details. You will supply these details when you save the token in the final step.
We recommend that you enable the verify
option to check that the APM details are valid before creating the customer token. However, you might omit this if you already know that the details are reliable.
curl -X POST https://api.processout.com/customers/cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks/tokens \
-u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
-d return_url="https://www.super-merchant.com/return" \
-d verify=true \
# or, if you're integrating on mobile:
-d return_url="your.application.id://processout/return"
client.newToken().create({
customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
return_url: "https://www.super-merchant.com/return",
verify: true,
// Or, if you're integrating on mobile:
return_url: "your.application.id://processout/return"
}).then(function(token) {
//
}, function(err) {
// An error occured
});
token = client.new_token().create({
"customer_id": "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
"verify": true,
"return_url": "https://www.super-merchant.com/return",
# Or, if you're integrating on mobile:
"return_url": "your.application.id://processout/return"
})
token = client.token.create(
customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
return_url: "https://www.super-merchant.com/return",
verify: true,
# Or, if you're integrating on mobile:
return_url: "your.application.id://processout/return"
)
$token = $client->newToken()->create(array(
"customer_id" => "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
"return_url" => "https://www.super-merchant.com/return",
"verify" => true,
// Or, if you're integrating on mobile:
"return_url" => "your.application.id://processout/return",
));
token, err := client.NewToken().Create(&processout.TokenCreateParameters{
Token: &processout.Token{
CustomerID: processout.String("cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj"),
ReturnURL: processout.String("https://www.super-merchant.com/return"),
// Or, if you're integrating on mobile:
ReturnURL: processout.String("your.application.id://processout/return"),
},
Verify: true,
})
Step 3: Choosing an APM for the token
You must now pass the id
values of both the Customer
object and the blank customer Token
object back to the client to let the user choose an APM for the token. Smart Router lets you generate the list of APMs dynamically on the client, which will automatically include only APMs that are currently available and support tokenization.
Both the flow and the code are very similar for APM tokenization and Alternative payment methods but here, we are supplying a Customer
object for validation rather than an Invoice
. Note that the token
value that the code generates is actually a gateway request token. You can only use this to generate the eventual customer token and you should not try to use it as a payment source.
client.fetchGatewayConfigurations({
customerID: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
tokenID: "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
filter: "alternative-payment-methods-with-tokenization"
}, processoutAPMsReady, function(err) {
console.log("Woops, couldn't fetch APMs: "+err);
});
var configFunctions = [];
function createHandler(i, conf) {
return function(){
conf.handleCustomerTokenAction(function(token) {
// The customer completed the gateway tokenization flow, so
// we can now send the token back to our server.
document.getElementById("success").innerHTML = "Success! Token "+token+" on customer "+document.getElementById('customer-id').value;
}, function(err) {
// An error occured during tokenization. This could just be that the
// customer canceled the tokenization, or it could be an error with
// the payment gateway.
document.getElementById("errors").innerHTML = err.message;
});
}
}
function processoutAPMsReady(confs) {
var formWrapper = document.getElementById("apms-payment-form");
var elements = [];
for (var i = 0; i < confs.length; i++) {
var el = document.createElement("div");
el.className = "my-apm-link";
el.innerHTML = "Save payment details with " + confs[i].gateway.display_name;
configFunctions[i] = createHandler(i, confs[i]);
// Inject our APM element into the form.
formWrapper.appendChild(el);
elements[i] = el;
}
for (var i = 0; i < confs.length; i++) {
(function () {
var k = i;
elements[k].addEventListener("click", function() {
configFunctions[k]();
});
}());
}
}
// 1. List all the available APMs that support tokenization. You could
// do as proposed below or you can communicate with our API directly
// from your backend.
let gatewayConfigurationsRequest = POAllGatewayConfigurationsRequest(
filter: .alternativePaymentMethodsWithTokenization
)
let gatewayConfigurations = try await ProcessOut.shared
.gatewayConfigurations
.all(request: gatewayConfigurationsRequest)
.gatewayConfigurations
// 2. Once the user chooses the needed configuration, create a
// POAlternativePaymentTokenizationRequest.
let tokenizationRequest = POAlternativePaymentTokenizationRequest(
customerId: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
tokenId: "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
gatewayConfigurationId: gatewayConfigurations.first!.id
)
// 3. Execute request (implementation will present UI automatically)
let gatewayToken = try await ProcessOut.shared.alternativePayments
.tokenize(request: tokenizationRequest)
.gatewayToken
// 4. Create assignment request
let assignTokenRequest = POAssignCustomerTokenRequest(
customerId: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
tokenId: "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
source: gatewayToken
)
// 5. Assign gateway token to customer token
try await ProcessOut.shared.customerTokens.assignCustomerToken(
request: assignTokenRequest, threeDSService: threeDSService
)
// 1. Initialize a Custom Chrome Tabs launcher in the onCreate() method of
// your Activity or Fragment to register a callback.
private lateinit var customTabLauncher: POAlternativePaymentMethodCustomTabLauncher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create(from = this) { result ->
when (result) {
is ProcessOutResult.Success -> TODO() // 5. Send the result details to your backend.
is ProcessOutResult.Failure -> TODO()
}
}
}
// 2. List all the available APMs that support tokenization. You could
// do as shown below or communicate with the ProcessOut API directly
// from your backend.
val gatewayConfigurationsRequest = POAllGatewayConfigurationsRequest(
filter = Filter.ALTERNATIVE_PAYMENT_METHODS_WITH_TOKENIZATION
)
ProcessOut.instance.gatewayConfigurations.fetch(gatewayConfigurationsRequest).let { result ->
when (result) {
is ProcessOutResult.Success -> result.value.gatewayConfigurations
is ProcessOutResult.Failure -> result.code
}
}
// 3. Once the user chooses the needed configuration, create a POAlternativePaymentMethodRequest.
val alternativePaymentRequest = POAlternativePaymentMethodRequest(
invoiceId = String(), // Pass an empty string.
gatewayConfigurationId = gatewayConfigurations.first().id,
customerId = "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
tokenId = "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy"
)
// 4. Launch the activity.
customTabLauncher.launch(
request = alternativePaymentRequest,
returnUrl = "your.application.id://processout/return"
)
Alternatively, the merchant can choose to create their redirect URL without using the SDK.
The URL format is as follows:`
https://checkout.processout.com/{project_id}/{customer_id}/{token_id}/redirect/{gateway_configuration_id}
The customer will be redirected to the APM to complete the payment. Once the payment is completed, ProcessOut will post the Gateway Token (gway_req_abcde....
) to the URL stored in the invoice return_url
field.
Step 4: Save the completed token on the server
Your client-side code must send the new gateway request token
value back to the server to save the customer token with the payment details. You also need the id
values of the Customer
object and the customer Token
object from the previous steps.
curl -X PUT https://api.processout.com/customers/cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks/tokens/tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy \
-u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
-d source="gway_req_V2UncmUgaGlyaW5nIQ=="
client.newToken().find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy").then(function(token) {
token.save({
"source": "gway_req_V2UncmUgaGlyaW5nIQ==",
"verify": true
}).then(function(token) {
// Customer Token was saved with the new source. Check that
// token.is_chargeable is true
// token.verification_status is `success`
}, function(err) {
// The source could not be used on the Customer Token
})
}, function(err) {
// Customer Token could not be found
});
token = client.new_token().find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy")
token.save({
"source": "gway_req_V2UncmUgaGlyaW5nIQ==",
"verify": true
})
token = client.token.find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy")
token.save(
"source": "gway_req_V2UncmUgaGlyaW5nIQ==",
"verify":true
)
$token = $client->newToken()->find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy");
$token = $token->save(array(
"source" => "gway_req_V2UncmUgaGlyaW5nIQ==",
"verify" => true
));
token, _ := client.NewToken().Find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy")
_, err := token.Save(processout.TokenSaveParameters{
Source: "gway_req_V2UncmUgaGlyaW5nIQ==",
Verify: true,
})
After saving, the token
object is a complete customer token with the APM payment details in place.
Note that some APMs perform asynchronous tokenization, which means that the customer token may not be ready to use for payment immediately after you save it. You can use the verification_status
field of the token
to check this (the value will be success
if it is ready or pending
if you are still waiting for it to be verified). We recommend that you use webhooks to listen for customer token events that will notify you about any changes in the status of the token.
Updated 3 months ago