Verificado el codigo/README real de los dos unicos paquetes de Medusa que aparecen buscando 'PayU' (@tax1driver/medusa-payu y medusa-plugin-payu de rigby-sh, este ultimo archivado desde 2025-03-21): ambos usan la terminologia de la API de PayU Polonia/CEE (clientId/posId/secondKey, OAuth), no la de PayU LatAm (apiLogin/apiKey/accountId, developers.payulatam.com, la que cubre Colombia). 'PayU' es una marca compartida por dos productos distintos. La decision de negocio (PayU como proveedor) se mantiene -- cambia la via tecnica: integracion via modulo de pago custom (AbstractPaymentProvider de Medusa) contra la API real de PayU LatAm, mismo tipo de esfuerzo que EnvioClick, no instalacion de plugin. De paso, documenta las shipping options ya creadas y verificadas en produccion (stock location, fulfillment set, service zone Colombia, shipping option de 5.000 COP) incluyendo el paso no documentado que las dejaba invisibles para el Store API (vinculo a sales channel).
228 lines
13 KiB
Markdown
228 lines
13 KiB
Markdown
# ADR 0009 — Checkout real (pago en línea + envíos) para ARI Shopping
|
||
|
||
**Estado:** aceptada — proveedor de pago decidido (PayU, §3);
|
||
implementación real (credenciales, shipping options, código) pendiente
|
||
**Fecha:** 2026-08-28
|
||
|
||
## Contexto
|
||
|
||
El carrito hoy no completa una transacción real: arma el pedido en el
|
||
cliente y lo cierra con un botón "Continuar por WhatsApp"
|
||
(`components/layout/navbar.tsx`, `mobile-add-to-cart.tsx`) — un
|
||
placeholder intencional, no un bug. El diálogo de cuenta ya lo admite
|
||
explícitamente ("El inicio de sesión quedará conectado al backend
|
||
headless. Mientras tanto..."). Ni checkout ni auth de clientes tienen
|
||
ADR previo ni intento de implementación en el historial de git de
|
||
ningún repo del lab — este es terreno nuevo.
|
||
|
||
Investigación previa a este ADR (2026-08-28, confirmado contra código
|
||
real y documentación oficial vigente, no memoria de entrenamiento —
|
||
misma disciplina que ADR 0006):
|
||
|
||
- **Pagos:** `commerce-backend/medusa-config.ts` no registra ningún
|
||
provider de pago — el único módulo configurado hoy es
|
||
`@medusajs/medusa/file` (storage S3).
|
||
- **Envíos:** existe una sola región ("Colombia", COP), creada a mano
|
||
vía Admin API durante el incidente de precios de agosto
|
||
(`incidente-medusa-precios-region-id-2026-08.md`), no vía ningún
|
||
script de seed — no existe ningún script de seed en el repo. **No
|
||
hay ninguna shipping option configurada en ningún lado.** Sin esto
|
||
el checkout no puede completarse aunque el pago ya funcione — es un
|
||
prerequisito invisible, independiente del proveedor de pago elegido.
|
||
- **Auth:** cero código de cliente/login en todo el storefront (grep
|
||
sobre `lib/`, `app/`, `components/` sin resultados para
|
||
`customer`/`login`/`useAuth`/`/store/customers`). El módulo de
|
||
auth/customer de Medusa viene incluido en el core — falta
|
||
enteramente el lado del storefront, no el backend. **Medusa soporta
|
||
checkout como invitado**, así que auth no bloquea checkout y puede
|
||
secuenciarse por separado — fuera de alcance de este ADR.
|
||
|
||
## 1. Flujo de checkout real en Medusa v2 (Store API)
|
||
|
||
Confirmado contra `docs.medusajs.com` (2026-08-28): el flujo son 7
|
||
pasos encadenados — crear carrito → agregar items → dirección de envío
|
||
→ **seleccionar método de envío** (`POST` a la ruta de shipping method
|
||
del carrito, requiere que ya exista al menos una shipping option) →
|
||
payment collection → payment session → completar orden. Ningún paso
|
||
es opcional; el paso de método de envío es exactamente el que hoy no
|
||
tiene ninguna shipping option que ofrecer.
|
||
|
||
## 2. Estado real de soporte de pago en Medusa 2.18.0
|
||
|
||
- **Stripe es de primera clase** (`@medusajs/medusa/payment-stripe`,
|
||
incluido en el core, no un plugin de terceros — a diferencia de la
|
||
situación de Meilisearch en ADR 0006). Configuración mínima
|
||
verificada:
|
||
|
||
```ts
|
||
{
|
||
resolve: "@medusajs/medusa/payment",
|
||
options: {
|
||
providers: [{
|
||
resolve: "@medusajs/medusa/payment-stripe",
|
||
id: "stripe",
|
||
options: { apiKey: process.env.STRIPE_API_KEY },
|
||
}],
|
||
},
|
||
}
|
||
```
|
||
|
||
Env vars: `STRIPE_API_KEY` (siempre) y `STRIPE_WEBHOOK_SECRET`
|
||
(obligatorio en producción). Tras configurar, hay que habilitar el
|
||
provider **en la región** desde el Admin — un paso manual aparte,
|
||
igual que ya pasó con la región misma.
|
||
|
||
- **Bloqueador real de negocio, no técnico: Stripe no acepta cuentas
|
||
de comercio registradas en Colombia.** Confirmado contra
|
||
`stripe.com/global` (2026-08-28) — Brasil y México sí están en la
|
||
lista de países soportados para abrir cuenta, Colombia no aparece.
|
||
La única vía sería registrar una entidad de negocio en un país
|
||
soportado, fuera de alcance realista para este lab. Esto invalida a
|
||
Stripe como opción pese a ser la integración técnicamente más
|
||
simple y mejor documentada.
|
||
- **Alternativas con presencia real en Colombia y algún plugin de
|
||
Medusa existente:**
|
||
- **PayU** (`@tax1driver/medusa-payu`): el propio repo declara
|
||
compatibilidad explícita con Medusa `v2.4.0+` — cubre nuestra
|
||
versión (2.18.0). Webhook auto-registrado en `/hooks/payment/payu`.
|
||
- **Wompi** (`medusa-payment-wompi-col`, respaldado por Bancolombia,
|
||
liquidación directa en COP): revisé el repo directamente
|
||
(2026-08-28) y **no documenta contra qué versión de Medusa se
|
||
probó, ni tiene releases/actividad reciente visible** — mayor
|
||
riesgo de compatibilidad no verificada que PayU, aunque el
|
||
encaje de negocio (banco local, moneda nativa) sea el más
|
||
directo. Antes de elegirlo habría que repetir el nivel de
|
||
verificación que ADR 0006 le hizo al plugin de Meilisearch: leer
|
||
el código fuente real, no confiar en el README.
|
||
- **Riesgo de red ya conocido, no nuevo:** cualquier webhook de pago
|
||
entra por la misma cadena Cloudflare → NPM → Traefik que ya causó
|
||
un incidente real con otro servicio
|
||
(`incidente-cloudflared-504-quic-2026-08.md`). Validar la entrega de
|
||
webhooks en vivo antes de confiar en el flujo, no asumir que
|
||
"funciona igual que todo lo demás".
|
||
|
||
## 3. Decisión de proveedor — PayU
|
||
|
||
Dato de negocio real que decidió esto (aportado por el usuario,
|
||
2026-08-28): las cuentas bancarias disponibles son Davivienda, Lulo
|
||
Bank y Banco de Bogotá — **ninguna es Bancolombia ni Nequi**.
|
||
|
||
Verificado contra fuentes reales el mismo día (no memoria):
|
||
|
||
- **Wompi exige cuenta Bancolombia o, como mínimo, cuenta Nequi para
|
||
poder registrarse como comercio** (modelo Agregador) — confirmado
|
||
contra el reglamento de comercios de Wompi y su centro de soporte.
|
||
La liquidación de ventas sí puede ir después a cualquier banco
|
||
colombiano, pero la puerta de entrada al registro sigue siendo
|
||
Bancolombia/Nequi. Con los bancos disponibles hoy, esto exigiría
|
||
abrir una cuenta Nequi nueva solo para poder registrarse — paso
|
||
extra evitable.
|
||
- **PayU solo exige "cuenta bancaria colombiana a nombre del
|
||
comercio"**, sin banco específico — Davivienda, Lulo Bank o Banco de
|
||
Bogotá califican igual, sin pasos previos. Además ya soporta **PSE**
|
||
(transferencia desde cualquier banco colombiano del cliente, sin
|
||
relación con el banco del comercio), **Nequi** y **Daviplata** como
|
||
métodos de pago para los clientes vía redirección — cubre exactamente
|
||
los métodos que se pidieron (PSE + Nequi "y demás bancos") sin que
|
||
la cuenta bancaria propia del comercio sea Bancolombia.
|
||
|
||
**Decisión:** PayU como proveedor de pago (negocio) se mantiene. La vía
|
||
técnica cambió el 2026-08-29 — ver nota de implementación al final de
|
||
este documento: ningún plugin comunitario de Medusa sirve para PayU
|
||
LatAm, hace falta un módulo de pago custom. Wompi queda descartado por
|
||
el requisito de registro Bancolombia/Nequi, no por limitación técnica.
|
||
|
||
**Pendiente antes de implementar (no resoluble en este ADR):**
|
||
|
||
- Registro real de comercio en PayU (persona natural o jurídica —
|
||
jurídica pide NIT, representante legal y estados financieros
|
||
básicos) y credenciales reales (`apiLogin`, `apiKey`, `merchantId`,
|
||
`accountId` — terminología real de la API de PayU LatAm, ver nota de
|
||
implementación) — nunca generadas ni asumidas por mí.
|
||
- Escribir el módulo de pago custom para PayU LatAm (no instalar un
|
||
plugin — no existe ninguno real para esta API, ver nota de
|
||
implementación).
|
||
|
||
## Opciones consideradas
|
||
|
||
| Opción | A favor | En contra |
|
||
|---|---|---|
|
||
| **Stripe** | Integración core de Medusa, mejor documentada, configuración mínima | No acepta comercios registrados en Colombia — bloqueador de negocio, no técnico |
|
||
| **PayU** — **elegido** (proveedor de negocio; integración técnica ahora es custom, ver nota de implementación) | Solo exige cuenta bancaria colombiana a nombre del comercio, sin banco específico (encaja con Davivienda/Lulo Bank/Banco de Bogotá); soporta PSE + Nequi + Daviplata para el cliente sin importar el banco del comercio | Ningún plugin de Medusa real sirve para su API LatAm (`apiLogin`/`apiKey`/`accountId`) — los dos paquetes que parecían encajar por nombre son en realidad para PayU Polonia/CEE (`clientId`/`posId`/`secondKey`), uno de ellos archivado; requiere módulo de pago custom |
|
||
| **Wompi** (`medusa-payment-wompi-col`) | Liquidación directa en COP vía Bancolombia | Exige cuenta Bancolombia o, como mínimo, Nequi solo para **registrarse** como comercio — ninguno de los bancos disponibles (Davivienda/Lulo Bank/Banco de Bogotá) califica sin abrir una cuenta nueva; además, sin versión de Medusa documentada ni actividad reciente visible en el plugin |
|
||
| **Seguir con WhatsApp** (no pago en línea) | Cero riesgo nuevo, cero dependencia externa | No resuelve el objetivo real (transacción completa sin intervención manual); no es checkout, es lo que ya existe hoy |
|
||
|
||
## Consecuencias
|
||
|
||
- Las shipping options son un prerequisito independiente del proveedor
|
||
y deben configurarse primero — sin ellas ningún flujo de checkout
|
||
completa, sea cual sea el pago.
|
||
- El pago de PayU se implementa como módulo custom (`AbstractPaymentProvider`
|
||
de Medusa) contra la API real de PayU LatAm, no como instalación de
|
||
un plugin de terceros — más superficie propia para mantener, mismo
|
||
patrón de esfuerzo que ya se identificó para EnvíoClick en §2.
|
||
- Los webhooks de pago (a definir en el módulo custom, apuntando a la
|
||
URL de notificación real de PayU LatAm) deben validarse en vivo
|
||
contra la cadena de red real del lab antes de confiar en el flujo,
|
||
dado el precedente de `incidente-cloudflared-504-quic-2026-08.md`.
|
||
- Se acepta como deuda: si en el futuro se abre una cuenta Bancolombia
|
||
o Nequi, Wompi (liquidación más directa en COP) vuelve a ser una
|
||
opción válida a reevaluar — no se descarta por limitación técnica,
|
||
solo por el requisito de registro actual.
|
||
- Revertir esta decisión implica deshabilitar el provider en la región
|
||
y volver al botón de WhatsApp — el carrito y el catálogo no cambian
|
||
de forma, solo el paso final.
|
||
|
||
## Nota de implementación (2026-08-29): shipping options creadas
|
||
|
||
Bloqueador de §1 resuelto en producción, verificado extremo a extremo
|
||
contra el Store API real (no solo creado en Admin):
|
||
|
||
- Stock location "Bodega principal ARI Shopping" (`sloc_01M15G3D1336V8WQW3VZQRP6AN`).
|
||
- Fulfillment set + zona de servicio "Colombia" (`geo_zone` tipo país, `co`).
|
||
- Provider `manual_manual` habilitado en la ubicación.
|
||
- Shipping option "Envío nacional estándar", tarifa fija $15.000 COP
|
||
(placeholder de negocio dentro del rango real de mercado
|
||
$12.000–$18.000 que dio el usuario, no un cálculo por peso/volumen —
|
||
eso es justamente lo que EnvíoClick resolvería más adelante),
|
||
vinculada al Default Shipping Profile y a la región Colombia/COP.
|
||
- **Paso no documentado en ningún lado y que rompía todo en silencio:**
|
||
la stock location tiene que estar vinculada al Sales Channel
|
||
(`/admin/stock-locations/{id}/sales-channels`) para que el Store API
|
||
devuelva la shipping option — sin esto, `GET /store/shipping-options`
|
||
devolvía `[]` vacío pese a que el fulfillment set, la zona de
|
||
servicio y el provider ya estaban bien configurados. Confirmado con
|
||
un carrito de prueba real (dirección `co` + `cart_id`) antes y
|
||
después del vínculo.
|
||
|
||
## Nota de implementación 2 (2026-08-29): corrección — no existe plugin real para PayU LatAm
|
||
|
||
Antes de instalar nada se verificó el código/README real de los dos
|
||
únicos paquetes de Medusa que aparecen buscando "PayU" — misma
|
||
disciplina que ya aplicó ADR 0006 al plugin de Meilisearch, esta vez
|
||
con un resultado distinto: **ninguno de los dos sirve.**
|
||
|
||
- `@tax1driver/medusa-payu`: opciones de configuración `clientId`,
|
||
`clientSecret`, `merchantPosId`, `secondKey` — terminología exacta
|
||
de la API de **PayU Polonia/CEE** (OAuth + `posId`), no de PayU
|
||
LatAm.
|
||
- `medusa-plugin-payu` (`rigby-sh/medusa-plugin-payu`): mismo problema
|
||
(`clientId`, `posId`, sandbox en `secure.snd.payu.com`, dominio de
|
||
PayU Europa) y además **archivado desde 2025-03-21** — sin
|
||
mantenimiento.
|
||
- La API real que se necesita, PayU LatAm
|
||
(`developers.payulatam.com`, la que cubre Colombia junto con
|
||
Argentina/Brasil/Chile/México/Panamá/Perú), autentica con
|
||
`apiLogin`/`apiKey` en el objeto merchant y `accountId` en el
|
||
objeto order — un modelo de autenticación distinto, no solo nombres
|
||
de campo distintos.
|
||
|
||
"PayU" es una marca compartida por dos productos/API históricamente
|
||
separados (LatAm vs. Europa/CEE); un paquete que dice "PayU" a secas
|
||
sin especificar LatAm es, con alta probabilidad, para el producto
|
||
europeo. **Consecuencia:** la integración de pago se implementa como
|
||
módulo custom de Medusa (`AbstractPaymentProvider`) contra
|
||
`developers.payulatam.com` directamente — mismo tipo de esfuerzo que
|
||
ya se había anticipado para EnvíoClick en §2, no una instalación de
|
||
plugin como asumía la decisión original de este ADR.
|