Tokenize an alternative payment method
Additionnally to simply accepting payments through alternative methods— such as local ones as iDeal, Oxxo or Alipay— ProcessOut also allows you to tokenize some of these payment methods to enable future one-click purchases.
APM tokenization flow
Similarly to simple payments on APMs, ProcessOut SDKs also allow to merchants to let merchants keep control of their customer flow, even after a redirection. The customer is transparently redirected to the payment method’s payment page in a new window, and once the payment is completed, the alternative payment method’s window is closed and the original merchant payment page is notified of the payment via Javascript on the web, or the customer is returned back to the app on mobile.
Create a customer & initialize an empty token
Before showing the tokenization methods to the customer, we’ll first need to
create a Customer
↗ and a Customer Token
↗
that’ll be used to store the payment details.
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="yourapp://processout.return"
var ProcessOut = require("processout");
var client = new ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
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: "yourapp://processout.return"
}).then(function(customer) {
//
}, function(err) {
// An error occured
});
import processout
client = processout.ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
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": "yourapp://processout.return"
})
require "processout"
client = ProcessOut::Client.new(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
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: "yourapp://processout.return"
)
<?php
$client = new \ProcessOut\ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
$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" => "yourapp://processout.return",
));
import "github.com/processout/processout-go"
var client = processout.New(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB",
)
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("yourapp://processout.return"),
},
})
return_url
is not mandatory but highly suggested. In some browsers (Samsung Internet Browser and Facebook iOS (Messenger, Instagram…)), the callback to your existing page is not possible due to an incompatibility of this feature.The
return_url
is mandatory on mobile integrations, or your users might not able to be redirected back to your application.Note: While the currency field is also optionnal, it will be automatically set to the currency of the first invoice your customer will pay. It is also immutable.
As we mentioned earlier, let’s also initialize an empty Customer Token
↗ that
will store the tokenized payment method for future billing. As the Token
will remain empty until final tokenization, we do not need to pass any source
to it yet, we only need to link it to a previously created Customer.
We recommend setting the field verify
to true if you want the token to be verified.
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" \
# or, if you're integrating on mobile:
-d return_url="yourapp://processout.return"
var ProcessOut = require("processout");
var client = new ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
client.newToken().create({
customer_id: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
return_url: "https://www.super-merchant.com/return",
// Or, if you're integrating on mobile:
return_url: "yourapp://processout.return"
}).then(function(token) {
//
}, function(err) {
// An error occured
});
import processout
client = processout.ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
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": "yourapp://processout.return"
})
require "processout"
client = ProcessOut::Client.new(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
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: "yourapp://processout.return"
)
<?php
$client = new \ProcessOut\ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
$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" => "yourapp://processout.return",
));
import "github.com/processout/processout-go"
var client = processout.New(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB",
)
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("yourapp://processout.return"),
},
Verify: true,
})
return_url
is not mandatory but highly suggested. In some browsers (Samsung Internet Browser and Facebook iOS (Messenger, Instagram…)), the callback to your existing page is not possible due to an incompatibility of this feature.The
return_url
is mandatory on mobile integrations, or your users might not able to be redirected back to your application.Fetch available APMs and redirect
As ProcessOut provides a fully dynamic implementation of alternative payment methods, our SDKs allow you to dynamically fetch the available ones so you can display dynamic payment links to your customers.
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,
// we can send the token back to our backend to finish the flow
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 the
// customer that canceled the tokenization, or 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 in 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]();
});
}());
}
}
// In your application, list all the available APMs for display:
client.fetchGatewayConfigurations(filter: .AlternativePaymentMethodWithTokenization) { (gateways, error) in
// Iterate over the available gateways and display the ones
// you want in your UI. Once a user choses one, execute the
// `makeAPMToken` call below to redirect your user:
client.makeAPMToken(
gateway: gateways![0],
customerId: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
tokenId: "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
)
}
// Additionally to the above, register the AppDelegate to handle
// the user return:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let apmReturn = ProcessOut.handleAPMURLCallback(url: url) {
guard apmReturn.error == nil else {
// Error while parsing the URL
return false
}
switch apmReturn.returnType {
case .CreateToken:
// Call backend to update the customer token with:
// - customerId: apmReturn.customerId
// - tokenId: apmReturn.tokenId
// - source: apmReturn.token
break
}
}
return false
}
// In your application, list all the available APMs for display:
final Context with = this;
p.fetchGatewayConfigurations(ProcessOut.GatewaysListingFilter.AlternativePaymentMethod, new FetchGatewaysConfigurationsCallback() {
@Override
public void onSuccess(ArrayList<GatewayConfiguration> gateways) {
// Iterate over the available gateways and display the ones
// you want in your UI. Once a user choses one, execute the
// `makeAPMToken` call below to redirect your user:
p.makeAPMToken(
gateways.get(0),
"cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
"tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
with
);
}
@Override
public void onError(Exception e) {
Log.e("PROCESSOUT", e.toString());
}
});
// Additionally to the above, add the user return in your
// `onCreate` method of the `Activity`:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Check if we're landing back with a URL in our intent
Intent intent = getIntent();
Uri data = intent.getData();
if (data == null) {
return;
}
ProcessOut p = new ProcessOut(this, proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x);
APMTokenReturn apmReturn = p.handleAMPURLCallback(data);
if (apmReturn == null) {
return;
}
switch (apmReturn.getType()) {
case TokenCreation:
// Call backend to update the customer token with:
// - customerId: apmReturn.getCustomerId()
// - tokenId: apmReturn.getTokenId()
// - source: apmReturn.getToken()
break;
}
}
}
Finish the tokenization on the server
ProcessOut.js
sent the token
back to our server so we need to validate
its tokenization. We’ll need both the token
, the customer ID
, and the token ID
we created earlier and used to redirect the customer.
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=="
var ProcessOut = require("processout");
var client = new ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
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
});
import processout
client = processout.ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
token = client.new_token().find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy")
token.save({"source": "gway_req_V2UncmUgaGlyaW5nIQ==", "verify": true})
require "processout"
client = ProcessOut::Client.new(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
token = client.token.find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy")
token.save("source": "gway_req_V2UncmUgaGlyaW5nIQ==","verify":true)
<?php
$client = new \ProcessOut\ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
$token = $client->newToken()->find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy");
$token = $token->save(array("source" => "gway_req_V2UncmUgaGlyaW5nIQ==", "verify" => true));
import "github.com/processout/processout-go"
var client = processout.New(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB",
)
token, _ := client.NewToken().Find("cust_WtaVdUjAGpOlbLiYWYXBR67whr91Rlks", "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy")
_, err := token.Save(processout.TokenSaveParameters{
Source: "gway_req_V2UncmUgaGlyaW5nIQ==",
Verify: true,
})
Asynchronous Tokenisation
, upon creating a token, the verification_status
would first be pending
,
and will transition to either success
or failed
. The updated status of the
token verification can be seen upon fetching a token ↗, given the tokenID.
Note, async tokenisation is subject to the support of the payment provider/
Note: The gateway request token sent by ProcessOut.js
is actually only
an abstraction of the request done by the customer on the gateway. The content
of the token is therefore directly encoded inside it, in base64.
The final token call should return the Token object. Upon return, it should
have its attribute is_chargeable
set to true; this makes sure that a
chargeable source is indeed behind this token. We highly recommend that you
check the verification_status
field to make sure that the Token has been verified
successfully, if requested, and it can in fact be used to place future
payments.