Commit Graph
4 Commits
Author SHA1 Message Date
devopsandClaude Sonnet 5 12c4afa09c fix(payments): reemplazar PayU LatAm por ePayco como pasarela PSE
PayU LatAm (integrado en el commit anterior) resultó inviable: Rapyd
compró ese negocio completo en LatAm/África en 2023, y el registro real
del usuario quedó en la plataforma de Rapyd (board.rapyd.net,
credenciales access_key/secret_key incompatibles con el módulo ya
construido, API completamente distinta). Se evaluó reconstruir contra
Rapyd, pero el usuario comparó costos para su volumen real (~8
ventas/mes) y encontró que ePayco cobra menos en PSE. Se investigó
ePayco en vivo: es 100% de Davivienda desde 2024, acepta liquidación a
cualquier banco colombiano (no exige Bancolombia/Nequi como Wompi) y da
tarifa preferencial a cuentas Davivienda -- el usuario ya tiene una.
Mejor encaje de negocio y más barato que PayU/Rapyd. Confirmado como
elección definitiva.

- src/modules/epayco-pse/ reemplaza a src/modules/payu-pse/: mismo
  contrato AbstractPaymentProvider (initiatePayment/authorizePayment
  "pending_authorization"/getWebhookActionAndData con firma SHA256
  verificada), pero envuelve el SDK oficial `epayco-sdk-node` en vez de
  llamar la API cruda -- leyendo el código fuente del SDK (no la
  documentación pública, que resultó desactualizada en más de un campo)
  se confirmó que el protocolo real de ePayco para PSE es bastante más
  particular de lo que sugieren los ejemplos: cada campo del payload se
  cifra con AES usando la private key del comercio, más un login previo
  que da un bearer token que también viaja en la misma request.
  Reimplementar esto a mano tenía mucho más riesgo de error que delegar
  en el SDK mantenido por el propio proveedor.
- Corregido en el camino: el método real para listar bancos es
  `bank.getBanks()`, no `pseBank()` (así aparecía en un resumen de
  búsqueda, pero no existe en el SDK real -- confirmado leyendo
  node_modules/epayco-sdk-node/lib/resources/bank.js directamente). El
  campo real para la URL de confirmación es `method_confirmation`
  (con guión bajo en medio), no `metodoconfirmacion` como mostraba la
  documentación pública -- confirmado contra tests/bank.js del propio
  paquete.
- retrievePayment sí se implementó esta vez (endpoint de validación de
  ePayco documentado); refundPayment sigue sin implementar -- ese
  endpoint no quedó verificado en vivo, tira error explícito en vez de
  fingir éxito.
- Frontend: el step de pago recupera el selector de banco + tipo de
  persona + documento (a diferencia del diseño que se había planeado
  para Rapyd, donde esos datos se recolectaban en la página del propio
  Rapyd/SafetyPay) -- ePayco sí necesita esos campos de nuestro lado,
  como PayU.
- Dos puntos quedan sin verificar contra un sandbox real (anotados en
  el código con qué hacer si fallan): el nombre exacto del campo con la
  URL del banco en la respuesta de bank.create() (initiatePayment tira
  un error con el payload completo si no matchea ningún candidato
  probado), y si p_cust_id_cliente/p_key del webhook son iguales a las
  API keys o valores aparte del dashboard de ePayco.

Verificado: build real de Medusa con las 7 env vars EPAYCO_* dummy,
smoke test del wrapper del SDK cargando y la verificación de firma
dando el resultado esperado contra un caso conocido, typecheck+lint+
build completo del frontend.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-07 03:58:33 +00:00
devopsandClaude Sonnet 5 2ceea23f40 feat(payments): integrar PayU LatAm (PSE) como pasarela de pagos
ADR 0009 ya había decidido PayU LatAm como proveedor (única opción que
no exige cuenta Bancolombia/Nequi) y confirmado que no existe ningún
plugin real de Medusa para su API -- los paquetes que aparecen
buscando "PayU" son para PayU Polonia/CEE, API completamente distinta
(clientId/posId/secondKey vs. apiLogin/apiKey/merchantId/accountId).
Este commit implementa esa decisión: módulo de pago custom
(AbstractPaymentProvider) para PSE (transferencia bancaria), primer
alcance del método de pago -- Nequi/Daviplata quedan fuera por ahora.

Backend (commerce-backend):
- src/modules/payu-pse/: initiatePayment arma y postea SUBMIT_TRANSACTION
  contra la API real de PayU LatAm; authorizePayment devuelve
  "pending_authorization" (PSE es async, la confirmación real llega por
  webhook, no en el momento de completar el carrito); getWebhookActionAndData
  valida la firma MD5 del webhook antes de confiar en cualquier
  confirmación de pago -- falla cerrado si no matchea. refundPayment/
  retrievePayment tiran error explícito en vez de simular éxito: esos
  endpoints de PayU no quedaron verificados en vivo y hay dinero real de
  por medio.
- No hace falta un subscriber propio para completar la orden -- el
  subscriber nativo de Medusa (payment-webhook + process-payment-workflow)
  ya hace ese trabajo con los métodos de arriba bien implementados
  (confirmado leyendo el código fuente instalado de @medusajs/medusa
  2.18.0, no la documentación).
- Correlación webhook↔sesión: Medusa inyecta su propio session_id en
  `data` antes de initiatePayment; se usa ese mismo valor como
  referenceCode de PayU, así el reference_sale que vuelve en el webhook
  ya es directamente el session_id de Medusa.
- src/lib/payu.ts: cliente HTTP + firmas (orden y webhook, fórmulas
  distintas, confirmadas contra el SDK PHP oficial de PayU).
- src/api/store/payments/pse-banks/route.ts: lista de bancos PSE,
  cacheada en memoria.
- medusa-config.ts, Dockerfile, configmap/secrets.template: 7 env vars
  nuevas (PAYU_API_LOGIN/API_KEY/MERCHANT_ID/ACCOUNT_ID/ENV/RESPONSE_URL/
  NOTIFY_URL), dummies de build agregados, secrets con placeholder
  "CAMBIAR".

Frontend (ecommerce):
- Nuevo step "payment" en components/checkout/checkout-client.tsx
  (reemplaza al step "review" que solo ofrecía WhatsApp): selector de
  banco, tipo de persona (N/J), tipo/número de documento, botón "Pagar
  con PSE" que crea la sesión de pago, completa el carrito y redirige
  al banco (navegación completa, no iframe -- así lo exige PayU).
  WhatsApp queda como respaldo manual si falla la iniciación del pago.
- lib/headless/providers/medusa-payment.ts + 3 rutas proxy nuevas bajo
  app/api/cart/, mismo patrón que el resto del carrito
  (medusaCartFetch, ahora exportado desde medusa-cart.ts).
- app/checkout/pse/return/: página de vuelta del banco -- nunca afirma
  éxito, la confirmación real es el webhook async.

Arranca contra las credenciales de sandbox genéricas y públicas de
PayU (developers.payulatam.com) mientras el usuario termina su propio
registro de comercio -- swap de las 4 credenciales via secret cuando
estén listas, sin cambios de código.

Verificado: build real de Medusa con las env vars dummy (no solo tsc),
tipos de AbstractPaymentProvider/WebhookActionResult/PaymentActions
confirmados contra @medusajs/utils y @medusajs/types instalados
(node_modules limpiado después), typecheck + lint + build completo del
frontend, páginas nuevas renderizando sin error. Pendiente: prueba
end-to-end contra el sandbox real de PayU (necesita el backend
desplegado) y habilitar el provider en la región Colombia desde el
Admin.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-07 01:17:22 +00:00
devops 751817ed61 Fase 1 ADR 0006 Etapa 2: infra de Meilisearch (StatefulSet+Service)
Motor de busqueda self-hosted, sin que Medusa lo conozca todavia
(eso es Fase 2). Mismo patron que redis/postgres/minio en este mismo
namespace: StatefulSet + PVC via volumeClaimTemplates en local-path,
nunca Deployment ni emptyDir -- el indice de Meilisearch necesita
sobrevivir a un reinicio del pod para no reindexar todo desde cero.

Imagen getmeili/meilisearch:v1.53.1 (ultimo release estable no-RC,
verificado contra la API de GitHub, no memoria de entrenamiento).
Recursos exactos del ADR (request 100m/256Mi, limit 500m/768Mi).
Sin securityContext explicito -- verificado en vivo que la imagen no
trae un usuario no-root (Alpine minimo, /etc/passwd sin usuario
dedicado), misma convencion que redis.yaml/minio.yaml en este repo,
que tampoco fuerzan runAsNonRoot.

MEILISEARCH_HOST (no secreto) va al ConfigMap commerce-config.
MEILI_MASTER_KEY/MEILISEARCH_API_KEY (mismo valor, dos nombres de env
var -- uno para el servidor, otro para el futuro plugin de Medusa en
Fase 2) documentados en secrets.template.yaml; el valor real ya se
aplico al Secret commerce-secrets en el cluster antes de este merge
(patch puntual, no via create-commerce-secrets.sh completo -- ese
script regenera TODAS las credenciales en cada corrida y hubiera
rotado postgres/redis/minio ya en uso).
2026-08-26 15:01:04 -05:00
devops aa857bc2ae fase 1
Build and Push Medusa / Construir y publicar Medusa (push) Failing after 15m12s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 9m23s
2026-07-31 23:27:01 -05:00