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.

Preview

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.