Docs publiques per integrar en producció

Integra pagaments Redsys a Andorra amb una API clara i sense custòdia.

L'objectiu és que un desenvolupador vegi el model complet en cinc minuts: clau server-side, checkout allotjat, webhook signat, idempotència, errors i contracte OpenAPI. Pensat per a comerços amb el seu propi TPV Redsys.

URL base

https://pontpay.ad/api/v1

Versió

2026-01-01

Auth

Bearer API key

Primera peticiócopy / paste
curl -X POST https://pontpay.ad/api/v1/payments \
  -H "Authorization: Bearer $PONTPAY_API_KEY" \
  -H "PontPay-Version: 2026-01-01" \
  -H "Idempotency-Key: order_4821_attempt_1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4500,
    "currency": "EUR",
    "description": "Reserva mesa 12",
    "customerName": "Ana Garcia",
    "customerEmail": "ana@example.com",
    "successUrl": "https://tu-dominio.com/gracias",
    "cancelUrl": "https://tu-dominio.com/cancelado",
    "webhookUrl": "https://tu-dominio.com/api/pontpay/webhook"
  }'

Vista ràpida

Tot el necessari per integrar sense obrir el dashboard.

Flux recomanat

El backend crea el pagament, el frontend redirigeix a checkoutUrl, el backend entrega per webhook signat.

Sense custòdia

Redsys i el banc del comerç processen. PontPay no reté ni mou fons.

Preparat per IA

OpenAPI, resum LLM i prompt protegit perquè Codex/Cursor no inventin contractes.

Model d'integració

Usuaris del panell, API keys i credencials Redsys són coses diferents.

1. Configura el comerç

Cada comerç inicia sessió, configura el seu FUC, terminal, secret Redsys i entorn. PontPay xifra aquestes credencials.

2. Crea el pagament

El teu servidor crea un pagament amb Bearer key, versió fixa i Idempotency-Key. El client mai veu claus secretes.

3. Redirigeix al checkout

PontPay retorna checkoutUrl. Redsys cobra amb el contracte bancari del comerç.

4. Entrega per webhook

El teu backend verifica X-PontPay-Signature, dedupica l'esdeveniment i confirma l'estat abans d'entregar.

Inici ràpid

Crea un pagament allotjat i envia el comprador a PontPay.

Crear pagamentPOST /api/v1/payments
curl -X POST https://pontpay.ad/api/v1/payments \
  -H "Authorization: Bearer $PONTPAY_API_KEY" \
  -H "PontPay-Version: 2026-01-01" \
  -H "Idempotency-Key: order_4821_attempt_1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4500,
    "currency": "EUR",
    "description": "Reserva mesa 12",
    "customerName": "Ana Garcia",
    "customerEmail": "ana@example.com",
    "successUrl": "https://tu-dominio.com/gracias",
    "cancelUrl": "https://tu-dominio.com/cancelado",
    "webhookUrl": "https://tu-dominio.com/api/pontpay/webhook"
  }'

Truca a PontPay des del teu backend, mai des de JavaScript públic.

Fixa PontPay-Version per evitar canvis silenciosos.

Fes servir una Idempotency-Key per cada intent lògic de checkout.

Fes servir un webhookUrl HTTPS públic; en local, fes servir un túnel HTTPS en comptes de localhost o IPs.

Persisteix payment.id, orderId i checkoutUrl abans de redirigir.

Entrega només amb webhook signat o GET /payments/{id}.

Checkout allotjat

El contracte principal és petit, estable i fàcil de mapar.

Respostapayment.checkoutUrl
{
  "id": "pay_01J2PONTPAY7K4D3",
  "amountCents": 4500,
  "currency": "EUR",
  "description": "Reserva mesa 12",
  "orderId": "PP2407010001",
  "status": "requires_payment",
  "checkoutUrl": "https://pontpay.ad/checkout?pay=pay_01J2PONTPAY7K4D3",
  "createdAt": "2026-07-01T10:30:00.000Z",
  "updatedAt": "2026-07-01T10:30:00.000Z"
}
Helper TypeScriptserver only
type PontPayPayment = {
  id: string;
  orderId: string;
  checkoutUrl: string;
  status: "requires_payment" | "succeeded" | "failed" | "expired";
};

export async function createPontPayPayment(input: {
  amount: number;
  description: string;
  customerEmail?: string;
  successUrl: string;
  cancelUrl: string;
  webhookUrl: string;
}) {
  const response = await fetch("https://pontpay.ad/api/v1/payments", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PONTPAY_API_KEY}`,
      "Content-Type": "application/json",
      "PontPay-Version": "2026-01-01",
      "Idempotency-Key": `checkout:${input.description}:${input.amount}`,
    },
    body: JSON.stringify({ currency: "EUR", ...input }),
  });

  const payload = await response.json();
  if (!response.ok) {
    throw new Error(payload.error ?? "pontpay_request_failed");
  }

  return payload as PontPayPayment;
}
POSTBearer key

/api/v1/payments

Crear pagament i obtenir checkoutUrl.

Idempotència: Obligatòria

GETBearer key

/api/v1/payments/{id}

Consultar l'estat final, orderId i traçabilitat.

Idempotència: No

POSTBearer key

/api/v1/payment-links

Crear links per a QR, reserves, dipòsits o factures.

Idempotència: Obligatòria

GETPublic

/api/v1/redsys/request/{paymentId}

Construir el payload signat per al checkout Redsys.

Idempotència: No

POSTRedsys

/api/v1/redsys/notify

Rebre i verificar la notificació bancària.

Idempotència: No

POSTBearer key

/api/v1/webhooks/test

Enviar un esdeveniment signat de prova al teu endpoint.

Idempotència: No

Webhooks

La redirecció és UX. Els webhooks són la font de veritat.

Verificar signaturaX-PontPay-Signature
import crypto from "node:crypto";

function verifyPontPaySignature(rawBody: string, signature: string) {
  const expected = crypto
    .createHmac("sha256", process.env.PONTPAY_WEBHOOK_SECRET!)
    .update(rawBody)
    .digest("hex");

  const received = Buffer.from(signature, "hex");
  const expectedBuffer = Buffer.from(expected, "hex");

  return (
    received.length === expectedBuffer.length &&
    crypto.timingSafeEqual(received, expectedBuffer)
  );
}

export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get("x-pontpay-signature") ?? "";

  if (!verifyPontPaySignature(rawBody, signature)) {
    return Response.json({ error: "invalid_signature" }, { status: 400 });
  }

  const event = JSON.parse(rawBody) as {
    type: string;
    data: { id: string; orderId: string; status: string };
  };

  // Store event.type + data.id before side effects.
  // Fulfill only after payment.succeeded or after GET /payments/{id}.

  return Response.json({ received: true });
}

Llegeix el raw body exacte abans de parsejar JSON.

Verifica HMAC-SHA256 en temps constant.

Dedupica per event type + data.id.

Respon 2xx ràpid i processa la feina pesada després.

Els reintents de PontPay són com a mínim una vegada; el teu handler ha de ser idempotent.

payment.succeeded

Quan arriba

Redsys va autoritzar i PontPay va confirmar.

Acció recomanada

Marca la comanda cobrada i entrega si no depèn de revisió.

payment.failed

Quan arriba

Redsys va rebutjar o va fallar l'intent.

Acció recomanada

No entreguis. Allibera la reserva o mostra recuperació de pagament.

payment.pending_confirmation

Quan arriba

El rail està en verificació intermèdia.

Acció recomanada

Manté l'usuari esperant; no facis fulfilment encara.

payment.refunded

Quan arriba

El pagament ha estat reemborsat completament.

Acció recomanada

Actualitza saldo, suport i comptabilitat interna.

redsys.signature_invalid

Quan arriba

Una notificació Redsys no s'ha pogut verificar.

Acció recomanada

Alerta operacions; no exposis detall tècnic al client.

Seguretat API

Controls obligatoris abans de producció.

Claus només al backend

L'API key de PontPay, el secret de webhook i les credencials Redsys viuen al servidor. No han d'aparèixer al navegador, apps mòbils, repositoris, prompts ni logs.

Callbacks públics HTTPS

webhookUrl i els endpoints registrats han d'utilitzar hostnames públics HTTPS. PontPay bloqueja http, localhost, IPs directes, sufixos interns, metadata hosts i credencials incrustades.

Reintents idempotents

Cada operació lògica de creació porta la seva Idempotency-Key estable. El backend del comerç dedupica webhooks abans d'entregar comandes o reserves.

Signatura i estat final

successUrl només confirma l'experiència del comprador. L'entrega es decideix amb webhook HMAC verificat o consultant GET /payments/{id}.

Permès: https://api.el-teu-domini.com/pontpay/webhook.

Bloquejat: http://, localhost, 127.0.0.1, IPs directes, .local, .internal i user:pass@host.

Guarda request_id, payment.id i orderId per a suport, auditoria i traçabilitat.

Revisa /llms.txt i OpenAPI quan canviï el contracte perquè els agents generin integracions correctes.

Fiabilitat

Reintenta sense duplicar cobraments ni entregues.

Regles d'idempotència

Mateixa operació lògica: reutilitza la mateixa Idempotency-Key.

Un altre carret, import, reserva o factura: crea una clau nova.

Registra request_id amb el teu orderId per a suport i auditoria.

Tracta el 409 com un conflicte entre clau i cos fins revisar-ho.

Backoff segur408/409/429/5xx
async function pontpayRequest(path: string, init: RequestInit) {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch(`https://pontpay.ad${path}`, init);

    if (response.ok) {
      return response.json();
    }

    if (![408, 409, 429, 500, 502, 503, 504].includes(response.status)) {
      const payload = await response.json().catch(() => ({}));
      throw new Error(payload.error ?? `pontpay_http_${response.status}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 300 * 2 ** attempt));
  }

  throw new Error("pontpay_retry_exhausted");
}

requires_payment

Pagament llest per entrar al checkout allotjat.

Acció

Redirigir a checkoutUrl.

redirected_to_redsys

Client enviat a Redsys.

Acció

Esperar notify/webhook.

succeeded

Redsys va autoritzar i PontPay va confirmar.

Acció

Entregar després de webhook/API fetch.

failed

Redsys va rebutjar o va fallar el pagament.

Acció

Mostrar reintent o alternativa.

expired

El checkout ha caducat.

Acció

Crear un nou pagament si encara cal.

Errors

Missatges segurs per a l'usuari, detall operatiu per als logs.

400idempotency_key_required

Genera una clau estable per operació.

401authentication_required

Envia Authorization: Bearer <key>.

404payment_not_found

Comprova entorn, tenant i id.

409payment_transition_invalid

Consulta l'estat actual abans de reintentar.

429rate_limited

Respecta retry-after i reintenta amb backoff.

503database_unavailable

Reintenta més tard i registra request_id.

Documentació per a IA

Copia un prompt que obliga l'agent a respectar el contracte real.

Prompt protegitCodex / Cursor / Claude
Estás integrando PontPay en mi backend.
Antes de escribir codigo, lee esta documentacion publica: https://pontpay.ad/developers

Reglas obligatorias:
- Usa la clave de API solo desde servidor. Nunca la expongas en navegador o app movil.
- PontPay no custodia fondos: Redsys y el banco del comercio procesan y liquidan.
- Crea pagos con POST /api/v1/payments, PontPay-Version e Idempotency-Key estable.
- Redirige al comprador a payment.checkoutUrl.
- No marques pedidos como pagados por successUrl. Usa webhook verificado o GET /payments/{id}.
- Las URLs de webhook deben ser HTTPS con hostname publico. No uses localhost, IPs, .local, .internal ni credenciales en URL.
- Verifica X-PontPay-Signature sobre el raw body exacto.
- Deduplica eventos por type + data.id.
- Si falta un campo o endpoint en la documentacion, pregunta. No inventes contratos.

Implementa:
1. Crear pago desde backend.
2. Redireccion a checkoutUrl.
3. Webhook firmado con idempotencia local.
4. Consulta final del pago antes de entregar.
5. Tests o smoke check acotados.