Andorpay
Navegación de la documentación

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_.

.env.local
dotenv
ANDORPAY_API_KEY=ap_test_replace_me
ANDORPAY_API_URL=https://api.andorpay.com
ANDORPAY_WEBHOOK_SECRET=whsec_replace_me
lib/andorpay.ts
typescript
import "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.

app/api/andorpay/checkout/route.ts
typescript
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.

Redirigir desde el navegador
typescript
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. 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. 2

    Crea una sesión

    La ruta local debe responder con hostedUrl y el backend de AndorPay con estado 201.
  3. 3

    Completa un pago sandbox

    Prueba autorización, 3DS y rechazo con las credenciales publicadas en la guía de pruebas.
  4. 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.

app/api/andorpay/webhook/route.ts
typescript
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íntomaCausa y corrección
401 api_key_invalidLa clave falta, está revocada o no se cargó en el proceso servidor.
400 checkout_idempotency_key_not_allowedElimina Idempotency-Key de la creación de checkout.
404 product_not_foundCopia un productId activo que pertenezca al comercio de la API key.
El navegador ve la API keyElimina NEXT_PUBLIC_, mueve la llamada a un módulo server-only y vuelve a desplegar.
hostedUrl no abreNo reconstruyas la URL. Usa el valor completo de la respuesta antes de expiresAt.
Firma inválidaVerifica 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.

Siguiente paso

WhatsApp