Payment Integration
  • 19 Jun 2024
  • 7 Minutes to read

Payment Integration


Article summary

Payment Integration

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

  1. Service authentication

  2. Tokenize Card

  3. Register Order

  4. Register payment

1.- Services authentication

You will need to copy the API keys, to call the OrkestaPay authentication service and obtain an access token that will be used to call the rest of the 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 measures 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 the OrkestPay 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, that is, it will help identify the devices from which payments are made. 

Both 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

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 for which the customer is being charged are detailed, practically 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": "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 last step is to record 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 that were obtained in step 1 and that are generated from the web client, in addition the Idempotency-Key header must also 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?