Changelog y deprecación#
Esta página cubre tres cosas distintas que conviene no mezclar: qué cambió, cómo se avisa de lo que va a desaparecer y cuánto tiempo se mantiene cada línea.
Contrato /v1#
La fuente de verdad es contracts/openapi/payment-api.v1.yaml. La referencia de la API se genera de
ahí: corregir la referencia es corregir el YAML, nunca la página.
1.0.0#
Primera versión del contrato público. Superficie completa:
POST /v1/payment-intents sk_ Idempotency-Key obligatoria
GET /v1/payment-intents/{id} sk_ | pk_ + X-Client-Secret
POST /v1/payment-intents/{id}/confirm sk_ | pk_ + X-Client-Secret
POST /v1/payment-intents/{id}/cancel sk_
POST /v1/refunds sk_ Idempotency-Key obligatoria
GET /v1/refunds/{id} sk_
GET /v1/transactions sk_ cursor + limit
POST /v1/gateways/{gatewayId}/webhooks sin auth firma por pasarela
Decisiones que quedaron fijadas con esta versión y que no pueden cambiar dentro de /v1:
- Autenticación por header
X-API-Key; elclient_secretsiempre enX-Client-Secret, nunca como query param. - Dinero:
amount_minorentero en unidades menores +currencyISO 4217. CLP tiene exponente 0. - Identificadores públicos opacos con prefijo:
pi_,re_,txn_,evt_. - Errores RFC 9457
application/problem+jsoncon el miembro de extensióncodeestable. - Paginación sólo por cursor, nunca por offset.
- Siete estados canónicos del intent y su matriz de transiciones.
- Diecisiete códigos de error. Ver Errores.
- Envelope de webhooks salientes
{ id, type, created, livemode, data: { object } }y firmaApiPay-Signature: t=…,v1=…sobre"{t}.{cuerpo_crudo}". - Siete tipos de evento:
payment_intent.succeeded|failed|canceled|requires_action|expired,refund.succeeded|failed.
Pasarelas disponibles#
gateway_id | Estado | Notas |
|---|---|---|
sandbox | Disponible, sólo livemode=false | Resultados deterministas por los dos últimos dígitos del monto |
webpay | Disponible | Redirección top-level y segundo confirm con token_ws |
mercadopago | Parcial | Creación y preferencia con back_urls cableadas; la confirmación con el token del Brick necesita que el confirm transporte importe y divisa |
sandbox se niega a resolver con una clave live y responde 400 validation_error; el backoffice no lo
ofrece en la configuración live de un tenant.
Este catálogo no incluye Stripe. Hubo un adaptador de Stripe, escrito como ejemplo del SPI
PaymentGateway, y se retiró antes de publicar 1.0.0 porque no es un medio de pago que la
plataforma quiera ofrecer. Se anota aquí porque el catálogo de pasarelas es parte de lo que ves, no
por versionado: como 1.0.0 nunca llegó a publicarse, retirarlo no es un cambio incompatible ni
arrastra un /v2: no existió integración que pudiera depender de gateway_id: "stripe".
Deudas conocidas del contrato#
Diferencias reales entre el YAML y la implementación, listadas aquí porque afectan a lo que ves:
| Punto | Situación |
|---|---|
payment_method | El contrato lo declara opcional y un ejemplo del confirm lo muestra poblado con marca y last4. En la práctica es siempre null: ningún adaptador captura metadatos de tarjeta por alcance PCI DSS SAQ A. No construyas UI sobre ese campo |
gateway_params en el confirm | El borde REST lo acepta (es el canal del token_ws de Webpay) y fusiona además los parámetros de query, pero el schema del contrato no lo declara. Los cuatro SDKs modelan sólo payment_method_token y return_url |
title de los 422 | El contrato dice Unprocessable Entity; el servicio emite Unprocessable Content. Es inocuo —title es texto para humanos y sólo se usa como relleno— pero el desactualizado es el YAML |
Idempotency-Replayed | El contrato define el header, y ningún SDK lo expone. Hoy no puedes distinguir una creación nueva de la respuesta replicada tras reintentar con la misma clave |
POST /v1/…/capture | No existe. SEPARATE_CAPTURE es una capacidad del adaptador de MercadoPago sin superficie pública: no uses capture_method: manual |
SDKs#
SemVer independiente por SDK: un fix del cliente PHP no obliga a publicar nada en Python. La única regla estructural es que el major del contrato arrastra el major de los cuatro a la vez.
| Versión del contrato | @apipay/node | apipay/apipay-php | apipay | com.apipay:apipay-java |
|---|---|---|---|---|
/v1 | 1.x | 1.x | 1.x | 1.x |
/v2 (futuro) | 2.x | 2.x | 2.x | 2.x |
1.0.0 — los cuatro server-side#
Primera versión. Superficie idéntica en los cuatro, y la capa artesanal completa:
- Transporte propio sin SDK de terceros:
fetchnativo en Node 22, PSR-18 descubierto en PHP,httpxen Python (síncrono y asíncrono) yjava.net.httpen Java. - Asimetría de reintentos:
GETsí,POSTdel que se recibió respuesta jamás. Backoff exponencial con jitter completo (base 500 ms, factor 2, tope 8 s,maxRetries2),Retry-Afterhonrado sólo en429y503y con tope de 60 s. Idempotency-KeyUUID v4 generada con el CSPRNG del lenguaje y expuesta al llamador.- Auto-paginación por cursor;
cursorynext_cursornunca visibles. - Los diecisiete códigos mapeados a excepciones tipadas, con
code,status,problemyrequestId. - Verificación de webhooks con HMAC en tiempo constante, tolerancia 300 s y varias firmas
v1. - Una
pk_se rechaza localmente al construir el cliente, en los cuatro.
1.0.0 — @apipay/checkout-js#
- Monta el checkout alojado en un iframe de
checkout.apipay.io; cero dependencias de runtime y menos de 15 kB gzip, verificado en CI. - Cuatro validaciones en cascada sobre cada
postMessage: origen exacto,event.sourceidéntico al iframe montado, forma tipada y nonce por sesión de 16 bytes. client_secrety nonce viajan en el fragment delsrc, no en el querystring: no llegan a logs de servidor ni a la cabeceraReferer.- Reenvía
token_wsyTBK_TOKENde la URL del comercio al iframe, que es lo que permite el segundoconfirmde Webpay desde un iframe de otro origen. - Distribución dual: npm y script tag en rutas versionadas inmutables con SRI.
Estado de verificación#
| SDK | Qué se ejecutó | Resultado |
|---|---|---|
@apipay/node | typecheck, lint, test, build | 92 tests en verde |
apipay (Python) | pytest, mypy --strict, ruff | 165 tests en verde |
com.apipay:apipay-java | build (incluye compilar los ejemplos) | 28 tests en verde |
apipay/apipay-php | — | Sin ejecutar: hace falta una máquina con PHP 8.3 y Composer |
285 tests en verde en los tres lenguajes verificables. El de PHP se revisó por lectura exhaustiva y el
veredicto fue que pasaría, pero eso no sustituye a ejecutarlo: es requisito antes de publicar
apipay/apipay-php.
Divergencias conocidas entre los cuatro#
| Punto | Situación |
|---|---|
| Verificación de webhooks en PHP | Estática (ApiPay\Webhooks::constructEvent) en lugar de colgar del cliente. Deliberada: el handler no debe construir un cliente con la sk_ para comprobar un HMAC |
| Jerarquía de errores de PHP | Más gruesa: 16 clases frente a 22 en Python y 23 en Java. card_declined y gateway_rejected comparten GatewayDeclinedException y toda la familia 422 cae en ValidationException. Un comercio en PHP no puede escribir catch (CardDeclinedException) |
| Reintento de errores de red pre-envío | Python y Java lo distinguen; TypeScript y PHP no pueden (fetch y NetworkExceptionInterface no separan pre de post envío) y por eso no reintentan. Es la mitad más conservadora, nunca la menos segura |
| Baseline de Java | 17, no el 25 del backend: el artefacto lo consumen terceros |
| Modelos "generados" | Los cuatro los tienen escritos a mano contra el contrato. El generador existe y funciona, pero escribe en sdks/generator/out/ como material de comparación: no sobreescribe código artesanal. Un cambio del contrato hay que reflejarlo a mano en los cuatro SDKs, en el mismo PR |
Política de deprecación#
Cambios aditivos: no rompen versión#
Estos cambios pueden llegar en cualquier momento dentro de /v1 y tu integración tiene que
tolerarlos:
- Campos opcionales nuevos en las respuestas.
- Valores nuevos y documentados en un enum.
- Tipos de evento nuevos en el catálogo de webhooks.
- Miembros de extensión nuevos en un documento
problem+json.
Cambios incompatibles: /v2#
Un cambio incompatible exige payment-api.v2.yaml con path /v2 y el major sincronizado de los
cuatro SDKs, con un periodo de convivencia /v1–/v2. Mientras exista sólo /v1 hay una única
versión del portal; cuando nazca /v2, el contenido se publica en paralelo con selector de versión y
cada versión referencia el YAML de su major.
Son cambios incompatibles, entre otros: eliminar o renombrar un campo, hacer obligatorio uno opcional,
cambiar el tipo de un campo, retirar un valor de enum, cambiar el significado de un code y cambiar el
formato de la firma de webhooks.
Aviso: Deprecation y Sunset#
Todo endpoint o versión en retirada anuncia su estado en las propias respuestas:
| Cabecera | RFC | Qué dice |
|---|---|---|
Deprecation | RFC 9745 | Que el recurso está deprecado, y desde cuándo |
Sunset | RFC 8594 | La fecha a partir de la cual dejará de responder |
Entre el anuncio y el corte hay un mínimo de seis meses. Los avisos se publican además en el portal
(banner y esta página) y por email a los tenants afectados, identificados por telemetría de uso
real: el User-Agent de los SDKs y las métricas por endpoint de OpenTelemetry. No por listas
manuales, que siempre están desactualizadas y siempre avisan a quien no toca.
Las cabeceras Deprecation y Sunset todavía no aparecen en el contrato: nada está deprecado en
1.0.0, así que no hay nada que anunciar. Se añadirán al YAML con la primera deprecación real.
Política de soporte de SDKs#
| Línea | Estado | Recibe | Duración |
|---|---|---|---|
| Major actual (N) | Activa | Funcionalidad, fixes y seguridad | Mientras sea la vigente |
| Major anterior (N−1) | Mantenimiento | Sólo parches de seguridad | 12 meses desde la publicación de N |
| N−2 y anteriores | Fin de vida | Nada; el portal y el README lo marcan EOL | — |
Hoy sólo existe la línea 1.x, así que no hay nada en mantenimiento ni en EOL. Los runtimes mínimos
son parte del contrato de cada major y sólo pueden subir en un major:
| SDK | Runtime mínimo de 1.x |
|---|---|
@apipay/node | Node.js 22 LTS |
apipay/apipay-php | PHP 8.3 |
apipay | Python 3.12 |
com.apipay:apipay-java | Java 17 |
@apipay/checkout-js | Navegadores evergreen y Safari 16.4+ |
Cómo se genera este changelog#
No se redacta: se deriva. Los conventional commits de las áreas sdks/ y contracts/ alimentan
la decisión de bump SemVer y el texto de cada entrada. release-sdks.yml lo compila al publicar cada
tag sdk-v<X.Y.Z>-<lenguaje> (o -all para los majors sincronizados), publica el GitHub Release y el
portal se reconstruye en el siguiente push a main.
Suscribirse a los cambios#
Mientras no exista el feed automático, lo fiable es:
- Vigilar las cabeceras
DeprecationySunseten tus respuestas, y loguearlas. Es el aviso que llega a tu código sin que nadie tenga que leer un correo. - Ramificar siempre por
code, no pordetail, y tener eldefault: ignoraren su sitio. - Fijar la versión mayor del SDK (
^1.0.0,1.x) y no la exacta: los patches de seguridad de la línea activa llegan solos. - Mantener el
User-Agentcon telemetría activada para que el aviso por email te alcance.