Capa de interoperabilidad · Prueba técnica de arquitectura

ADR-035: dos caminos para escribir la tarjeta en Stripe, con interruptor

Aceptado el 15/11/2026 (pedido de Andrés). Complementa el ADR-013: agrega un segundo camino de captura de la tarjeta, la página alojada por Stripe, junto al componente de Stripe que el 013 ya definía. La saga, el contrato de la capa y las llaves de `payment-service` no cambian.

Contexto

Andrés, al ver la página de pago publicada (15/11/2026): «el checkout se contradice. Estamos haciendo el formulario de pago nosotros, y el flujo debería ir a Stripe para que sea allá que se ingresa la tarjeta.» Y después: «importante mostrar los dos escenarios y que sea switcheable, para mostrar los dos caminos y aún así las tarjetas no pasan por nosotros.» Ya había fijado que el proyecto no se mete en nada que involucre PCI DSS (Payment Card Industry Data Security Standard, el estándar de seguridad de datos de tarjetas; aplica a quien captura, guarda o transmite números de tarjeta).

Con el ADR-013, la tienda montaba el componente de tarjeta de Stripe (Card Element) dentro de su página. El número va del navegador a Stripe por un marco servido por Stripe, y la tienda recibe solo el pm_.... A la vista, sin embargo, el formulario parece de la tienda. En modo simulado, la tienda mostraba además un campo propio de número de tarjeta.

Restricción: no cambiar el contrato de la capa. payment-service cobra con POST /v1/payment_intents, payment_method = payment.token y confirm=true, sin customer.

Opciones

Opción Cómo Contrato de la capa Qué cuesta
A. Componente de Stripe (incrustado, el del 013) Marco de Stripe (js.stripe.com) dentro de la página de pago; Stripe.js devuelve el pm_... Igual El campo se ve dentro de la tienda, aunque lo sirve Stripe
B. Checkout en modo setup (alojado) La tienda manda al comprador a la página de Stripe, que solo guarda el medio de pago; a la vuelta, el Worker lee la sesión y toma el pm_... de su SetupIntent Igual: payment.token = pm_... Un salto fuera del sitio; una llave restringida en el Worker
C. Checkout en modo payment con captura manual Stripe autoriza en su página y la capa captura o cancela Cambia: payment.token pasa a ser un PaymentIntent autorizado y payment-service deja de crear el cobro Cambio de contrato y de servicio; otro carril

Verificado en la documentación vigente de Stripe (15/11/2026, docs.stripe.com):

Supuesto (sin confirmar en la documentación): un medio de pago sin cliente, guardado por una sesión setup, se puede cobrar al menos una vez. Si admite más de un cobro no importa: la saga cobra una sola vez por orden. Si la prueba de punta a punta de Andrés lo desmiente, el camino alojado se desactiva quitando la llave restringida y queda el incrustado, sin tocar código.

Decisión

Quedan las opciones A y B, con un interruptor. La C se descarta: cambia el contrato.

Incrustado (A) Alojado (B)
Dónde se escribe la tarjeta En el marco de Stripe dentro de la página de pago En checkout.stripe.com
Qué recibe la tienda pm_..., del navegador (Stripe.js) pm_..., que el Worker lee de la sesión
Llave del Worker STRIPE_PUBLISHABLE_KEY (pk_test_, pública) STRIPE_SECRET_KEY (rk_test_, restringida)
Orden en la capa POST /v1/orders con payment.token = pm_... Igual

Flujo alojado:

  1. El comprador escribe sus datos de entrega y pulsa "Continuar a Stripe". Los datos quedan en su pestaña: no viajan a Stripe ni se guardan en el Worker.
  2. El Worker crea una sesión de Checkout en modo setup (payment_method_types=card, client_reference_id = llave de idempotencia de la compra, sin customer), con esa llave como Idempotency-Key de Stripe.
  3. Stripe captura la tarjeta y vuelve a /pago/vuelta?session_id=...; cancelar vuelve a /carrito?pago=cancelado, sin orden.
  4. El Worker lee la sesión y exige status=complete, el SetupIntent succeeded y que la sesión sea de esta compra. Crea la orden con payment.token = pm_... y la Idempotency-Key de siempre.
  5. Volver dos veces o recargar no crea otra orden: la sesión queda ligada a su orden en ORDERS (sin datos personales) y la capa deduplica por la llave.

Interruptor:

Cada camino dice en la página, en una frase, dónde se escribe la tarjeta y quién la recibe.

Llave del camino alojado

Restringida y de prueba (rk_test_), con solo:

Permiso de Stripe Nivel
Checkout Sessions Write
SetupIntents Read

Todo lo demás en None. Va como secreto STRIPE_SECRET_KEY del Worker, nunca en el repositorio. La crea y la carga Andrés (docs/demo/12-pago-con-stripe-dos-caminos.md). No sirve la llave de acceso total ni la de payment-service.

Qué se sacrifica

Por qué ninguno trae la tienda ni la capa al alcance de PCI DSS

En los dos, la tarjeta se escribe en un elemento servido por Stripe (su página o su marco) y va directo a Stripe. La tienda, su Worker y la capa solo ven el pm_.... Es el modelo de integración de Stripe para quien no quiere tocar datos de tarjeta. La forma exacta en que cada uno lo declararía ante un auditor no se evaluó: no aplica a un demo sin cobro real.

Cómo se verifica