@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
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
parseCheckoutInput,
|
|
13
13
|
toMoneyString,
|
|
14
14
|
} from '../lib/utils'
|
|
15
|
-
import { assertValidCheckoutStatusTransition } from '../lib/transaction-status-machine'
|
|
16
15
|
|
|
17
16
|
function resolveTransactionScope(input: { tenantId?: string | null; organizationId?: string | null }) {
|
|
18
17
|
if (!input.organizationId || !input.tenantId) {
|
|
@@ -169,63 +168,11 @@ const updateTransactionStatusCommand: CommandHandler<Record<string, unknown>, {
|
|
|
169
168
|
const previousTerminal = isTerminalCheckoutStatus(previousStatus)
|
|
170
169
|
const nextTerminal = isTerminalCheckoutStatus(nextStatus)
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
assertValidCheckoutStatusTransition(previousStatus, nextStatus)
|
|
177
|
-
|
|
178
|
-
// Resolve the new field values before the write.
|
|
179
|
-
const newPaymentStatus = parsed.paymentStatus ?? transaction.paymentStatus ?? null
|
|
180
|
-
const newGatewayTransactionId = parsed.gatewayTransactionId ?? transaction.gatewayTransactionId ?? null
|
|
181
|
-
|
|
182
|
-
// Atomic compare-and-swap: the WHERE clause pins the current status so
|
|
183
|
-
// a concurrent writer that already advanced the status to a terminal
|
|
184
|
-
// state will cause this update to match 0 rows — eliminating the TOCTOU
|
|
185
|
-
// window between the findOne above and this write.
|
|
186
|
-
const affected = await tx.nativeUpdate(
|
|
187
|
-
CheckoutTransaction,
|
|
188
|
-
{
|
|
189
|
-
id: parsed.id,
|
|
190
|
-
organizationId: scope.organizationId,
|
|
191
|
-
tenantId: scope.tenantId,
|
|
192
|
-
status: previousStatus,
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
status: nextStatus,
|
|
196
|
-
paymentStatus: newPaymentStatus,
|
|
197
|
-
gatewayTransactionId: newGatewayTransactionId,
|
|
198
|
-
updatedAt: new Date(),
|
|
199
|
-
},
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
if (affected === 0) {
|
|
203
|
-
// A concurrent writer already advanced the status. Force a DB round-trip
|
|
204
|
-
// (refresh: true bypasses the MikroORM identity map) so the reported
|
|
205
|
-
// currentStatus reflects the winning writer's value, not the stale snapshot.
|
|
206
|
-
const actualTx = await tx.findOne(
|
|
207
|
-
CheckoutTransaction,
|
|
208
|
-
{ id: parsed.id, organizationId: scope.organizationId, tenantId: scope.tenantId },
|
|
209
|
-
{ fields: ['status'], refresh: true },
|
|
210
|
-
)
|
|
211
|
-
throw new CrudHttpError(409, {
|
|
212
|
-
error: `[internal] Transaction status was already updated by a concurrent process (expected "${previousStatus}", actual "${actualTx?.status ?? 'unknown'}")`,
|
|
213
|
-
code: 'concurrent_status_update',
|
|
214
|
-
expectedStatus: previousStatus,
|
|
215
|
-
currentStatus: actualTx?.status ?? 'unknown',
|
|
216
|
-
requestedStatus: nextStatus,
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Sync the entity state in the unit of work by refreshing it.
|
|
221
|
-
// This loads the updated values (status, paymentStatus, gatewayTransactionId)
|
|
222
|
-
// from the DB and marks the entity as clean, avoiding a redundant second write
|
|
223
|
-
// during the subsequent tx.flush().
|
|
224
|
-
await tx.refresh(transaction)
|
|
171
|
+
transaction.status = nextStatus
|
|
172
|
+
transaction.paymentStatus = parsed.paymentStatus ?? transaction.paymentStatus ?? null
|
|
173
|
+
transaction.gatewayTransactionId = parsed.gatewayTransactionId ?? transaction.gatewayTransactionId ?? null
|
|
174
|
+
await tx.flush()
|
|
225
175
|
|
|
226
|
-
// Only apply terminal link state and emit the terminal event when the
|
|
227
|
-
// status actually changes — prevents double-notification on idempotent
|
|
228
|
-
// redeliveries (e.g. authorized → captured, both mapping to 'completed').
|
|
229
176
|
if (!previousTerminal && nextTerminal) {
|
|
230
177
|
const { usageLimitReached } = applyTerminalTransactionState(link, nextStatus)
|
|
231
178
|
await tx.flush()
|
|
@@ -235,7 +182,7 @@ const updateTransactionStatusCommand: CommandHandler<Record<string, unknown>, {
|
|
|
235
182
|
usageLimitReachedLinkSlug = link.slug
|
|
236
183
|
}
|
|
237
184
|
}
|
|
238
|
-
if (nextTerminal
|
|
185
|
+
if (nextTerminal) {
|
|
239
186
|
terminalEventPayload = {
|
|
240
187
|
transactionId: transaction.id,
|
|
241
188
|
linkId: transaction.linkId,
|
|
@@ -253,16 +200,14 @@ const updateTransactionStatusCommand: CommandHandler<Record<string, unknown>, {
|
|
|
253
200
|
}
|
|
254
201
|
}
|
|
255
202
|
})
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
await emitCheckoutEvent('checkout.transaction.expired', terminalEventPayload).catch(() => undefined)
|
|
265
|
-
}
|
|
203
|
+
if (parsed.status === 'completed') {
|
|
204
|
+
await emitCheckoutEvent('checkout.transaction.completed', terminalEventPayload ?? {}).catch(() => undefined)
|
|
205
|
+
} else if (parsed.status === 'failed') {
|
|
206
|
+
await emitCheckoutEvent('checkout.transaction.failed', terminalEventPayload ?? {}).catch(() => undefined)
|
|
207
|
+
} else if (parsed.status === 'cancelled') {
|
|
208
|
+
await emitCheckoutEvent('checkout.transaction.cancelled', terminalEventPayload ?? {}).catch(() => undefined)
|
|
209
|
+
} else if (parsed.status === 'expired') {
|
|
210
|
+
await emitCheckoutEvent('checkout.transaction.expired', terminalEventPayload ?? {}).catch(() => undefined)
|
|
266
211
|
}
|
|
267
212
|
if (emitUsageLimitReached && usageLimitReachedLinkId && usageLimitReachedLinkSlug) {
|
|
268
213
|
await emitCheckoutEvent('checkout.link.usageLimitReached', {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { extensionPoints } from '@open-mercato/checkout/modules/checkout/extension-points'
|
|
5
4
|
import Link from 'next/link'
|
|
6
5
|
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
|
6
|
+
import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
|
|
7
7
|
import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
|
|
8
8
|
import { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'
|
|
9
9
|
import type { CustomFieldDisplayEntry } from '@open-mercato/shared/lib/crud/custom-fields'
|
|
@@ -270,17 +270,17 @@ export type PayPageFooterProps = {
|
|
|
270
270
|
themeTokens: PayPageThemeTokens
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
const PAGE_HANDLE =
|
|
274
|
-
const HEADER_HANDLE =
|
|
275
|
-
const DESCRIPTION_HANDLE =
|
|
276
|
-
const SUMMARY_HANDLE =
|
|
277
|
-
const PRICING_HANDLE =
|
|
278
|
-
const PAYMENT_HANDLE =
|
|
279
|
-
const CUSTOMER_FORM_HANDLE =
|
|
280
|
-
const LEGAL_CONSENT_HANDLE =
|
|
281
|
-
const GATEWAY_FORM_HANDLE =
|
|
282
|
-
const HELP_HANDLE =
|
|
283
|
-
const FOOTER_HANDLE =
|
|
273
|
+
const PAGE_HANDLE = ComponentReplacementHandles.page('checkout.pay-page')
|
|
274
|
+
const HEADER_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'header')
|
|
275
|
+
const DESCRIPTION_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'description')
|
|
276
|
+
const SUMMARY_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'summary')
|
|
277
|
+
const PRICING_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'pricing')
|
|
278
|
+
const PAYMENT_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'payment')
|
|
279
|
+
const CUSTOMER_FORM_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'customer-form')
|
|
280
|
+
const LEGAL_CONSENT_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'legal-consent')
|
|
281
|
+
const GATEWAY_FORM_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'gateway-form')
|
|
282
|
+
const HELP_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'help')
|
|
283
|
+
const FOOTER_HANDLE = ComponentReplacementHandles.section('checkout.pay-page', 'footer')
|
|
284
284
|
|
|
285
285
|
// Checkout supports merchant branding defaults; keep those theme fallbacks isolated from general UI state colors.
|
|
286
286
|
const CHECKOUT_THEME_FALLBACKS = {
|
|
@@ -1222,10 +1222,10 @@ export function PayPagePaymentForm({
|
|
|
1222
1222
|
</Alert>
|
|
1223
1223
|
) : null}
|
|
1224
1224
|
|
|
1225
|
-
<InjectionSpot spotId=
|
|
1225
|
+
<InjectionSpot spotId="checkout.pay-page:gateway-widget:before" context={injectionContext} />
|
|
1226
1226
|
{embeddedPaymentSession && embeddedRenderer ? (
|
|
1227
1227
|
<>
|
|
1228
|
-
<InjectionSpot spotId=
|
|
1228
|
+
<InjectionSpot spotId="checkout.pay-page:gateway-widget:renderer:before" context={injectionContext} />
|
|
1229
1229
|
{React.createElement(embeddedRenderer, {
|
|
1230
1230
|
providerKey: embeddedPaymentSession.providerKey ?? '',
|
|
1231
1231
|
transactionId: activeTransactionId ?? '',
|
|
@@ -1235,11 +1235,11 @@ export function PayPagePaymentForm({
|
|
|
1235
1235
|
onComplete,
|
|
1236
1236
|
onError,
|
|
1237
1237
|
})}
|
|
1238
|
-
<InjectionSpot spotId=
|
|
1238
|
+
<InjectionSpot spotId="checkout.pay-page:gateway-widget:renderer:after" context={injectionContext} />
|
|
1239
1239
|
</>
|
|
1240
1240
|
) : (
|
|
1241
1241
|
<>
|
|
1242
|
-
<InjectionSpot spotId=
|
|
1242
|
+
<InjectionSpot spotId="checkout.pay-page:gateway-widget:actions:before" context={injectionContext} />
|
|
1243
1243
|
<Button
|
|
1244
1244
|
type="button"
|
|
1245
1245
|
className="h-12 w-full rounded-xl text-base"
|
|
@@ -1256,10 +1256,10 @@ export function PayPagePaymentForm({
|
|
|
1256
1256
|
: t('checkout.payPage.actions.payNow', 'Pay now')}
|
|
1257
1257
|
</span>
|
|
1258
1258
|
</Button>
|
|
1259
|
-
<InjectionSpot spotId=
|
|
1259
|
+
<InjectionSpot spotId="checkout.pay-page:gateway-widget:actions:after" context={injectionContext} />
|
|
1260
1260
|
</>
|
|
1261
1261
|
)}
|
|
1262
|
-
<InjectionSpot spotId=
|
|
1262
|
+
<InjectionSpot spotId="checkout.pay-page:gateway-widget:after" context={injectionContext} />
|
|
1263
1263
|
|
|
1264
1264
|
{paymentSession ? (
|
|
1265
1265
|
<Button
|
|
@@ -1369,7 +1369,6 @@ export function PayPageFooter({ payload, themeTokens }: PayPageFooterProps) {
|
|
|
1369
1369
|
pl: t('common.languages.polish', 'Polski'),
|
|
1370
1370
|
es: t('common.languages.spanish', 'Español'),
|
|
1371
1371
|
de: t('common.languages.german', 'Deutsch'),
|
|
1372
|
-
ko: t('common.languages.korean', '한국어'),
|
|
1373
1372
|
}), [t])
|
|
1374
1373
|
|
|
1375
1374
|
const setLocale = React.useCallback(async (nextLocale: Locale) => {
|
|
@@ -2145,11 +2144,11 @@ export function PayPage({
|
|
|
2145
2144
|
|
|
2146
2145
|
const leftColumn = (
|
|
2147
2146
|
<div className="space-y-6">
|
|
2148
|
-
<InjectionSpot spotId=
|
|
2147
|
+
<InjectionSpot spotId="checkout.pay-page:header:before" context={injectionContext} />
|
|
2149
2148
|
<div data-component-handle={HEADER_HANDLE}>
|
|
2150
2149
|
<HeaderComponent payload={payload} preview={isPreview} themeTokens={themeTokens} />
|
|
2151
2150
|
</div>
|
|
2152
|
-
<InjectionSpot spotId=
|
|
2151
|
+
<InjectionSpot spotId="checkout.pay-page:header:after" context={injectionContext} />
|
|
2153
2152
|
|
|
2154
2153
|
{(payload.description || publicCustomFields.length > 0) ? (
|
|
2155
2154
|
<>
|
|
@@ -2160,11 +2159,11 @@ export function PayPage({
|
|
|
2160
2159
|
themeTokens={themeTokens}
|
|
2161
2160
|
/>
|
|
2162
2161
|
</div>
|
|
2163
|
-
<InjectionSpot spotId=
|
|
2162
|
+
<InjectionSpot spotId="checkout.pay-page:description:after" context={injectionContext} />
|
|
2164
2163
|
</>
|
|
2165
2164
|
) : null}
|
|
2166
2165
|
|
|
2167
|
-
<InjectionSpot spotId=
|
|
2166
|
+
<InjectionSpot spotId="checkout.pay-page:customer-fields:before" context={injectionContext} />
|
|
2168
2167
|
{shouldCollectCustomerDetails ? (
|
|
2169
2168
|
<div data-component-handle={CUSTOMER_FORM_HANDLE}>
|
|
2170
2169
|
<CustomerFormComponent
|
|
@@ -2178,13 +2177,13 @@ export function PayPage({
|
|
|
2178
2177
|
/>
|
|
2179
2178
|
</div>
|
|
2180
2179
|
) : null}
|
|
2181
|
-
<InjectionSpot spotId=
|
|
2180
|
+
<InjectionSpot spotId="checkout.pay-page:customer-fields:after" context={injectionContext} />
|
|
2182
2181
|
</div>
|
|
2183
2182
|
)
|
|
2184
2183
|
|
|
2185
2184
|
const paymentFlow = (
|
|
2186
2185
|
<>
|
|
2187
|
-
<InjectionSpot spotId=
|
|
2186
|
+
<InjectionSpot spotId="checkout.pay-page:pricing:before" context={injectionContext} />
|
|
2188
2187
|
<div data-component-handle={PRICING_HANDLE}>
|
|
2189
2188
|
<PricingComponent
|
|
2190
2189
|
payload={payload}
|
|
@@ -2216,9 +2215,9 @@ export function PayPage({
|
|
|
2216
2215
|
themeTokens={themeTokens}
|
|
2217
2216
|
/>
|
|
2218
2217
|
</div>
|
|
2219
|
-
<InjectionSpot spotId=
|
|
2218
|
+
<InjectionSpot spotId="checkout.pay-page:pricing:after" context={injectionContext} />
|
|
2220
2219
|
|
|
2221
|
-
<InjectionSpot spotId=
|
|
2220
|
+
<InjectionSpot spotId="checkout.pay-page:summary:before" context={injectionContext} />
|
|
2222
2221
|
<div data-component-handle={SUMMARY_HANDLE}>
|
|
2223
2222
|
<SummaryComponent
|
|
2224
2223
|
payload={payload}
|
|
@@ -2229,9 +2228,9 @@ export function PayPage({
|
|
|
2229
2228
|
themeTokens={themeTokens}
|
|
2230
2229
|
/>
|
|
2231
2230
|
</div>
|
|
2232
|
-
<InjectionSpot spotId=
|
|
2231
|
+
<InjectionSpot spotId="checkout.pay-page:summary:after" context={injectionContext} />
|
|
2233
2232
|
|
|
2234
|
-
<InjectionSpot spotId=
|
|
2233
|
+
<InjectionSpot spotId="checkout.pay-page:legal-consent:before" context={injectionContext} />
|
|
2235
2234
|
<div data-component-handle={LEGAL_CONSENT_HANDLE}>
|
|
2236
2235
|
<LegalConsentComponent
|
|
2237
2236
|
payload={payload}
|
|
@@ -2243,9 +2242,9 @@ export function PayPage({
|
|
|
2243
2242
|
themeTokens={themeTokens}
|
|
2244
2243
|
/>
|
|
2245
2244
|
</div>
|
|
2246
|
-
<InjectionSpot spotId=
|
|
2245
|
+
<InjectionSpot spotId="checkout.pay-page:legal-consent:after" context={injectionContext} />
|
|
2247
2246
|
|
|
2248
|
-
<InjectionSpot spotId=
|
|
2247
|
+
<InjectionSpot spotId="checkout.pay-page:submit:before" context={injectionContext} />
|
|
2249
2248
|
<div data-component-handle={GATEWAY_FORM_HANDLE}>
|
|
2250
2249
|
<PaymentFormComponent
|
|
2251
2250
|
payload={payload}
|
|
@@ -2273,35 +2272,35 @@ export function PayPage({
|
|
|
2273
2272
|
themeTokens={themeTokens}
|
|
2274
2273
|
/>
|
|
2275
2274
|
</div>
|
|
2276
|
-
<InjectionSpot spotId=
|
|
2275
|
+
<InjectionSpot spotId="checkout.pay-page:submit:after" context={injectionContext} />
|
|
2277
2276
|
|
|
2278
|
-
<InjectionSpot spotId=
|
|
2277
|
+
<InjectionSpot spotId="checkout.pay-page:help:before" context={injectionContext} />
|
|
2279
2278
|
<div data-component-handle={HELP_HANDLE}>
|
|
2280
2279
|
<HelpComponent payload={payload} preview={isPreview} themeTokens={themeTokens} />
|
|
2281
2280
|
</div>
|
|
2282
|
-
<InjectionSpot spotId=
|
|
2281
|
+
<InjectionSpot spotId="checkout.pay-page:help:after" context={injectionContext} />
|
|
2283
2282
|
</>
|
|
2284
2283
|
)
|
|
2285
2284
|
|
|
2286
2285
|
const rightColumn = (
|
|
2287
2286
|
<>
|
|
2288
|
-
<InjectionSpot spotId=
|
|
2287
|
+
<InjectionSpot spotId="checkout.pay-page:payment:before" context={injectionContext} />
|
|
2289
2288
|
<div data-component-handle={PAYMENT_HANDLE}>
|
|
2290
2289
|
<PaymentSectionComponent payload={payload} preview={isPreview} themeTokens={themeTokens}>
|
|
2291
2290
|
{paymentFlow}
|
|
2292
2291
|
</PaymentSectionComponent>
|
|
2293
2292
|
</div>
|
|
2294
|
-
<InjectionSpot spotId=
|
|
2293
|
+
<InjectionSpot spotId="checkout.pay-page:payment:after" context={injectionContext} />
|
|
2295
2294
|
</>
|
|
2296
2295
|
)
|
|
2297
2296
|
|
|
2298
2297
|
const footer = (
|
|
2299
2298
|
<>
|
|
2300
|
-
<InjectionSpot spotId=
|
|
2299
|
+
<InjectionSpot spotId="checkout.pay-page:footer:before" context={injectionContext} />
|
|
2301
2300
|
<div data-component-handle={FOOTER_HANDLE}>
|
|
2302
2301
|
<FooterComponent payload={payload} themeTokens={themeTokens} />
|
|
2303
2302
|
</div>
|
|
2304
|
-
<InjectionSpot spotId=
|
|
2303
|
+
<InjectionSpot spotId="checkout.pay-page:footer:after" context={injectionContext} />
|
|
2305
2304
|
</>
|
|
2306
2305
|
)
|
|
2307
2306
|
|
|
@@ -380,7 +380,6 @@
|
|
|
380
380
|
"checkout.payPage.validation.priceSelectionRequired": "Bitte eine Zahlungsoption auswaehlen, um fortzufahren.",
|
|
381
381
|
"checkout.payPage.validation.requiredField": "Bitte {field} ausfuellen.",
|
|
382
382
|
"checkout.payPage.validation.thisField": "dieses Feld",
|
|
383
|
-
"checkout.search.subtitle.linkTemplate": "Link-Vorlage",
|
|
384
383
|
"checkout.statusPage.backToPayment": "Zuruck zur Zahlungsseite",
|
|
385
384
|
"checkout.statusPage.cancel.cancelled": "Zahlung abgebrochen",
|
|
386
385
|
"checkout.statusPage.cancel.failed": "Zahlung fehlgeschlagen",
|
|
@@ -380,7 +380,6 @@
|
|
|
380
380
|
"checkout.payPage.validation.priceSelectionRequired": "Choose a payment option to continue.",
|
|
381
381
|
"checkout.payPage.validation.requiredField": "Enter {field}.",
|
|
382
382
|
"checkout.payPage.validation.thisField": "this field",
|
|
383
|
-
"checkout.search.subtitle.linkTemplate": "Link Template",
|
|
384
383
|
"checkout.statusPage.backToPayment": "Back to payment page",
|
|
385
384
|
"checkout.statusPage.cancel.cancelled": "Payment cancelled",
|
|
386
385
|
"checkout.statusPage.cancel.failed": "Payment failed",
|
|
@@ -380,7 +380,6 @@
|
|
|
380
380
|
"checkout.payPage.validation.priceSelectionRequired": "Elige una opcion de pago para continuar.",
|
|
381
381
|
"checkout.payPage.validation.requiredField": "Completa {field}.",
|
|
382
382
|
"checkout.payPage.validation.thisField": "este campo",
|
|
383
|
-
"checkout.search.subtitle.linkTemplate": "Plantilla de enlace",
|
|
384
383
|
"checkout.statusPage.backToPayment": "Volver a la pagina de pago",
|
|
385
384
|
"checkout.statusPage.cancel.cancelled": "Pago cancelado",
|
|
386
385
|
"checkout.statusPage.cancel.failed": "Pago fallido",
|
|
@@ -380,7 +380,6 @@
|
|
|
380
380
|
"checkout.payPage.validation.priceSelectionRequired": "Wybierz opcję płatności, aby kontynuować.",
|
|
381
381
|
"checkout.payPage.validation.requiredField": "Uzupełnij pole {field}.",
|
|
382
382
|
"checkout.payPage.validation.thisField": "to pole",
|
|
383
|
-
"checkout.search.subtitle.linkTemplate": "Szablon linku",
|
|
384
383
|
"checkout.statusPage.backToPayment": "Wróć do strony płatności",
|
|
385
384
|
"checkout.statusPage.cancel.cancelled": "Płatność anulowana",
|
|
386
385
|
"checkout.statusPage.cancel.failed": "Płatność nieudana",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { SearchModuleConfig } from '@open-mercato/shared/modules/search'
|
|
2
|
-
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
3
2
|
import { CHECKOUT_ENTITY_IDS } from './lib/constants'
|
|
4
3
|
|
|
5
4
|
function asSearchText(value: unknown): string {
|
|
@@ -35,52 +34,15 @@ export const searchConfig: SearchModuleConfig = {
|
|
|
35
34
|
searchable: ['name', 'title'],
|
|
36
35
|
excluded: ['passwordHash', 'gatewaySettings', 'customerFieldsSchema'],
|
|
37
36
|
},
|
|
38
|
-
buildSource: async (ctx) => {
|
|
39
|
-
const { t } = await resolveTranslations()
|
|
40
|
-
const linkTemplate = t('checkout.search.subtitle.linkTemplate', 'Link Template')
|
|
41
|
-
return {
|
|
42
|
-
text: [`${asSearchText(ctx.record.name)}: ${asSearchText(ctx.record.title)}`],
|
|
43
|
-
presenter: { title: asSearchText(ctx.record.name), subtitle: linkTemplate },
|
|
44
|
-
checksumSource: { record: ctx.record, customFields: ctx.customFields },
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
formatResult: async (ctx) => {
|
|
48
|
-
const { t } = await resolveTranslations()
|
|
49
|
-
return {
|
|
50
|
-
title: asSearchText(ctx.record.name),
|
|
51
|
-
subtitle: t('checkout.search.subtitle.linkTemplate', 'Link Template'),
|
|
52
|
-
icon: 'lucide:file-text',
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
entityId: CHECKOUT_ENTITY_IDS.transaction,
|
|
58
|
-
enabled: true,
|
|
59
|
-
priority: 6,
|
|
60
|
-
fieldPolicy: {
|
|
61
|
-
searchable: ['email', 'firstName', 'lastName'],
|
|
62
|
-
},
|
|
63
37
|
buildSource: async (ctx) => ({
|
|
64
|
-
text: [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
asSearchText(ctx.record.lastName),
|
|
68
|
-
],
|
|
69
|
-
fields: {
|
|
70
|
-
email: ctx.record.email,
|
|
71
|
-
first_name: ctx.record.firstName,
|
|
72
|
-
last_name: ctx.record.lastName,
|
|
73
|
-
},
|
|
74
|
-
presenter: {
|
|
75
|
-
title: asSearchText(ctx.record.email),
|
|
76
|
-
subtitle: `${asSearchText(ctx.record.firstName)} ${asSearchText(ctx.record.lastName)}`.trim(),
|
|
77
|
-
},
|
|
78
|
-
checksumSource: { record: ctx.record },
|
|
38
|
+
text: [`${asSearchText(ctx.record.name)}: ${asSearchText(ctx.record.title)}`],
|
|
39
|
+
presenter: { title: asSearchText(ctx.record.name), subtitle: 'Link Template' },
|
|
40
|
+
checksumSource: { record: ctx.record, customFields: ctx.customFields },
|
|
79
41
|
}),
|
|
80
42
|
formatResult: async (ctx) => ({
|
|
81
|
-
title: asSearchText(ctx.record.
|
|
82
|
-
subtitle:
|
|
83
|
-
icon: 'lucide:
|
|
43
|
+
title: asSearchText(ctx.record.name),
|
|
44
|
+
subtitle: 'Link Template',
|
|
45
|
+
icon: 'lucide:file-text',
|
|
84
46
|
}),
|
|
85
47
|
},
|
|
86
48
|
],
|
|
@@ -1,65 +1,14 @@
|
|
|
1
1
|
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
2
|
import { ensureCheckoutFieldsetsAndDefinitions } from './seed/customFields'
|
|
4
3
|
import { seedCheckoutExamples } from './seed/examples'
|
|
5
|
-
import { CHECKOUT_EXPIRY_QUEUE } from './workers/transaction-expiry.worker'
|
|
6
4
|
export { DEFAULT_CHECKOUT_CUSTOMER_FIELDS } from './lib/defaults'
|
|
7
5
|
|
|
8
|
-
function stableUuidFromString(input: string): string {
|
|
9
|
-
const bytes = createHash('sha256').update(input).digest().subarray(0, 16)
|
|
10
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x50
|
|
11
|
-
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
|
12
|
-
const hex = Buffer.from(bytes).toString('hex')
|
|
13
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type SchedulerServiceLike = {
|
|
17
|
-
register: (registration: {
|
|
18
|
-
id: string
|
|
19
|
-
name: string
|
|
20
|
-
description?: string
|
|
21
|
-
scopeType: 'organization'
|
|
22
|
-
organizationId: string
|
|
23
|
-
tenantId: string
|
|
24
|
-
scheduleType: 'cron' | 'interval'
|
|
25
|
-
scheduleValue: string
|
|
26
|
-
timezone?: string
|
|
27
|
-
targetType: 'queue'
|
|
28
|
-
targetQueue: string
|
|
29
|
-
targetPayload?: Record<string, unknown>
|
|
30
|
-
sourceType: 'module'
|
|
31
|
-
sourceModule: string
|
|
32
|
-
isEnabled?: boolean
|
|
33
|
-
}) => Promise<void>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
6
|
export const setup: ModuleSetupConfig = {
|
|
37
7
|
async seedDefaults(ctx) {
|
|
38
8
|
await ensureCheckoutFieldsetsAndDefinitions(ctx.em, {
|
|
39
9
|
tenantId: ctx.tenantId,
|
|
40
10
|
organizationId: ctx.organizationId,
|
|
41
11
|
})
|
|
42
|
-
|
|
43
|
-
const cradle = ctx.container as { hasRegistration?: (name: string) => boolean }
|
|
44
|
-
if (typeof cradle.hasRegistration !== 'function' || !cradle.hasRegistration('schedulerService')) return
|
|
45
|
-
|
|
46
|
-
const schedulerService = ctx.container.resolve('schedulerService') as SchedulerServiceLike
|
|
47
|
-
await schedulerService.register({
|
|
48
|
-
id: stableUuidFromString(`checkout:transaction-expiry:${ctx.tenantId}:${ctx.organizationId}`),
|
|
49
|
-
name: 'Checkout transaction expiry',
|
|
50
|
-
description: 'Marks stale checkout transactions as expired and runs usage limit updates.',
|
|
51
|
-
scopeType: 'organization',
|
|
52
|
-
organizationId: ctx.organizationId,
|
|
53
|
-
tenantId: ctx.tenantId,
|
|
54
|
-
scheduleType: 'interval',
|
|
55
|
-
scheduleValue: '10m',
|
|
56
|
-
timezone: 'UTC',
|
|
57
|
-
targetType: 'queue',
|
|
58
|
-
targetQueue: CHECKOUT_EXPIRY_QUEUE,
|
|
59
|
-
sourceType: 'module',
|
|
60
|
-
sourceModule: 'checkout',
|
|
61
|
-
isEnabled: true,
|
|
62
|
-
})
|
|
63
12
|
},
|
|
64
13
|
|
|
65
14
|
defaultRoleFeatures: {
|
|
@@ -14,8 +14,6 @@ const EXPIRY_TIMEOUT_MS = 2 * 60 * 60 * 1000 // 2 hours
|
|
|
14
14
|
|
|
15
15
|
export type CheckoutExpiryJob = {
|
|
16
16
|
batchSize?: number
|
|
17
|
-
tenantId: string
|
|
18
|
-
organizationId: string
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export const metadata: WorkerMeta = {
|
|
@@ -34,23 +32,14 @@ export default async function handle(job: QueuedJob<CheckoutExpiryJob>, ctx: Han
|
|
|
34
32
|
const batchSize = job.payload?.batchSize ?? 100
|
|
35
33
|
const cutoff = new Date(Date.now() - EXPIRY_TIMEOUT_MS)
|
|
36
34
|
|
|
37
|
-
if (!job.payload?.tenantId || !job.payload?.organizationId) {
|
|
38
|
-
throw new Error('[internal] tenantId and organizationId are required in CheckoutExpiryJob')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { tenantId, organizationId } = job.payload
|
|
42
|
-
|
|
43
35
|
const staleTransactions = await findWithDecryption(
|
|
44
36
|
em,
|
|
45
37
|
CheckoutTransaction,
|
|
46
38
|
{
|
|
47
39
|
status: 'processing',
|
|
48
40
|
createdAt: { $lt: cutoff },
|
|
49
|
-
tenantId,
|
|
50
|
-
organizationId,
|
|
51
41
|
},
|
|
52
42
|
{ limit: batchSize, orderBy: { createdAt: 'ASC' } },
|
|
53
|
-
{ tenantId, organizationId },
|
|
54
43
|
)
|
|
55
44
|
|
|
56
45
|
for (const transaction of staleTransactions) {
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "@playwright/test";
|
|
2
|
-
import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
-
import {
|
|
4
|
-
createCustomerData,
|
|
5
|
-
createFixedTemplateInput,
|
|
6
|
-
createLinkFixture,
|
|
7
|
-
deleteCheckoutEntityIfExists,
|
|
8
|
-
listCheckoutTransactions,
|
|
9
|
-
submitPayLink
|
|
10
|
-
} from "./helpers/fixtures.js";
|
|
11
|
-
test.describe("TC-CHKT-042: Transaction search over encrypted customer data", () => {
|
|
12
|
-
test("finds a newly created transaction through its token index", async ({ request }) => {
|
|
13
|
-
const email = `token-search-${Date.now()}@example.test`;
|
|
14
|
-
let token = null;
|
|
15
|
-
let linkId = null;
|
|
16
|
-
try {
|
|
17
|
-
token = await getAuthToken(request);
|
|
18
|
-
const link = await createLinkFixture(request, token, createFixedTemplateInput({ status: "active" }));
|
|
19
|
-
linkId = link.id;
|
|
20
|
-
const submitted = await submitPayLink(request, link.slug, {
|
|
21
|
-
customerData: createCustomerData({ email }),
|
|
22
|
-
acceptedLegalConsents: {},
|
|
23
|
-
amount: 49.99
|
|
24
|
-
});
|
|
25
|
-
expect(submitted.ok()).toBeTruthy();
|
|
26
|
-
await expect.poll(async () => {
|
|
27
|
-
const result = await listCheckoutTransactions(
|
|
28
|
-
request,
|
|
29
|
-
token,
|
|
30
|
-
`search=${encodeURIComponent(email)}&page=1&pageSize=10`
|
|
31
|
-
);
|
|
32
|
-
return result.items.some((item) => item.email === email);
|
|
33
|
-
}, { timeout: 15e3 }).toBeTruthy();
|
|
34
|
-
} finally {
|
|
35
|
-
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
//# sourceMappingURL=TC-CHKT-042.spec.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/modules/checkout/__integration__/TC-CHKT-042.spec.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n createCustomerData,\n createFixedTemplateInput,\n createLinkFixture,\n deleteCheckoutEntityIfExists,\n listCheckoutTransactions,\n submitPayLink,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-042: Transaction search over encrypted customer data', () => {\n test('finds a newly created transaction through its token index', async ({ request }) => {\n const email = `token-search-${Date.now()}@example.test`\n let token: string | null = null\n let linkId: string | null = null\n\n try {\n token = await getAuthToken(request)\n const link = await createLinkFixture(request, token, createFixedTemplateInput({ status: 'active' }))\n linkId = link.id\n const submitted = await submitPayLink(request, link.slug, {\n customerData: createCustomerData({ email }),\n acceptedLegalConsents: {},\n amount: 49.99,\n })\n expect(submitted.ok()).toBeTruthy()\n\n await expect.poll(async () => {\n const result = await listCheckoutTransactions(\n request,\n token!,\n `search=${encodeURIComponent(email)}&page=1&pageSize=10`,\n )\n return result.items.some((item) => item.email === email)\n }, { timeout: 15_000 }).toBeTruthy()\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,KAAK,SAAS,gEAAgE,MAAM;AAClF,OAAK,6DAA6D,OAAO,EAAE,QAAQ,MAAM;AACvF,UAAM,QAAQ,gBAAgB,KAAK,IAAI,CAAC;AACxC,QAAI,QAAuB;AAC3B,QAAI,SAAwB;AAE5B,QAAI;AACF,cAAQ,MAAM,aAAa,OAAO;AAClC,YAAM,OAAO,MAAM,kBAAkB,SAAS,OAAO,yBAAyB,EAAE,QAAQ,SAAS,CAAC,CAAC;AACnG,eAAS,KAAK;AACd,YAAM,YAAY,MAAM,cAAc,SAAS,KAAK,MAAM;AAAA,QACxD,cAAc,mBAAmB,EAAE,MAAM,CAAC;AAAA,QAC1C,uBAAuB,CAAC;AAAA,QACxB,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,UAAU,GAAG,CAAC,EAAE,WAAW;AAElC,YAAM,OAAO,KAAK,YAAY;AAC5B,cAAM,SAAS,MAAM;AAAA,UACnB;AAAA,UACA;AAAA,UACA,UAAU,mBAAmB,KAAK,CAAC;AAAA,QACrC;AACA,eAAO,OAAO,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,KAAK;AAAA,MACzD,GAAG,EAAE,SAAS,KAAO,CAAC,EAAE,WAAW;AAAA,IACrC,UAAE;AACA,YAAM,6BAA6B,SAAS,OAAO,SAAS,MAAM;AAAA,IACpE;AAAA,EACF,CAAC;AACH,CAAC;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "@playwright/test";
|
|
2
|
-
import { getAuthToken, apiRequest } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
-
import { readJsonSafe } from "@open-mercato/core/modules/core/__integration__/helpers/generalFixtures";
|
|
4
|
-
import {
|
|
5
|
-
createFixedTemplateInput,
|
|
6
|
-
createLinkFixture,
|
|
7
|
-
deleteCheckoutEntityIfExists,
|
|
8
|
-
findGatewayTransactionIdForCheckout,
|
|
9
|
-
readCheckoutTransaction,
|
|
10
|
-
sendMockGatewayWebhook,
|
|
11
|
-
submitPayLink,
|
|
12
|
-
waitForCheckoutStatus
|
|
13
|
-
} from "./helpers/fixtures.js";
|
|
14
|
-
test.describe("TC-CHKT-043 (concurrency): Two-contender race \u2014 only one updateStatus call wins", () => {
|
|
15
|
-
test("concurrent completed + processing status updates: one wins, one gets 409, completionCount incremented once", async ({ request }) => {
|
|
16
|
-
let token = null;
|
|
17
|
-
let linkId = null;
|
|
18
|
-
try {
|
|
19
|
-
token = await getAuthToken(request);
|
|
20
|
-
const link = await createLinkFixture(request, token, {
|
|
21
|
-
...createFixedTemplateInput({ status: "active", collectCustomerDetails: false })
|
|
22
|
-
});
|
|
23
|
-
linkId = link.id;
|
|
24
|
-
const submitResponse = await submitPayLink(request, link.slug, {
|
|
25
|
-
customerData: {},
|
|
26
|
-
acceptedLegalConsents: {},
|
|
27
|
-
amount: 49.99
|
|
28
|
-
});
|
|
29
|
-
expect(submitResponse.status(), `Submit failed: ${submitResponse.status()}`).toBe(201);
|
|
30
|
-
const submitBody = await submitResponse.json();
|
|
31
|
-
const transactionId = submitBody.transactionId;
|
|
32
|
-
expect(transactionId).toBeTruthy();
|
|
33
|
-
const gatewayTransactionId = await findGatewayTransactionIdForCheckout(request, token, transactionId);
|
|
34
|
-
const gatewayTransaction = await apiRequest(request, "GET", `/api/payment_gateways/transactions/${encodeURIComponent(gatewayTransactionId)}`, { token });
|
|
35
|
-
const gtBody = await readJsonSafe(gatewayTransaction);
|
|
36
|
-
const providerSessionId = gtBody?.transaction?.providerSessionId;
|
|
37
|
-
expect(providerSessionId, "providerSessionId is required for webhook simulation").toBeTruthy();
|
|
38
|
-
const [webhookResult, directResult] = await Promise.allSettled([
|
|
39
|
-
sendMockGatewayWebhook(request, token, providerSessionId, "captured", 49.99),
|
|
40
|
-
apiRequest(request, "POST", `/api/checkout/transactions/${encodeURIComponent(transactionId)}/status`, {
|
|
41
|
-
token,
|
|
42
|
-
data: { status: "processing", paymentStatus: "processing" }
|
|
43
|
-
})
|
|
44
|
-
]);
|
|
45
|
-
expect(webhookResult.status === "fulfilled" || directResult.status === "fulfilled").toBe(true);
|
|
46
|
-
const finalTransaction = await waitForCheckoutStatus(request, token, transactionId, "completed", {
|
|
47
|
-
attempts: 30,
|
|
48
|
-
intervalMs: 200
|
|
49
|
-
});
|
|
50
|
-
expect(finalTransaction.status).toBe("completed");
|
|
51
|
-
const updatedLinkResponse = await apiRequest(request, "GET", `/api/checkout/links/${encodeURIComponent(link.id)}`, { token });
|
|
52
|
-
const updatedLinkBody = await readJsonSafe(updatedLinkResponse);
|
|
53
|
-
expect(updatedLinkBody?.completionCount).toBe(1);
|
|
54
|
-
const storedTransaction = await readCheckoutTransaction(request, token, transactionId);
|
|
55
|
-
expect(storedTransaction.status).toBe("completed");
|
|
56
|
-
expect(storedTransaction.gatewayTransactionId).toBeTruthy();
|
|
57
|
-
} finally {
|
|
58
|
-
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
//# sourceMappingURL=TC-CHKT-043-concurrent-status-update.spec.js.map
|