- 18 Jul 2024
- 14 Minutes to read
Payment Integration
- Updated on 18 Jul 2024
- 14 Minutes to read
Payment Integration
To perform a direct payment integration (with a credit and/or debit card), without being redirected to OrkestaPay’s checkout, the following steps are necessary:
Service authentication
Tokenize Card
Register Order
Register payment
1.- Service Authentication
Copy the API access credentials to call the OrkestaPay authentication service and obtain an access token, which will be used to call the other services.
Service request
After copying the credentials, locate the placeholders "REPLACE_WITH_YOUR_CLIENT_ID" and "REPLACE_WITH_YOUR_CLIENT_SECRET" in the script below. Replace them with your actual values to execute the service call using shell:
- client_id: Client ID
- client_secret: Client Secret
Type of permissions
The "grant_type" property should always be set to "client_credentials", because this is the operating model of the OAuth 2.0 protocol used by OrkestaPay to authenticate services.
curl --request POST \
--url https://api.sand.orkestapay.com/v1/oauth/tokens \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '
{
"client_id": "REPLACE_WITH_YOUR_CLIENT_ID",
"client_secret": "REPLACE_WITH_YOUR_CLIENT_SECRET",
"grant_type": "client_credentials"
}
'
Service response
As a result of the authentication service call, a JWT token will be returned. This token must be used in all subsequent calls to OrkestaPay API services.
{
"token_type": "Bearer",
"expires_in": 1800,
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwenJMTnEwbzBab1R4NTlaeWVPaTI1RGxZLWl6cV91SVFSLThWS0RaWjlFIn0.eyJleHAiOjE2Njk4NTAzNTAsImlhdCI6MTY2OTg1MDE3MCwianRpIjoiMWI4MWZhMDItMzk2ZC00NGNjLWJlMzctZGU4ZWQyODg2MTEyIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYXV0aC56ZW5raS5maS9hdXRoL3JlYWxtcy9wYnciLCJzdWIiOiIxMjgyNjJhOS00NDgxLTQ4OGItYTczNi1iNmI5MTA1NjQ4MzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI1MDg3ODE3MDhjNzk5MTE5NTJkZGJlYWZkZjM5NjNmNTcxYjNjYzE4YzE5YmNkY2YiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vcG9ydGFsLWRldi56ZW5raS5maSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYXBpIiwiYXBpX3plbmtpcGF5Il19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIzNS44NS4yMy4xOTAiLCJjbGllbnRJZCI6IjUwODc4MTcwOGM3OTkxMTk1MmRkYmVhZmRmMzk2M2Y1NzFiM2NjMThjMTliY2RjZiIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC01MDg3ODE3MDhjNzk5MTE5NTJkZGJlYWZkZjM5NjNmNTcxYjNjYzE4YzE5YmNkY2YiLCJjbGllbnRBZGRyZXNzIjoiMzUuODUuMjMuMTkwIn0.Ds5eQ-tkn4ckTUHI-mrJn6eYBaUa-6uZNxzrGRfYc5neI1TvB2RHu_IDsktDVi9XdR5P_P0mSpzar9jWJOrxxA_csTnn9ZXy8rDeRqjMm9j03xWz-tZcxiUM6xvN1qvOeBGFzISIP9y24jyL0Jqpl8YhkSGF8xBfFvfhOvEMvgLby5n7dTDoZVi2Bw8G1kZJKPejmBu8MJetl08OoVk_obp6lW3YetQPYTwsutOc_yIxBIUkPSH2Gj3wpBxBa8EfMES4J1SAT7Thpw_CmZ_PNB9rEDUJI4bzE7QM2Z0n4LNXzbo5JFuWudKwfhqOcryH0slmHOamJgbtR5EGryf8LQ"
}
2.- Tokenize card
For security reasons, the card must be tokenized from the web client to prevent this information from traveling to the merchant’s server. This requires using OrkestaPay’s JavaScript library (https://checkout.orkestapay.com/script/orkestapay.js).
A device_session_id must also be generated to help prevent fraud. Both pieces of data need to be sent to the server to continue with the payment process.
Here is a basic example of the implementation:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Example 1</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
</head>
<body>
<main class="container">
<form id="payment-form">
<section class="row mt-5">
<h1 id="card-title" class="col-12 text-center"></h1>
<article class="pt-3 col-12 col-lg-4">
<label for="card-number" class="form-label">Card number</label>
<input
type="text"
id="card-number"
class="form-control"
autocomplete="cc-number"
inputmode="numeric"
name="cc-number"
/>
<small id="card-number-error" class="text-danger"></small>
</article>
<article class="pt-3 col-6 col-lg-2">
<label for="card-expiration" class="form-label">Expiration</label>
<input
type="text"
id="card-expiration"
class="form-control"
placeholder="MM/YY"
autocomplete="cc-exp"
name="cc-exp"
/>
<small id="card-expiration-error" class="text-danger"></small>
</article>
<article class="pt-3 col-6 col-lg-2">
<label for="card-csc" class="form-label">CSC</label>
<input
type="text"
id="card-csc"
class="form-control"
autocomplete="cc-csc"
inputmode="numeric"
name="cc-csc"
/>
<small id="card-csc-error" class="text-danger"></small>
</article>
<article class="pt-3 col-12 col-lg-4">
<label for="card-promotions" class="form-label">Promotions</label>
<select
name="card-promotions"
id="card-promotions"
class="form-select"
></select>
</article>
<article class="pt-3 col-12 col-lg-4">
<label for="card-holder-name" class="form-label">Holder name</label>
<input
type="text"
id="card-holder-name"
class="form-control"
autocomplete="cc-given-name"
name="cc-given-name"
/>
<small id="card-holder-name-error" class="text-danger"></small>
</article>
<article class="pt-3 col-12 col-lg-4">
<label for="card-holder-last-name" class="form-label">
Holder last name
</label>
<input
type="text"
id="card-holder-last-name"
class="form-control"
autocomplete="cc-additional-name"
name="cc-additional-name"
/>
<small id="card-holder-last-name-error" class="text-danger"></small>
</article>
<article
class="pt-3 col-12 col-lg-4 d-flex justify-content-end align-items-end"
>
<button
type="button"
id="btn-create-payment-method"
class="btn btn-primary"
>
Create payment method
</button>
</article>
<article class="pt-3 col-12">
<div
id="payment-method-details"
class="w-100 alert"
role="alert"
></div>
</article>
</section>
</form>
</main>
<script
type="text/javascript"
src="https://checkout.orkestapay.com/script/orkestapay.js"
></script>
<script type="text/javascript">
const currency = 'MXN';
const total_amount = '100.00';
const card_number_id = 'card-number';
const expiration_date_id = 'card-expiration';
const verification_code_id = 'card-csc';
const holder_name_id = 'card-holder-name';
const holder_last_name_id = 'card-holder-last-name';
(async function main() {
initTitle();
const orkestapay = createOrkestaPay();
const orkestapay_card = await createOrkestaPayCard(orkestapay);
getDeviceSessionId(orkestapay);
handleButtonClickEvent(orkestapay_card);
handlePromotionChanges(orkestapay_card);
handleErrorEventChanges(orkestapay_card);
})();
function initTitle() {
const card_title_id = 'card-title';
const card_title = document.getElementById(card_title_id);
card_title.textContent = `Create a payment method with ${currency} $${total_amount}`;
}
function createOrkestaPay() {
const is_sandbox = true;
const merchant_id = '{YOUR_MERCHANT_ID}';
const public_key = '{YOUR_PUBLIC_KEY_OR_DEVICE_KEY}';
return initOrkestaPay({
is_sandbox,
merchant_id,
public_key,
});
}
function createOrkestaPayCard(orkestapay) {
const card_number = document.getElementById(card_number_id);
const expiration_date = document.getElementById(expiration_date_id);
const verification_code = document.getElementById(verification_code_id);
const holder_name = document.getElementById(holder_name_id);
const holder_last_name = document.getElementById(holder_last_name_id);
const promotions_params = { currency, total_amount };
return orkestapay.createCard({
card_number,
expiration_date,
verification_code,
holder_name,
holder_last_name,
promotions_params,
});
}
function handleButtonClickEvent(orkestapay_card) {
const btn_create_payment_method_id = 'btn-create-payment-method';
const btn_create_payment_method = document.getElementById(
btn_create_payment_method_id
);
btn_create_payment_method.addEventListener('click', async () => {
await createPaymentMethod(orkestapay_card);
});
}
async function createPaymentMethod(orkestapay_card) {
const payment_method_details_id = 'payment-method-details';
const payment_method_details = document.getElementById(
payment_method_details_id
);
try {
const one_time_use = true;
const payment_method = await orkestapay_card.createToken({
one_time_use,
});
payment_method_details.classList.remove('alert-danger');
payment_method_details.classList.add('alert-success');
payment_method_details.textContent = JSON.stringify(
payment_method,
null,
2
);
} catch (error) {
payment_method_details.classList.remove('alert-success');
payment_method_details.classList.add('alert-danger');
payment_method_details.textContent = parseError2String(error);
logError(
createPaymentMethod.name,
orkestapay_card.createToken.name,
error
);
}
}
function handlePromotionChanges(orkestapay_card) {
const card_promotions_id = 'card-promotions';
const card_promotions = document.getElementById(card_promotions_id);
orkestapay_card.card_number.promotions$.subscribe((promotions) => {
card_promotions.replaceChildren();
const option = document.createElement('option');
option.value = null;
option.textContent = 'Select promotion';
card_promotions.appendChild(option);
for (const type of promotions) {
for (const promotion of type.installments) {
const option = document.createElement('option');
option.value = promotion;
option.textContent = `${promotion} ${type.type}`;
card_promotions.appendChild(option);
}
}
});
}
function getDeviceSessionId(orkestapay) {
orkestapay.getDeviceInfo().then((data) => {
const device_session_input = document.createElement("input");
device_session_input.type = "hidden";
device_session_input.value = data.device_session_id;
device_session_input.name = "device_session_id";
const container = document.getElementById("payment-form");
container.appendChild(device_session_input);
}).catch(err => console.error(err))
}
function handleErrorEventChanges(orkestapay_card) {
const card_number_error_id = `${card_number_id}-error`;
const expiration_date_error_id = `${expiration_date_id}-error`;
const verification_code_error_id = `${verification_code_id}-error`;
const holder_name_error_id = `${holder_name_id}-error`;
const holder_last_name_error_id = `${holder_last_name_id}-error`;
const card_number_error =
document.getElementById(card_number_error_id);
const expiration_date_error = document.getElementById(
expiration_date_error_id
);
const verification_code_error = document.getElementById(
verification_code_error_id
);
const holder_name_error =
document.getElementById(holder_name_error_id);
const holder_last_name_error = document.getElementById(
holder_last_name_error_id
);
orkestapay_card.card_number.errors$.subscribe((error) => {
card_number_error.textContent = parseError2String(error);
logError(handleErrorEventChanges.name, card_number_id, error);
});
orkestapay_card.expiration_date.errors$.subscribe((error) => {
expiration_date_error.textContent = parseError2String(error);
logError(handleErrorEventChanges.name, expiration_date_id, error);
});
orkestapay_card.verification_code.errors$.subscribe((error) => {
verification_code_error.textContent = parseError2String(error);
logError(handleErrorEventChanges.name, verification_code_id, error);
});
orkestapay_card.holder_name.errors$.subscribe((error) => {
holder_name_error.textContent = parseError2String(error);
logError(handleErrorEventChanges.name, holder_name_id, error);
});
orkestapay_card.holder_last_name.errors$.subscribe((error) => {
holder_last_name_error.textContent = parseError2String(error);
logError(handleErrorEventChanges.name, holder_last_name_id, error);
});
}
function parseError2String(error) {
return error?.message ?? '';
}
function logError(origin, name, error) {
error && console.error(origin, name, error.code, error);
}
</script>
</body>
</html>
2.1.- Generate Device Session ID
As mentioned in the previous point, it is necessary to generate a device_session_id, which will serve as a fraud prevention mechanism, that is, it will help identify the devices from which payments are made.
This value is required at the time of sending a call to the payment registration service, that is, it will be necessary to send it to the server along with the card token.
Although the example described above is the example in an integral way, we will detail it here.
Initialize the Orkestapay instance with the credentials.
Call the getDeviceInfo() function that resolves a promise with the device_session_id.
Record the return value in an input within the form that will send the data to the server. This value can also be assigned to some state of the application, depending on the frontend technology you are using.
const orkestapay = initOrkestaPay({ merchant_id, public_key, is_sandbox });
getDeviceSessionId(orkestapay);
function getDeviceSessionId(orkestapay) {
orkestapay.getDeviceInfo().then((data) => {
const device_session_input = document.createElement("input");
device_session_input.type = "hidden";
device_session_input.value = data.device_session_id;
device_session_input.name = "device_session_id";
const container = document.getElementById("payment-form");
container.appendChild(device_session_input);
}).catch(err => console.error(err))
}
Documentation
For more information about the Javascript library, you can consult our Headless Checkout documentation.
3.- Register order
In this step, the items and amounts being charged to the customer are detailed, practically the purchase checkout. The “create order” service should be called, and the ORDER_ID should be sent as a path parameter.
Request to the service
The "create order" service must be called and as part of the request the order ID (ORDER_ID) must be sent as a path parameter.
curl --request POST \
--url https://api.sand.orkestapay.com/v1/orders \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer REPLACE_WITH_YOUR_ACCESS_TOKEN' \
--data '
{
"merchant_order_id": "1366656595193",
"currency": "MXN",
"subtotal_amount": 1000,
"country_code": "MX",
"discounts": [
{
"amount": 10
}
],
"total_amount": 990,
"products": [
{
"id": "7197",
"name": "Pantalla TCL Smart TV Serie A3 A343 HD Android TV 40",
"quantity": 1,
"unit_price": 1000
}
],
"customer": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@yopmail.com"
}
}
'
Service response
When registering the order, the following information will be returned. At the moment, only the order_id, which we will need to complete the payment in the next and final step, will be of interest.
{
"order_id": "ord_a73c91e6f6f949a3a39c9557f353d308",
"status": "CREATED",
"expires_at": "1713566914212",
"merchant_order_id": "1366656595193",
"country": "México",
"country_code": "MX",
"currency": "MXN",
"taxes": [],
"discounts": [
{
"amount": 10
}
],
"subtotal_amount": 1000,
"total_amount": 990,
"products": [
{
"product_id": "7197",
"quantity": 1,
"unit_price": 1000,
"name": "Pantalla TCL Smart TV Serie A3 A343 HD Android TV 40"
}
],
"customer": {
"customer_id": "cus_414bae1120844159bf10f1d6c7b30d74",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@yopmail.com",
"created_at": "1713480514197",
"updated_at": "1713480514197"
},
"placed_at": "1713480514267",
"metadata": {}
}
API Documentation
To learn more about the ordering service, please visit our OrkestaPay API documentation: https://orkestapay.readme.io/reference/create-order
4.- Register payment
The final step is to register the payment with the information obtained in the previous steps. The “create payment” service should be called, including the card token (payment_method_id) and the device_session_id obtained from the web client. Additionally, the Idempotency-Key header should be sent with a unique value for each new payment request.
curl --request POST \
--url https://api.sand.orkestapay.com/v1/payments \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: adffaf7a3141474555d988fbb5e43e85' \
--header 'Authorization: Bearer REPLACE_WITH_YOUR_ACCESS_TOKEN' \
--data '
{
"payment_source": {
"type" : "CARD",
"payment_method_id": "{{REPLACE_WITH_CARD_TOKEN}}",
"settings": {
"card": {
"capture":true
}
}
},
"device_session_id": "{{REPLACE_WITH_DEVICE_SESSION_ID}}",
"order_id": "{{REPLACE_WITH_ORDER_ID}}"
}
'
Service response
When you record the payment, the following information will be returned. In case the payment has been successful the status will be COMPLETED.
{
"payment_id": "pay_99a7678b55ac437394817d823c888573",
"order_id": "ord_a73c91e6f6f949a3a39c9557f353d308",
"status": "COMPLETED",
"payment_source": {
"type": "CARD",
"settings": {
"card": {
"capture": true
}
},
"payment_method_id": "pym_3863efa7d67d49099cdfeb7a0746b8d5"
},
"amount": {
"captured": 990,
"currency": "MXN"
},
"transactions": [
{
"type": "PURCHASE",
"transaction_id": "ctx_4657f781dbbd4b3ca59a7fcf4db93ebe",
"status": "SUCCESS",
"amount": 990,
"code": "APPROVED",
"message": "Approved or completed successfully",
"description": "Shopping World",
"provider": {
"merchant_provider_id": "mpv_4b324c00f62f41b39d4e8d3f0415e39b",
"name": "Stripe",
"provider_transaction_id": "ctx_4657f781dbbd4b3ca59a7fcf4db93ebe",
"message": "Transaction approved",
"code": ""
},
"authorization_code": "pi_3P6azvGYqhDa3Ul61yfIB9ih",
"created_at": "1713369973147"
}
],
"created_at": "1713369969765",
"updated_at": "1713369969765"
}
API Documentation
To learn more about the ordering service, please visit our OrkestaPay API documentation: https://orkestapay.readme.io/reference/create-payment
This concludes the integration of "Direct Payment" with OrkestaPay.
NOTE: It is recommended that during the flow you save the ID's or data that you consider relevant for your integration.