Payment Integration
  • 18 Jul 2024
  • 14 Minutes to read

Payment Integration


Article summary

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:

  1. Service authentication

  2. Tokenize Card

  3. Register Order

  4. 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.

OrkestaPay (9)(1)(2)

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. 

  1. Initialize the Orkestapay instance with the credentials.

  2. Call the getDeviceInfo() function that resolves a promise with the device_session_id.

  3. 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.


Was this article helpful?