Saltar al contenido principal
En esta página

Empieza con Nequi#

Esta página proporciona una guía completa para integrar exitosamente Nequi con DEUNA en su proceso de pago.

Nequi es una billetera digital administrada por Bancolombia y muy utilizada en Colombia.

Cómo funciona#

La integración de DEUNA con Nequi permite a los usuarios pagar a través de un flujo de notificaciones push, completamente manejado dentro del widget de pago DEUNA, sin necesidad de redirecciones externas.

Proceso de pago

La siguiente lista describe un proceso de pago válido con Nequi:

  1. El usuario selecciona Nequi dentro del widget de pago.
  2. El usuario ingresa su número de teléfono real de Nequi.
  3. Se envía una notificación automática a su aplicación móvil Nequi.
  4. El usuario aprueba el pago directamente en la aplicación.
  5. DEUNA recibe el estado de pago.
  6. DEUNA activa el evento de devolución de llamada apropiado en el widget:
    • onSuccess para pagos aprobados
    • onError para los rechazados o vencidos

manejo de eventos onError

El widget DEUNA activa la onError devolución de llamada en los siguientes escenarios:

  1. El número de teléfono proporcionado en el formulario de pago es válido pero no está asociado a una cuenta Nequi.
  2. El usuario rechaza la notificación push de la aplicación Nequi.

En ambos casos, DEUNA devuelve una respuesta que incluye un código de error y un mensaje de error que describe la falla. Puede manejar la respuesta según su lógica empresarial y la experiencia de usuario deseada.

Requisitos#

El siguiente contenido enumera todos los requisitos para una integración exitosa con Nequi.

Tus credenciales de sandbox y producción de Nequi deben solicitarse directamente a tu gerente de cuenta de Bancolombia o Nequi.

Entornos:

Completa el proceso de certificación con Nequi y obtén las siguientes credenciales proporcionadas por Nequi:

  • Merchant ID

  • Contraseña del comerciante

  • Clave pública.

  • Código de identificación

  • Tiempo para caducar (máx. 45min)

Configurar webhooks en Nequi#

Configura webhooks en tu sesión de Nequi para cada tienda.

Si no configura los webhooks de Nequi, las actualizaciones de estado en Nequi tardarán entre dos y cinco minutos.

Asegúrese de que su punto final de webhook tenga alta disponibilidad y sea escalable. Opcionalmente, puede configurar la reversión de la transacción si la confirmación del pago falla debido a la falta de disponibilidad del servicio.

Carga útil del webhook

Nequi enviará la siguiente carga útil JSON:

JSON
{
  "commerceCode": "29603",
  "value": "1",
  "phoneNumber": "3195414070",
  "messageId": "60396545535",
  "transactionId": "350-12345-34000201-60396545535",
  "region": "C001",
  "receivedAt": "2023-02-27T15:50:13.527Z",
  "paymentStatus": "SUCCESS"
}

Campos de carga útil

CampoDescripción
commerceCodeTu código interno de comerciante Nequi
valueMonto del pago
phoneNumberNúmero de teléfono móvil del pagador
messageIdIdentificador de transacción único
transactionIdIdentificador de pago
regionRegión de pago: P001 (Panamá) o C001 (Colombia)
receivedAtMarca de tiempo de pago en formato JSON
paymentStatusEstado de pago: SUCCESS, CANCELEDo DENIED

Verificación de solicitud de seguridad

Todas las solicitudes de webhooks de Nequi incluyen encabezados de seguridad que debes verificar:

Encabezados de solicitud

JSON
{
  "Content-Type": "application/json",
  "Digest": "SHA-256=43GpOk5L54gfpAMBE0xNX1bj2hJA9JJ1RR0dErHfZhI=",
  "Signature": "keyId=\"YourClientId\",algorithm=\"hmac-sha384\",headers=\"content-type digest\",signature=\"...\""
}

Proceso de verificación

Debes verificar dos cosas para cada solicitud:

1. Verificar el resumen corporal

El Digest El encabezado contiene un hash SHA-256 del cuerpo de la solicitud:

JavaScript
// Convert body to string
const parsedBody = JSON.stringify(request.body);

// Calculate SHA-256 hash
const calculatedDigest = `SHA-256=${createHash('sha256')
  .update(parsedBody)
  .digest('base64')}`;

// Verify it matches the header
if (calculatedDigest !== request.headers.Digest) {
  return 401; // Invalid Digest
}
2. Verificar la firma de la solicitud

El Signature El encabezado asegura que la solicitud vino de Nequi:

  1. Analiza el encabezado de la firma.
JavaScript
const parts = request.headers.Signature.split(',');
const signature = {};

for (const part of parts) {
  const [key, value] = part.split('=');
  signature[key] = value.slice(1, -1); // Remove quotes
}

// Result:
// {
//   keyId: "YourClientId",
//   algorithm: "hmac-sha384",
//   headers: "content-type digest",
//   signature: "..."
// }
  1. Construya el texto de firma.
JavaScript
const headerNames = signature.headers.split(' '); // ["content-type", "digest"]
const linesForSignature = [];

for (const headerName of headerNames) {
  const value = request.headers[headerName.toLowerCase()];
  linesForSignature.push(`${headerName}: ${value}`);
}

const textForSignature = linesForSignature.join('\n');
// Result:
// "content-type: application/json
// digest: SHA-256=43GpOk5L54gfpAMBE0xNX1bj2hJA9JJ1RR0dErHfZhI="
  1. Calcule y verifique HMAC.
JavaScript
const APP_SECRET = 'YourSharedSecret'; // Store securely, never hardcode

// Calculate HMAC-SHA384
const base64hmac = createHmac('sha384', APP_SECRET)
  .update(textForSignature)
  .digest('base64');

// Convert to base64url format
const calculatedSignature = base64hmac
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

// Verify signature
if (calculatedSignature !== signature.signature) {
  return 401; // Invalid Signature
}

Ejemplo de implementación completo (Node.js)

JavaScript
const { createHash, createHmac } = require('node:crypto');

// Store securely - use environment variables
const APP_SECRET = process.env.NEQUI_WEBHOOK_SECRET;

async function handleNequiWebhook(request, response) {
  try {
    // 1. Verify Body Digest
    const parsedBody = JSON.stringify(request.body);
    const calculatedDigest = `SHA-256=${createHash('sha256')
      .update(parsedBody)
      .digest('base64')}`;
    
    if (calculatedDigest !== request.headers.digest) {
      return response.status(401).send('Invalid Digest');
    }

    // 2. Parse Signature Header
    const parts = request.headers.signature.split(',');
    const signatureData = {};
    
    for (const part of parts) {
      const [key, value] = part.split('=');
      signatureData[key] = value.slice(1, -1);
    }

    // 3. Build Signing Text
    const headerNames = signatureData.headers.split(' ');
    const linesForSignature = headerNames.map(name => 
      `${name}: ${request.headers[name.toLowerCase()]}`
    );
    const textForSignature = linesForSignature.join('\n');

    // 4. Verify Signature
    const base64hmac = createHmac('sha384', APP_SECRET)
      .update(textForSignature)
      .digest('base64');
    
    const calculatedSignature = base64hmac
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');

    if (calculatedSignature !== signatureData.signature) {
      return response.status(401).send('Invalid Signature');
    }

    // 5. Process the payment notification asynchronously
    processPaymentAsync(request.body);

    // 6. Respond immediately (within 10 seconds)
    return response.status(200).send('OK');

  } catch (error) {
    console.error('Webhook processing error:', error);
    return response.status(500).send('Internal Server Error');
  }
}

async function processPaymentAsync(paymentData) {
  // Process payment in background
  // Update your database, trigger notifications, etc.
  
  const { transactionId, paymentStatus, value, phoneNumber } = paymentData;
  
  if (paymentStatus === 'SUCCESS') {
    // Handle successful payment
  } else if (paymentStatus === 'CANCELED' || paymentStatus === 'REFUSED') {
    // Handle failed payment
  }
}

Integrar Nequi#

Ahora que se establecen los requisitos técnicos, puede iniciar la integración paso a paso.

1. Crea un order_token

Para realizar una compra, debes crear un pedido DEUNA.

Realizar una solicitud al Crear un pedido endpoint.

La API devuelve un order_token, que se utiliza durante todo el flujo.

2. Consigue el order_token

Realizar una solicitud al Obtener pedido endpoint.

Utilice este token para los siguientes pasos.

3. Representa el widget de pago.

Después de recibir el token de pedido, puede renderizar el Widget DEUNA.

JavaScript
await DeunaSDK.initPaymentWidget({
    orderToken: "<DEUNA order token>",
    callbacks: ...,
    paymentMethods: [
        {
            paymentMethod: "voucher",
            processors: ["nequi_push_voucher"],
        },
    ],
});

4. Realizar un pago con tarjeta

Realizar una solicitud de pago con tarjeta al Compra V2 punto final y procesar el pago.

Respuesta

Card payment exampleJavaScript
{
  "order": {
    "cash_change": 0,
    "currency": "USD",
    "discounts": [],
    "display_items_total_amount": "",
    "display_shipping_amount": "",
    "display_sub_total": "",
    "display_tax_amount": "",
    "display_total_amount": "",
    "display_total_discount": "",
    "gift_card": [],
    "items": [
      {
        "brand": "",
        "category": "",
        "color": "",
        "description": "Papa Fritas",
        "details_url": "",
        "discounts": [],
        "id": "001",
        "image_url": "https://images-staging.getduna.com/95463fb5-6279-4ec3-8ff9-fe07aacd2142/cd928351d12c6b96_domicilio_316_744x744.png?d=600x600",
        "isbn": "",
        "manufacturer": "",
        "name": "Papa Fritas",
        "options": "",
        "quantity": 1,
        "size": "",
        "sku": "",
        "tax_amount": {
          "amount": 44,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "taxable": false,
        "total_amount": {
          "amount": 594,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": "",
          "display_original_amount": "",
          "display_total_discount": "",
          "original_amount": 0,
          "total_discount": 0
        },
        "type": "",
        "unit_price": {
          "amount": 550,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "uom": "",
        "upc": "",
        "weight": {
          "unit": "",
          "weight": 0
        }
      },
      {
        "brand": "",
        "category": "",
        "color": "",
        "description": "Hamburguesa ",
        "details_url": "",
        "discounts": [],
        "id": "002",
        "image_url": "https://images-staging.getduna.com/95463fb5-6279-4ec3-8ff9-fe07aacd2142/cd928351d12c6b96_domicilio_51330_744x744_1646338877.png?d=600x600",
        "isbn": "",
        "manufacturer": "",
        "name": "Hamburguesa",
        "options": "",
        "quantity": 2,
        "size": "",
        "sku": "",
        "tax_amount": {
          "amount": 88,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "taxable": false,
        "total_amount": {
          "amount": 3088,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": "",
          "display_original_amount": "",
          "display_total_discount": "",
          "original_amount": 0,
          "total_discount": 0
        },
        "type": "",
        "unit_price": {
          "amount": 1500,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "uom": "",
        "upc": "",
        "weight": {
          "unit": "",
          "weight": 0
        }
      }
    ],
    "items_total_amount": 3682,
    "metadata": {
      "key1": "NO REQUERIDO",
      "key2": "NO REQUERIDO"
    },
    "order_id": "order116",
    "payment": {
      "data": {
        "amount": {
          "amount": 3770,
          "currency": "USD"
        },
        "authorization_3ds": {
          "html_content": "<div></div>",
          "url_challenge": "",
          "version": "1.1.1"
        },
        "authorization_code": "TEST00",
        "created_at": "2022-07-22 20:22:48.408419489 +0000 UTC",
        "customer": {
          "email": "jhondoe@deuna.com",
          "id": "xxxxxx-0eb3-450f-8b26-4ff23208470f"
        },
        "from_card": {
          "card_brand": "Visa",
          "first_six": "411111",
          "last_four": "1111"
        },
        "id": "order116",
        "installments": {
          "installment_amount": 5999,
          "installment_rate": 0.12,
          "installment_type": "MCI",
          "installments": 3,
          "plan_id": "7471ed27-094d-44c4-a62d-225644b782f7",
          "plan_option_id": "87309ea8-3942-4fdf-95ec-ce29a792aff2"
        },
        "merchant": {
          "id": "9a85e296-cc3d-454b-b591-208d6e538126",
          "store_code": "all"
        },
        "metadata": {
          "authorization_code": "TEST00"
        },
        "method_type": "credit_card",
        "processor": "paymentez",
        "reason": "",
        "status": "processed",
        "updated_at": "2022-07-22 20:22:48.408809765 +0000 UTC"
      }
    },
    "redirect_url": "",
    "scheduled_at": "",
    "shipping": null,
    "shipping_address": {
      "additional_description": "Piso 9",
      "address_type": "home",
      "address1": "Av. de los Incas 15-33, Ambato 180202, Ecuador",
      "address2": "Av. de los Incas 15-33, Ambato 180202, Ecuador",
      "city": "Ambato",
      "country_code": "EC",
      "created_at": "0001-01-01T00:00:00Z",
      "first_name": "Jhon",
      "id": 0,
      "identity_document": "146565656",
      "is_default": true,
      "last_name": "Doe",
      "lat": -1.2480678792202227,
      "lng": -78.62532788804577,
      "phone": " 946565665",
      "state_name": "Tungurahua",
      "updated_at": "0001-01-01T00:00:00Z",
      "user_id": "xxxxx4e2-xxxx-xxxx-xxxx-xxxxx5b7b2e",
      "zipcode": "180202"
    },
    "shipping_amount": 0,
    "shipping_method": null,
    "shipping_methods": [],
    "shipping_options": {},
    "status": "succeeded",
    "store_code": "",
    "sub_total": 3550,
    "tax_amount": 132,
    "timezone": "",
    "total_amount": 3770,
    "total_discount": 0,
    "user_instructions": "Piso 9",
    "webhook_urls": null
  },
  "order_token": "0b98dbe8-d265-49bc-b80d-536cea46509c"
}

Prueba Nequi#

Para probar Nequi, debe solicitar los datos de prueba de Nequi. Si es necesario, el pago se puede reembolsar a través del Panel de administración de DEUNA o a través del API de reembolsos.