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 on the web, or the customer is returned back to the app on mobile.




<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" \
    # 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");

// Let's create an invoice
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 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",
    # Or, if you're integrating on mobile:
    "return_url": "yourapp://processout.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",
    # Or, if you're integrating on mobile:
    return_url: "yourapp://processout.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",
    // 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",
)

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

On the web, 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 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({
  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(){
    conf.handleInvoiceAction(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

ProcessOut.js or your mobile app 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 of an alternative payment method 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 "github.com/processout/processout-go"

var client = processout.New(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB",
)

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

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 recommend 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.