Documentación para desarrolladores
Next.js
Mantén la API key en el servidor, crea una sesión desde un Route Handler y redirige al comprador al checkout alojado.
Antes de empezar
Necesitas un proyecto Next.js con una ruta de servidor, una cuenta AndorPay activa, una API key de pruebas y el identificador de un producto activo. El ejemplo usa App Router y runtime Node.js.
Configuración
Define estas variables en .env.local durante desarrollo y en el gestor de secretos de tu entorno al desplegar. No uses el prefijo NEXT_PUBLIC_.
ANDORPAY_API_KEY=ap_test_replace_me
ANDORPAY_API_URL=https://api.andorpay.com
ANDORPAY_WEBHOOK_SECRET=whsec_replace_meimport "server-only"
type CheckoutInput = {
productId: string
externalCustomerId: string
customer: {
email: string
name?: string
}
successUrl?: string
failUrl?: string
}
type CheckoutResponse = {
id: string
sessionId: string
hostedUrl: string
expiresAt: string
request_id: string
}
const apiUrl = process.env.ANDORPAY_API_URL ?? "https://api.andorpay.com"
export async function createAndorpayCheckout(input: CheckoutInput) {
const apiKey = process.env.ANDORPAY_API_KEY
if (!apiKey) throw new Error("ANDORPAY_API_KEY is not configured")
const response = await fetch(`${apiUrl}/v1/checkouts`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Andorpay-Version": "2026-01-01",
"Content-Type": "application/json",
},
body: JSON.stringify(input),
cache: "no-store",
})
const payload = await response.json()
if (!response.ok) {
throw Object.assign(new Error(payload.message ?? "AndorPay request failed"), payload)
}
return payload as CheckoutResponse
}Crear un pago
El Route Handler recibe sólo los datos que tu aplicación ya validó. La API key identifica al comercio; no envíesmerchantId, priceId ni Idempotency-Key a POST /v1/checkouts.
import { NextResponse } from "next/server"
import { createAndorpayCheckout } from "@/lib/andorpay"
export const runtime = "nodejs"
export async function POST(request: Request) {
const input = await request.json()
const checkout = await createAndorpayCheckout({
productId: input.productId,
externalCustomerId: input.customerId,
customer: {
email: input.email,
name: input.name,
},
successUrl: new URL("/payment/success", request.url).toString(),
failUrl: new URL("/payment/failed", request.url).toString(),
})
return NextResponse.json({ hostedUrl: checkout.hostedUrl })
}Desde tu componente cliente, llama a la ruta local y abre exactamente el hostedUrl devuelto.
const response = await fetch("/api/andorpay/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ productId, customerId, email, name }),
})
const payload = await response.json()
if (!response.ok) throw new Error(payload.message ?? "Checkout failed")
window.location.assign(payload.hostedUrl)Probar la integración
- 1
Arranca Next.js con ap_test_
Confirma que ANDORPAY_API_KEY sólo existe en el proceso servidor y no en el HTML o los bundles del navegador. - 2
Crea una sesión
La ruta local debe responder con hostedUrl y el backend de AndorPay con estado 201. - 3
Completa un pago sandbox
Prueba autorización, 3DS y rechazo con las credenciales publicadas en la guía de pruebas. - 4
Comprueba el evento
payment.succeeded debe llegar firmado y producir el efecto de negocio una sola vez.
Configurar webhooks
Crea una segunda ruta Node.js. Lee request.text() antes de JSON, verificaAndorpay-Signature y conecta el evento ya validado con una función de tu dominio.
import { createHmac, timingSafeEqual } from "node:crypto"
import { NextResponse } from "next/server"
export const runtime = "nodejs"
function validSignature(rawBody: string, header: string, secret: string) {
const values = new Map(header.split(",").map((part) => part.trim().split("=", 2)))
const timestamp = values.get("t")
const signature = values.get("v1")
if (!timestamp || !signature || !/^[a-f0-9]{64}$/i.test(signature)) return false
const timestampSeconds = Number(timestamp)
if (!Number.isFinite(timestampSeconds)) return false
if (Math.abs(Date.now() / 1000 - timestampSeconds) > 300) return false
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex")
const receivedBuffer = Buffer.from(signature, "hex")
const expectedBuffer = Buffer.from(expected, "hex")
return receivedBuffer.length === expectedBuffer.length && timingSafeEqual(receivedBuffer, expectedBuffer)
}
export async function POST(request: Request) {
const rawBody = await request.text()
const signature = request.headers.get("Andorpay-Signature")
const secret = process.env.ANDORPAY_WEBHOOK_SECRET
if (!signature || !secret || !validSignature(rawBody, signature, secret)) {
return NextResponse.json({ code: "invalid_signature" }, { status: 400 })
}
const event = JSON.parse(rawBody) as { id: string; type: string }
await acceptVerifiedAndorpayEvent(event)
return NextResponse.json({ received: true })
}Configura la URL final en el dashboard. Una respuesta 2xx acepta la entrega; otros estados y timeouts provocan reintento con el mismo event.id.
Errores frecuentes
| Síntoma | Causa y corrección |
|---|---|
| 401 api_key_invalid | La clave falta, está revocada o no se cargó en el proceso servidor. |
| 400 checkout_idempotency_key_not_allowed | Elimina Idempotency-Key de la creación de checkout. |
| 404 product_not_found | Copia un productId activo que pertenezca al comercio de la API key. |
| El navegador ve la API key | Elimina NEXT_PUBLIC_, mueve la llamada a un módulo server-only y vuelve a desplegar. |
| hostedUrl no abre | No reconstruyas la URL. Usa el valor completo de la respuesta antes de expiresAt. |
| Firma inválida | Verifica el texto exacto, el secreto activo y la marca temporal antes de JSON.parse. |
Pasar a producción
Configura las credenciales Redsys productivas, genera una clave ap_live_ y actualiza el secreto de servidor. Mantén la misma frontera server-only y valida un pago real de importe reducido.
Comprueba el resultado en Redsys, AndorPay y tu aplicación. Las suscripciones requieren tokenización o COF y MIT habilitados por el banco para el terminal productivo.