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.

👍

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

Con esto se mostrará la vista de OTP, la cual tomará en cuenta el correo electrónico enviado en el parámetro de búsqueda email. En caso de que se envíe un orderToken, se tomará el correo electrónico que fue agregado en el billing_address.

En caso que se envíe el correo tanto en el parámetro de búsqueda como en el billing_address, se tomará como prioridad el correo que este disponible en billing_addresspara buscar un usuario para autenticar.

DEUNA cuenta con un mecanismo de device fingerprinting, esto permite que si el usuario asociado al correo que estamos usando para autenticarlo ya se había autenticado antes en el mismo dispositivo, ya no se le pedirá OTP. Esta funcionalidad es altamente recomendada (pero opcional) ya que le ofrece el usuario final una mejor experiencia de compra.

📘

Importante: Para habilitar este flujo, deberas solicitarlo con tu contacto dentro de DEUNA.

¿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 'vaultStarted':
      // 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