- hero-carousel.tsx: slides derivados de productos featured/nuevos reales (imagen, nombre, precio, link, badge de % OFF) en vez de un array hardcodeado; fallback estático solo si la tienda no tiene productos activos. - navbar.tsx: en mobile/tablet el buscador pasa a una segunda fila a todo el ancho debajo de logo+iconos (antes competía por espacio en una sola fila y quedaba recortado); desktop sin cambios. - docs: ADR 0010 documentando el pivote de pago PayU→Rapyd(descartado)→ ePayco, la integración de EnvíoClick y checklist de pruebas reales pendientes desde el storefront; puntero agregado desde ADR 0009. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
231 lines
13 KiB
Markdown
231 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); la
|
||
implementación real terminó usando ePayco en vez de PayU (PayU LatAm
|
||
quedó inviable, Rapyd se evaluó y se descartó sin código) y sumó
|
||
EnvíoClick para envío calculado — ver [ADR 0010](./0010-pago-envio-reales-y-storefront.md)
|
||
para el pivote completo y el estado final desplegado
|
||
**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.
|