Skip to main content
Version: v1

Payment form integration

The integration process consists on:

  1. Create a payment in Payment Hub
  2. Get the returned URL and redirect the user to that page
  3. Integrate a "payment finished" page with information about the payment status. It could be successful or failed.

1. Get the payment UUID

Getting the payment UUID is step 2 from the diagram.

The initial step is to obtain the UUID of the payment from the endpoint to incorporate it into the payment form.

For creating the payment, we also need the customer information required:

  • First name
  • Last name
  • Email

So to get the payment UUID, we need to first perform a POST request to "payment creation" endpoint with needed data. That endpoint is fully documented (with data types, specific responses, etc) here, and should look like this:

{
"partner": 1,
"customer": {
"first_name": "API customer name",
"last_name": "API customer name",
"email": "apiendpoint@test.com"
},
"amount": 95.20,
"type": "credit_card"
}

Where the fields refer to:

  • partner: The ID of the MyBB partner, related to the payment.
  • customer: A dict with the data of the customer. Email value is mandatory.
  • amount: The amount of the payment.
  • type: At this moment, only credit card payment type is allowed.

We are doing some validations in order to block payment creation for partners that are not really related with the marketplace:

  1. Check that we have an existing partner with given ID.
  2. Check that the user that is performing the request (the user with the token that we receive in the request's authorization header) "belongs" to that partner.

To do such a thing, we should include the same user of the marketplace, within the partner that is going to be set on the payment instance. That endpoint will return a successful response (201 Created) with the UUID of the payment that will be used later.

2. Building the payment page

HTML

You can build a bare-bones example form like this.

<form method="POST" id="payment-form">
<!-- Payment UUID -->
<input type="hidden" name="payment_uuid" value="{{ payment_uuid }}" id="id_payment_uuid">
<!-- Return URL -->
<input type="hidden" name="return_url" value="{{ return_url }}" id="id_return_url">
<!-- Environment -->
<input type="hidden" name="environment" value="sandbox" id="id_environment">
<!-- Credit card number -->
<label for="id_credit_card_number"> Credit card number </label>
<input
type="text"
name="credit_card_number"
class="cc-number"
placeholder="XXXX XXXX XXXX XXXX"
inputmode="numeric"
autocorrect="off"
spellcheck="false"
autocomplete="cc-number"
minlength="15"
required=""
id="id_credit_card_number"
/>
<!-- Expiration date -->
<label for="id_card_expiration">Expiration</label>
<input
type="text"
name="card_expiration"
class="cc-exp"
placeholder="MM / AA"
inputmode="numeric"
autocorrect="off"
spellcheck="false"
autocomplete="cc-exp"
required=""
id="id_card_expiration"
/>
<!-- CVC -->
<label for="credit_card_cvc">CVC</label>
<input
type="text"
name="credit_card_cvc"
class="cc-csc"
placeholder="XXX"
inputmode="numeric"
autocorrect="off"
spellcheck="false"
autocomplete="cc-csc"
maxlength="3"
minlength="3"
required=""
id="id_credit_card_cvc"
/>
<!-- Full name -->
<label for="id_credit_card_full_name">Full name</label>
<input
type="text"
name="credit_card_full_name"
class="cc-name"
placeholder="Introduce your full name here"
autocorrect="off"
spellcheck="false"
autocomplete="cc-name"
required=""
id="id_credit_card_full_name"
/>
<!-- 3DSV2 required fields -->
<div>
<input type="hidden" name="java_enabled" id="id_java_enabled">
<input type="hidden" name="language" id="id_language">
<input type="hidden" name="color_depth" id="id_color_depth">
<input type="hidden" name="screen_height" id="id_screen_height">
<input type="hidden" name="screen_width" id="id_screen_width">
<input type="hidden" name="time_zone_offset" id="id_time_zone_offset">
<input type="hidden" name="user_agent" id="id_user_agent">
<input type="hidden" name="javascript_enabled" id="id_javascript_enabled">
</div>
<button type="submit">Pay</button>
</form>

There are three required control fields for the payment form to work:

  • payment_uuid: The payment UUID got from the endpoint in step 3 of the diagram.
  • return_url: The return URL after going to the 3DS page. Ideally, this would be a "payment status" page where the user could see the payment process currently being processed in the PSP/MyBeezBox servers. This is an example of URL return page: https://<my_domain.com>/?payment_uuid=00000000-0000-0000-00000000000000000
  • environment: The value of this field should be either sandbox or production. You can change the sandbox environment to production when everything works as expected during the test.

It's possible to use a library like Cleave to improve the user experience introducing the numbers.

Example:

let cc_exp = document.getElementsByClassName('cc-exp')
if (cc_exp.length > 0) {
var cleave = new Cleave('.cc-exp', {
date: true,
datePattern: ['m', 'y']
})
}
let cc_number = document.getElementsByClassName('cc-number')
if (cc_number.length > 0) {
var cleave = new Cleave('.cc-number', {
creditCard: true
})
}

Here is a simple example of a payment form:

Scripts

You need some javascript for the payment page to work.

<script src="https://staging.bonkdo.com/static/payment_hub/dist/payments_api.js"></script>
<script>
document.getElementById('id_java_enabled').value= navigator.javaEnabled();
document.getElementById('id_language').value= navigator.language || navigator.userLanguage;
document.getElementById('id_color_depth').value= screen.colorDepth;
document.getElementById('id_screen_height').value= screen.height;
document.getElementById('id_screen_width').value= screen.width;
document.getElementById('id_time_zone_offset').value= new Date().getTimezoneOffset().toString();
document.getElementById('id_user_agent').value= navigator.userAgent;
document.getElementById('id_javascript_enabled').value= true;
</script>

This script will intercept the submit event of the form, performing all the required steps using XHR requests.

The script expects an overlay to be shown when the data is submited. Just before the close tag of the body, this is required:

<div id="spinner-back" class=""></div>
<div id="spinner-front" class="">
<div class="lds-dual-ring"></div>
<p class="f1">Processing the payment...</p>
<p class="f2">Please don't close this tab or click the back button.</p>
</div>

Customize the overlay to your requirements. Here you can find and example of the css for the overlay:

/* Spinner */
#spinner-front,
#spinner-back {
position: fixed;
top: 0;
left: 0;
visibility: hidden;
opacity: 0;
width: 100%;
transition: all 1s;
}
#spinner-front {
z-index: 999;
margin-top: 35vh;
color: #fff;
text-align: center;
}
#spinner-back {
z-index: 998;
height: 100vh;
background: #000;
}
#spinner-front.show {
visibility: visible;
opacity: 1;
}
#spinner-back.show {
visibility: visible;
opacity: 0.7;
}

/* Pure CSS loaders */
/* See: https://loading.io/css/ */

/* dual ring */
.lds-dual-ring {
display: inline-block;
width: 80px;
height: 80px;
}
.lds-dual-ring:after {
content: ' ';
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

/* rings */
.lds-ring {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 64px;
height: 64px;
margin: 8px;
border: 8px solid #fff;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

/* spinner */
.lds-spinner {
color: official;
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-spinner div {
transform-origin: 40px 40px;
animation: lds-spinner 1.2s linear infinite;
}
.lds-spinner div:after {
content: ' ';
display: block;
position: absolute;
top: 3px;
left: 37px;
width: 6px;
height: 18px;
border-radius: 20%;
background: #fff;
}
.lds-spinner div:nth-child(1) {
transform: rotate(0deg);
animation-delay: -1.1s;
}
.lds-spinner div:nth-child(2) {
transform: rotate(30deg);
animation-delay: -1s;
}
.lds-spinner div:nth-child(3) {
transform: rotate(60deg);
animation-delay: -0.9s;
}
.lds-spinner div:nth-child(4) {
transform: rotate(90deg);
animation-delay: -0.8s;
}
.lds-spinner div:nth-child(5) {
transform: rotate(120deg);
animation-delay: -0.7s;
}
.lds-spinner div:nth-child(6) {
transform: rotate(150deg);
animation-delay: -0.6s;
}
.lds-spinner div:nth-child(7) {
transform: rotate(180deg);
animation-delay: -0.5s;
}
.lds-spinner div:nth-child(8) {
transform: rotate(210deg);
animation-delay: -0.4s;
}
.lds-spinner div:nth-child(9) {
transform: rotate(240deg);
animation-delay: -0.3s;
}
.lds-spinner div:nth-child(10) {
transform: rotate(270deg);
animation-delay: -0.2s;
}
.lds-spinner div:nth-child(11) {
transform: rotate(300deg);
animation-delay: -0.1s;
}
.lds-spinner div:nth-child(12) {
transform: rotate(330deg);
animation-delay: 0s;
}
@keyframes lds-spinner {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

3. Payment status page

After making a payment, the scripts take care of checking the status of the payment periodically.

We get redirected to the return_url as soon as the payment is ordered, but the result will return asynchronously and will be communicated using a webhook.

As soon as the user pays, the system redirects us to the return_url, but the payment is not yet confirmed at this stage. MyBeezBox communicates the result asynchronously and sends it using a webhook.

4. Webhooks

When the payment finishes, we send a notification to a webhook.

We need two endpoints for each notification:

  • Endpoint for payment succeeded
  • Endpoint for payment failed

We call each endpoint for each event with the payment_uuid as a parameter. So, for example, if the endpoint for the payment succeeded is https://mydomain.com/webhooks/payment_ok/, then we will call that endpoint with a GET request to https://mydomain.com/webhooks/payment_ok/?payment_uuid=<the_payment_uuid>.

The app expects a 200 response. However, if the endpoint times out or returns a different HTTP response, then MyBeezBox will try to call the endpoint again after a certain time.