Idempotencia, reintentos y duplicados
Estado documental: Contrato propuesto. Las ventanas temporales, campos de comparación y mecanismo de confirmación deben validarse en implementación.
Tres controles diferentes
Idempotency-Key
→ protege contra reintentos técnicos
external_transaction_id
→ identifica la venta en el sistema del cliente
Detección de posible duplicado
→ identifica dos IDs diferentes que parecen representar la misma venta
Reintento técnico
Cada operación de creación o modificación debe enviar:
Idempotency-Key: <UUID_UNICO>
Misma clave y mismo payload
YUPY devuelve la misma operación.
HTTP/1.1 200 OK
Idempotency-Replayed: true
{
"result": "existing_transaction",
"idempotency_replayed": true,
"yupy_transaction_id": "ypt_01JXYZ",
"external_transaction_id": "ORDER-10482",
"state": {
"operational": "active",
"financial": "awaiting_payment"
}
}
Misma clave y contenido distinto
HTTP/1.1 409 Conflict
{
"error": {
"code": "idempotency_conflict",
"message": "La clave de idempotencia ya fue utilizada con otro contenido.",
"request_id": "req_01JXYZ"
}
}
Identidad comercial
empresa
+ integración
+ ambiente
+ external_transaction_id
El timestamp de la solicitud no forma parte de la identidad.
ORDER-10482 enviado a las 10:00
ORDER-10482 enviado a las 10:01
→ es la misma operación
Mismo ID externo y mismos datos
{
"result": "existing_transaction",
"duplicate": true,
"duplicate_type": "same_external_transaction",
"yupy_transaction_id": "ypt_01JXYZ",
"external_transaction_id": "ORDER-10482"
}
Mismo ID externo y datos críticos diferentes
HTTP/1.1 409 Conflict
{
"error": {
"code": "external_transaction_conflict",
"message": "La transacción externa ya existe con datos diferentes.",
"external_transaction_id": "ORDER-10482",
"yupy_transaction_id": "ypt_01JXYZ",
"conflicting_fields": [
"amount"
],
"request_id": "req_01JABC"
}
}
YUPY no debe cambiar silenciosamente la operación original.
Posible duplicado con otro ID
YUPY puede comparar monto, moneda, nombre, turno, dispositivo, ruta, asiento, pedido, producto, servicio, referencias opcionales y proximidad temporal normalizada.
Los campos de contexto siguen siendo opcionales. Su ausencia no invalida la operación, pero reduce la cantidad de señales disponibles.
No bloquear únicamente por monto
Dos operaciones de S/ 80.00
≠ necesariamente la misma venta
YUPY no debe marcar una operación como duplicada solo porque el monto coincide.
Contexto que demuestra operaciones distintas
Orden A:
S/ 85.50
Ruta 184
Asiento 12A
Orden B:
S/ 85.50
Ruta 184
Asiento 12B
Estas operaciones pueden ser legítimamente distintas.
Contexto que eleva la sospecha
Orden A:
S/ 85.50
Ruta 184
Asiento 12A
Orden B:
S/ 85.50
Ruta 184
Asiento 12A
Con IDs externos diferentes y cercanía temporal, YUPY puede devolver una advertencia.
HTTP/1.1 409 Conflict
{
"error": {
"code": "possible_duplicate_transaction",
"message": "Existe una operación reciente con características equivalentes.",
"candidate": {
"yupy_transaction_id": "ypt_01JXYZ",
"external_transaction_id": "ORDER-10482",
"amount": "85.50",
"currency": "PEN",
"state": {
"operational": "active",
"financial": "awaiting_payment"
}
},
"matching_signals": [
"amount",
"currency",
"buyer_name",
"shift_id",
"device_id",
"custom_reference_1",
"custom_reference_2"
],
"duplicate_confirmation_token": "<OPAQUE_CONFIRMATION_TOKEN>",
"resolution": "review_before_creating",
"request_id": "req_01JABC"
}
}
Confirmar que es una operación diferente
{
"external_transaction_id": "ORDER-10483",
"amount": "85.50",
"buyer_name": "María Ramos",
"integration_experience": "chat_pos",
"shift_id": "SHIFT-44",
"device_id": "DEVICE-03",
"context": {
"custom_reference_1": "ROUTE-184",
"custom_reference_2": "SEAT-12A"
},
"duplicate_confirmation_token": "<OPAQUE_CONFIRMATION_TOKEN>"
}
La confirmación debe quedar auditada.
Reintento después de timeout
Correcto:
Primer intento:
external_transaction_id = ORDER-10482
Idempotency-Key = abc-123
Timeout
Segundo intento:
external_transaction_id = ORDER-10482
Idempotency-Key = abc-123
Incorrecto:
Primer intento:
ORDER-10482
Timeout
Segundo intento:
ORDER-10483
Cambiar el ID después de un timeout puede convertir accidentalmente un reintento en una nueva venta.
Matriz de comportamiento
| Situación | Resultado propuesto |
|---|---|
Misma Idempotency-Key, mismo payload |
Devuelve la misma operación |
Misma Idempotency-Key, payload distinto |
409 idempotency_conflict |
| Mismo ID externo, mismos datos | Devuelve la operación existente |
| Mismo ID externo, datos críticos distintos | 409 external_transaction_conflict |
| ID diferente, señales equivalentes | 409 possible_duplicate_transaction |
| Solo coincide el monto | No se bloquea automáticamente |
| Otro asiento o referencia | Puede considerarse una operación diferente |
| Confirmación explícita con token | Crea una nueva operación auditable |
Criterios de aceptación
- Los reintentos utilizan
Idempotency-Key. - El timestamp no define la identidad comercial.
- El mismo ID externo no crea dos órdenes.
- Un cambio de monto genera conflicto.
- YUPY detecta posibles duplicados con IDs distintos.
- El contexto es opcional.
- El asiento ayuda a distinguir ventas.
- La coincidencia de monto por sí sola no bloquea.
- La creación forzada requiere confirmación explícita.
- La confirmación queda auditada.