@open-mercato/checkout 0.6.7-develop.6862.1.c11a64ce0a → 0.6.7
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/api/pay/[slug]/submit/route.js +28 -43
- package/dist/modules/checkout/api/pay/[slug]/submit/route.js.map +2 -2
- package/dist/modules/checkout/api/transactions/route.js +6 -23
- package/dist/modules/checkout/api/transactions/route.js.map +2 -2
- package/dist/modules/checkout/backend/checkout/pay-links/page.js +1 -2
- package/dist/modules/checkout/backend/checkout/pay-links/page.js.map +2 -2
- package/dist/modules/checkout/backend/checkout/templates/page.js +1 -2
- package/dist/modules/checkout/backend/checkout/templates/page.js.map +2 -2
- package/dist/modules/checkout/backend/checkout/transactions/page.js +1 -2
- package/dist/modules/checkout/backend/checkout/transactions/page.js.map +2 -2
- package/dist/modules/checkout/commands/links.js +1 -1
- package/dist/modules/checkout/commands/links.js.map +2 -2
- package/dist/modules/checkout/commands/transactions.js +13 -45
- package/dist/modules/checkout/commands/transactions.js.map +2 -2
- package/dist/modules/checkout/components/PayPage.js +38 -39
- package/dist/modules/checkout/components/PayPage.js.map +2 -2
- package/dist/modules/checkout/search.js +6 -44
- package/dist/modules/checkout/search.js.map +2 -2
- package/dist/modules/checkout/setup.js +0 -28
- package/dist/modules/checkout/setup.js.map +2 -2
- package/dist/modules/checkout/workers/transaction-expiry.worker.js +2 -9
- package/dist/modules/checkout/workers/transaction-expiry.worker.js.map +2 -2
- package/package.json +9 -10
- package/src/modules/checkout/api/pay/[slug]/submit/__tests__/route.test.ts +0 -77
- package/src/modules/checkout/api/pay/[slug]/submit/route.ts +28 -58
- package/src/modules/checkout/api/transactions/route.ts +6 -30
- package/src/modules/checkout/backend/checkout/pay-links/page.tsx +1 -2
- package/src/modules/checkout/backend/checkout/templates/page.tsx +1 -2
- package/src/modules/checkout/backend/checkout/transactions/page.tsx +1 -2
- package/src/modules/checkout/commands/links.ts +1 -5
- package/src/modules/checkout/commands/transactions.ts +13 -68
- package/src/modules/checkout/components/PayPage.tsx +37 -38
- package/src/modules/checkout/i18n/de.json +0 -1
- package/src/modules/checkout/i18n/en.json +0 -1
- package/src/modules/checkout/i18n/es.json +0 -1
- package/src/modules/checkout/i18n/pl.json +0 -1
- package/src/modules/checkout/search.ts +6 -44
- package/src/modules/checkout/setup.ts +0 -51
- package/src/modules/checkout/workers/transaction-expiry.worker.ts +0 -11
- package/dist/modules/checkout/__integration__/TC-CHKT-042.spec.js +0 -39
- package/dist/modules/checkout/__integration__/TC-CHKT-042.spec.js.map +0 -7
- package/dist/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.js +0 -62
- package/dist/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.js.map +0 -7
- package/dist/modules/checkout/extension-points.js +0 -68
- package/dist/modules/checkout/extension-points.js.map +0 -7
- package/dist/modules/checkout/lib/transaction-status-machine.js +0 -31
- package/dist/modules/checkout/lib/transaction-status-machine.js.map +0 -7
- package/dist/modules/checkout/subscribers/search-reindex-transaction-created.js +0 -32
- package/dist/modules/checkout/subscribers/search-reindex-transaction-created.js.map +0 -7
- package/src/modules/checkout/__integration__/TC-CHKT-042.spec.ts +0 -41
- package/src/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.ts +0 -112
- package/src/modules/checkout/__tests__/search.test.ts +0 -21
- package/src/modules/checkout/__tests__/setup-schedules.test.ts +0 -99
- package/src/modules/checkout/api/transactions/__tests__/route.test.ts +0 -113
- package/src/modules/checkout/commands/__tests__/update-transaction-status.test.ts +0 -363
- package/src/modules/checkout/extension-points.ts +0 -67
- package/src/modules/checkout/i18n/ko.json +0 -463
- package/src/modules/checkout/lib/__tests__/transaction-status-machine.test.ts +0 -152
- package/src/modules/checkout/lib/transaction-status-machine.ts +0 -71
- package/src/modules/checkout/subscribers/__tests__/search-reindex-transaction-created.test.ts +0 -29
- package/src/modules/checkout/subscribers/search-reindex-transaction-created.ts +0 -39
- package/src/modules/checkout/workers/__tests__/transaction-expiry.worker.test.ts +0 -124
|
@@ -1,71 +0,0 @@
|
|
|
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
|
-
}
|
package/src/modules/checkout/subscribers/__tests__/search-reindex-transaction-created.test.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import handle from '../search-reindex-transaction-created'
|
|
2
|
-
|
|
3
|
-
const emitEvent = jest.fn(async () => undefined)
|
|
4
|
-
const context = { resolve: jest.fn(() => ({ emitEvent })) }
|
|
5
|
-
|
|
6
|
-
describe('checkout transaction search reindex subscriber', () => {
|
|
7
|
-
beforeEach(() => jest.clearAllMocks())
|
|
8
|
-
|
|
9
|
-
it('bridges transaction creation to the query index', async () => {
|
|
10
|
-
await handle({ transactionId: 'transaction-1', tenantId: 'tenant-1', organizationId: 'org-1' }, context)
|
|
11
|
-
|
|
12
|
-
expect(emitEvent).toHaveBeenCalledWith('query_index.upsert_one', {
|
|
13
|
-
entityType: 'checkout:checkout_transaction',
|
|
14
|
-
recordId: 'transaction-1',
|
|
15
|
-
organizationId: 'org-1',
|
|
16
|
-
tenantId: 'tenant-1',
|
|
17
|
-
crudAction: 'created',
|
|
18
|
-
coverageBaseDelta: 1,
|
|
19
|
-
}, { tenantId: 'tenant-1', organizationId: 'org-1' })
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('skips incomplete payloads and unavailable event buses', async () => {
|
|
23
|
-
await handle({ transactionId: 'transaction-1' }, context)
|
|
24
|
-
await handle({ transactionId: 'transaction-1', tenantId: 'tenant-1' }, {
|
|
25
|
-
resolve: () => { throw new Error('unavailable') },
|
|
26
|
-
})
|
|
27
|
-
expect(emitEvent).not.toHaveBeenCalled()
|
|
28
|
-
})
|
|
29
|
-
})
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export const metadata = {
|
|
2
|
-
event: 'checkout.transaction.created',
|
|
3
|
-
persistent: false,
|
|
4
|
-
id: 'checkout:query-index-reindex-transaction-created',
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type Payload = {
|
|
8
|
-
transactionId?: string
|
|
9
|
-
tenantId?: string | null
|
|
10
|
-
organizationId?: string | null
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
type EventBus = { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> }
|
|
14
|
-
type HandlerContext = { resolve: <T = unknown>(name: string) => T }
|
|
15
|
-
|
|
16
|
-
export default async function handle(payload: Payload, ctx: HandlerContext): Promise<void> {
|
|
17
|
-
const recordId = typeof payload.transactionId === 'string' ? payload.transactionId : null
|
|
18
|
-
const tenantId = typeof payload.tenantId === 'string' ? payload.tenantId : null
|
|
19
|
-
if (!recordId || !tenantId) return
|
|
20
|
-
|
|
21
|
-
let eventBus: EventBus | null = null
|
|
22
|
-
try {
|
|
23
|
-
eventBus = ctx.resolve<EventBus>('eventBus')
|
|
24
|
-
} catch {
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
await eventBus.emitEvent('query_index.upsert_one', {
|
|
29
|
-
entityType: 'checkout:checkout_transaction',
|
|
30
|
-
recordId,
|
|
31
|
-
organizationId: payload.organizationId ?? null,
|
|
32
|
-
tenantId,
|
|
33
|
-
crudAction: 'created',
|
|
34
|
-
coverageBaseDelta: 1,
|
|
35
|
-
}, {
|
|
36
|
-
tenantId,
|
|
37
|
-
organizationId: payload.organizationId ?? null,
|
|
38
|
-
}).catch(() => undefined)
|
|
39
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
|
|
2
|
-
import type { JobContext, QueuedJob } from '@open-mercato/queue'
|
|
3
|
-
import { CheckoutTransaction } from '../../data/entities'
|
|
4
|
-
import type { CheckoutExpiryJob } from '../transaction-expiry.worker'
|
|
5
|
-
|
|
6
|
-
const findWithDecryption = jest.fn()
|
|
7
|
-
jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
|
|
8
|
-
findWithDecryption: (...args: unknown[]) => findWithDecryption(...args),
|
|
9
|
-
}))
|
|
10
|
-
|
|
11
|
-
describe('checkout transaction-expiry worker', () => {
|
|
12
|
-
const mockCommandBus = {
|
|
13
|
-
execute: jest.fn().mockResolvedValue({ ok: true }),
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type MockEntityManager = {
|
|
17
|
-
fork: () => MockEntityManager
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const mockEm: MockEntityManager = {
|
|
21
|
-
fork: () => mockEm,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const resolve = <ResolvedValue = unknown>(name: string): ResolvedValue => {
|
|
25
|
-
if (name === 'em') return mockEm as ResolvedValue
|
|
26
|
-
if (name === 'commandBus') return mockCommandBus as ResolvedValue
|
|
27
|
-
throw new Error(`[internal] Missing dependency: ${name}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const mockContext: JobContext & { resolve: typeof resolve } = {
|
|
31
|
-
resolve,
|
|
32
|
-
jobId: 'job-1',
|
|
33
|
-
attemptNumber: 1,
|
|
34
|
-
queueName: 'checkout-transaction-expiry',
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const baseJob = {
|
|
38
|
-
id: 'job-1',
|
|
39
|
-
createdAt: '2026-08-01T00:00:00.000Z',
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
jest.clearAllMocks()
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('throws an error if tenantId or organizationId is missing', async () => {
|
|
47
|
-
const { default: handle } = await import('../transaction-expiry.worker')
|
|
48
|
-
|
|
49
|
-
await expect(
|
|
50
|
-
handle(
|
|
51
|
-
{
|
|
52
|
-
...baseJob,
|
|
53
|
-
payload: { batchSize: 10 },
|
|
54
|
-
} as unknown as QueuedJob<CheckoutExpiryJob>,
|
|
55
|
-
mockContext,
|
|
56
|
-
),
|
|
57
|
-
).rejects.toThrow('[internal] tenantId and organizationId are required in CheckoutExpiryJob')
|
|
58
|
-
|
|
59
|
-
expect(findWithDecryption).not.toHaveBeenCalled()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('queries only transactions matching the payload tenantId and organizationId and passes decryption scope', async () => {
|
|
63
|
-
const { default: handle } = await import('../transaction-expiry.worker')
|
|
64
|
-
|
|
65
|
-
const mockTransactions = [
|
|
66
|
-
{
|
|
67
|
-
id: 'txn-1',
|
|
68
|
-
organizationId: 'org-123',
|
|
69
|
-
tenantId: 'tenant-456',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: 'txn-2',
|
|
73
|
-
organizationId: 'org-123',
|
|
74
|
-
tenantId: 'tenant-456',
|
|
75
|
-
},
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
findWithDecryption.mockResolvedValueOnce(mockTransactions)
|
|
79
|
-
|
|
80
|
-
await handle(
|
|
81
|
-
{
|
|
82
|
-
...baseJob,
|
|
83
|
-
payload: {
|
|
84
|
-
batchSize: 50,
|
|
85
|
-
tenantId: 'tenant-456',
|
|
86
|
-
organizationId: 'org-123',
|
|
87
|
-
},
|
|
88
|
-
} satisfies QueuedJob<CheckoutExpiryJob>,
|
|
89
|
-
mockContext,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
expect(findWithDecryption).toHaveBeenCalledWith(
|
|
93
|
-
mockEm,
|
|
94
|
-
CheckoutTransaction,
|
|
95
|
-
expect.objectContaining({
|
|
96
|
-
status: 'processing',
|
|
97
|
-
tenantId: 'tenant-456',
|
|
98
|
-
organizationId: 'org-123',
|
|
99
|
-
}),
|
|
100
|
-
expect.objectContaining({
|
|
101
|
-
limit: 50,
|
|
102
|
-
}),
|
|
103
|
-
{
|
|
104
|
-
tenantId: 'tenant-456',
|
|
105
|
-
organizationId: 'org-123',
|
|
106
|
-
},
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
expect(mockCommandBus.execute).toHaveBeenCalledTimes(2)
|
|
110
|
-
expect(mockCommandBus.execute).toHaveBeenNthCalledWith(
|
|
111
|
-
1,
|
|
112
|
-
'checkout.transaction.updateStatus',
|
|
113
|
-
expect.objectContaining({
|
|
114
|
-
input: {
|
|
115
|
-
id: 'txn-1',
|
|
116
|
-
status: 'expired',
|
|
117
|
-
paymentStatus: 'expired',
|
|
118
|
-
organizationId: 'org-123',
|
|
119
|
-
tenantId: 'tenant-456',
|
|
120
|
-
},
|
|
121
|
-
}),
|
|
122
|
-
)
|
|
123
|
-
})
|
|
124
|
-
})
|