Handle alternative payment methods

On top of helping you accept payments using credit cards, ProcessOut also lets you accept payments through alternative payment methods, such as PayPal or cryptocurrencies with coinpayments.


Alternative payment methods flow

Because of their nature, most alternative payment methods are asynchronous. This means that you normally wouldn’t be able to synchronously tell the customer if its payment correctly made it through until you receive a confirmation from the payment method webhooks (such as from the PayPal IPN). This is mainly due to the fact that a redirection is usually required.

ProcessOut tries to bridge that gap by letting 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.

<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);
    }
  }
});

Set up your payments page for APMs

Before showing the payment methods to the customer, we’ll first need to create an invoice, as some of the gateways will need some information before the payment actually takes place.

# Let's create an invoice
curl https://api.processout.com/invoices \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d name="Awesome invoice" \
    -d amount="9.99" \
    -d currency=USD \
    -d return_url="https://www.super-merchant.com/return"
var ProcessOut = require("processout");
var client = new ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");

// Let's create an invoice
client.newInvoice().create({
    name:       "Amazing item",
    amount:     "4.99",
    currency:   "USD",
    return_url: "https://www.super-merchant.com/return"
}).then(function(invoice) {
    //

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

});
import processout
client = processout.ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

# Let's create an invoice
invoice = client.new_invoice().create({
    "name":       "Amazing item",
    "amount":     "4.99",
    "currency":   "USD",
    "return_url": "https://www.super-merchant.com/return"
})
require "processout"
client = ProcessOut::Client.new(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

# Let's create an invoice
invoice = client.invoice.create(
    name:       "Amazing item",
    amount:     "4.99",
    currency:   "USD",
    return_url: "https://www.super-merchant.com/return"
)
<?php
$client = new \ProcessOut\ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");

// Let's create an invoice
$invoice = $client->newInvoice()->create(array(
    "name"       => "Amazing item",
    "amount"     => "4.99",
    "currency"   => "USD",
    "return_url" => "https://www.super-merchant.com/return",
));
import "gopkg.in/processout.v4"
client := processout.New(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

// 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"),
    },
})
if err != nil {
    panic(err)
}

The 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 will be used instead.

Now that we’ve created our Invoice, we can also set up a simple HTML form in which we’ll inject the payment buttons for each APM. The form action URL will be used to post back the final token generated when your user pays on the APM.

<form action="/your-capture-endpoint" method="POST" id="apms-payment-form">
  <!-- We'll inject our payment links in the next part -->
</form>

Load up ProcessOut.js

Now that we’ve set up our form and invoice, we’re ready to start showing our APMs payment buttons!

Please keep in mind that when using the ProcessOut sandbox, your actual enabled gateways aren’t returned, but instead a single entry sandbox is. Its main purpose is to provide you with a way to easily test successful and errored checkouts. Because this guide provides you with a generic implementation that works with all alternative payments available on ProcessOut, switching over to production will not break any of your logic, and alternative payment solutions such as PayPal will work right out of the box.

For the APMs integration to work properly, you will need to load ProcessOut.js. It is important to always load it from our CDN like shown below. Please note that ProcessOut.js has no dependency, meaning it can work without jQuery.

<script src="https://js.processout.com/processout.js"></script>

Now that ProcessOut.js is loaded, we can start using it. Let’s first instanciate a new ProcessOut instance in a different script tag as soon as the DOM is fully loaded, and fetch your enabled APMs:

document.addEventListener("DOMContentLoaded", function() {
  var client = new ProcessOut.ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");

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

Note: Remember to replace the project ID in the example with your own. When testing, prepend your project ID with test- like so: test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x.

In the previous step, our call to fetchGatewayConfigurations makes a reference to the function processoutAPMsReady when the APMs list is successfully fetched. We now want to define this function to finally update our DOM with the payment links.

The function takes one parameter: a list of Gateway Configuration.

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

    conf.hookForInvoice(el, function(token) {
      // The customer completed the gateway payment flow,
      // we can send the token back to our backend to finish the
      // capture
      var field   = document.createElement("input");
      field.type  = "hidden";
      field.name  = "token";
      field.value = token;
      formWrapper.appendChild(field);
      formWrapper.submit();

    }, function(err) {

      // An error occured during checkout. This could just be the
      // customer that canceled the payment, or an error with
      // the payment gateway.
      alert(err);
    });

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

If you’d rather handle the DOM elements and hooks yourself, you can simply keep a reference to each conf element and call the following method to initiate the payment flow individually:

conf.handleInvoiceAction(function(token) {
  // The customer completed the gateway payment flow,
  // we can send the token back to our backend to finish the
  // capture
  var field   = document.createElement("input");
  field.type  = "hidden";
  field.name  = "token";
  field.value = token;
  formWrapper.appendChild(field);
  formWrapper.submit();

}, function(err) {

  // An error occured during checkout
})

Handle the capture on the server

ProcessOut.js sent the token back to our server so we need to finish its capture. We’ll need both the token and the invoice ID we created earlier and used to redirect the customer.

The code to capture the token returned by ProcessOut.js during an alternative gateway checkout is identical to the capture of a card token.

curl https://api.processout.com/invoices/iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT/capture \
    -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");

invoice.capture("gway_req_V2UncmUgaGlyaW5nIQ==").then(
    function(transaction) {
        //

    }, function(err) {
        // The invoice could not be captured
    });
import processout
client = processout.ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

transaction = invoice.capture("gway_req_V2UncmUgaGlyaW5nIQ==")
require "processout"
client = ProcessOut::Client.new(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

transaction = invoice.capture("gway_req_V2UncmUgaGlyaW5nIQ==")
<?php
$client = new \ProcessOut\ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");

$transaction = $invoice->capture("gway_req_V2UncmUgaGlyaW5nIQ==");
import "gopkg.in/processout.v4"
client := processout.New(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

tr, _ := iv.Capture("gway_req_V2UncmUgaGlyaW5nIQ==")

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 capture should return a transaction if it was successful. It is strongly advised to check its status attribute is set to completed to make sure the payment made it through. We highly recommand you to set up a way to receive webhooks ↗ as this will make you able to handle updates on payments made using alternative payment methods.