Saltar al contenido principal

Pagos (Pay In)

Este módulo permite a los Comercios (Merchants) crear órdenes de pago (pay-in) para recibir fondos de sus usuarios o clientes. Las órdenes creadas quedan disponibles para ser procesadas por los Proveedores de Pago integrados en la red de Pago46.

Endpoint Base

Todas las rutas descritas a continuación son relativas a la URL base de la API: /api/v1

Flujo General

El proceso de pago para comercios se divide en tres etapas principales:

  1. Creación de Orden: El comercio crea una orden de pago especificando el monto, país, datos del pagador y URLs de notificación.
  2. Procesamiento: La orden es procesada por un Proveedor de Pago de la red, quien recibe el efectivo del usuario final.
  3. Notificaciones (Webhooks): El comercio recibe actualizaciones del estado de la orden a través de webhooks configurados.

Requisitos Previos

Antes de comenzar, asegúrate de tener:

  • Credenciales de API: Tu Provider-Key y Provider-Secret proporcionados por Pago46.
  • Autenticación HMAC: Familiarízate con el esquema de autenticación descrito en la sección de Autenticación.
  • Endpoint de Webhook: Una URL pública HTTPS donde recibirás las notificaciones de cambios de estado.
Seguridad

Todas las peticiones deben incluir los headers de autenticación HMAC: Provider-Key, Message-Date y Message-Hash.


1. Crear Orden de Pago

Para iniciar un cobro, debes crear una orden proporcionando la información del monto, país, pagador y configuración de notificaciones.

Endpoint: POST /merchants/orders/pay-in/

Parámetros de Request

Campos Obligatorios

CampoTipoDescripción
order_typeStringTipo de orden: LocalCurrencyOrder
countryStringCódigo ISO del país (ej: MX, CL, CO)
priceDecimalMonto del pago (formato: "1500.00")
descriptionStringDescripción de la transacción
merchant_order_idStringID único de tu sistema (max 127 caracteres)
notify_urlString (URL)URL para recibir webhooks de cambios de estado
redirect_urlString (URL)URL de redirección después del proceso
return_urlString (URL)URL de retorno para el usuario
expiryDateTimeFecha y hora de expiración (formato ISO 8601)

Campos Opcionales

CampoTipoDescripción
consumer_emailStringEmail del pagador
consumer_phone_numberStringTeléfono del pagador (max 128 caracteres)

Ejemplo de Request

curl -X POST "https://api.sandbox.pago46.io/api/v1/merchants/orders/pay-in/" \
-H "Provider-Key: <TU_PROVIDER_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{
"order_type": "LocalCurrencyOrder",
"country": "MX",
"price": "1500.00",
"description": "Pago de suscripción - Usuario ABC123",
"merchant_order_id": "ORDER-2024-001234",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"redirect_url": "https://tu-comercio.com/pago/completado",
"return_url": "https://tu-comercio.com/pago/volver",
"consumer_email": "usuario@ejemplo.com",
"consumer_phone_number": "+525512345678",
"expiry": "2024-12-31T23:59:59Z"
}'

Respuesta Exitosa (201 Created)

{
"id": "123e4567-e89b-12d3-a456-426614174000",
"order_type": "LocalCurrencyOrder",
"country": "MX",
"price": "1500.00",
"price_currency": "MXN",
"description": "Pago de suscripción - Usuario ABC123",
"merchant_order_id": "ORDER-2024-001234",
"status": "CREATED",
"redirect_url": "https://tu-comercio.com/pago/completado",
"return_url": "https://tu-comercio.com/pago/volver",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"consumer_email": "usuario@ejemplo.com",
"consumer_phone_number": "+525512345678",
"expiry": "2024-12-31T23:59:59Z",
"paid": null
}
Guardando el ID

Guarda el id de la orden devuelto en la respuesta. Lo necesitarás para consultar el estado de la orden posteriormente.


2. Consultar Orden

Puedes consultar el estado actual de una orden en cualquier momento usando su ID.

Endpoint: GET /merchants/orders/pay-in/{id}/

Parámetros

ParámetroUbicaciónDescripción
idPathUUID de la orden

Ejemplo de Request

curl -X GET "https://api.sandbox.pago46.io/api/v1/merchants/orders/pay-in/123e4567-e89b-12d3-a456-426614174000/" \
-H "Provider-Key: <TU_PROVIDER_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>"

Respuesta (200 OK)

{
"id": "123e4567-e89b-12d3-a456-426614174000",
"order_type": "LocalCurrencyOrder",
"country": "MX",
"price": "1500.00",
"price_currency": "MXN",
"description": "Pago de suscripción - Usuario ABC123",
"merchant_order_id": "ORDER-2024-001234",
"status": "CREATED",
"redirect_url": "https://tu-comercio.com/pago/completado",
"return_url": "https://tu-comercio.com/pago/volver",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"consumer_email": "usuario@ejemplo.com",
"consumer_phone_number": "+525512345678",
"expiry": "2024-12-31T23:59:59Z",
"paid": null
}

Estados de la Orden

Cada orden de pago pasa por diferentes estados durante su ciclo de vida. Es importante que tu sistema maneje correctamente cada uno de estos estados.

EstadoDescripción¿Qué significa para el comercio?
CREATEDOrden creada exitosamenteLa orden se ha registrado y está pendiente de asignación a un proveedor
PAYMENT_STARTEDPago en procesoUn proveedor ha bloqueado la orden y está procesando el pago
COMPLETEDCompletadaEl pago se completó exitosamente. El proveedor recibió el efectivo del usuario
CANCELLEDCanceladaLa orden fue cancelada y no se procesará

Diagrama de Transición de Estados


Webhooks (Notificaciones)

Cada vez que el estado de una orden cambia, Pago46 enviará una notificación HTTP POST a la URL especificada en el campo notify_url de tu orden.

Estructura del Webhook

El webhook será enviado con autenticación HMAC. Debes verificar la firma para asegurar que la notificación proviene de Pago46.

Headers del Webhook

POST /webhooks/pago46 HTTP/1.1
Host: tu-comercio.com
Content-Type: application/json
Provider-Key: PAGO46_SYSTEM
Message-Date: 1704463200.123
Message-Hash: a1b2c3d4e5f6...

Payload del Webhook

{
"id": "123e4567-e89b-12d3-a456-426614174000",
"order_type": "LocalCurrencyOrder",
"country": "MX",
"price": "1500.00",
"price_currency": "MXN",
"description": "Pago de suscripción - Usuario ABC123",
"merchant_order_id": "ORDER-2024-001234",
"status": "PAYMENT_STARTED",
"redirect_url": "https://tu-comercio.com/pago/completado",
"return_url": "https://tu-comercio.com/pago/volver",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"consumer_email": "usuario@ejemplo.com",
"consumer_phone_number": "+525512345678",
"expiry": "2024-12-31T23:59:59Z",
"paid": null
}

Verificación de Webhooks

Es crítico que verifiques la autenticidad de cada webhook recibido para evitar procesamiento de notificaciones fraudulentas.

Ejemplo de Verificación en Python

import hmac
import hashlib
import json
from flask import Flask, request, jsonify

app = Flask(__name__)

# Tu Provider Secret (obtenido de Pago46)
PROVIDER_SECRET = "tu_provider_secret_aqui"

@app.route('/webhooks/pago46', methods=['POST'])
def webhook_handler():
# 1. Extraer headers
provider_key = request.headers.get('Provider-Key')
message_date = request.headers.get('Message-Date')
received_hash = request.headers.get('Message-Hash')

# 2. Obtener el body raw
body_str = request.get_data(as_text=True)

# 3. Construir string to sign
# Formato: PROVIDER_KEY:MESSAGE_DATE:METHOD:PATH:BODY
method = request.method # "POST"
path = request.path # "/webhooks/pago46"
string_to_sign = f"{provider_key}:{message_date}:{method}:{path}:{body_str}"

# 4. Calcular HMAC
calculated_hash = hmac.new(
PROVIDER_SECRET.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()

# 5. Verificar
if not hmac.compare_digest(calculated_hash, received_hash):
return jsonify({"error": "Invalid signature"}), 403

# 6. Procesar la notificación
order_data = json.loads(body_str)
order_id = order_data.get('id')
order_status = order_data.get('status')
merchant_order_id = order_data.get('merchant_order_id')

print(f"Orden {merchant_order_id} ({order_id}) cambió a estado: {order_status}")

# Actualizar tu base de datos
if order_status == 'COMPLETED':
# Marcar como completada
paid_at = order_data.get('paid')
print(f"Pago completado el: {paid_at}")
# Activar servicio, entregar producto, etc.
elif order_status == 'CANCELLED':
# Marcar como cancelada
print("Pago cancelado")

# 7. Responder con 200 OK
return jsonify({"status": "received"}), 200

if __name__ == '__main__':
app.run(port=5000)

Ejemplo de Verificación en Node.js

const express = require('express');
const crypto = require('crypto');
const app = express();

const PROVIDER_SECRET = 'tu_provider_secret_aqui';

app.post('/webhooks/pago46', express.text({ type: '*/*' }), (req, res) => {
// 1. Extraer headers
const providerKey = req.headers['provider-key'];
const messageDate = req.headers['message-date'];
const receivedHash = req.headers['message-hash'];

// 2. Body como string
const bodyStr = req.body;

// 3. Construir string to sign
const method = req.method;
const path = req.path;
const stringToSign = `${providerKey}:${messageDate}:${method}:${path}:${bodyStr}`;

// 4. Calcular HMAC
const calculatedHash = crypto
.createHmac('sha256', PROVIDER_SECRET)
.update(stringToSign)
.digest('hex');

// 5. Verificar
if (calculatedHash !== receivedHash) {
return res.status(403).json({ error: 'Invalid signature' });
}

// 6. Procesar notificación
const orderData = JSON.parse(bodyStr);
const { id, status, merchant_order_id, paid } = orderData;

console.log(`Orden ${merchant_order_id} (${id}) cambió a estado: ${status}`);

if (status === 'COMPLETED') {
console.log(`Pago completado el: ${paid}`);
// Activar servicio, entregar producto, etc.
} else if (status === 'CANCELLED') {
console.log('Pago cancelado');
}

// 7. Responder
res.status(200).json({ status: 'received' });
});

app.listen(5000, () => {
console.log('Webhook server listening on port 5000');
});
Respuesta al Webhook

Debes responder con un código HTTP 200 o 201 para confirmar la recepción. Si no respondes exitosamente, Pago46 reintentará enviar la notificación.


Errores Comunes

Validación de Campos

Si envías datos inválidos o incompletos, recibirás un error 400 Bad Request con detalles específicos:

{
"country": [
"This field is required."
],
"price": [
"A valid number is required."
],
"expiry": [
"Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]."
]
}

Tabla de Errores HTTP

Código HTTPDescripciónSolución
400 Bad RequestDatos inválidos o campos faltantesVerifica que todos los campos obligatorios estén presentes y con el formato correcto
403 ForbiddenAutenticación fallidaVerifica tus credenciales y la firma HMAC
404 Not FoundOrden no encontradaVerifica que el ID de la orden sea correcto
422 Unprocessable EntityError de lógica de negocioRevisa el mensaje de error específico

Mejores Prácticas

1. Idempotencia

Utiliza el campo merchant_order_id para identificar órdenes únicas en tu sistema. Si necesitas reintentar una creación de orden, usa el mismo merchant_order_id para evitar duplicados.

2. Manejo de Webhooks

  • Procesa webhooks de forma asíncrona: No bloquees la respuesta HTTP mientras procesas la lógica de negocio.
  • Implementa reintentos: Si tu servidor webhook está caído, Pago46 reintentará el envío.
  • Valida siempre la firma HMAC: Nunca confíes en webhooks sin verificar su autenticidad.

3. Expiración de Órdenes

Establece un tiempo de expiración razonable (campo expiry). Órdenes típicamente expiran entre 24-72 horas después de su creación.

4. Monitoreo de Estados

  • Monitorea órdenes en estado PAYMENT_STARTED que no transicionan a COMPLETED en un tiempo razonable.
  • Implementa alertas para órdenes que permanezcan en estados intermedios por mucho tiempo.

5. URLs de Notificación

  • Usa URLs HTTPS para notify_url.
  • Asegúrate de que el endpoint esté siempre disponible.
  • Implementa logging para debugging.

Ejemplo Completo de Integración

A continuación, un ejemplo completo en Python que muestra cómo crear una orden y manejar webhooks:

import hmac
import hashlib
import time
import requests
import json
from flask import Flask, request, jsonify

# Configuración
API_BASE_URL = "https://api.sandbox.pago46.io"
PROVIDER_KEY = "tu_provider_key"
PROVIDER_SECRET = "tu_provider_secret"

app = Flask(__name__)

def generate_hmac(method, path, body_dict=None):
"""Genera la firma HMAC para autenticación"""
timestamp = str(time.time())
body_str = json.dumps(body_dict) if body_dict else ""

string_to_sign = f"{PROVIDER_KEY}:{timestamp}:{method}:{path}:{body_str}"

signature = hmac.new(
PROVIDER_SECRET.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()

return {
"Provider-Key": PROVIDER_KEY,
"Message-Date": timestamp,
"Message-Hash": signature,
"Content-Type": "application/json"
}

def create_payin_order(amount, user_email, user_phone, merchant_order_id):
"""Crea una orden de pago"""
path = "/api/v1/merchants/orders/pay-in/"

order_data = {
"order_type": "LocalCurrencyOrder",
"country": "MX",
"price": str(amount),
"description": f"Pago de usuario {user_email}",
"merchant_order_id": merchant_order_id,
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"redirect_url": "https://tu-comercio.com/pago/completado",
"return_url": "https://tu-comercio.com/pago/volver",
"consumer_email": user_email,
"consumer_phone_number": user_phone,
"expiry": "2024-12-31T23:59:59Z"
}

headers = generate_hmac("POST", path, order_data)

response = requests.post(
f"{API_BASE_URL}{path}",
headers=headers,
json=order_data
)

if response.status_code == 201:
order = response.json()
print(f"✅ Orden creada exitosamente: {order['id']}")
return order
else:
print(f"❌ Error al crear orden: {response.status_code}")
print(response.text)
return None

@app.route('/webhooks/pago46', methods=['POST'])
def webhook_handler():
"""Maneja webhooks de Pago46"""
# Verificar firma HMAC
provider_key = request.headers.get('Provider-Key')
message_date = request.headers.get('Message-Date')
received_hash = request.headers.get('Message-Hash')
body_str = request.get_data(as_text=True)

string_to_sign = f"{provider_key}:{message_date}:{request.method}:{request.path}:{body_str}"
calculated_hash = hmac.new(
PROVIDER_SECRET.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()

if not hmac.compare_digest(calculated_hash, received_hash):
return jsonify({"error": "Invalid signature"}), 403

# Procesar webhook
order = json.loads(body_str)

print(f"📩 Webhook recibido para orden: {order['merchant_order_id']}")
print(f" Estado: {order['status']}")

# Actualizar base de datos según el estado
if order['status'] == 'CREATED':
print(" ⏳ Orden creada y esperando que el usuario elija un proveedor")
elif order['status'] == 'PAYMENT_STARTED':
print(" 🔄 Un proveedor está procesando el pago")
elif order['status'] == 'COMPLETED':
print(f" ✅ Pago completado el {order['paid']}")
# Activar servicio, entregar producto, etc.
elif order['status'] == 'CANCELLED':
print(" ❌ Pago cancelado")

return jsonify({"status": "received"}), 200

if __name__ == '__main__':
# Ejemplo: Crear una orden de pago
order = create_payin_order(
amount=1500.00,
user_email="usuario@ejemplo.com",
user_phone="+525512345678",
merchant_order_id="ORDER-2024-" + str(int(time.time()))
)

# Iniciar servidor de webhooks
print("\n🚀 Iniciando servidor de webhooks...")
app.run(port=5000)

Próximos Pasos

Soporte

Si tienes preguntas o necesitas ayuda con tu integración, contacta al equipo de soporte de Pago46.