Pago directo

Integración de pago directo

Para realizar una integración de pago directo (con tarjeta de crédito y/o débito), es decir, sin ser redireccionado al checkout de OrkestaPay, es necesario seguir los siguientes pasos.

  1. Autenticación de servicios
  2. Tokenizar tarjeta
  3. Registrar orden
  4. Registrar pago

1.- Autenticación de servicios

Deberás de copiar las credenciales de acceso al API, para llamar al servicio de autenticación de OrkestaPay y obtener un token de acceso que se utilizará para llamar al resto de servicios.


Petición hacia el servicio

Después de copiar las credenciales, deberás de buscar los textos REPLACE_WITH_YOUR_CLIENT_ID y REPLACE_WITH_YOUR_CLIENT_SECRET en el script de abajo y reemplazarlos con los valores copiados para poder ejecutar la llamada al servicio mediante shell:

  • client_id: Llave de acceso
  • client_secret: Llave secreta

Tipo de permisos

La propiedad grant_type siempre se deberá de llevar el valor client_credentials, dado que es el modelo de funcionamiento del protocolo oAuth 2.0 que utiliza OrkestaPay para autenticar los servicios.

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"
}
'

Respuesta del servicio

Como resultado de la llamada al servicio de autenticación se regresará un token JWT, el cual se utilizará en todos los siguientes llamados a los servicios del API de OrkestaPay.

{
    "token_type": "Bearer",
    "expires_in": 1800,
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwenJMTnEwbzBab1R4NTlaeWVPaTI1RGxZLWl6cV91SVFSLThWS0RaWjlFIn0.eyJleHAiOjE2Njk4NTAzNTAsImlhdCI6MTY2OTg1MDE3MCwianRpIjoiMWI4MWZhMDItMzk2ZC00NGNjLWJlMzctZGU4ZWQyODg2MTEyIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYXV0aC56ZW5raS5maS9hdXRoL3JlYWxtcy9wYnciLCJzdWIiOiIxMjgyNjJhOS00NDgxLTQ4OGItYTczNi1iNmI5MTA1NjQ4MzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI1MDg3ODE3MDhjNzk5MTE5NTJkZGJlYWZkZjM5NjNmNTcxYjNjYzE4YzE5YmNkY2YiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vcG9ydGFsLWRldi56ZW5raS5maSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYXBpIiwiYXBpX3plbmtpcGF5Il19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIzNS44NS4yMy4xOTAiLCJjbGllbnRJZCI6IjUwODc4MTcwOGM3OTkxMTk1MmRkYmVhZmRmMzk2M2Y1NzFiM2NjMThjMTliY2RjZiIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC01MDg3ODE3MDhjNzk5MTE5NTJkZGJlYWZkZjM5NjNmNTcxYjNjYzE4YzE5YmNkY2YiLCJjbGllbnRBZGRyZXNzIjoiMzUuODUuMjMuMTkwIn0.Ds5eQ-tkn4ckTUHI-mrJn6eYBaUa-6uZNxzrGRfYc5neI1TvB2RHu_IDsktDVi9XdR5P_P0mSpzar9jWJOrxxA_csTnn9ZXy8rDeRqjMm9j03xWz-tZcxiUM6xvN1qvOeBGFzISIP9y24jyL0Jqpl8YhkSGF8xBfFvfhOvEMvgLby5n7dTDoZVi2Bw8G1kZJKPejmBu8MJetl08OoVk_obp6lW3YetQPYTwsutOc_yIxBIUkPSH2Gj3wpBxBa8EfMES4J1SAT7Thpw_CmZ_PNB9rEDUJI4bzE7QM2Z0n4LNXzbo5JFuWudKwfhqOcryH0slmHOamJgbtR5EGryf8LQ"
}

2.- Tokenizar tarjeta

Por medidas de seguridad es necesario tokenizar la tarjeta desde el cliente web, para evitar que esta información viaje al servidor del comercio, para ello es necesario utilizar la librería de Javascript de OrkestPay (https://checkout.orkestapay.com/script/orkestapay.js).

También es necesario generar un device_session_id, el cual servirá como mecanismo de prevención de fraudes, es decir, ayudará a identificar los dispositivos desde el cual se realizan los pagos.

Ambos datos deben ser enviados al servidor para continuar con el proceso de pago.

Para llevar a cabo la implementación, son necesarias las credenciales obtenidas desde el dashboard de OrkestaPay:

  • ID del comercio
  • Llave pública

A continuación un ejemplo básico de la implementación:

<!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
      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.- Generar Device Session ID

Como se menciona en el punto anterior, es necesario generar un device_session_id, el cual servirá como mecanismo de prevención de fraudes, es decir, ayudará a identificar los dispositivos desde el cual se realizan los pagos.

Este valor es requerido al momento de mandar llamar al servicio del registro de pago, es decir, será necesario enviarlo al servidor junto con el token de tarjeta.

Si bien en el ejemplo antes descrito viene el ejemplo de forma integral, aquí lo detallaremos.

  1. Inicializar la instancia de Orkestapay con las credenciales.
  2. Llamar a la función getDeviceInfo() que resuelve una promesa con el device_session_id.
  3. Registrar el valor devuelto en un input dentro del formulario que enviará los datos al servidor. También este valor puede quedar asignado en algún estado de la aplicación, dependiendo de la tecnología de frontend que estes utilizando.
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))
}

📘

Documentación

Para más información sobre la librería de Javascript, puedes consultar nuestra documentación de Headless Checkout.


3.- Registrar orden

En este paso, se detallan los artículos y montos por cuales se está realizando el cobro al cliente, prácticamente el checkout de compra.

Petición hacia el servicio

Se deberá llamar al servicio de "crear orden" y como parte de la petición se deberá de enviar como path parameter el ID de la orden (ORDER_ID).

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]"
    }
}
'

Respuesta del servicio

Al registrar la orden, se devolverá la siguiente información. Por el momento, solo será de interés el order_id, el cual necesitaremos para completar el pago en el siguiente y último paso.

{
    "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": {}
}

📘

Documentación API

Para obtener más información sobre el servicio de órdenes, visita nuestra documentación del API de OrkestaPay: https://orkestapay.readme.io/reference/create-order


4.- Registrar pago

El último paso es registrar el pago con la información obtenida en los pasos anteriores.


Petición hacia el servicio

Se deberá llamar al servicio de "crear pago" y como parte de la petición el token de la tarjeta (payment_method_id) y el device_session_id que fueron obtenidos en el paso 1 y que son generado desde el cliente web, adicional también se deberá de enviar la cabecera Idempotency-Key con un valor único por cada nueva petición de pago.

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}}"
}
'

Respuesta del servicio

Al registrar el pago, se regresará la siguiente información. En caso de que el pago haya sido exitoso el status será 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"
}

📘

Documentación API

Para obtener más información sobre el servicio de órdenes, visita nuestra documentación del API de OrkestaPay: https://orkestapay.readme.io/reference/create-payment


Así concluye la integración de "Pago Directo" con OrkestaPay.

NOTA: Es recomendable que durante el flujo guardes los ID's o datos que consideres relevantes para tu integración.