Direct payment

Direct payment integration

To perform a direct payment integration (with credit and/or debit card), that is, without being redirected to the OrkestaPay checkout, it is necessary to follow these steps:

  1. Service authentication
  2. Tokenize card
  3. Register order
  4. Register payment

1.- Service authentication

You must copy the API access credentials to call the OrkestaPay authentication service and obtain an access token that will be used to call the rest of the services.


Request to the service

After copying the credentials, you must find the texts REPLACE_WITH_YOUR_CLIENT_ID and REPLACE_WITH_YOUR_CLIENT_SECRET in the script below and replace them with the copied values in order to execute the service call via shell:

  • client_id: Access key
  • client_secret: Secret key

Permission type

The grant_type property must always carry the value client_credentials, since it is the operating model of the oAuth 2.0 protocol that OrkestaPay uses 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"
}
'
📘

Documentation

Visit our API documentation: https://docs-en.orkestapay.com/reference/get-access-token


Service response

As a result of the call to the authentication service, a JWT token will be returned, which will be used in all subsequent calls to the 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"
}
📘

Documentation

Visit our API documentation: https://docs-en.orkestapay.com/reference/get-access-token


2.- Tokenize card

For security reasons, it is necessary to tokenize the card from the web client, to prevent this information from traveling to the merchant's server. For this, it is necessary to use OrkestaPay's JavaScript library (https://checkout.orkestapay.com/script/orkestapay.js).

It is also necessary to generate a device_session_id, which will serve as a fraud prevention mechanism, meaning it will help identify the devices from which payments are made.

Both pieces of data must be sent to the server to continue with the payment process.

To carry out the implementation, the credentials obtained from the OrkestaPay dashboard are required:

  • Merchant ID
  • Public key

Required resources:

<script
  src="https://checkout.orkestapay.com/script/orkestapay.js"
  integrity="sha384-lQoLMYDY2Cz/Y+8BLiNbhfLVHWawqsA/gE/8WRTpF6ZS3EABNKXhrq/0bBu4M1aI"
  crossorigin="anonymous"
></script>

Example

Below 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/[email protected]/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
      src="https://checkout.orkestapay.com/script/orkestapay.js"
      integrity="sha384-lQoLMYDY2Cz/Y+8BLiNbhfLVHWawqsA/gE/8WRTpF6ZS3EABNKXhrq/0bBu4M1aI"
      crossorigin="anonymous"
    ></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, meaning it will help identify the devices from which payments are made.

This value is required when calling the payment registration service, meaning it will need to be sent to the server along with the card token.

While the example described above includes it in a complete form, here we will detail it further.

  1. Initialize the Orkestapay instance with the credentials.
  2. Call the getDeviceInfo() function which resolves a promise with the device_session_id.
  3. Store the returned value in an input within the form that will send the data to the server. This value can also be assigned to some state in 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));
}

3.- Register order

In this step, the items and amounts for which the customer is being charged are detailed — essentially the purchase checkout.


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": "[email protected]"
    }
}
'
📘

Documentation

Visit our API documentation: https://docs-en.orkestapay.com/reference/create-order


Service response

When registering the order, the following information will be returned. For now, only the order_id will be of interest, which we will need to complete the payment in the next and final step.

{
  "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": "[email protected]",
    "created_at": "1713480514197",
    "updated_at": "1713480514197"
  },
  "placed_at": "1713480514267",
  "metadata": {}
}
📘

Documentation

Visit our API documentation: https://docs-en.orkestapay.com/reference/create-order


4.- Register payment

The last step is to register the payment with the information obtained in the previous steps.


Request to the service

The "create payment" service must be called, and as part of the request, the card token (payment_method_id) and the device_session_id obtained in step 1 and generated from the web client must be sent. Additionally, the Idempotency-Key header must 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}}"
}
'
📘

Documentation

Visit our API documentation: https://docs-en.orkestapay.com/reference/create-payment


Service response

When registering the payment, the following information will be returned. If the payment was 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

Visit our API documentation: https://docs-en.orkestapay.com/reference/create-payment


NOTE: It is recommended that during the flow you save the IDs or data you consider relevant for your integration.