Alternative payment methods

Smart Router lets you accept payments from alternative payment methods (APMs) as well as cards. These include well-known services like PayPal and also cryptocurrency systems such as CoinPayments.

Alternative payment methods flow

Card payment methods are generally synchronous, which means that you get confirmation of the success or failure of a payment directly after it happens. APMs, on the other hand, are generally asynchronous because they involve a redirection to the Payment Service Provider (PSP) web page from your own page or mobile app. As a result, the only way you can usually get confirmation of a successful payment is by listening for an update message from the PSP. This is inconvenient for the design of your app because it is harder to inform the customer of success within the normal payment flow.

ProcessOut processes APMs transactions by opening a new window for the payment page after redirection. When payment is complete, our code closes this window and returns the payment token to your original window or app screen using a callback function. This means that the structure of your code is very similar to the code you would use to handle a synchronous payment.

The following code sample demonstrates how the Smart Router API passes control to the APM in a separate window and then regains control after the payment is complete. Note that the invoice ID used in the code is pre-generated on our server for demonstration purposes. The sections below explain how to create the invoice in your own code and accept payment on it.




<div class="container">
  <form action="" method="POST" id="apms-form">
    <div id="results">
      <div id="errors"></div>
      <div id="success"></div>
      <div id="loading">Loading...</div>
      <input id="success-token" class="hidden" />
    </div>
  </form>
</div>
/*! PocketGrid 1.1.0
* Copyright 2013 Arnaud Leray
* MIT License
*/
* {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* Clearfix */
.block-group {
  *zoom: 1;
}
.block-group:before, .block-group:after {
  display: table;
  content: "";
  line-height: 0;
}
.block-group:after {
  clear: both;
}

.block-group {
  /* ul/li compatibility */
  list-style-type: none;
  padding: 0;
  margin: 0;
}

/* Nested grid */
.block-group > .block-group {
  clear: none;
  float: left;
  margin: 0 !important;
}

/* Default block */
.block {
  float: left;
  width: 100%;
}
.b75 {
  width: 74%;
}
.b25 {
  width: 24%;
  margin-left: 2%;
}

html, body {
  background: #ECEFF1;
  font-size: 16px;
}

.container {
  width: 100%;
  max-width: 400px;
  background: white;
  margin: 3em auto;
  padding: 1em;
  border-radius: 4px;
  box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
}

input {
  border: 1px solid #ECEFF1;
  border-radius: 4px;
  box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
  padding: 0.5em;
  width: 100%;
  margin-bottom: 1em;
  font-size: 14px;
  min-height: 2em;
}

.button {
  margin: 0; margin-bottom: 1em;
  padding: 0.75em;
  text-align: center;
  box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
  background: #3F51B5;
  color: white;
  border-radius: 4px;
  border: 1px solid #303F9F;
  cursor: pointer;
}

.hidden {
  display: none;
}
#errors, #success, #loading {
  margin-bottom: 1em;
  text-align: center;
  font-size: 0.9em;
  color: #D84315;
}
#success {
  color: #4CAF50;
}
#loading {
  color: #bdc3c7;
}
document.addEventListener("DOMContentLoaded", function() {
  var client = new ProcessOut.ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
  client.fetchGatewayConfigurations({
    invoiceID: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT",
    filter:    "alternative-payment-methods"
  }, processoutAPMsReady, function(err) {
    document.getElementById("loading").className = "hidden";
    document.getElementById("errors").innerHTML = "Woops, couldn't fetch APMs: "+err;
  });

  function processoutAPMsReady(confs) {
    document.getElementById("loading").className = "hidden";
    var formWrapper = document.getElementById("apms-form");
    for (var conf of confs) {
      var el = document.createElement("div");
      el.className = "button";
      el.innerHTML = "Pay with " + conf.gateway.display_name;

      conf.hookForInvoice(el, function(token) {
        document.getElementById("errors").innerHTML = "";
        document.getElementById("success").innerHTML = "Your user went through the entire APM payment flow. Use the token below to verify the payment in your backend.";
        document.getElementById("success-token").value = token;
        document.getElementById("success-token").className = "";
      }, function(err) {
        document.getElementById("errors").innerHTML = err.message;
        document.getElementById("success").innerHTML = "";
        document.getElementById("success-token").className = "hidden";
      });

      // Inject our APM element in the form
      formWrapper.appendChild(el);
    }
  }
});

Below is a sequence diagram that shows the flow for an APM. The following sections explain the steps in more detail.

Sequence diagram showing the flow for an APM

Setting up your payments page for APMs

As usual, you must create an Invoice as the first step in receiving payment. The main difference with an APM-compatible invoice is the return_url field, for mobile apps.

For mobile apps, this 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 enable our code to pass control back to your app.

For in-browser payments, the return_url is optional. ProcessOut.js will trigger an event once the customer has returned from the APM site. See function(token) from the example below.

Note: Some browsers such as Samsung Internet Browser and Facebook iOS browsers (eg, Messenger and Instagram) do not support redirection back to your web page after payment with an APM.

The code sample below shows how to create the Invoice with the return_url. See the page about setting up the SDKs to learn how to install our SDK for your language and access it using the client object.

curl https://api.processout.com/invoices \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d name="Amazing item" \
    -d amount="9.99" \
    -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"
client.newInvoice().create({
    name:       "Amazing item",
    amount:     "4.99",
    currency:   "USD",
    return_url: "https://www.super-merchant.com/return",
    // Or, if you're integrating on mobile:
    return_url: "yourapp://processout.return"
}).then(function(invoice) {
    //

}, function(err) {
    // An error occurred

});
invoice = client.new_invoice().create({
    "name":       "Amazing item",
    "amount":     "4.99",
    "currency":   "USD",
    "return_url": "https://www.super-merchant.com/return",
    # Or, if you're integrating on mobile:
    "return_url": "yourapp://processout.return"
})
invoice = client.invoice.create(
    name:       "Amazing item",
    amount:     "4.99",
    currency:   "USD",
    return_url: "https://www.super-merchant.com/return",
    # Or, if you're integrating on mobile:
    return_url: "yourapp://processout.return"
)
$invoice = $client->newInvoice()->create(array(
    "name"       => "Amazing item",
    "amount"     => "4.99",
    "currency"   => "USD",
    "return_url" => "https://www.super-merchant.com/return",
    // Or, if you're integrating on mobile:
    "return_url" => "yourapp://processout.return",
));
// Let's create an invoice
iv, err := client.NewInvoice().Create(processout.InvoiceCreateParameters{
    Invoice: &processout.Invoice{
        Name:      processout.String("Amazing item"),
        Amount:    processout.String("4.99"),
        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"),
    },
})
if err != nil {
    panic(err)
}

List the available APMs and redirect

You can have several APMs configured for use with Smart Router at the same time (go to Dashboard › Payment providers to see the available APMs or configure new ones). However, this does not mean that all of them will be available for a particular purchase. For example, some APMs might not operate in every country. The Smart Router API lets you find the set of APMs that are available for an invoice so that you can list them for your customer to choose from.

The code sample below shows how you can use ProcessOut.fetchGatewayConfigurations() to retrieve a list of available APMs for your invoice and use them to generate payment links. The callback function (eg, the parameter supplied to client.handleInvoiceActionAdditionalData() in the web version) receives the token that the APM payment generates. You should send this back to your server for capture within the callback.

client.fetchGatewayConfigurations({
  invoiceID: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT",
  filter:    "alternative-payment-methods"
}, processoutAPMsReady, function(err) {
  console.log("Woops, couldn't fetch APMs: "+err);
});

var configFunctions = [];

function createHandler(i, conf) {
    return function() {
      client.handleInvoiceActionAdditionalData(
        invoiceId,
        conf,
        {"issuer_country": "BE"},
        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 = "Pay 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: .AlternativePaymentMethod) { (gateways, error) in
  // Iterate over the available gateways and display the ones
  // you want in your UI. Once a user choses one, execute the
  // `makeAPMPayment` call below to redirect your user:
  client.makeAPMPayment(gateway: gateways![0], invoiceId: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT")
}

// 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 .Authorization:
      // Finish the charge on your backend with 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
    // `makeAPMPayment` call below to redirect your user:
    p.makeAPMPayment(gateways.get(0), "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT", 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 Authorization:
        // Call backend to charge with apmReturn.getToken()
        break;
    }
  }
}

Handle the capture on the server

The callback for the APM response described above should send the new token back to the server along with the invoice ID. After this point, the process that you use to capture the payment on the server is exactly the same as the process for a card payment. See the page about capturing a payment for a full description.