QuickPay Embedded

(Documentation for Embedded V1 can be found here)

The Embedded payment method gives you the option of accepting credit card payments without having to redirect to QuickPay Hosted payment window. The payment form is embedded directly into your page - giving you full control over design.

Implementation is very simple and can be included into your existing checkout form! You just need to add input fields for cardnumber, expiration and cvd and a little bit of JavaScript.

Please note! QuickPay Embedded is currently only available with the Clearhaus acquirer.

2 ways to use embedded

Card token

Create saved card and use card-token to create payment from backend. This is achieved by submitting the credit card data directly to QuickPay who then hands back a token that in turn can be used for payment (one payment only) using our API.

Create a payment-link using our API and tell the JavaScript to use it. Both payment and subscription link is allowed.

Security concerns

The credit card data is submitted directly to QuickPay via HTTPS. Thus, no credit card data is passing through your system.

Still, there are few additonal requirements than necessary when using the QuickPay Link or QuickPay Form:

Parameters

The JavaScript is initialized like so QuickPay.Embedded.Form('<selector>', {<parameters>}); and accepts the following parameters:

Parameter Type Description
R merchant_id /^\d+$/ The merchant id (Card token only)
R agreement_id /^\d+$/ The agreement id (Card token only)
R payment_link http://payment.quickpay.net/payments/... Payment-link as returned by API (Not with Card token)
brandChanged function(brand) Callback executed when brand of entered cardnumber is determined or changes
beforeCreate function(form) Callback executed before card is submitted
feeChanged function(form, data) Callback executed on fee change
data:
{
  success: {true/false},
  fee: {fee amount},
  total: {total amount}
}           
validChanged function(form, isValid, invalidFields) Callback executed on validation state change
success function(form, data) Callback executed after tokenization/authorization.
data:
Card token
Card.Token(
  id: {Saved card id},
  token: "{ Card token }",
  brand: "{ Card brand }",
  last4: "{ Last 4 digits in card number}",
  exp_month: { Card expiration month },
  exp_year: { Card expiration year},
  country: { Card country },
  is_used: false,
  created_at: { ISO8601 timestamp }
)           
payment-link
{
  qp_status_code: "authorize status code",
  qp_status_msg: "authorize status message"
}         
See our API for possible qp_status_code values
Return false to prevent form submit
failure function(form, source, message, data) Callback executed if validation error, card tokenizing failed or payment-link authorize failed

source:
authorize Card authorization was rejected.
fee Fee request failed.
validation Form validation failure
message:
authorize Card rejection message
fee Error message
validation invalid
data:
authorize
{ status: "status code", raw: "raw response" }
(Only payment-link)
validation Array of invalid fields

Submitted data

In addition to your own form input data the following is submitted:

Card token

Parameter Type Description
card_token UUID The card token
Parameter Type Description
quickpay_status \d{5} QuickPay Status code (qp_status_code)

Upgrading from Embedded v1

Following has changed between v1 and v2

QuickPay.Embedded.Form

  • beforeCreateToken renamed to beforeCreate
  • failure validation has the list of invalid fields is the 4th parameter data instead of 3rd message

Examples

Frontend - Checkout form

To prevent card data being submitted by mistake the card input fields should not have name arguments but instead be identified by data-quickpay="cardnumber", data-quickpay="expiration" and data-quickpay="cvd"

When form is submitted card data will first be submitted directly to QuickPay for tokenization. If the tokenization is successful it will submit the form to your defined action with a card_token parameter which can be used to authorize the payment on your backend.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<form id="checkout-form" class="form-horizontal" action="/shop/pay" method="post">
  <label for="name">Name</label>
  <input type="text" name="name" value="">

  <label for="address">Address</label>
  <input type="text" name="address" value="">

  <label for="zipcode">Zipcode</label>
  <input type="text" name="zipcode" value="">

  <labe for="city">City</label>
  <input type="text" name="city" value="">

  <label>Card number</label>
  <div class="card">
    <div class="card-brand"></div>
    <input type="text" autocomplete="off" data-quickpay="cardnumber">
  </div>

  <label>Expiration</label>
  <input type="text" placeholder="MM / YY" autocomplete="off" data-quickpay="expiration">

  <label>CVV/CVD</label>
  <input type="text" maxlength="4" autocomplete="off" data-quickpay="cvd">

  <button class="btn btn-primary" type="submit">Pay</button>
</form>

<style type="text/css">
  .card {
    position: relative;
  }

  .card-brand {
    position: absolute;
    right: 5px;
    top: 5px;
    font-weight: bold;
  }

  input.error {
    border: 1px solid red;
  }
</style>

<script src="https://payment.quickpay.net/embedded/v2/quickpay.js"></script>
<script type="text/javascript">
  QuickPay.Embedded.Form(document.querySelector('#checkout-form'), {
    merchant_id: 1234,
    agreement_id: 1234,
    brandChanged: function(brand) {
      document.querySelector(".card-brand").innerHTML = brand;
    },
    beforeCreate: function(form) {
      var button = document.querySelector("#checkout-form button");
      button.setAttribute("disabled", "disabled");
      button.innerHTML = "Please wait...";

      var inputs = document.querySelectorAll("input.error");
      for (var i = 0; i < inputs.length; i++) {
        inputs[i].classList.remove("error");
      }
    },
    success: function(form, data) {
      return true; // Return false to prevent form submit
    },
    failure: function(form, type, message, data) {
      switch (type) {
        case "validation":
          for (var i = 0; i < data.length; i++) {
            document.querySelector('input[data-quickpay=' + data[i] + ']').classList.add('error');
          }
          break;
        default:
          alert(type + ': ' + message);
      }

      document.querySelector("#checkout-form button").innerHTML = "Pay";
    }
  });
</script>

Backend

These are simplified examples that will almost certainly not fit your shop system. However, it should give an idea on how to use the Embedded payment form.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
require 'sinatra/base'
require 'quickpay/api/client' # Find the QuickPay Ruby client at https://learn.quickpay.net/tech-talk/opensource

class App < Sinatra::Base
  enable :sessions

  post '/shop/pay' do
    order = Order.new(
      name:    params[:name],
      address: params[:address],
      zipcode: params[:zipcode],
      city:    params[:city]
    )

    order.save!

    # See https://learn.quickpay.net/tech-talk/api/services
    client = QuickPay::API::Client.new(api_key: ENV['QUICKPAY_API_KEY'])
    token  = params[:card_token]

    payment = client.post('/payments', {
      order_id: order.order_id, # MUST be unique
      currency: 'EUR'
    })

    order.payment_id = payment['id']
    order.save!

    payment = client.post("/payments/#{order.payment_id}/authorize?synchronized", {
      amount: 12345, # 123.45
      card: { token: token }
    })

    if payment['approved']
      order.authorized = true
    end

    order.qp_status_code = payment['operations'].last['qp_status_code']
    order.qp_status_msg  = payment['operations'].last['qp_status_msg']
    order.aq_status_code = payment['operations'].last['aq_status_code']
    order.aq_status_msg  = payment['operations'].last['aq_status_msg']
    order.save!

    if order.authorized?
      session[:order_id] = order.id
      redirect to('/shop/receipt')
    else
      erb :error
    end
  end

  get '/shop/receipt' do
    @order = Order.find(session[:order_id])
    erb :receipt
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php

/**
 * This is only an very simple mocked example.
 */

require_once 'QuickPay'; // Find the QuickPay PHP client at https://learn.quickpay.net/tech-talk/opensource

use QuickPay/QuickPay as Client;
use Models/Order;

class PaymentController
{
    public function pay(Request $request)
    {
        $order = new Order([
            'name'    => $request('name'),
            'address' => $request('address'),
            'zipcode' => $request('zipcode'),
            'city'    => $request('city')
        ]);

        $order->save();

        $client = new Client(":{$this->config('API_KEY')}");

        $token = $request('card_token');

        $payment = $client->request->post("/payments", [
            'order_id' => $order->order_id, // Must be unique
            'currency' => 'EUR'
        ])->as_object();

        $order->payment_id = $payment->id;
        $order->save();

        $payment = $client->request->post("/payments/{$order->payment_id}/authorize?synchronized", [
            'amount' => 12345,
            'card'   => [ 'token' => $token ]
        ]);

        if ($payment->accepted) {
            $order->authorized = true;
        }

        $authorize = end($payment->operations);

        $order->qp_status_code = $authorize->qp_status_code;
        $order->qp_status_msg  = $authorize->qp_status_msg;
        $order->aq_status_code = $authorize->aq_status_code;
        $order->aq_status_msg  = $authorize->aq_status_msg;
        $order->save();

        if ($order->authorized()) {
            $this->session('order_id', $order->id);
            $this->redirectTo('/payment/receipt');
        } else {
            $this->render('error');
        }
    }

    public function receipt()
    {
        $order = Order::find($this->session('order_id'));
        $this->render('receipt', compact('order'));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from flask import Flask, session, render_template, redirect, url_for, request
from flask.ext.session import Session

# manage session using Flask-Session
sessionProvider = Session()

# Initialize flask app
app = Flask("Quickpay")
sessionProvider.init_app(app)


@app.route('/shop/pay')
def pay():
    params = request.form
    order = Order(name=params['name'], address=params['address'],
                  zipcode=params['zipcode'], city=params['city'])
    order.save

    # See https://learn.quickpay.net/tech-talk/api/services
    client = QPClient(":%s" % os.environ['QUICKPAY_API_KEY'])
    token = params['card_token']

    payment = client.post('/payments',
                          order_id=order.order_id,  # MUST be unique
                          currency='EUR')

    order.payment_id = payment['id']
    order.save

    payment = client.post(
        "/payments/%s/authorize?synchronized" % order.payment_id,
        amount=12345,  # 123.45
        card={"token": token}
    )

    if payment['approved']:
        order.authorized = true

    order.qp_status_code = payment['operations'][-1]['qp_status_code']
    order.qp_status_msg = payment['operations'][-1]['qp_status_msg']
    order.aq_status_code = payment['operations'][-1]['aq_status_code']
    order.aq_status_msg = payment['operations'][-1]['aq_status_msg']
    order.save

    if order.authorized:
        session['order_id'] = order.id
        return redirect(url_for('/shop/receipt'))
    else:
        return render_template("error.html", order=order)


@app.route('/shop/receipt')
def show_receipt():
    order = Order.query.filter_by(id=session.get('order_id'))
    return render_template('receipt.html', order=order)