Accepting Payments, SCA-ready with 3DS2

Accepting payments online is becoming increasingly complex. Merchants need to optimize the collection of their customers’ card information, they need to do it securely (read: PCI DSS compliance), while also following new regulations happening in their markets.

ProcessOut abstracts this complexity with its suite of tools and SDKs. ProcessOut.js and ProcessOut’s iOS and Android SDKs provide easy to use client-side libraries allowing you to securely accept card numbers on your website and application while keeping your PCI DSS compliance requirements minimal.

ProcessOut.js and ProcessOut mobile SDKs are SCA and 3DS2-ready, meaning that the new requirements mandated by new regulations in Europe will be handled automatically for you. Follow our migration guide ↗ if you already have a ProcessOut integration and wish to support new SCA flows.
ProcessOut.js and ProcessOut mobile SDKs can be fully customized to match the look of your website, application and checkout. Preview what the web checkout process looks like when using ProcessOut.js and being fully PCI compliant, while staying on top of new regulations like DSP2’s SCA (Strong Customer Authentication, mostly enforced through 3DS2 or 3-D Secure version 2) in Europe.



<div class="container">
  <form action="" method="POST" id="card-form">
    <div class="block-group">
            <input type="text" placeholder="John Smith" class="b75 block" id="cardholdername">
      <input type="text" placeholder="10018" class="b25 block" id="cardholderzip">
        </div>

    <div class="block-group">
      <div class="block" data-processout-input="cc-number" 
      data-processout-placeholder="4242 4242 4242 4242"></div>
      <div class="block" data-processout-input="cc-exp" 
        data-processout-placeholder="MM / YY"></div>
      <div class="block" data-processout-input="cc-cvc" 
        data-processout-placeholder="CVC"></div>
    </div>

    <div>
      <input type="submit" id="paymentBtn">
    </div>
    <div id="errors"></div>
    <div id="success"></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: 25%;
  margin-left: 1%;
}

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

[data-processout-input], 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;
}
::-webkit-input-placeholder { /* WebKit, Blink, Edge */
  color:    #ECEFF1;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
  color:    #ECEFF1;
  opacity:  1;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
  color:    #ECEFF1;
  opacity:  1;
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
  color:    #ECEFF1;
}
[data-processout-input]:nth-child(1) {
  width: 48%;
  display: inline-block;
}
[data-processout-input]:nth-child(2) {
  width: 25%;
  display: inline-block;
  margin-left: 1%;
}
[data-processout-input]:nth-child(3) {
  width: 25%;
  display: inline-block;
  margin-left: 1%;
}

input[type="submit"] {
  margin: 0;
  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;
}

#errors, #success {
  margin-top: 1em;
  text-align: center;
  font-size: 0.9em;
  color: #D84315;
}
#success {
  color: #4CAF50;
}
document.addEventListener("DOMContentLoaded", function() {
  var processout = new ProcessOut.ProcessOut("test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
  var formElement = document.getElementById("card-form");
  processout.setupForm(formElement, {
    style: {
      fontSize: "14px",
      "::placeholder": {
        color: "#ECEFF1",
      },
    }
  }, function(form) {
    form.getNumberField().on("focus", function(e) {
      document.getElementById("errors").innerHTML = "";
    });
    form.getExpiryField().on("focus", function(e) {
      document.getElementById("errors").innerHTML = "";
    });

    form.addEventListener("submit", function(e) {
      e.preventDefault();
      document.getElementById("paymentBtn").disabled = true;

      // Let's tokenize the card
      processout.tokenize(form, {
        name: document.getElementById("cardholdername").value,
        contact: {
          zip:  document.getElementById("cardholderzip").value
        },

        // Also, if you want to offer the customer a preferred scheme
        // to pay on (for example, if the customer's card supports 
        // co-schemes such as carte bancaire)
        preferred_scheme: "carte bancaire"
      }, function(token) {
        document.getElementById("success").innerHTML = "Success! Your created card token is "+token;
        document.getElementById("paymentBtn").disabled = false;
      }, function(err) {
        document.getElementById("errors").innerHTML = err.message;
        document.getElementById("paymentBtn").disabled = false;
      });

      return false;
    });
  }, function(err) {
    console.log(err);
  });
});


Step 1: Create an invoice

Prior to getting paid, you first need to create an Invoice resource meant to define how much your customer will get charged, and set metadata related to the payment. This Invoice will also be sent back to your frontend to properly authenticate your customer, if required by local regulations.

curl -X POST https://api.processout.com/invoices \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d name="Awesome invoice" \
    -d amount="4.99" \
    -d currency=USD
var ProcessOut = require("processout");
var client = new ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");

client.newInvoice().create({
    name:     "Awesome invoice",
    amount:   "4.99",
    currency: "USD",
    device:   client.newInvoiceDevice({
        channel: "web" // optional; can be web, ios or android
    })
}).then(function(invoice) {
    // invoice is our newly created resource

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

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

invoice = client.new_invoice().create({
    "name":     "Awesome invoice",
    "amount":   "4.99",
    "currency": "USD",
    "device":   client.new_invoice_device({
        "device": "web" # optional; can be web, ios or android
    })
})
require "processout"
client = ProcessOut::Client.new(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

invoice = client.invoice.create(
    name:     "Awesome invoice",
    amount:   "4.99",
    currency: "USD",
    device:   client.invoice_device.new(
        channel: "web" # optional; can be web, ios or android
    )
)
<?php
$client = new \ProcessOut\ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");

$invoice = $client->newInvoice()->create(array(
    "name"     => "Awesome invoice",
    "amount"   => "4.99",
    "currency" => "USD",
    "device"   => $client->newInvoiceDevice(array(
        "channel" => "web" // optional; can be web, ios or android
    ))
));
import "github.com/processout/processout-go"

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

iv, err := client.NewInvoice().Create(processout.InvoiceCreateParameters{
    Invoice: &processout.Invoice{
        Name:     processout.String("Awesome invoice"),
        Amount:   processout.String("4.99"),
        Currency: processout.String("USD"),
        InvoiceDevice: client.NewInvoiceDevice(processout.InvoiceDevice{
            // optional; can be web, ios or android
            Channel: processout.String("web"),
        }),
    },
})

Note that, although it is optional, you can send the additional InvoiceDevice object within the Invoice to determine which channel will be used to process the transaction. This is especially useful to make use of native 3DS2 SDKs.

Optionally, you may also choose to tokenize the card with a Customer Token to be able to re-use the Card as you please in the future, either for one-click payments, subscriptions, or one-off charges.

Step 2: Gather the card or token IDs for payment

In order to proceed with the payment, you’ll need to have an actual payment source. For card payments, this source can be either a Card ID, or a Customer Token ID if you tokenized the card details of your customer for future usage/one-click payments.

Step 3: Make payment on the Invoice

In the example below, we’ll simply get this card ID and use it to place our payment. If you choose to create a Customer Token to be able to re-use the card details for future payments, you can also use that token directly instead of the card token in the makeCardPayment call.

For mobile, 3DS2 flow is native. For full documentation on how to handle the `handler` function parameter, please refer to the more in-depth mobile SDKs reference ↗
.
function processoutCardTokenized(token) {
    // make sure `invoiceID` generated from
    // your backend is available in this scope
    client.makeCardPayment(invoiceID, token, {
        authorize_only: false, // set to true if you don’t want to capture directly
        // If you want to offer the customer a preferred scheme
        // to pay on (for example, if the customer's card supports
        // co-schemes such as carte bancaire)
        preferred_scheme: "carte bancaire"
    }, function(iv) {
        var field   = document.createElement("input");
        field.type  = "hidden";
        field.name  = "invoice_id";
        field.value = iv;

        // Enable back the button
        document.getElementById("paymentBtn").disabled = false;

        // We add the invoice_id input so that it’s sent back to the server.
        // The only thing left to do is to submit the form
        formElement.appendChild(field);
        formElement.submit();
    }, function(err) {
        document.getElementById("errors").innerHTML = err.message;
    });
}
var authorizationRequest = AuthorizationRequest(
    source:"card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ",
    invoiceID: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT"
);

// optional
authorizationRequest.thirdPartySDKVersion = ThreeDS2Service.ADY3DS2SDKVersion()
authorizationRequest.preferredScheme = "carte bancaire"

ProcessOut.makeCardPayment(
    authorizationRequest,
    handler,
    with: self
);
AuthorizationRequest authorizationRequest = new AuthorizationRequest(
    "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", // source
    "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT" // invoiceId
);

// optional
authorizationRequest.setThirdPartySDKVersion(ThreeDS2Service.getSDKVersion())
authorizationRequest.setPreferredScheme("carte bancaire")

client.makeCardPayment(
    authorizationRequest,
    handler
);

Step 4: Confirm the payment in your backend (synchronous)

The final step in processing the transaction is confirming the payment from your backend. To do so, you should make an authorization or capture call on the Invoice object. Even if the capture was performed from the frontend earlier, this call will let your code verify the status of the transaction.

curl -X POST https://api.processout.com/invoices/iv_lEZFFcQg5WwpKcolBrlzioeeFnnuPmyE/capture \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d source=card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ
var ProcessOut = require("processout");
var client = new ProcessOut(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");

invoice.capture("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ").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("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ")
require "processout"
client = ProcessOut::Client.new(
    "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x", 
    "key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")

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

$transaction = $invoice->capture("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ");
import "github.com/processout/processout-go"

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

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

If the capture was successful, a Transaction object is returned. You may then check that its status attribute is set to completed in order to confirm the payment. Learn more about the possible statuses for a transaction here ↗.

Step 5: Listen to webhooks for payment updates

Although ProcessOut.js supports a synchronous flow, allowing you to give your users a better experience, we strongly recommend that you also set up an endpoint to receive the webhooks we send with payment updates.

This can be particularly important when the user drops from your checkout flow after the payment, which could lead to a transaction not being logged in your systems. Webhooks will ensure that your backend is always up-to-date with the correct status for your transactions.