Integración paso a paso

Paso 1: Configuración del componente


Primero, activa desde nuestro admin e integra el widget en tu aplicación. Esto implica configurar un iframe con una URL específica que proporcionamos, que debe incluir parámetros esenciales como la clave API pública y datos del usuario.

Configuración en el ADMIN de DEUNA

Inicializar un Iframe en tu aplicación con la URL de DEUNA

URL para integración

https://elements.sandbox.deuna.io/vault?publicApiKey=${apiKey}&firstName=${encodeURIComponent(firstname)}&lastName=${encodeURIComponent(lastname)}&email=${encodeURIComponent(email)}
https://elements.deuna.com/vault?publicApiKey=${apiKey}&firstName=${encodeURIComponent(firstname)}&lastName=${encodeURIComponent(lastname)}&email=${encodeURIComponent(email)}

Es esencial entender que como desarrollador, tu responsabilidad es integrar este componente dentro de un iframe en tu sitio web. Esta integración es vital para garantizar una experiencia de usuario segura y fluida.

Atributos permitidos en Query Params

Cuando inicialices el iframe con la URL de DEUNA, incluirás varios parámetros de consulta. Cada uno de estos parámetros juega un papel importante en la personalización y el funcionamiento correcto del componente

AtributoDescripción
publicApiKeyLa llave pública de tu cuenta con DEUNA.
firstNameEl nombre del usuario que está siendo registrado o autenticado.
lastNameEl apellido del usuario.
emailLa dirección de correo electrónico del usuario.
cssFile (opcional)UUID proporcionado por DEUNA. Esto aplica si quieres configurar un archivo css personalizado.
orderToken(optional)UUID obtenido al crear una orden con DEUNA. Este query param es requerido para mostrar installments en el vault.
allowSaveUserInfo(optional)(Boolean) Si se quiere guardar la tarjeta del usuario para uso futuro
showSaveCard(optional)(Boolean) Si se le quiere mostrar a un usuario nuevo el toggle al usuario para seleccionar si quiere guardar la tarjeta o no para uso futuro. Los usuarios guest no tiene la opción de guardar tarjeta ya que esta es de un solo uso.
userToken (optional)El bearer token del usuario de DEUNA. Cuando este es enviado, todas las acciones dentro del widget van a hacer sobre este usuario de DEUNA. Adicionalmente, cuando se envía este userToken, no es necesario mandar ni el email, firstName, lastName. Solo será requerido el userTokeny el publicApiKey.

👍

Importante: Asegúrate de que los Query Params estén codificados adecuadamente en caso de que contengan caracteres especiales. Esto es fundamental para evitar posibles problemas en las solicitudes.

A continuación, te mostramos un ejemplo de cómo integrar esta URL en tu sitio web usando HTML, CSS y JavaScript

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vault Widget Integration</title>
    <style>
      /* Estilos básicos para el iframe */
      #vault-widget-iframe {
          width: 100%;
          height: 500px; /* Ajusta la altura según tus necesidades */
          border: none;
      }
    </style>
  </head>
  <body>
    <iframe
      id="vault-widget-iframe"
      src="https://elements.sandbox.deuna.io/vault?publicApiKey=${apiKey}&firstName=${encodeURIComponent(firstname)}&lastName=${encodeURIComponent(lastname)}&email=${encodeURIComponent(email)}"
      allow="payment"
    >
    </iframe>

    <script>
      // Escucha los mensajes de postMessage para recibir el card_id (a.k.a card token)
      window.addEventListener('message', function(event) {
          if (event.origin !== "https://elements.sandbox.deuna.io") {
              return;
          }
          let firstParsedData;
          if (typeof event.data === 'string') {
              firstParsedData = JSON.parse(event.data);
          } else {
              firstParsedData = event.data;
          }

          const parsedData = JSON.parse(firstParsedData);
          console.log('postMessage:', parsedData);

          if (
            parsedData &&
            parsedData.type === "vaultSaveSuccess"
        ) {
          // obtener el card_id (a.k.a card token)
          const cardId = parsedData.data.metadata.createdCard.id;

          // crear pago con el card_id
        }
      });
    </script>
  </body>
</html>

El componente de DEUNA se encargará de la gestión de usuarios y proceso de guardado de tarjeta. Una vez que este proceso sea exitoso, DEUNA enviará un evento mediante postMessage con el card_id válido. Este card_ides un elemento clave que debes enviar al realizar la llamada al endpoint de purchase desde tu backend para completar el pago.

Usuario autenticado

Para habilitar el flujo de usuario autenticado y mostrar las tarjetas guardadas por el usuario, existen dos métodos:

  1. Mediante un userToken
    En caso se haya iniciado el Vault Widget con un userToken se usará ese userToken para todas las operaciones subsecuentes del widget como lo sería mostrarle sus tarjetas guardas (en caso tenga alguna), etc.
  2. Mediante OTP
    Para mostrar las tarjetas de un usuario sin usar un userToken es necesario enviar en los query params el email del cliente y ademas tener habilitado en las configuraciones del vault el flujo de usuario autenticado.
    Para habilitar el flujo de usuario autenticado y desplegar las tarjetas guardadas por el usuario, es necesario crear configuración de vault y adicionar el siguiente campo:
    {
      "vault_config": {
        "init_with_guest_user": false,
        "user_authentication_flow": true
      }
    }
    
    Importante: Para habilitar este flujo, debes solicitarlo a tu contacto en DEUNA.
    El widget usará el email del cliente para mandarle un OTP (One-Time-Password) para que el usuario se autentique.
    El email del cliente se obtendrá de la siguiente manera:
    • En caso que el Vault Widget NO se haya iniciado con un orderToken se usará el email del queryParam.
    • En caso si se tenga un orderToken se usará el email de billing_address.emailen caso exista en ese campo un email.
    • En caso que se cuente con un email tanto en los queryParams como en el orderToken se le dará prioridad al que venga en el billing_address.email dentro de la orden.

    En caso que no se haya iniciado el Vault Widget con el userToken y se vaya a autenticar el usuario por email, DEUNA tendrá en cuenta el algoritmo de device fingerprint generado para tratar de autenticar el usuario sin fricción, es decir, sin pedirlo un OTP.

¿En el flujo de usuario autenticado cuando se le muestra al usuario final los Términos y Condiciones?

  • Usuarios registrado -> Se muestra vista de OTP -> Agrega el OTP: No se muestra TyC, esto dado que solo requiere aceptarlos una vez y fue al inicio cuando se registró.
  • Usuarios registrado -> Se muestra vista de OTP -> NO agrega OTP y decide continuar como guest: No se muestra TyC, esto dado que es un usuarios previamente registrado que quiere pagar con una tarjeta de un único uso (No se guardará en su perfil).
  • Usuarios nuevo -> Se muestra directo el formularios de tarjeta: SI se muestra TyC, esto dado que al hacer su primera compra se registrarán los datos de compra y forma de pago.

Paso 2: Manejo de eventos con postMessage


Tras procesar una tarjeta con éxito en DEUNA, recibirás un evento postMessage con el card_id. Este card_id es esencial para procesar el pago final en tu backend.

window.addEventListener("message", (event) => {
  if (event.origin !== "URL_DE_CONFIANZA") { // Reemplaza con tu URL de confianza
    return;
  }

  switch (event.data.type) {
    case 'checkoutStarted':
      // Vault está listo
      break;

    case 'vaultSaveSuccess':
      // Tarjeta guardada con éxito, obtén el card_id
      const cardId = event.data.metadata.createdCard.id;
      // Usa card_id para procesar el pago
      
      // stored card sera true si fue una tarjeta seleccionada de las
      // tarjetas guardas, de lo contrario sera false si es nueva
      const storedCard = event.data.metadata.createdCard.storedCard
      break;

    case 'vaultSaveError':
    case 'vaultFailed':
      // Manejo de errores
      const errorCode = event.data.metadata.errorCode;
      const errorMessage = event.data.metadata.errorMessage;
      break;

    case 'vaultProcessing':
      // Pago en proceso
      break;
    case 'onBinDetected':
    	// Cuando el usuario ingresa los primeros 6 dígitos de la tarjeta
    case 'onInstallmentSelected':
    	const planOptionId = event.data.metadata.installment.plan_option_id;

    // Añade más casos según sea necesario
  }
});

Utiliza este ejemplo como guía para configurar tu manejador de eventos y reacciona adecuadamente a cada tipo de mensaje.

Visita la sección Manejo de eventos con postMessage donde encontrarás información detallada sobre estos eventos, incluyendo descripciones completas y recomendaciones sobre cómo manejarlos efectivamente.

Aplicar cambios en la orden después de cargar el Vault

Si deseas implementar una promoción o descuento que afecte la orden en el widget, es necesario emitir un mensaje al iFrame utilizando el postmessage con type refetchOrder. Esto permite que el widget actualice la orden correspondiente y muestre los cuotas de pago necesarias, en caso de ser aplicable. A continuación, te presentamos un ejemplo de cómo integrar esta funcionalidad:

Pasos de Integración

if ( parsedData && parsedData.type === elementsLinkEvents.onBinDetected) {
          const cardBrand = parsedData.data?.metadata?.cardBrand
           
          // 1.Verificar si la tarjeta es mastercard  
          if (cardBrand === 'mastercard') {
            
            //Si es así aplicar un descuento del 20% (función del comercio)
            const cartUpdated  = applyDiscount(cart, 20);
            dispatcher(setDiscountedCart(cartUpdated));
            
            //Modifica la orden apuntando a un endpoint de Deuna
            await updateOrder({...});

            //2. Notificale al widget que hay que actualizar la orden e installments
            iframeRef.current?.contentWindow?.postMessage(
              JSON.stringify({
                type: 'refetchOrder',
                data: null // debe siempre ir en null
              }),
              '*'
            );
             // 3. Incluso podrías enviar un postMessage adicional para mostrar
             // un tag promocional, 
            iframeRef.current?.contentWindow?.postMessage(
            JSON.stringify({
              type: elementsLinkEvents.setCustomCSS,
              data: getCustomCSS(cardBrand)
            }),
            '*'
          );
          }
        }

Notas Importantes

  1. Verificación del tipo de evento y marca de la tarjeta: reconocemos la marca de la tarjeta a partir del metadata del evento.
  2. Aplicación de descuento si la tarjeta es Mastercard por el comercio: Si la marca de la tarjeta es 'mastercard', se aplica un descuento del 20% al carrito de compras actual. Es importante destacar que esta acción es realizada 100% por el comercio.
  3. Actualización de la orden: Después de aplicar el descuento, el comercio se encarga de actualizar la orden utilizando un endpoint del servicio de Deuna.
  4. Notificación al widget para actualizar la orden e installments: Es momento de comunicarle estos cambios al Widget. Esto mediante el uso del método postMessage, el cual envía un mensaje al widget indicando que debe refrescar la orden, cuotas de pago y demás lógica que dependa de la orden. Es fundamental incluir un objeto con el tipo 'refetchOrder' y con un valor de data igual a null.
  5. Mostrar un tag promocional: Además de actualizar la orden y las cuotas de pago, es posible aplicar un estilo CSS personalizado mediante la función getCustomCSS(cardBrand) al widget. En este ejemplo mostramos una etiqueta promocional relacionada con la marca de la tarjeta. Referirse a la documentación para mayor información.

Paso 3: Proceso para finalizar el pago


Una vez que el proceso de pago se haya iniciado en tu aplicación mediante el backend, el siguiente paso crucial es integrar el endpoint para finalizar la compra. Este proceso implica configurar una solicitud específica al endpoint de la API para concluir la transacción. Por ejemplo, puedes utilizar una solicitud curl como se muestra a continuación:

const requestBody = {
    payer_info: {
        email: "[email protected]"
    },
    payment_source: {
        method_type: "credit_card",
        card_info: {
            card_id: "8d369b6e-113a-4640-9e63-dc29957fc86aza",
            installment: {
            	plan_option_id: "<uuid que se retorna en el postMessage 'onInstallmentSelected'>"
            }
        }
    },
    // ... Resto del body
};

fetch('https://api.sandbox.deuna.io/v2/merchants/orders/purchase', {
    method: 'POST',
    headers: {
        'x-api-key': '<YOUR_PRIVATE_API_KEY>',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(requestBody)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
curl --location 'https://api.sandbox.deuna.io/v2/merchants/orders/purchase' \
--header 'x-api-key: YOUR_PRIVATE_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
    "payer_info": {
        "email": "[email protected]"
    },
    "payment_source": {
        "method_type": "credit_card",
        "card_info": {
            "card_id": "8d369b6e-113a-4640-9e63-dc29957fc86aza"
        }
    },
    "order": {
        "order_id": "testapi-0003",
        "currency": "MXN",
        "items": [
          {
              "id": "216",
              "name": "10 Cellphones",
              "description": "",
              "options": "string option",
              "total_amount": {
                  "original_amount": 2000,
                  "amount": 2000,
                  "currency": "MXN",
                  "currency_symbol": "$"
              },
              "unit_price": {
                  "amount": 200,
                  "currency": "MXN",
                  "currency_symbol": "$"
              },
              "tax_amount": {
                  "amount": 0,
                  "currency": "MXN",
                  "currency_symbol": "$"
              },
              "quantity": 1,
              "uom": "string",
              "upc": "string",
              "sku": "",
              "isbn": "",
              "brand": "",
              "manufacturer": "",
              "category": "",
              "color": "",
              "size": "",
              "weight": {
                  "amount": 0,
                  "unit": "kg"
              },
              "image_url": "https://images-staging.getduna.com/95463fb5-6279-4ec3-8ff9-fe07aacd2142/db5b698c57654116_domicilio_216_750x750_1662162887.png?d=200x200&format=webp",
              "details_url": "",
              "type": "physcal",
              "taxable": true
          }
        ],
        "sub_total": 2000,
        "total_amount": 2000,
        "store_code": "all",
        "billing_address": {
            "address1": "presa angostura 36PH",
            "address2": "",
            "address_type": "home",
            "city": "CDMX",
            "country_code": "MX",
            "email": "[email protected]",
            "first_name": "efren",
            "identity_document": "162915134",
            "is_default": true,
            "last_name": "garcia",
            "phone": "+525222222222",
            "state_code": "MX",
            "state_name": "miguel hidalgo",
            "zipcode": "11500"
        }
    }
}'
$curl = curl_init();

$requestBody = json_encode(array(
    "payer_info" => array(
        "email" => "[email protected]"
    ),
    "payment_source" => array(
        "method_type" => "credit_card",
        "card_info" => array(
            "card_id" => "8d369b6e-113a-4640-9e63-dc29957fc86aza"
        )
    ),
    // Aquí puedes continuar añadiendo el resto del cuerpo de la solicitud
    "order" => array(
        "order_id" => "testapi-0003",
        "currency" => "MXN",
        "items" => array(
            // Aquí van los detalles de los items
        ),
        // Continúa con el resto de los detalles del pedido
    )
));

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://api.sandbox.deuna.io/v2/merchants/orders/purchase",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => $requestBody, // Aquí se utiliza la variable
  CURLOPT_HTTPHEADER => array(
    "x-api-key: YOUR_PRIVATE_API_KEY", // Asegúrate de reemplazar con tu API key real
    "Content-Type: application/json"
  ),
));


$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
require 'uri'
require 'net/http'
require 'json'

url = URI("https://api.sandbox.deuna.io/v2/merchants/orders/purchase")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request_body = {
    payer_info: {
        email: "[email protected]"
    },
    payment_source: {
        method_type: "credit_card",
        card_info: {
            card_id: "8d369b6e-113a-4640-9e63-dc29957fc86aza"
        }
    }
    # ... Agrega aquí el resto del cuerpo de la solicitud
}.to_json

request = Net::HTTP::Post.new(url)
request["x-api-key"] = "YOUR_PRIVATE_API_KEY" # Reemplaza con tu API key
request["Content-Type"] = "application/json"
request.body = request_body

response = http.request(request)
puts response.read_body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONObject;

public class Main {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();
        
        JSONObject requestBody = new JSONObject()
            .put("payer_info", new JSONObject().put("email", "[email protected]"))
            .put("payment_source", new JSONObject()
                .put("method_type", "credit_card")
                .put("card_info", new JSONObject().put("card_id", "8d369b6e-113a-4640-9e63-dc29957fc86aza")))
            // ... Agrega aquí el resto del cuerpo de la solicitud

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.sandbox.deuna.io/v2/merchants/orders/purchase"))
            .header("x-api-key", "YOUR_PRIVATE_API_KEY") // Reemplaza con tu API key
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
            .build();

        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println)
            .join();
    }
}

👍

**

Importante**: Para revisar en detalle el endpoint te recomendamos ir a realizar pago de orden

Demo de experiencia


Felicidades, ya haz implementado click to pay. Ahora podrás ver una experiencia similar a la siguiente

Personalización de CSS en el componente (Opcional)


El componente te permite realizar personalizaciones en el formulario de tarjetas, para más información de configuración ir a la sección Personalización CSS de componentes