Enunciado y contexto aplicable
Diseña un sistema de procesamiento de pagos para comercio electrónico. Después de que un comprador envía un pedido, el sistema utiliza un proveedor de servicios de pago externo para procesar un pago único con tarjeta. Autoriza en el momento del checkout, realiza la captura después de reservar el inventario y libera la autorización si el pedido se cancela. Tras la captura, admite múltiples reembolsos parciales, pero su monto acumulado nunca debe exceder el monto capturado. Un método de pago puede requerir autenticación adicional como 3DS, y el resultado final puede llegar después de que el navegador retorne.
Asume cinco millones de intentos de pago por día, alrededor de 58 TPS en promedio y 500 TPS en horas pico. La API create-payment tiene un p99 inferior a 300 milisegundos, las lecturas de estado tienen un p99 inferior a 200 milisegundos y la API local tiene un objetivo de disponibilidad mensual del 99.99%. Esa disponibilidad no promete la finalización por parte del proveedor. El dinero es un entero en la unidad menor de la moneda, y un pago tiene exactamente una moneda. El proveedor, la red y los procesos locales pueden fallar. Estos números y plazos son suposiciones para la entrevista, no promesas realizadas por un producto de pagos.
El alcance incluye la creación de pagos, autenticación adicional, autorización, captura, cancelación de autorización, reembolsos parciales y totales, el ledger de dinero, webhooks del proveedor y conciliación. Las devoluciones de cargo (chargebacks), un modelo de fraude, el cambio de divisas, los pagos a comerciantes (payouts), los impuestos y un programa integral de cumplimiento de PCI están fuera de alcance, pero la respuesta debe identificar esos límites. La página alojada por el proveedor o su componente de tokenización recopila los detalles de la tarjeta. Este sistema almacena un token del método de pago y no debe recibir ni registrar en logs el número de tarjeta en texto sin formato ni el código de seguridad.
Qué evalúa el entrevistador
La primera señal es si el candidato separa la intención de negocio, los intentos del proveedor y los hechos monetarios. Un carrito se asigna a un payment_id interno, pero puede tener múltiples intentos de autenticación o del proveedor. Una solicitud con tiempo de espera agotado (timeout) tampoco implica un pago fallido. Una respuesta sólida no comprime todo el ciclo de vida en paid=true. Almacena por separado el agregado de pago, cada operación y resultado desconocido, las referencias del proveedor y las entradas del ledger.
La segunda señal es el razonamiento preciso a través de un límite distribuido. Una transacción de base de datos no puede incluir de forma atómica a un proveedor de pagos externo. El proveedor podría capturar mientras su respuesta se pierde, un webhook podría llegar antes de la respuesta sincrónica, o un proceso podría bloquearse tras un commit local. Las claves de idempotencia del llamador, los ID de operación internos, las claves de idempotencia del proveedor, un transactional outbox, la deduplicación de webhooks y la consulta de estado hacen que la ejecución repetida converja. No crean una transacción exacta de extremo a extremo de tipo exactly-once.
La tercera señal es una máquina de estados monótona y auditable. La autorización y la captura son etapas monetarias distintas. REQUIRES_ACTION no es un fallo, y un timeout del proveedor que produce una operación UNKNOWN no puede hacer fallar inmediatamente el pago. Un reembolso debe hacer referencia a un pago capturado, usar dinero exacto y preservar el invariante de que los reembolsos confirmados más los en curso no excedan el monto capturado bajo concurrencia.
La cuarta señal es la división entre el estado del flujo de trabajo y la contabilidad. La tabla de pagos responde a qué puede hacer el usuario a continuación. Un ledger de solo adición (append-only) responde por qué un balance tiene su valor actual. Los hechos asentados nunca se editan in situ; los reembolsos y correcciones agregan asientos de reversión o compensación. Los débitos, créditos, moneda y referencia de negocio de cada asiento diario (journal) se validan transaccionalmente y luego se concilian con los reportes del proveedor y los depósitos bancarios.
La señal final es la seguridad y la falsabilidad. La respuesta debe reducir la exposición de los datos de tarjetas, verificar firmas de webhooks, proteger los secretos del proveedor, restringir permisos y logs, e inyectar respuestas perdidas, webhooks duplicados o desordenados, capturas y reembolsos concurrentes, journals desbalanceados y discrepancias de conciliación. Un diagrama de servicios sin invariantes, rutas de recuperación y verificación no demuestra corrección.
Preguntas para aclarar antes de responder
- ¿Quién recopila los datos de la tarjeta? Este enunciado utiliza la página alojada por el proveedor o el componente de tokenización. El backend del negocio recibe únicamente un token del método de pago y no almacena números de tarjeta sin procesar, datos de banda magnética (track data), PIN ni códigos de seguridad. Un equipo de cumplimiento aún debe confirmar el alcance real de PCI.
- ¿Cuándo ocurren la autorización y la captura? Se autoriza al enviar el pedido y se captura tras la reserva de inventario. Se cancela antes de la expiración si falla el inventario. Una tabla de capacidades determina si un método de pago admite captura retrasada; el diseño no puede asumir que todos los métodos lo hagan.
- ¿Qué establece el éxito? Una redirección del navegador es solo una señal de experiencia de usuario y no puede autorizar el cumplimiento (fulfillment). Una respuesta autenticada del proveedor, un webhook o una consulta activa proporcionan evidencia fidedigna, que la máquina de estados local debe aceptar antes de que ocurra una acción de negocio.
- ¿Cuál es el contrato de reembolso? Admitir varios reembolsos parciales y un reembolso total, sin exceder nunca la captura. No existen reembolsos independientes sin un pago original. Un reembolso puede completarse de forma asíncrona o ser rechazado por el proveedor.
- ¿Necesitamos múltiples proveedores? La versión uno tiene un proveedor, pero la interfaz almacena un ID de operación interno y una referencia del proveedor. Nunca se debe realizar una conmutación por error (failover) automáticamente mientras se desconoce el resultado, ya que ambos proveedores podrían realizar el cobro.
- ¿Qué cubre el ledger? Este enunciado registra las cuentas por cobrar del procesador y las cuentas por pagar del comerciante. Las comisiones del proveedor, los payouts a comerciantes y los impuestos están fuera de alcance. Cada moneda se balancea de forma independiente; no se permite dinero en punto flotante ni tipos de cambio implícitos en el ledger.
- ¿Cuáles son los requisitos de retención y auditoría? Retener pagos, operaciones, recibos de webhooks y referencias del ledger de acuerdo con las políticas regulatorias y de la empresa, al tiempo que se minimiza, cifra y audita el acceso a payloads sensibles. PCI SSC prohíbe almacenar datos de autenticación sensibles después de la autorización, incluso si están cifrados.
- ¿Cuál es el balance entre disponibilidad y consistencia? Si el proveedor no está disponible, el sistema puede aceptar trabajo y mostrar "procesando", pero no puede mostrar un falso éxito. La corrección del dinero y la trazabilidad tienen prioridad sobre una respuesta terminal inmediata.
Estructura de respuesta en 30 segundos
“Utilizaría un payment_id estable para una única intención de pago y separaría el estado del pago de las operaciones de autorización, captura y reembolso. Las claves de idempotencia del llamador almacenan atómicamente el resumen (digest) de la solicitud, la operación y el outbox; el worker reutiliza el ID de operación ante el proveedor. Un timeout pasa a ser UNKNOWN y converge a través de webhooks verificados, consultas de estado y conciliación en lugar de un nuevo cobro. Los resultados fidedignos hacen avanzar una máquina de estados monótona y agregan atómicamente un journal balanceado y un evento de negocio. La creación de reembolsos reserva condicionalmente el valor reembolsable restante. Los reportes del proveedor y los depósitos bancarios concilian luego el ledger interno. La tokenización del proveedor mantiene los datos de tarjetas fuera del backend, y una página de éxito del navegador no puede desencadenar el fulfillment”.
Análisis detallado paso a paso
Paso 1: Comenzar con capacidad, invariantes y propiedad de datos (ownership)
Cinco millones divididos entre 86,400 segundos son aproximadamente 58 TPS. Un pico de 500 TPS no requiere fragmentar (shard) cada tabla desde el primer día. La corrección, la auditabilidad y la recuperación a través de un límite externo dominan este diseño. La API de pagos escala horizontalmente, una base de datos relacional mantiene el estado fidedigno y una cola durable enrutada por payment_id absorbe los picos y aísla la latencia del proveedor. Establece primero los invariantes:
amount > 0
currency is immutable after the first provider attempt
captured_amount <= authorized_amount
refunded_amount + pending_refund_amount <= captured_amount
for every journal: sum(debits) == sum(credits), per currency
one merchant + one idempotency_key describes one immutable request intent
one successful business operation produces at most one journal referenceA esta escala, una base de datos relacional con una región de escritura y conmutación por error facilita preservar las claves de idempotencia, las versiones de estado y el orden del ledger en comparación con escrituras multirregionales activo-activo. Activo-activo puede reducir el tiempo de failover regional, pero debe resolver el uso concurrente de la misma clave y operaciones monetarias en conflicto; solo se justifica mediante un requisito explícito de disponibilidad regional. El event sourcing completo también preserva el historial, pero distribuye la complejidad de reproducción (replay), migración de esquemas y consultas a lo largo de todo el flujo de pagos. Este diseño mantiene únicamente los journals de dinero como append-only y utiliza un agregado actual versionado para el estado del flujo de trabajo, emparejando el beneficio de auditoría con el costo operativo.
El servicio de pedidos es propietario del inventario y del fulfillment. El servicio de pagos es propietario del estado de pago, las operaciones y las referencias a los hechos monetarios. El proveedor es propietario del estado de la red de tarjetas. El ledger es propietario de los hechos monetarios internos. El servicio de pedidos no puede escribir filas de pago directamente, y un webhook de pago no puede marcar directamente un pedido como enviado. El servicio de pagos publica eventos de negocio estables que el servicio de pedidos consume de manera idempotente mediante payment_id.
Paso 2: Definir las API, la sesión de idempotencia y el modelo de datos
La interfaz principal puede ser:
POST /payments create one payment for an order
POST /payments/{id}/capture capture an authorized amount
POST /payments/{id}/cancel cancel an uncaptured authorization
POST /payments/{id}/refunds request a partial or full refund
GET /payments/{id} return current status and allowed next actions
POST /provider/webhooks persist a verified provider eventCada llamada que modifique dinero requiere una clave de idempotencia proporcionada por el llamador. Una restricción unique en (merchant_id, operation_type, idempotency_key) protege un digest de solicitud normalizado, el estado en progreso y una respuesta reproducible. La primera solicitud crea payments, payment_operations y un registro de outbox en una única transacción de base de datos. La misma clave y digest reproducen la respuesta conocida. La misma clave con diferente monto, moneda, pago o tipo de operación devuelve un conflicto. La guía de ingeniería de primera parte de Amazon recomienda de manera similar un request ID proporcionado por el llamador y un límite ACID que incluya tanto el ID como la mutación; un ID reutilizado con una intención diferente se rechaza.
Almacena las responsabilidades por separado:
payments: referencia del pedido, monto, moneda, estado del agregado, totales autorizados/capturados/reembolsados y versión;payment_operations: tipo, ID de operación, monto solicitado, estado, proveedor, referencia del proveedor, intentos y razón del estado desconocido;provider_events: ID de evento del proveedor, resultado de la firma, hora de recepción, referencia del payload cifrado y estado de procesamiento;journal_entries: ID de journal, cuenta, débito o crédito, monto, moneda y referencia de operación;outbox_events: un evento de negocio commiteado localmente y en espera de publicación.
El ID de operación interno puede convertirse en la clave de idempotencia del proveedor. Adyen documenta el reintento seguro con la misma clave después de un timeout de pago, junto con el alcance de la clave, la retención y los límites entre regiones. Por lo tanto, el sistema modela el contrato real del proveedor en lugar de tratar un UUID como una garantía global permanente.
Paso 3: Modelar el pago y la operación como dos máquinas de estado
El agregado de pago es el ciclo de vida de cara al pedido y al usuario:
CREATED -> REQUIRES_ACTION -> AUTHORIZED -> CAPTURED
\-> FAILED \-> CANCELED
CAPTURED -> PARTIALLY_REFUNDED -> REFUNDEDCada autorización, captura, cancelación o reembolso tiene un ciclo de vida de operación independiente:
PENDING -> SUCCEEDED | FAILED | UNKNOWN
UNKNOWN -> SUCCEEDED | FAILED (after query, webhook, or reconciliation)FAILED significa que la evidencia fidedigna indica que esta operación no tendrá éxito más adelante. Un timeout de red, 5xx o una respuesta perdida es UNKNOWN. El agregado solo acepta transiciones monótonas válidas. Un webhook que informe de un estado antiguo se retiene para auditoría, pero no puede mover el agregado hacia atrás. Cuando un webhook y una consulta activa compiten, una versión de fila o una actualización condicional commitea la transición una sola vez. Stripe documenta una intención de pago como un recurso que abarca desde la creación hasta el checkout, incluida la autenticación adicional, y un pago de captura manual que pasa a ser capturable antes de la captura. Esto respalda un recurso de pago de larga duración en lugar de equiparar el pago a una sola respuesta HTTP.
La autorización del pedido se desacopla de la confirmación del inventario. El éxito del inventario desencadena la captura, mientras que el fallo desencadena la cancelación. Esas operaciones pueden competir, por lo que una actualización condicional de la base de datos permite que solo una reclame la versión actual de AUTHORIZED. Un webhook puede llegar antes de que retorne la llamada al proveedor. Tanto la respuesta sincrónica como el webhook deben ingresar a la misma función de aplicación de estado en lugar de implementar dos rutas de transición.
Paso 4: Aceptar la no atomicidad externa y hacer que el flujo de trabajo converja
Una transacción local commitea el estado de la operación junto con el outbox. El relay publica al menos una vez (at-least-once), y un worker reclama por ID de operación. El worker reutiliza ese ID como la clave de idempotencia del proveedor y aplica límites estrictos de plazos de conexión, solicitud y totales. Un resultado definitivo guarda la referencia del proveedor y un digest de respuesta. Una respuesta perdida marca UNKNOWN y programa la consulta de estado. Nunca crea una nueva operación ni cambia a ciegas de proveedor.
Existen tres ventanas de fallo críticas:
- Un bloqueo antes de que la transacción local haga commit no deja ninguna operación visible, por lo que el llamador reintenta con la misma clave.
- Un acuse de recibo de publicación perdido tras el commit del outbox hace que el relay vuelva a publicar; el worker reclama la misma operación.
- El éxito del proveedor con una respuesta perdida utiliza la misma clave del proveedor, la consulta por referencia del proveedor o el webhook para converger.
Esto proporciona una expresión auditable y reintentable de una única intención, no una transacción entre empresas. Si el resultado sigue siendo desconocido tras la ventana de retención de idempotencia del proveedor, los reintentos automáticos se detienen. Los reportes del proveedor y un operador deben establecer el resultado; el tiempo transcurrido por sí solo no prueba el fallo.
Paso 5: Recibir webhooks asíncronos de forma segura y tolerar el reordenamiento
El endpoint retiene el cuerpo de la solicitud sin procesar y verifica su firma y la ventana de marca de tiempo (timestamp) con secretos actuales y antiguos en período de rotación. Rechaza firmas inválidas. Se persiste un recibo (provider, provider_event_id) único, tras lo cual el endpoint devuelve rápidamente un 2xx y encola el procesamiento asíncrono. Los logs contienen ID de eventos, referencias de proveedores y códigos de razón, no payloads sensibles completos ni secretos.
Los webhooks pueden duplicarse, retrasarse y reordenarse. Stripe documenta explícitamente reintentos automáticos en modo live y ninguna garantía en el orden de eventos. Un handler no puede asumir que la autorización siempre llegue antes de la captura. Puede recuperar el recurso actual del proveedor a través de la referencia de objeto del evento, o mapear el hecho externo a una transición local permitida. Cada evento se deduplica mediante el mismo ID de operación o referencia del proveedor. Una captura asentada no se puede asentar dos veces, y una autorización antigua no puede degradar CAPTURED a AUTHORIZED.
El navegador solo sondea o se suscribe al estado del pago local. Un parámetro "success" en una URL de retorno no puede cumplir un pedido, y el cliente no puede enviar CAPTURED. Tras el éxito de la captura fidedigna y el commit del ledger, el servicio de pagos publica PaymentCaptured a través del outbox. El servicio de pedidos realiza el fulfillment de manera idempotente mediante el ID de evento.
Paso 6: Expresar hechos monetarios con un ledger de solo adición (append-only)
El estado de pago es una vista operativa; el ledger es el registro de auditoría del dinero. Este enunciado simplifica la contabilidad a dos cuentas: las cuentas por cobrar del procesador son un activo y las cuentas por pagar del comerciante son un pasivo. Una captura de 100.00 CNY, donde la unidad menor es el fen, asienta:
journal capture-<operation_id>, CNY
debit processor_receivable 10000
credit merchant_payable 10000Un reembolso de 30.00 CNY añade un journal de reversión:
journal refund-<operation_id>, CNY
debit merchant_payable 3000
credit processor_receivable 3000Cada journal tiene al menos dos asientos y totales de débito y crédito iguales por moneda. Su journal_id y referencia de operación de negocio son únicos. La transacción que avanza el estado fidedigno a CAPTURED o a un reembolso exitoso también inserta el journal y el evento de outbox. Si las comisiones, chargebacks o payouts a comerciantes entran en el alcance, agrega cuentas explícitas y nuevos asientos; nunca reescribas asientos antiguos. Los balances se derivan de los asientos o se aceleran mediante una proyección reconstruible. La proyección no puede convertirse en la fuente de la verdad del dinero.
Los reembolsos concurrentes reclaman primero capacidad condicionalmente en la fila de pago. Se permite una nueva operación y el aumento en el monto en curso solo cuando captured_amount - refunded_amount - pending_refund_amount es suficiente. El éxito transfiere el monto de pendiente a reembolsado, el fallo definitivo lo libera y el estado desconocido retiene la reserva. Eso evita que otro reembolso supere el límite. Un proveedor podría aplicar su propio tope de reembolso, pero el invariante local no puede depender de ese respaldo externo.
Paso 7: Conciliar discrepancias silenciosas y repararlas de forma segura
La ruta en tiempo real no puede probar que nunca se haya omitido nada. La primera capa de conciliación resuelve operaciones UNKNOWN mediante referencia del proveedor o clave de idempotencia y compara monto, moneda, tipo de operación y estado terminal. La segunda carga diariamente reportes inmutables de transacciones o liquidaciones (settlements) del proveedor y coteja referencias de pagos, operaciones, journals y pedidos. La tercera coteja lotes de liquidación del proveedor con depósitos bancarios reales y distingue montos no liquidados, comisiones, reembolsos y chargebacks.
Clasifica las discrepancias como: solo externas, éxito interno/ausente externamente, monto o moneda incorrectos, estado obsoleto, referencia duplicada, ledger desbalanceado o ítem de liquidación faltante. Una discrepancia de alto riesgo bloquea la liberación del balance del comerciante afectado y genera una alerta. El reparador es idempotente: importar una operación del proveedor ya confirmada añade un nuevo journal con una razón de auditoría, mientras que una corrección contable utiliza un journal de compensación en lugar de un UPDATE sobre el historial. La documentación de reportes de Stripe describe las transacciones de balance como inmutables, con una nueva transacción de reembolso que anula el hecho original, y concilia por separado los pagos, los lotes de payout y los recibos bancarios.
Las métricas separan el éxito y p99 de la API, la distribución de estados de pago, la cantidad y antigüedad de operaciones UNKNOWN, errores y limitación de tasa (throttling) del proveedor, fallos de firma/duplicados/retrasos de webhooks, tiempo de autorización a captura, capacidad de reembolso reservada, backlog del outbox y colas, journals desbalanceados rechazados, conteo de discrepancias y discrepancia no resuelta más antigua. Los reportes de negocio mantienen diferenciadas las tasas de autorización, captura, reembolso y liquidación. "Solicitud aceptada" no debe contabilizarse como "dinero recibido".
Paso 8: Minimizar la superficie sensible e inyectar fallos
El componente de tokenización del proveedor recopila los datos de la tarjeta directamente. El backend almacena solo un token irreversible del proveedor y campos de visualización aprobados, como la marca y los últimos cuatro dígitos. Los secretos del cliente nunca entran en las URL ni en los logs. Las claves del proveedor se guardan y rotan en un sistema de secretos controlado. Los secretos de webhooks son independientes, y el entorno de pruebas (sandbox) y producción están aislados. Ver pagos, iniciar reembolsos, leer el ledger y realizar reparaciones manuales utilizan permisos independientes. Cada acción de alto riesgo registra al operador y el motivo.
PCI SSC prohíbe explícitamente retener códigos de verificación de tarjeta, PIN y bloques de PIN después de la autorización, incluso si están cifrados. La recopilación alojada reduce la exposición de este enunciado, pero una evaluación formal sigue determinando el cumplimiento. Una respuesta de entrevista no puede afirmar que "usar un token elimina PCI".
Las pruebas de aceptación cubren: un cliente que reintenta con la misma clave y cambia el monto bajo esa clave; éxito del proveedor con respuesta perdida; llegada de webhook antes de la respuesta de la API; webhooks duplicados, desordenados y retrasados; publicación duplicada en el outbox; competencia entre autorización y cancelación; dos reembolsos compitiendo por la misma capacidad; un reembolso total después de un reembolso parcial; expiración de idempotencia del proveedor; rotación del secreto de webhook; rechazo de un journal desbalanceado; una transacción solo externa encontrada mediante conciliación; ejecución repetida de reparación; y puesta al día tras una interrupción prolongada de la cola o del proveedor. Cada escenario valida el estado del pago, el estado de la operación, el conteo de journals, los efectos secundarios en los pedidos y las alertas.
Ejemplo de respuesta de alta calidad
“Modelaría el pago como un recurso de negocio de larga duración en lugar de una sola llamada HTTP. Un pedido tiene un payment_id estable. El agregado almacena el monto, la moneda y el estado de autorización/captura/reembolso. Cada autorización, captura, cancelación y reembolso utiliza un ID de operación independiente y un estado PENDING, SUCCEEDED, FAILED o UNKNOWN. Un timeout externo pasa a ser UNKNOWN; no puede fallar inmediatamente ni cambiar de proveedor.
La creación del pago y cada operación monetaria requieren una clave de idempotencia del llamador. El comerciante, el tipo de operación y la clave son únicos. La primera solicitud almacena atómicamente su digest, el pago o la operación y el outbox; la misma solicitud reproduce su resultado, mientras que un monto o moneda diferente bajo la misma clave genera un conflicto. Un worker utiliza el ID de operación interno como clave del proveedor. El outbox, la cola y el worker son todos al menos una vez (at-least-once), y la misma operación aplica una sola transición fidedigna.
La respuesta sincrónica del proveedor, un webhook con firma verificada y la consulta activa ingresan a la misma función de aplicación de estado. El webhook verifica el payload sin procesar, deduplica su ID de evento, persiste antes de retornar 2xx y tolera duplicados y desorden. Un evento antiguo no puede mover el pago hacia atrás. El navegador solo muestra el estado local y no puede desencadenar el fulfillment. Solo el éxito fidedigno de la captura, commiteado con un journal balanceado y un outbox de negocio, permite al servicio de pedidos realizar el fulfillment de manera idempotente.
Un journal de captura debita las cuentas por cobrar del procesador y acredita las cuentas por pagar del comerciante. Un reembolso agrega un journal de reversión; el historial es inmutable. La creación de reembolsos reserva condicionalmente el valor reembolsable disponible, de modo que los reembolsos confirmados más los en curso nunca excedan la captura. El dinero utiliza unidades menores enteras, la moneda es inmutable tras el primer intento y cada moneda se balancea de forma independiente.
La recuperación tiene tres capas: las operaciones desconocidas reutilizan la clave del proveedor o consultan el estado, los reportes diarios del proveedor concilian pagos, operaciones y journals, y los lotes de liquidación se cotejan con los depósitos bancarios. Las discrepancias se ponen en cuarentena y se alertan; la reparación solo añade una importación idempotente o un journal de compensación. Un componente alojado del proveedor recopila la tarjeta, por lo que el backend no almacena el número de tarjeta ni el código de seguridad. Las respuestas perdidas, los webhooks duplicados o desordenados, las capturas y reembolsos concurrentes, la caída del proveedor, un journal desbalanceado y un ítem de conciliación solo externo demuestran entonces que cada resultado externo converge en un hecho monetario auditable”.
Errores comunes
- Tratar la página de éxito del navegador como éxito del pago → la redirección es falsificable y el pago aún puede estar esperando autenticación o confirmación asíncrona → Solo la evidencia fidedigna del proveedor aceptada por la máquina de estados local desencadena el fulfillment.
- Usar un único campo
paidpara todo el ciclo de vida → no puede expresar autenticación adicional, autorización, captura, reembolso parcial o resultado desconocido → Separa el agregado de pago de los intentos de operación. - Reintentar con un nuevo ID o con otro proveedor tras un timeout → el primer intento pudo haber cobrado, creando un cobro doble → Reutiliza la operación y la clave del proveedor, luego consulta o concilia los resultados desconocidos.
- Comparar únicamente la clave de idempotencia, no los parámetros → un llamador que reutilice una clave con un monto modificado obtiene un resultado de negocio incorrecto → Persiste un digest de solicitud normalizado y genera conflicto si la intención cambia.
- Depender del orden de los webhooks → el proveedor puede reintentar, retrasar y desordenar eventos → Deduplica y recupera el estado actual o aplica solo transiciones monótonas válidas.
- Tratar el balance de la tabla de pagos como un ledger → las actualizaciones in situ pierden el motivo de los cambios monetarios y no se pueden conciliar de forma independiente → Agrega journals balanceados; mantén el balance como una proyección reconstruible.
- Leer y luego escribir el saldo reembolsable de forma concurrente → dos llamadores pueden ver capacidad simultáneamente y exceder la captura → Reserva capacidad con una actualización condicional transaccional vinculada de forma única a la operación.
- Monitorear solo las respuestas API 200 → aceptado, autorizado, capturado y liquidado tienen significados diferentes → Mide cada estado, la antigüedad de los estados desconocidos y las discrepancias de conciliación.
- Afirmar que la tokenización elimina automáticamente la responsabilidad de PCI → las páginas, logs, scripts y procesos operativos pueden permanecer dentro del alcance → Minimiza los datos de tarjetas y haz que el equipo de cumplimiento confirme el límite real.
- Editar asientos históricos para reparar una discrepancia → se rompe la cadena de auditoría y los reportes históricos no se pueden reproducir → Agrega una importación justificada o un journal de compensación.
Preguntas de seguimiento y respuestas
Pregunta de seguimiento 1: El proveedor realizó la captura, pero tanto la respuesta sincrónica como el webhook se perdieron. ¿Qué sucede?
La operación permanece en UNKNOWN, reteniendo la autorización relacionada o la reserva de reembolso y evitando otra operación para la misma intención. Primero, reintenta una consulta admitida o llama con la clave de idempotencia original del proveedor, luego consulta activamente por la referencia del proveedor. Si sigue siendo indeterminado, espera la conciliación del reporte de transacciones. El éxito confirmado entra en la misma función de aplicación de estado y asienta el journal y el outbox; solo el fallo confirmado libera la capacidad. Una vez que expire la retención de la clave del proveedor sin evidencia, envía el caso a los operadores en lugar de inferir el fallo a partir del tiempo transcurrido.
Pregunta de seguimiento 2: Dos reembolsos de 60 CNY apuntan concurrentemente a una captura de 100 CNY. ¿Cómo evitas el sobre-reembolso?
Usa una actualización condicional en el pago o en la fila dedicada al saldo reembolsable. Esta incrementa pending_refund_amount en 60 CNY y crea la operación solo si quedan al menos 60 CNY. Ambas transacciones compiten en una misma versión, por lo que una tiene éxito y la otra vuelve a leer capacidad insuficiente. Un reembolso desconocido mantiene su reserva, un fallo definitivo la libera y un éxito traslada el monto de pendiente a reembolsado. La validación del proveedor es solo la segunda línea de defensa.
Pregunta de seguimiento 3: CAPTURED llega por webhook antes de AUTHORIZED. ¿Cómo se procesa?
Persiste y deduplica ambos recibos. Si la firma, monto, moneda y referencia del proveedor de CAPTURED coinciden, aplica la transición de captura y el journal. El evento posterior AUTHORIZED es un hecho anterior en el tiempo. La máquina de estados rechaza el rollback y actualiza únicamente la auditoría del webhook y las métricas de retraso. Si el payload carece de evidencia de versión suficiente, recupera el recurso de pago actual del proveedor en lugar de sobrescribir por orden de llegada.
Pregunta de seguimiento 4: ¿Por qué tener tanto una tabla de pagos como un ledger?
La tabla de pagos es un agregado de flujo de trabajo adecuado para responder si se permite la captura, cancelación o reembolso. El ledger de solo adición es un registro monetario adecuado para explicar cómo se formó un balance y qué evento de negocio causó cada cambio. El pago puede pasar de CAPTURED a PARTIALLY_REFUNDED, mientras que el ledger retiene la captura original y cada journal de reembolso balanceado independiente. Una referencia de operación única los une, y una transición fidedigna commitea ambos en una transacción local. Ninguna responsabilidad reemplaza a la otra.
Pregunta de seguimiento 5: ¿Cómo demuestras que un reparador de conciliación no puede asentar dos veces?
Asigna a cada fila del reporte externo una clave de origen estable, como proveedor, tipo de reporte y referencia de transacción. Vincula la reparación al ID de discrepancia y añade una restricción unique en (source_key, repair_type). La primera transacción agrega el journal, marca la discrepancia y escribe en el outbox. Si ocurre un fallo y se vuelve a ejecutar, encuentra la misma reparación y reproduce su resultado. Interrumpe el proceso antes del commit, después del commit y después de la publicación del evento; el conteo de journals, el balance y el conteo de eventos aguas abajo nunca deben incrementarse por segunda vez.
Pregunta de seguimiento 6: Si se agrega un segundo proveedor más adelante, ¿cuándo se permite el failover?
Crea una operación en el segundo proveedor solo cuando el primero indique explícitamente que la operación nunca se creó o que falló definitivamente, y la operación local no tenga ningún hecho monetario. El timeout de conexión, 5xx y el estado desconocido no satisfacen esa condición. Audita la elección de enrutamiento, la capacidad del proveedor, el monto, la moneda y el motivo. Si ambos resultados pudieran existir, congela el fulfillment y la liberación del balance mientras la consulta y la conciliación eliminan un posible cobro doble. Un reembolso automático no puede ocultar un resultado desconocido.