@open-mercato/checkout 0.6.7-develop.6842.1.8cd8a8883c → 0.6.7-develop.6859.1.e4727b568a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.js +62 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.js.map +7 -0
- package/dist/modules/checkout/api/pay/[slug]/submit/route.js +43 -28
- package/dist/modules/checkout/api/pay/[slug]/submit/route.js.map +2 -2
- package/dist/modules/checkout/commands/transactions.js +45 -13
- package/dist/modules/checkout/commands/transactions.js.map +2 -2
- package/dist/modules/checkout/lib/transaction-status-machine.js +31 -0
- package/dist/modules/checkout/lib/transaction-status-machine.js.map +7 -0
- package/package.json +5 -5
- package/src/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.ts +110 -0
- package/src/modules/checkout/api/pay/[slug]/submit/__tests__/route.test.ts +77 -0
- package/src/modules/checkout/api/pay/[slug]/submit/route.ts +58 -28
- package/src/modules/checkout/commands/__tests__/update-transaction-status.test.ts +363 -0
- package/src/modules/checkout/commands/transactions.ts +68 -13
- package/src/modules/checkout/lib/__tests__/transaction-status-machine.test.ts +152 -0
- package/src/modules/checkout/lib/transaction-status-machine.ts +71 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
2
|
+
import type { CheckoutTransaction } from '../data/entities'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Allowed status transitions for a CheckoutTransaction.
|
|
6
|
+
*
|
|
7
|
+
* Terminal states (completed, failed, cancelled, expired) have an empty
|
|
8
|
+
* allowed-set, meaning NO transition out of them is permitted. This prevents
|
|
9
|
+
* the race condition where the submit route's late `updateStatus('processing')`
|
|
10
|
+
* call overwrites a `'completed'` status that the gateway webhook set first.
|
|
11
|
+
*
|
|
12
|
+
* Allowed path:
|
|
13
|
+
* pending ──► processing (transaction created, payment session initiated)
|
|
14
|
+
* pending ──► completed (captured webhook arrives before processing state)
|
|
15
|
+
* processing ──► completed (webhook: payment captured)
|
|
16
|
+
* processing ──► failed (webhook or session error)
|
|
17
|
+
* processing ──► cancelled (customer or merchant cancellation)
|
|
18
|
+
* processing ──► expired (expiry worker)
|
|
19
|
+
*
|
|
20
|
+
* Rejected (regression guard):
|
|
21
|
+
* completed ──► * REJECTED
|
|
22
|
+
* failed ──► * REJECTED
|
|
23
|
+
* cancelled ──► * REJECTED
|
|
24
|
+
* expired ──► * REJECTED
|
|
25
|
+
*/
|
|
26
|
+
export const VALID_CHECKOUT_TRANSITIONS: Record<CheckoutTransaction['status'], CheckoutTransaction['status'][]> = {
|
|
27
|
+
pending: ['processing', 'completed', 'failed', 'cancelled', 'expired'],
|
|
28
|
+
processing: ['completed', 'failed', 'cancelled', 'expired'],
|
|
29
|
+
// Terminal states — no transitions out
|
|
30
|
+
completed: [],
|
|
31
|
+
failed: [],
|
|
32
|
+
cancelled: [],
|
|
33
|
+
expired: [],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns `true` when transitioning from `from` to `to` is allowed by the
|
|
38
|
+
* state machine. Same-state transitions are allowed as safe no-ops to support
|
|
39
|
+
* idempotency.
|
|
40
|
+
*/
|
|
41
|
+
export function isValidCheckoutStatusTransition(
|
|
42
|
+
from: CheckoutTransaction['status'],
|
|
43
|
+
to: CheckoutTransaction['status'],
|
|
44
|
+
): boolean {
|
|
45
|
+
if (from === to) return true
|
|
46
|
+
const allowed = VALID_CHECKOUT_TRANSITIONS[from]
|
|
47
|
+
if (!allowed) return false
|
|
48
|
+
return allowed.includes(to)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Throws a structured 409 Conflict when the requested status transition is
|
|
53
|
+
* not allowed by the state machine. The error body is compatible with the
|
|
54
|
+
* `conflict` error shape that the frontend handles.
|
|
55
|
+
*
|
|
56
|
+
* This must be called **inside** a database transaction so that the check and
|
|
57
|
+
* the subsequent write are atomic. The atomic `nativeUpdate` WHERE-clause
|
|
58
|
+
* provides the TOCTOU safety net; this function is the human-readable guard.
|
|
59
|
+
*/
|
|
60
|
+
export function assertValidCheckoutStatusTransition(
|
|
61
|
+
from: CheckoutTransaction['status'],
|
|
62
|
+
to: CheckoutTransaction['status'],
|
|
63
|
+
): void {
|
|
64
|
+
if (isValidCheckoutStatusTransition(from, to)) return
|
|
65
|
+
throw new CrudHttpError(409, {
|
|
66
|
+
error: `[internal] Cannot transition payment from "${from}" to "${to}"`,
|
|
67
|
+
code: 'invalid_status_transition',
|
|
68
|
+
currentStatus: from,
|
|
69
|
+
requestedStatus: to,
|
|
70
|
+
})
|
|
71
|
+
}
|