Pagos IMD PRODUCCIÓN

Guía de integración

Este servicio es el único punto de la iglesia autorizado a procesar cobros con tarjeta. Las aplicaciones (Instituto, ofrendas, eventos) no manejan datos de tarjeta: le piden un cobro a este gateway y redirigen al usuario a CardNet.

Regla inviolable: nunca envíes número de tarjeta, CVV ni fecha de expiración a este gateway. Las peticiones que incluyan esos campos son rechazadas con 422. La tarjeta se digita únicamente en las pantallas de CardNet.

El flujo, paso a paso

  1. Tu aplicación llama a POST /api/v1/checkout con el monto y su referencia interna.
  2. El gateway crea la transacción, abre la sesión en CardNet y devuelve una orden de redirección.
  3. Tu aplicación publica ese formulario en el navegador del usuario (CardNet exige POST).
  4. El usuario paga en las pantallas de CardNet.
  5. CardNet devuelve al usuario al gateway, que consulta el resultado real y cierra la transacción.
  6. El gateway te notifica por webhook y redirige al usuario a tu return_url.

Los parámetros que llegan a tu return_url son solo informativos, para pintar una pantalla. El estado real se confirma por webhook o consultando GET /api/v1/checkout/{session_id}.

Firma de las peticiones

Toda petición autenticada lleva cuatro encabezados. La firma se calcula sobre:

MÉTODO\nRUTA\nTIMESTAMP\nNONCE\nSHA256(cuerpo)
$body      = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$nonce     = (string) Str::uuid();
$path      = '/api/v1/checkout';

$stringToSign = "POST\n{$path}\n{$timestamp}\n{$nonce}\n" . hash('sha256', $body);
$signature    = base64_encode(hash_hmac('sha256', $stringToSign, $apiSecret, true));

$response = Http::withHeaders([
    'X-API-Key'       => $apiKey,
    'X-Timestamp'     => $timestamp,
    'X-Nonce'         => $nonce,
    'X-Signature'     => $signature,
    'Idempotency-Key' => $idempotencyKey,  // el mismo si reintentas
    'Content-Type'    => 'application/json',
])->withBody($body, 'application/json')->post('https://pagos.iglesiamontededios.org.do' . $path);

La Idempotency-Key debe ser la misma en un reintento del mismo cobro: así un doble clic del usuario no genera dos cargos.

Endpoints

Iniciar un cobro

POST /api/v1/checkout

{
  "amount": 800.00,
  "description": "Inscripción ciclo 2026-2",
  "external_ref": "INS-21620",
  "return_url": "https://instituto.iglesiamontededios.org.do/pago/retorno",
  "cancel_url": "https://instituto.iglesiamontededios.org.do/pago/cancelado",
  "customer_name": "Juan Pérez",
  "customer_email": "juan@example.com"
}

Respuesta 201:

{
  "session_id": "01HX5K2VJT9N3Q7ZMFJCPB8YGE",
  "transaction_id": "01HX5K2VJT9N3Q7ZMFJCPB8YGF",
  "order_number": "ORD20260827164959AB12CD",
  "status": "pending",
  "expires_at": "2026-08-27T21:19:59+00:00",
  "redirect": {
    "url": "https://ecommerce.cardnet.com.do/authorize",
    "method": "POST",
    "fields": { "SESSION": "UI_ABC123" }
  }
}

external_ref es tu identificador interno. Es la pieza que te permite conectar el pago con tu propio registro cuando llegue el webhook.

Redirigir al usuario

<form id="cardnet" method="POST" action="{{ $redirect['url'] }}">
    <input type="hidden" name="SESSION" value="{{ $redirect['fields']['SESSION'] }}">
</form>
<script>document.getElementById('cardnet').submit();</script>

Consultar el estado

GET /api/v1/checkout/{session_id}
GET /api/v1/transactions/{transaction_id}

Devoluciones

POST /api/v1/transactions/{transaction_id}/refund

{ "amount": 800.00, "reason": "El estudiante canceló su inscripción" }

La devolución queda pending: CardNet no permite devolver por API en este flujo, así que el personal la tramita ante el banco y la marca como completada desde el panel.

Webhooks

El gateway notifica a la webhook_url registrada para tu aplicación. Verifica siempre la firma antes de procesar.

{
  "id": "01HX5K...",
  "event": "payment.captured",
  "created_at": "2026-08-27T21:05:12+00:00",
  "data": {
    "transaction_id": "01HX5K2VJT9N3Q7ZMFJCPB8YGF",
    "external_ref": "INS-21620",
    "status": "captured",
    "amount": 800.00,
    "currency": "DOP",
    "auth_code": "A1B2C3"
  }
}

Encabezados: X-Gateway-Event, X-Gateway-Delivery y X-Gateway-Signature = base64 del HMAC-SHA256 del cuerpo crudo, firmado con tu secreto. Responde 2xx o el gateway reintentará.

Códigos de respuesta