Guías de integración
Revisa las guías principales para integrar Andorpay: checkout alojado, suscripciones, webhooks, reembolsos y prompts de implementación para IA.
Guías rápidas
Checkout alojado
Crea checkouts desde tu servidor con la clave de API del comercio y redirige siempre al hostedUrl devuelto.
Suscripciones
Los productos recurrentes crean suscripciones. Tu aplicación concede o retira acceso a partir de eventos verificados.
Webhooks
Verifica firmas, evita duplicados con event.id y trata la entrega como al menos una vez sin conceder acceso dos veces.
Documentación preparada para IA
Copia prompts para Codex, Claude, Cursor y Windsurf que obligan al agente a respetar esta documentación.
Modelo de integración
Los usuarios del panel y las claves de API del comercio son identidades distintas
El alta usa el JWT de Supabase porque una persona está configurando el comercio. Checkout, reembolsos y facturación usan la clave de API del comercio porque actúa un servidor en su nombre.
1. Completa el onboarding en Andorpay
El comercio inicia sesión, configura Redsys y recibe la clave de API inicial una sola vez.
2. Guarda la clave de API en el servidor
Guárdala en el gestor de secretos del servidor del comercio. No la incluyas en aplicaciones móviles ni en paquetes de navegador.
3. Crea checkouts desde tu backend
La clave de API determina el alcance del comercio. Envía productId, los datos del cliente y las URL de éxito y fallo para cada petición.
Inicio rápido
Crea un checkout alojado y envía al comprador al pago
El checkout alojado es la integración recomendada: tu servidor crea el checkout, Andorpay devuelve hostedUrl, tu aplicación redirige al comprador y Andorpay encapsula la interacción con Redsys.
curl -X POST https://api.andorpay.com/v1/checkouts \
-H "Authorization: Bearer $ANDORPAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Andorpay-Version: 2026-01-01" \
-H "Idempotency-Key: checkout:user_42:starter:1" \
-d '{
"externalCustomerId": "user_42",
"customer": {
"email": "ana@example.com",
"name": "Ana Garcia",
"billingDetails": {
"line1": "Carrer Prat de la Creu 12",
"city": "Andorra la Vella",
"postal_code": "AD500",
"country": "AD"
},
"tax": {
"value": "F-123456-Z",
"type": "andorra_nrt"
}
},
"productId": "ap_prod_starter_monthly",
"successUrl": "https://example.com/billing/success",
"failUrl": "https://example.com/billing/cancelled"
}'Lista mínima para producción
- Llama a Andorpay desde tu backend, no desde JavaScript en el navegador.
- Fija Andorpay-Version y revísala en cambios de código.
- Genera una clave de idempotencia por cada intento lógico de checkout.
- Persiste checkout.id, orderId y paymentId antes de mostrar la interfaz de pago.
- Redirige siempre al hostedUrl devuelto por Andorpay.
- Entrega el producto desde webhooks payment.succeeded o subscription.created.
{
"id": "ap_chk_01HY8Q9A3Y9B5J7V2V0M9R4S8T",
"sessionId": "ap_chk_01HY8Q9A3Y9B5J7V2V0M9R4S8T",
"hostedUrl": "https://andorpay.com/checkout/ap_chk_01HY8Q9A3Y9B5J7V2V0M9R4S8T",
"expiresAt": "2026-05-17T10:30:00.000Z",
"request_id": "req_01HY8T2M0D6F4Q7F3K8A9V2N1C"
}type CheckoutCustomer = {
email: string
name?: string
billingDetails?: {
line1?: string
city?: string
postal_code?: string
country?: string
}
tax?: {
value: string
type: string
}
}
type CreateCheckoutSessionInput = {
externalCustomerId: string
productId: string
customer: CheckoutCustomer
successUrl: string
failUrl: string
amount?: number
}
const andorpayApiUrl = process.env.ANDORPAY_API_URL ?? "https://api.andorpay.com"
export async function createAndorpayCheckout(input: CreateCheckoutSessionInput, idempotencyKey: string) {
const response = await fetch(`${andorpayApiUrl}/v1/checkouts`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANDORPAY_API_KEY}`,
"Content-Type": "application/json",
"Andorpay-Version": "2026-01-01",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(input),
})
const payload = await response.json()
if (!response.ok) {
throw new Error(`No se pudo crear el checkout de Andorpay: ${payload.code ?? response.status}`)
}
return payload as {
id: string
sessionId: string
hostedUrl: string
expiresAt?: string
}
}
export function continueToAndorpayCheckout(checkout: { hostedUrl: string }) {
window.location.assign(checkout.hostedUrl)
}Referencia de API
Endpoints principales y entrada del checkout alojado
La entrada del checkout es explícita para que personas y agentes puedan mapear comercio electrónico, SaaS y facturación al mismo contrato. La clave de API aporta el alcance del comercio.
| Método | Ruta | Uso |
|---|---|---|
| POST | /v1/checkouts | Crea un checkout alojado. |
| POST | /v1/payment-methods/change | Crea un flujo alojado para actualizar el método de pago. |
| POST | /v1/subscriptions/{id}/cancel | Cancela una suscripción del comercio autenticado por la clave de API. |
| GET | /v1/products | Lista los productos del comercio autenticado. |
| GET | /v1/orders | Lista los pedidos del comercio autenticado. |
| GET | /v1/subscriptions | Lista las suscripciones del comercio autenticado. |
| GET | /v1/invoices | Lista las facturas del comercio autenticado. |
| GET | /v1/payments | Lista los pagos del comercio autenticado. |
| GET | /v1/billing/dashboard | Devuelve los contadores del panel de facturación. |
| POST | /v1/refunds | Crea un reembolso desde paymentId u orderId. |
| POST | /v1/manual-charges | Encola un cargo manual para un pedido o importe existente. |
Campos obligatorios del checkout
Campos opcionales habituales
Webhooks
Verifica, evita duplicados y actualiza el estado
Los webhooks son la fuente de verdad para entregas y suscripciones. Las páginas de redirección son experiencia de usuario, no prueba de pago.
{
"id": "evt_01HY8R3D4P7N7QGJ9M2K3W8X6B",
"type": "payment.succeeded",
"occurred_at": "2026-05-17T09:42:18.000Z",
"merchant_id": "mer_01J0ANDORPAYMERCHANT",
"customer": {
"id": "cus_123",
"external_customer_id": "user_42",
"email": "ana@example.com"
},
"product": {
"id": "ap_prod_starter_monthly",
"external_reference": "starter-monthly"
},
"subscription": {
"id": "ap_sub_123",
"status": "active",
"current_period_end": "2026-06-17T09:42:18.000Z"
},
"payment": {
"id": "ap_pay_123",
"rail": "redsys",
"status": "succeeded"
}
}Eventos soportados
Nota sobre fallos del rail: payment.failed, payment_method.updated_required y billing.operational_alert significan cosas distintas. No trates todos los fallos del rail como una cancelación del usuario.
| Evento | Cuándo se usa | Qué debe hacer tu sistema |
|---|---|---|
| payment.succeeded | Un pago ha sido autorizado y confirmado por el rail. | Marca pedido o pago como cobrado, entrega el producto y activa el acceso si no depende de una suscripción. |
| payment.failed | El intento de pago ha fallado o ha sido rechazado. | No entregues el producto. Libera reservas, muestra recuperación de pago y conserva el motivo operativo en logs. |
| subscription.created | Se ha creado una suscripción desde un checkout recurrente. | Crea o enlaza la suscripción local y concede acceso según el estado recibido. |
| subscription.updated | La suscripción ha cambiado de estado, periodo, plan o datos relevantes. | Sincroniza permisos, fechas de renovación, estado past_due/active y cambios de plan. |
| subscription.cancelled | La suscripción ha sido cancelada o finalizada. | Revoca o programa la retirada de acceso según la fecha efectiva y actualiza el estado local. |
| invoice.created | Se ha generado una factura o periodo de cobro. | Registra la factura, muéstrala en el panel del cliente y espera eventos de pago para entrega o renovación. |
| invoice.payment_failed | El cobro asociado a una factura no ha podido completarse. | Activa gracia, reintentos, aviso al cliente o flujo de actualización de método de pago. |
| refund.created | Se ha creado un reembolso total o parcial. | Actualiza el saldo del pedido, estado de pago y soporte/contabilidad interna. |
| payment_method.updated | El método de pago del cliente se ha actualizado correctamente. | Confirma la recuperación de cobro o marca el método como válido para futuros cargos. |
| payment_method.updated_required | El cliente debe actualizar o autenticar su método de pago. | Envía al cliente al flujo alojado de actualización y no canceles la suscripción automáticamente. |
| billing.operational_alert | Hay una incidencia operativa de billing, rail, credenciales, webhook o reintento. | Alerta a operadores internos. No muestres el detalle técnico al cliente final. |
import crypto from "node:crypto"
import { NextRequest, NextResponse } from "next/server"
function verifyAndorpaySignature(rawBody: string, signatureHeader: string, secret: string) {
const parts = new Map(signatureHeader.split(",").map((part) => {
const [key, value] = part.split("=")
return [key, value]
}))
const timestamp = parts.get("t")
const signature = parts.get("v1")
if (!timestamp || !signature) {
return false
}
const timestampMs = Number(timestamp) * 1000
if (!Number.isFinite(timestampMs) || Math.abs(Date.now() - timestampMs) > 5 * 60 * 1000) {
return false
}
const payload = `${timestamp}.${rawBody}`
const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex")
const signatureBuffer = Buffer.from(signature, "hex")
const expectedBuffer = Buffer.from(expected, "hex")
if (signatureBuffer.length !== expectedBuffer.length) {
return false
}
return crypto.timingSafeEqual(signatureBuffer, expectedBuffer)
}
export async function POST(request: NextRequest) {
const rawBody = await request.text()
const signature = request.headers.get("Andorpay-Signature")
if (!signature || !verifyAndorpaySignature(rawBody, signature, process.env.ANDORPAY_WEBHOOK_SECRET!)) {
return NextResponse.json({ error: "Firma no válida" }, { status: 400 })
}
const event = JSON.parse(rawBody) as {
id: string
type: string
customer?: { external_customer_id?: string }
subscription?: { id?: string; status?: string; current_period_end?: string }
payment?: { id?: string; status?: string }
}
// Guarda event.id antes de aplicar efectos. Ignora el evento si ya lo has procesado.
switch (event.type) {
case "payment.succeeded":
// Marca el pago o pedido local como pagado. En suscripciones, actualiza el acceso desde eventos subscription.*.
break
case "payment.failed":
// Mantén una política de gracia y pide al cliente que actualice o autentique el método de pago si hace falta.
break
case "subscription.created":
case "subscription.updated":
case "subscription.cancelled":
break
case "invoice.created":
case "invoice.payment_failed":
break
case "refund.created":
case "payment_method.updated":
case "payment_method.updated_required":
case "billing.operational_alert":
break
default:
break
}
return NextResponse.json({ received: true })
}Fiabilidad
Reintenta de forma segura con claves de idempotencia
La API evita duplicados en checkouts y reembolsos mediante Idempotency-Key. Reutiliza esa clave al reintentar la misma petición después de un tiempo de espera agotado, un límite de tasa o un error transitorio de la API.
Reglas para agentes
- No crees una clave nueva al reintentar el mismo checkout o reembolso.
- Sí debes crear una clave nueva para otro carrito, otro producto u otro reembolso.
- Registra el request_id de Andorpay junto al id interno del pedido.
- Trata el 409 como un desacople entre clave y cuerpo hasta demostrar lo contrario.
async function retryAndorpayCreate(request: RequestInit, idempotencyKey: string) {
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await fetch(`${process.env.ANDORPAY_API_URL ?? "https://api.andorpay.com"}/v1/checkouts`, {
...request,
headers: {
...request.headers,
"Idempotency-Key": idempotencyKey,
},
})
if (response.ok) {
return response.json()
}
if (![408, 409, 429, 500, 502, 503, 504].includes(response.status)) {
throw new Error(`Respuesta no reintentable de Andorpay: ${response.status}`)
}
await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt))
}
throw new Error("La petición a Andorpay falló después de varios reintentos")
}Suscripciones
Crea mediante checkout y cancela mediante la API
Un producto recurrente crea una suscripción mediante checkout alojado. Tu aplicación debe actualizar el acceso desde webhooks de suscripción verificados, no desde una redirección optimista.
curl -X POST https://api.andorpay.com/v1/subscriptions/sub_01HY8R62TFX01K5CM7Y4S0N6VA/cancel \
-H "Authorization: Bearer $ANDORPAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Andorpay-Version: 2026-01-01"Sesión de checkout
Crea el checkout y envía al cliente a hostedUrl. Andorpay muestra la UI alojada y encapsula internamente cualquier paso firmado que Redsys pueda requerir. Trata las redirecciones solo como experiencia de usuario.
Pago
Entrega el producto en payment.succeeded. En payment.failed, libera reservas o muestra una recuperación de pago.
Suscripción
Los permisos de acceso deben seguir subscription.created, subscription.updated y subscription.cancelled.
Factura
Andorpay emite invoice.created e invoice.payment_failed. Mantén el estado de la factura separado del acceso del usuario.
Errores
Devuelve mensajes seguros y registra el detalle operativo
Muestra al cliente un mensaje de recuperación genérico y conserva en logs el code, message y request_id estructurados de Andorpay.
{
"code": "product_not_found",
"message": "Producto no encontrado o inactivo",
"request_id": "req_01HY8T2M0D6F4Q7F3K8A9V2N1C"
}| Estado | Tipo | Tratamiento |
|---|---|---|
| 400 | checkout_amount_invalid | Corrige la forma de la petición o el valor del parámetro antes de reintentar. |
| 401 | andorpay_api_key_required | Envía Authorization: Bearer <ANDORPAY_API_KEY> desde el servidor. |
| 403 | andorpay_merchant_forbidden | No envíes otro merchantId; la clave de API define el alcance del comercio. |
| 409 | idempotency_key_conflict | Reutiliza una clave solo para el mismo método, ruta y cuerpo. |
| 404 | product_not_found | Comprueba que el producto pertenece al comercio y está activo. |
| 5xx | checkout_failed | Reintenta de forma segura, conserva la clave de idempotencia y registra request_id. |
IA-DX
Pega un prompt protegido en tu herramienta de código
Estos prompts indican a las herramientas de IA que lean esta URL, respeten la superficie documentada de la API y pregunten cuando falte un detalle.
Prompt de integración generado
Pulsa una tarjeta de herramienta para generar y copiar un prompt adaptado.
Ruta legible para LLM
Hay un resumen compacto de estilo llms.txt dentro del espacio de documentación para agentes que prefieren un contrato en texto plano.