Saving APM tokens
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 an
iOS Univeral Link
or Android App 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.
Note: 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="yourapp://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: "yourapp://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": "yourapp://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: "yourapp://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" => "yourapp://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("yourapp://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="yourapp://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: "yourapp://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": "yourapp://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: "yourapp://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" => "yourapp://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("yourapp://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
APM payments
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]();
});
}());
}
}
// 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 chooses one, execute the
// `makeAPMToken` call below to redirect them:
client.makeAPMToken(
gateway: gateways![0],
customerId: "cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
tokenId: "tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
)
}
// Also register the AppDelegate to handle
// the user's 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:
// Send back to the server 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 chooses one, execute the
// `makeAPMToken` call below to redirect them:
p.makeAPMToken(
gateways.get(0),
"cust_C4hZXQTU0aWoYeenHYC0DektYDqf8ocj",
"tok_fKK4btSG7wd13ZZaevzhMcuNbpjcu1Zy",
with
);
}
@Override
public void onError(Exception e) {
Log.e("PROCESSOUT", e.toString());
}
});
// Also add the user's return location to the
// `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:
// Send back to the server to update the customer token with:
// - customerId: apmReturn.getCustomerId()
// - tokenId: apmReturn.getTokenId()
// - source: apmReturn.getToken()
break;
}
}
}
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.