platform-types 1.10.711 → 1.10.713
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/dist/cjs/index.js +1 -0
- package/dist/cjs/mercadopago.js +3 -1
- package/dist/cjs/order.js +24 -0
- package/dist/cjs/orderCollectionAttempt.js +74 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mercadopago.d.ts +66 -4
- package/dist/mercadopago.js +3 -1
- package/dist/order.d.ts +23 -1
- package/dist/order.js +24 -0
- package/dist/orderCollectionAttempt.d.ts +163 -0
- package/dist/orderCollectionAttempt.js +71 -0
- package/dist/stock.d.ts +44 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -73,6 +73,7 @@ __exportStar(require("./mercadolibre.js"), exports);
|
|
|
73
73
|
__exportStar(require("./mercadopago.js"), exports);
|
|
74
74
|
__exportStar(require("./notification.js"), exports);
|
|
75
75
|
__exportStar(require("./order.js"), exports);
|
|
76
|
+
__exportStar(require("./orderCollectionAttempt.js"), exports);
|
|
76
77
|
__exportStar(require("./orderCreateAttempt.js"), exports);
|
|
77
78
|
__exportStar(require("./payment.js"), exports);
|
|
78
79
|
__exportStar(require("./platform.js"), exports);
|
package/dist/cjs/mercadopago.js
CHANGED
|
@@ -15,7 +15,9 @@ exports.MP_DYNAMIC_QR_ERROR_CODES = void 0;
|
|
|
15
15
|
* - 503 `PLATFORM_INTEGRATION_DISABLED`; 504 `MP_DYN_QR_UNCONFIRMED`, an unknown creation outcome.
|
|
16
16
|
* A thrown error answers `400 UNHANDLED_ERROR`, never a 500. The re-sync read
|
|
17
17
|
* (`GET /mercadopago/qr/dynamic?orderId=`) uses `VALIDATION_FAILED`, `PLAN_UPGRADE_REQUIRED`,
|
|
18
|
-
* `CAPABILITY_REQUIRED`, `ORDER_NOT_FOUND` and `ORDER_CANCELLED` from this list
|
|
18
|
+
* `CAPABILITY_REQUIRED`, `ORDER_NOT_FOUND` and `ORDER_CANCELLED` from this list; its
|
|
19
|
+
* `ORDER_CANCELLED` carries `data: MpDynamicQrCancelledDetails` when the dead order holds linked
|
|
20
|
+
* MercadoPago payments.
|
|
19
21
|
*
|
|
20
22
|
* An order-bound charge holds the order's QR fence while it mints, and another charge for the
|
|
21
23
|
* same order meanwhile answers `MP_DYN_QR_IN_PROGRESS`. A `contacting` attempt (MercadoPago may
|
package/dist/cjs/order.js
CHANGED
|
@@ -235,6 +235,15 @@ exports.orderFulfilmentProgress = orderFulfilmentProgress;
|
|
|
235
235
|
* is also `mode: 'edit'`'s and the update leg's answer to a re-price that
|
|
236
236
|
* would leave the order's net señas above its new charged total. Nothing was
|
|
237
237
|
* written on any of them.
|
|
238
|
+
* - `TILL_TENDER_REQUIRES_DEPOSIT` · 400 — `mode: 'tender'` adding till money
|
|
239
|
+
* (any leg whose `source` is not `account`) to an order that is NOT yet
|
|
240
|
+
* delivered and holds no seña: money taken before delivery is a seña and
|
|
241
|
+
* goes through `mode: 'deposit'`. Decided after replays are absorbed, so a
|
|
242
|
+
* pure repeat of legs already on the ledger still answers its 200; an
|
|
243
|
+
* account-only tender is never refused. Nothing was written and no
|
|
244
|
+
* idempotency marker remains (refused ahead of the claim, or the claim is
|
|
245
|
+
* released on the 400), so a collection lookup for it answers
|
|
246
|
+
* `unknown` / `NO_EVIDENCE`.
|
|
238
247
|
* - `PAYMENT_REFERENCED_BY_DEPOSIT` · 409 — the payment link (fresh, over an
|
|
239
248
|
* entry the order already holds, or a credit upgrade) of a payment whose id a
|
|
240
249
|
* seña on the order already carries as its `reference`: the same money would
|
|
@@ -442,6 +451,19 @@ exports.orderFulfilmentProgress = orderFulfilmentProgress;
|
|
|
442
451
|
* for another store or user session (`ORDER_ATTEMPT_SCOPE_TAG_LENGTH`). Mint
|
|
443
452
|
* a new key in this session. Nothing was written.
|
|
444
453
|
*
|
|
454
|
+
* ⚠️ **A money write's attempt identity is its `Idempotency-Key`, never its
|
|
455
|
+
* `reference`.** Mint one key per tender, seña or account payment and keep it
|
|
456
|
+
* for every retry. `reference` is a duplicate-money guard with its own
|
|
457
|
+
* horizon, not an outcome, and it runs across keys: a tender absorbs a repeat
|
|
458
|
+
* leg identical on money, method and source that carries the same `reference`
|
|
459
|
+
* indefinitely, or — neither side carrying one — was recorded by the same user
|
|
460
|
+
* within 30 seconds; a seña absorbs a same-user or same-`reference` repeat
|
|
461
|
+
* only inside its 30-second replay window, and an older lookalike is
|
|
462
|
+
* `409 DEPOSIT_DUPLICATE_REFERENCE`. To learn whether a keyed
|
|
463
|
+
* write landed after a lost reply, `IDEMPOTENCY_IN_PROGRESS` or
|
|
464
|
+
* `IDEMPOTENCY_KEY_FAILED`, ask `POST /orders { mode: 'collection-attempt' }`
|
|
465
|
+
* (`OrderCollectionAttemptVerdict`) — never resend it under a new key first.
|
|
466
|
+
*
|
|
445
467
|
* Idempotency answers, all six from `withIdempotency`:
|
|
446
468
|
* - `INVALID_IDEMPOTENCY_KEY` · 400 · `IDEMPOTENCY_KEY_REUSE` · 422 (the same
|
|
447
469
|
* key with a different body)
|
|
@@ -521,6 +543,8 @@ exports.ORDER_ERROR_CODES = [
|
|
|
521
543
|
'DEPOSIT_INVOICED_ORDER_MUST_FREEZE',
|
|
522
544
|
'DEPOSIT_REFERENCE_IS_LINKED_PAYMENT',
|
|
523
545
|
'DEPOSIT_DUPLICATE_REFERENCE',
|
|
546
|
+
// Till money on an undelivered order with no seña — `mode: 'deposit'` takes it.
|
|
547
|
+
'TILL_TENDER_REQUIRES_DEPOSIT',
|
|
524
548
|
// The payment link of a payment a seña on the order already references.
|
|
525
549
|
'PAYMENT_REFERENCED_BY_DEPOSIT',
|
|
526
550
|
// Customer cancellation of an order still holding a seña.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Order-collection attempt lookup — the read a client runs when a money write
|
|
4
|
+
* on an order (a till tender, a seña, an account payment) left it without an
|
|
5
|
+
* answer: a lost reply, a timeout, or one of the two uncertain refusals
|
|
6
|
+
* `409 IDEMPOTENCY_IN_PROGRESS` and `409 IDEMPOTENCY_KEY_FAILED`.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ **The attempt's identity is its `Idempotency-Key`, never its `reference`.**
|
|
9
|
+
* Mint ONE key per collection attempt and keep it for every retry of that
|
|
10
|
+
* attempt. `reference` keeps its own, narrower meaning as a duplicate-money
|
|
11
|
+
* guard, and that guard runs ACROSS keys: a tender absorbs a repeat leg
|
|
12
|
+
* identical on money, method and source that carries the same `reference`
|
|
13
|
+
* however much later it arrives, OR — neither side carrying a reference — was
|
|
14
|
+
* recorded by the same user within 30 seconds; a seña absorbs a same-user or
|
|
15
|
+
* same-`reference` repeat only inside its 30-second replay window (an older
|
|
16
|
+
* lookalike is `409 DEPOSIT_DUPLICATE_REFERENCE`). So a NEW key is a new
|
|
17
|
+
* attempt, not a guaranteed new leg: two genuine reference-less handoffs of the
|
|
18
|
+
* same amount by one cashier inside 30 s land as ONE leg, and the second key
|
|
19
|
+
* answers `committed` all the same. Give each physical payment its own
|
|
20
|
+
* `reference` when that distinction matters. Neither guard is an outcome
|
|
21
|
+
* protocol.
|
|
22
|
+
*
|
|
23
|
+
* `POST /orders { mode: 'collection-attempt', operation, orderId, idempotencyKey }`
|
|
24
|
+
* answers from the write's own idempotency marker, scoped to the CALLER's store
|
|
25
|
+
* and user. It never claims, replays, reclaims or seals the marker, never
|
|
26
|
+
* writes the marker or any business row, and calls no provider; a claim it
|
|
27
|
+
* finds stranded past 60 s logs one deduped operator row.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.ORDER_COLLECTION_ATTEMPT_RETENTION_MS = exports.ORDER_COLLECTION_ATTEMPT_UNKNOWN_REASONS = exports.ORDER_COLLECTION_ATTEMPT_VERDICTS = exports.ORDER_COLLECTION_ATTEMPT_OPERATIONS = void 0;
|
|
31
|
+
/** Every `operation` the lookup answers for — see `OrderCollectionAttemptLookupRequest`. */
|
|
32
|
+
exports.ORDER_COLLECTION_ATTEMPT_OPERATIONS = ['tender', 'deposit', 'account'];
|
|
33
|
+
/** Every `verdict` the lookup answers — see `OrderCollectionAttemptVerdict`. */
|
|
34
|
+
exports.ORDER_COLLECTION_ATTEMPT_VERDICTS = ['committed', 'in-progress', 'sealed', 'unknown'];
|
|
35
|
+
/**
|
|
36
|
+
* Every `reason` on an `unknown` verdict:
|
|
37
|
+
*
|
|
38
|
+
* - `NO_EVIDENCE` — no marker under this key for the caller: never sent, sent
|
|
39
|
+
* by another user or store, refused or released before it committed,
|
|
40
|
+
* already deleted by its retention expiry — or STILL IN FLIGHT and not yet
|
|
41
|
+
* claimed. A write holds no marker until its claim lands, and before that
|
|
42
|
+
* it can be queued at the gateway, in a cold start, or (a tender) running
|
|
43
|
+
* its pre-claim reads. So it is never "not collected": follow the
|
|
44
|
+
* `unknown` remedy on `OrderCollectionAttemptVerdict` — resend the same
|
|
45
|
+
* body under the same key first.
|
|
46
|
+
* - `EVIDENCE_EXPIRED` — the marker outlived
|
|
47
|
+
* {@link ORDER_COLLECTION_ATTEMPT_RETENTION_MS} and is awaiting deletion,
|
|
48
|
+
* which DynamoDB does lazily (up to days late). That is the LOOKUP's
|
|
49
|
+
* horizon, not the writer's: the write never reads the expiry, so while
|
|
50
|
+
* the row survives a same-key resend still replays a committed `200` (or
|
|
51
|
+
* refuses a sealed or stranded key) and does NOT run again. Once DynamoDB
|
|
52
|
+
* deletes the row the same resend runs as a new write — so past the
|
|
53
|
+
* horizon verify where the operation writes before resending at all.
|
|
54
|
+
* - `UNBOUND_ATTEMPT` — the key belongs to a write that is not a collection
|
|
55
|
+
* attempt (another order mode), or to one admitted before the server
|
|
56
|
+
* started recording which collection a key was for.
|
|
57
|
+
* - `ATTEMPT_MISMATCH` — the key was sent for a different `operation` or
|
|
58
|
+
* `orderId` than the lookup names.
|
|
59
|
+
* - `REFUSED` — the write's cached answer was a refusal (non-`2xx`): nothing
|
|
60
|
+
* moved under this key. No collection write caches a refusal today; this
|
|
61
|
+
* keeps one that ever does from reading as `committed`.
|
|
62
|
+
*/
|
|
63
|
+
exports.ORDER_COLLECTION_ATTEMPT_UNKNOWN_REASONS = [
|
|
64
|
+
'NO_EVIDENCE',
|
|
65
|
+
'EVIDENCE_EXPIRED',
|
|
66
|
+
'UNBOUND_ATTEMPT',
|
|
67
|
+
'ATTEMPT_MISMATCH',
|
|
68
|
+
'REFUSED',
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* How long the server keeps a collection attempt's evidence: 24 hours — the
|
|
72
|
+
* write's own idempotency retention. Past it the lookup answers `unknown`.
|
|
73
|
+
*/
|
|
74
|
+
exports.ORDER_COLLECTION_ATTEMPT_RETENTION_MS = 86_400_000;
|
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export * from './mercadolibre.ts';
|
|
|
49
49
|
export * from './mercadopago.ts';
|
|
50
50
|
export * from './notification.ts';
|
|
51
51
|
export * from './order.ts';
|
|
52
|
+
export * from './orderCollectionAttempt.ts';
|
|
52
53
|
export * from './orderCreateAttempt.ts';
|
|
53
54
|
export * from './payment.ts';
|
|
54
55
|
export * from './platform.ts';
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ export * from "./mercadolibre.js";
|
|
|
57
57
|
export * from "./mercadopago.js";
|
|
58
58
|
export * from "./notification.js";
|
|
59
59
|
export * from "./order.js";
|
|
60
|
+
export * from "./orderCollectionAttempt.js";
|
|
60
61
|
export * from "./orderCreateAttempt.js";
|
|
61
62
|
export * from "./payment.js";
|
|
62
63
|
export * from "./platform.js";
|
package/dist/mercadopago.d.ts
CHANGED
|
@@ -98,13 +98,73 @@ declare global {
|
|
|
98
98
|
state: 'reserved' | 'contacting';
|
|
99
99
|
since?: number;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* One MercadoPago payment linked to the order, as the `paid` arm of
|
|
103
|
+
* {@link MpDynamicQrStateResponse} reports it — read from the order's own
|
|
104
|
+
* `linkedPayments` entry, never matched by amount, customer or time.
|
|
105
|
+
*
|
|
106
|
+
* - `paymentId` — MercadoPago's payment id, the entry's key.
|
|
107
|
+
* - `amount` — what the store still holds for that payment, net of refunds
|
|
108
|
+
* (`LinkedPaymentEntry.total`), in `currency`.
|
|
109
|
+
* - `currency` — the payment's denomination as `LinkedPaymentEntry.currency`
|
|
110
|
+
* states it (an ISO code or a platform catalogId); for an entry naming
|
|
111
|
+
* none, the order's own currency, and `'ARS'` only when the order names
|
|
112
|
+
* none either.
|
|
113
|
+
* - `paidAt` — when the approved payment was linked to the order (epoch ms),
|
|
114
|
+
* which follows MercadoPago's approval, never precedes it.
|
|
115
|
+
*/
|
|
116
|
+
interface MpDynamicQrPaidPayment {
|
|
117
|
+
paymentId: string;
|
|
118
|
+
amount: number;
|
|
119
|
+
currency: string;
|
|
120
|
+
paidAt: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* `data` of the re-sync read's `409 ORDER_CANCELLED`
|
|
124
|
+
* (`GET /mercadopago/qr/dynamic?orderId=`), present only when the dead order
|
|
125
|
+
* still holds linked MercadoPago payments: the money that moved before (or
|
|
126
|
+
* despite) the cancellation, from the same consistent order read that
|
|
127
|
+
* refused. The `paid` arm of {@link MpDynamicQrStateResponse} without
|
|
128
|
+
* `pending` — a dead order's QR fence is not read. Absent when no
|
|
129
|
+
* MercadoPago payment is linked, which, exactly like `none`, is no evidence
|
|
130
|
+
* of non-payment. The POST's `409 ORDER_CANCELLED` never carries it.
|
|
131
|
+
*/
|
|
132
|
+
interface MpDynamicQrCancelledDetails {
|
|
133
|
+
state: 'paid';
|
|
134
|
+
payments: MpDynamicQrPaidPayment[];
|
|
135
|
+
serverNow: number;
|
|
136
|
+
}
|
|
101
137
|
/**
|
|
102
138
|
* `data` of `GET /mercadopago/qr/dynamic?orderId=` — the re-sync read of the order's
|
|
103
|
-
* dynamic QR. Never carries the attempt's key or owner.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
139
|
+
* dynamic QR. Never carries the attempt's key or owner.
|
|
140
|
+
*
|
|
141
|
+
* - `paid` — the order holds at least one approved MercadoPago payment
|
|
142
|
+
* (`payments`, oldest link first), from a consistent read of the order.
|
|
143
|
+
* When another attempt ALSO holds the fence (a QR for the remaining
|
|
144
|
+
* balance), `pending` carries its `phase` and `since`; absent otherwise.
|
|
145
|
+
* - `pending` — an attempt holds the order's fence (`since` = its reservation
|
|
146
|
+
* instant; no `qrData`).
|
|
147
|
+
* - `none` — nothing blocks the order and no MercadoPago payment is linked
|
|
148
|
+
* YET. ⚠️ `none` is never evidence of non-payment: an approval may not be
|
|
149
|
+
* linked yet — a late webhook, a `409` the payment hook is waiting to
|
|
150
|
+
* retry, or MercadoPago processing switched off by the platform kill
|
|
151
|
+
* switch while a QR minted earlier stays payable at MercadoPago. A client
|
|
152
|
+
* must not collect again on `none`.
|
|
153
|
+
*
|
|
154
|
+
* ⚠️ Creating or showing a QR is never payment: only `paid` says money moved,
|
|
155
|
+
* and only its absence AFTER the operator has checked MercadoPago is a reason
|
|
156
|
+
* to collect again. A dead order answers `409 ORDER_CANCELLED` instead, with
|
|
157
|
+
* `data: MpDynamicQrCancelledDetails` when it holds linked payments.
|
|
106
158
|
*/
|
|
107
159
|
type MpDynamicQrStateResponse = {
|
|
160
|
+
state: 'paid';
|
|
161
|
+
payments: MpDynamicQrPaidPayment[];
|
|
162
|
+
pending?: {
|
|
163
|
+
phase: 'reserved' | 'contacting';
|
|
164
|
+
since?: number;
|
|
165
|
+
};
|
|
166
|
+
serverNow: number;
|
|
167
|
+
} | {
|
|
108
168
|
state: 'pending';
|
|
109
169
|
phase: 'reserved' | 'contacting';
|
|
110
170
|
since?: number;
|
|
@@ -191,7 +251,9 @@ declare global {
|
|
|
191
251
|
* - 503 `PLATFORM_INTEGRATION_DISABLED`; 504 `MP_DYN_QR_UNCONFIRMED`, an unknown creation outcome.
|
|
192
252
|
* A thrown error answers `400 UNHANDLED_ERROR`, never a 500. The re-sync read
|
|
193
253
|
* (`GET /mercadopago/qr/dynamic?orderId=`) uses `VALIDATION_FAILED`, `PLAN_UPGRADE_REQUIRED`,
|
|
194
|
-
* `CAPABILITY_REQUIRED`, `ORDER_NOT_FOUND` and `ORDER_CANCELLED` from this list
|
|
254
|
+
* `CAPABILITY_REQUIRED`, `ORDER_NOT_FOUND` and `ORDER_CANCELLED` from this list; its
|
|
255
|
+
* `ORDER_CANCELLED` carries `data: MpDynamicQrCancelledDetails` when the dead order holds linked
|
|
256
|
+
* MercadoPago payments.
|
|
195
257
|
*
|
|
196
258
|
* An order-bound charge holds the order's QR fence while it mints, and another charge for the
|
|
197
259
|
* same order meanwhile answers `MP_DYN_QR_IN_PROGRESS`. A `contacting` attempt (MercadoPago may
|
package/dist/mercadopago.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* - 503 `PLATFORM_INTEGRATION_DISABLED`; 504 `MP_DYN_QR_UNCONFIRMED`, an unknown creation outcome.
|
|
13
13
|
* A thrown error answers `400 UNHANDLED_ERROR`, never a 500. The re-sync read
|
|
14
14
|
* (`GET /mercadopago/qr/dynamic?orderId=`) uses `VALIDATION_FAILED`, `PLAN_UPGRADE_REQUIRED`,
|
|
15
|
-
* `CAPABILITY_REQUIRED`, `ORDER_NOT_FOUND` and `ORDER_CANCELLED` from this list
|
|
15
|
+
* `CAPABILITY_REQUIRED`, `ORDER_NOT_FOUND` and `ORDER_CANCELLED` from this list; its
|
|
16
|
+
* `ORDER_CANCELLED` carries `data: MpDynamicQrCancelledDetails` when the dead order holds linked
|
|
17
|
+
* MercadoPago payments.
|
|
16
18
|
*
|
|
17
19
|
* An order-bound charge holds the order's QR fence while it mints, and another charge for the
|
|
18
20
|
* same order meanwhile answers `MP_DYN_QR_IN_PROGRESS`. A `contacting` attempt (MercadoPago may
|
package/dist/order.d.ts
CHANGED
|
@@ -2473,6 +2473,15 @@ export declare const orderFulfilmentProgress: (order: OrderFulfilmentProgressInp
|
|
|
2473
2473
|
* is also `mode: 'edit'`'s and the update leg's answer to a re-price that
|
|
2474
2474
|
* would leave the order's net señas above its new charged total. Nothing was
|
|
2475
2475
|
* written on any of them.
|
|
2476
|
+
* - `TILL_TENDER_REQUIRES_DEPOSIT` · 400 — `mode: 'tender'` adding till money
|
|
2477
|
+
* (any leg whose `source` is not `account`) to an order that is NOT yet
|
|
2478
|
+
* delivered and holds no seña: money taken before delivery is a seña and
|
|
2479
|
+
* goes through `mode: 'deposit'`. Decided after replays are absorbed, so a
|
|
2480
|
+
* pure repeat of legs already on the ledger still answers its 200; an
|
|
2481
|
+
* account-only tender is never refused. Nothing was written and no
|
|
2482
|
+
* idempotency marker remains (refused ahead of the claim, or the claim is
|
|
2483
|
+
* released on the 400), so a collection lookup for it answers
|
|
2484
|
+
* `unknown` / `NO_EVIDENCE`.
|
|
2476
2485
|
* - `PAYMENT_REFERENCED_BY_DEPOSIT` · 409 — the payment link (fresh, over an
|
|
2477
2486
|
* entry the order already holds, or a credit upgrade) of a payment whose id a
|
|
2478
2487
|
* seña on the order already carries as its `reference`: the same money would
|
|
@@ -2680,6 +2689,19 @@ export declare const orderFulfilmentProgress: (order: OrderFulfilmentProgressInp
|
|
|
2680
2689
|
* for another store or user session (`ORDER_ATTEMPT_SCOPE_TAG_LENGTH`). Mint
|
|
2681
2690
|
* a new key in this session. Nothing was written.
|
|
2682
2691
|
*
|
|
2692
|
+
* ⚠️ **A money write's attempt identity is its `Idempotency-Key`, never its
|
|
2693
|
+
* `reference`.** Mint one key per tender, seña or account payment and keep it
|
|
2694
|
+
* for every retry. `reference` is a duplicate-money guard with its own
|
|
2695
|
+
* horizon, not an outcome, and it runs across keys: a tender absorbs a repeat
|
|
2696
|
+
* leg identical on money, method and source that carries the same `reference`
|
|
2697
|
+
* indefinitely, or — neither side carrying one — was recorded by the same user
|
|
2698
|
+
* within 30 seconds; a seña absorbs a same-user or same-`reference` repeat
|
|
2699
|
+
* only inside its 30-second replay window, and an older lookalike is
|
|
2700
|
+
* `409 DEPOSIT_DUPLICATE_REFERENCE`. To learn whether a keyed
|
|
2701
|
+
* write landed after a lost reply, `IDEMPOTENCY_IN_PROGRESS` or
|
|
2702
|
+
* `IDEMPOTENCY_KEY_FAILED`, ask `POST /orders { mode: 'collection-attempt' }`
|
|
2703
|
+
* (`OrderCollectionAttemptVerdict`) — never resend it under a new key first.
|
|
2704
|
+
*
|
|
2683
2705
|
* Idempotency answers, all six from `withIdempotency`:
|
|
2684
2706
|
* - `INVALID_IDEMPOTENCY_KEY` · 400 · `IDEMPOTENCY_KEY_REUSE` · 422 (the same
|
|
2685
2707
|
* key with a different body)
|
|
@@ -2701,6 +2723,6 @@ export declare const orderFulfilmentProgress: (order: OrderFulfilmentProgressInp
|
|
|
2701
2723
|
* `SUBSCRIPTION_READONLY`) sit on the order CREATE leg only: an edit is not a new
|
|
2702
2724
|
* order, so `bareOrderPostHandler` routes past `requireMetered` entirely.
|
|
2703
2725
|
*/
|
|
2704
|
-
export declare const ORDER_ERROR_CODES: readonly ["ORDER_COST_DENOMINATION_UNKNOWN", "SERVICE_ORDER_DELIVERY_MANAGED", "ORDER_NOT_FOUND", "ORDER_LOCKED", "ORDER_NOT_READY", "ORDER_DELIVERED_ANOTHER_DAY", "ORDER_DELIVERY_STATE_UNCHANGED", "ORDER_ILLEGAL_TRANSITION", "ORDER_VERSION_CONFLICT", "CUSTOMER_NOT_FOUND", "CUSTOMER_MERGED", "ORDER_HAS_NO_CUSTOMER", "CUSTOMER_ANONYMIZED", "FX_CONVERSION_UNAVAILABLE", "ROLE_FORBIDDEN", "ORDER_CANCELLED", "PAYMENT_SETTLED_ON_ANOTHER_ORDER", "ORDER_HAS_SHIPMENTS", "ORDER_HAS_RETURNS", "SHIPMENT_QUANTITY_EXCEEDED", "SHIPMENT_LINE_MISMATCH", "SHIPMENT_NOT_FOUND", "SHIPMENT_LIMIT_REACHED", "SHIPMENT_TOO_LARGE", "ORDER_FULFILMENT_EXTERNAL", "SHIPMENT_FAILED", "ADMIN_REQUIRED", "IDEMPOTENCY_KEY_REQUIRED", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENT_RESPONSE_NOT_CACHED", "ORDER_CANCELLATION_NOT_PROTECTED", "ORDER_CANCELLATION_COMMITTED", "ORDER_CANCELLATION_LOCKED", "ORDER_CANCELLATION_TOO_LARGE", "ORDER_CANCELLATION_FAILED", "SALE_LEDGER_RETRACTION_REFUSED", "SALE_LEDGER_ROW_NOT_OWNED", "PRODUCT_NOT_FOUND", "PRODUCT_SALE_LIMIT_EXCEEDED", "PRODUCT_SALE_LIMIT_INVALID", "DEPOSIT_SOURCE_NOT_ALLOWED", "DEPOSIT_CURRENCY_MISMATCH", "DEPOSIT_EXCEEDS_TOTAL", "DEPOSIT_FREEZE_REQUIRES_INVOICE", "DEPOSIT_FREEZE_INVOICE_PENDING", "DEPOSIT_CHARACTERIZATION_CONFLICT", "DEPOSIT_FREEZE_INVOICE_NOT_QUALIFYING", "DEPOSIT_INVOICED_ORDER_MUST_FREEZE", "DEPOSIT_REFERENCE_IS_LINKED_PAYMENT", "DEPOSIT_DUPLICATE_REFERENCE", "PAYMENT_REFERENCED_BY_DEPOSIT", "ORDER_HAS_UNREFUNDED_DEPOSIT", "DEPOSIT_NOT_FOUND", "DEPOSIT_ALREADY_REFUNDED", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_REVISION_STALE", "UNIT_QUANTITY_UNREPRESENTABLE", "KIT_RECIPE_CHANGED", "CART_PROMOTION_UNAVAILABLE", "ORDER_HAS_KIT_LINES", "ORDER_TARGET_REQUIRED", "ORDER_SOURCE_CONFLICT", "ORDER_CUSTOMER_REQUIRED", "BASKET_EMPTY", "BASKET_CHANGED_DURING_CHECKOUT", "CART_VERSION_CREATE_ONLY", "CART_VERSION_REQUIRES_CART_ID", "CART_REVISION_STALE", "CART_CUSTOMER_MISMATCH", "COUNTER_SALE_CREATE_ONLY", "SOLD_AT_CREATE_ONLY", "SOLD_AT_IN_FUTURE", "ORDER_TENDER_CREATE_ONLY", "CREDIT_OVERRIDE_CREATE_ONLY", "ON_ACCOUNT_CREATE_ONLY", "ON_ACCOUNT_WITH_TENDERS", "TENDERS_REQUIRE_COUNTER_SALE", "LINES_REQUIRE_COUNTER_SALE", "PRODUCT_COST_UNKNOWN", "PRICE_CHANGE_DISABLED", "LINE_DISCOUNT_EXCEEDS_MAX", "CUSTOMER_DISABLED", "SHIFT_NOT_AVAILABLE", "SOLD_AT_OUTSIDE_SHIFT", "ORDER_CURRENCY_NOT_SUBSCRIBED", "PAYMENT_METHODS_NOT_CONFIGURED", "PAYMENT_METHOD_NOT_FOUND", "DELIVERY_METHOD_NOT_FOUND", "ORDER_BELOW_MINIMUM_PURCHASE", "ORDER_INVALID_CART", "EMAIL_VERIFICATION_REQUIRED", "ACCOUNT_DISABLED", "CURRENCY_MISMATCH", "SHIFT_NOT_OPEN", "ORDER_DISCOUNT_AFTER_INVOICE", "ORDER_DISCOUNT_ON_SERVICE_ORDER", "ORDER_DISCOUNT_ON_MARKETPLACE_ORDER", "ORDER_INVOICE_METHOD_OUTSTANDING_FACTURA", "ORDER_SERVICE_PERIOD_AFTER_INVOICE", "ORDER_ATTEMPT_COMMITTED", "ORDER_ATTEMPT_ABANDONED", "ORDER_ATTEMPT_KEY_EXPIRED", "ORDER_ATTEMPT_KEY_SCOPE_MISMATCH"];
|
|
2726
|
+
export declare const ORDER_ERROR_CODES: readonly ["ORDER_COST_DENOMINATION_UNKNOWN", "SERVICE_ORDER_DELIVERY_MANAGED", "ORDER_NOT_FOUND", "ORDER_LOCKED", "ORDER_NOT_READY", "ORDER_DELIVERED_ANOTHER_DAY", "ORDER_DELIVERY_STATE_UNCHANGED", "ORDER_ILLEGAL_TRANSITION", "ORDER_VERSION_CONFLICT", "CUSTOMER_NOT_FOUND", "CUSTOMER_MERGED", "ORDER_HAS_NO_CUSTOMER", "CUSTOMER_ANONYMIZED", "FX_CONVERSION_UNAVAILABLE", "ROLE_FORBIDDEN", "ORDER_CANCELLED", "PAYMENT_SETTLED_ON_ANOTHER_ORDER", "ORDER_HAS_SHIPMENTS", "ORDER_HAS_RETURNS", "SHIPMENT_QUANTITY_EXCEEDED", "SHIPMENT_LINE_MISMATCH", "SHIPMENT_NOT_FOUND", "SHIPMENT_LIMIT_REACHED", "SHIPMENT_TOO_LARGE", "ORDER_FULFILMENT_EXTERNAL", "SHIPMENT_FAILED", "ADMIN_REQUIRED", "IDEMPOTENCY_KEY_REQUIRED", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENT_RESPONSE_NOT_CACHED", "ORDER_CANCELLATION_NOT_PROTECTED", "ORDER_CANCELLATION_COMMITTED", "ORDER_CANCELLATION_LOCKED", "ORDER_CANCELLATION_TOO_LARGE", "ORDER_CANCELLATION_FAILED", "SALE_LEDGER_RETRACTION_REFUSED", "SALE_LEDGER_ROW_NOT_OWNED", "PRODUCT_NOT_FOUND", "PRODUCT_SALE_LIMIT_EXCEEDED", "PRODUCT_SALE_LIMIT_INVALID", "DEPOSIT_SOURCE_NOT_ALLOWED", "DEPOSIT_CURRENCY_MISMATCH", "DEPOSIT_EXCEEDS_TOTAL", "DEPOSIT_FREEZE_REQUIRES_INVOICE", "DEPOSIT_FREEZE_INVOICE_PENDING", "DEPOSIT_CHARACTERIZATION_CONFLICT", "DEPOSIT_FREEZE_INVOICE_NOT_QUALIFYING", "DEPOSIT_INVOICED_ORDER_MUST_FREEZE", "DEPOSIT_REFERENCE_IS_LINKED_PAYMENT", "DEPOSIT_DUPLICATE_REFERENCE", "TILL_TENDER_REQUIRES_DEPOSIT", "PAYMENT_REFERENCED_BY_DEPOSIT", "ORDER_HAS_UNREFUNDED_DEPOSIT", "DEPOSIT_NOT_FOUND", "DEPOSIT_ALREADY_REFUNDED", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_REVISION_STALE", "UNIT_QUANTITY_UNREPRESENTABLE", "KIT_RECIPE_CHANGED", "CART_PROMOTION_UNAVAILABLE", "ORDER_HAS_KIT_LINES", "ORDER_TARGET_REQUIRED", "ORDER_SOURCE_CONFLICT", "ORDER_CUSTOMER_REQUIRED", "BASKET_EMPTY", "BASKET_CHANGED_DURING_CHECKOUT", "CART_VERSION_CREATE_ONLY", "CART_VERSION_REQUIRES_CART_ID", "CART_REVISION_STALE", "CART_CUSTOMER_MISMATCH", "COUNTER_SALE_CREATE_ONLY", "SOLD_AT_CREATE_ONLY", "SOLD_AT_IN_FUTURE", "ORDER_TENDER_CREATE_ONLY", "CREDIT_OVERRIDE_CREATE_ONLY", "ON_ACCOUNT_CREATE_ONLY", "ON_ACCOUNT_WITH_TENDERS", "TENDERS_REQUIRE_COUNTER_SALE", "LINES_REQUIRE_COUNTER_SALE", "PRODUCT_COST_UNKNOWN", "PRICE_CHANGE_DISABLED", "LINE_DISCOUNT_EXCEEDS_MAX", "CUSTOMER_DISABLED", "SHIFT_NOT_AVAILABLE", "SOLD_AT_OUTSIDE_SHIFT", "ORDER_CURRENCY_NOT_SUBSCRIBED", "PAYMENT_METHODS_NOT_CONFIGURED", "PAYMENT_METHOD_NOT_FOUND", "DELIVERY_METHOD_NOT_FOUND", "ORDER_BELOW_MINIMUM_PURCHASE", "ORDER_INVALID_CART", "EMAIL_VERIFICATION_REQUIRED", "ACCOUNT_DISABLED", "CURRENCY_MISMATCH", "SHIFT_NOT_OPEN", "ORDER_DISCOUNT_AFTER_INVOICE", "ORDER_DISCOUNT_ON_SERVICE_ORDER", "ORDER_DISCOUNT_ON_MARKETPLACE_ORDER", "ORDER_INVOICE_METHOD_OUTSTANDING_FACTURA", "ORDER_SERVICE_PERIOD_AFTER_INVOICE", "ORDER_ATTEMPT_COMMITTED", "ORDER_ATTEMPT_ABANDONED", "ORDER_ATTEMPT_KEY_EXPIRED", "ORDER_ATTEMPT_KEY_SCOPE_MISMATCH"];
|
|
2705
2727
|
export type OrderErrorCode = (typeof ORDER_ERROR_CODES)[number];
|
|
2706
2728
|
export {};
|
package/dist/order.js
CHANGED
|
@@ -228,6 +228,15 @@ export const orderFulfilmentProgress = (order) => {
|
|
|
228
228
|
* is also `mode: 'edit'`'s and the update leg's answer to a re-price that
|
|
229
229
|
* would leave the order's net señas above its new charged total. Nothing was
|
|
230
230
|
* written on any of them.
|
|
231
|
+
* - `TILL_TENDER_REQUIRES_DEPOSIT` · 400 — `mode: 'tender'` adding till money
|
|
232
|
+
* (any leg whose `source` is not `account`) to an order that is NOT yet
|
|
233
|
+
* delivered and holds no seña: money taken before delivery is a seña and
|
|
234
|
+
* goes through `mode: 'deposit'`. Decided after replays are absorbed, so a
|
|
235
|
+
* pure repeat of legs already on the ledger still answers its 200; an
|
|
236
|
+
* account-only tender is never refused. Nothing was written and no
|
|
237
|
+
* idempotency marker remains (refused ahead of the claim, or the claim is
|
|
238
|
+
* released on the 400), so a collection lookup for it answers
|
|
239
|
+
* `unknown` / `NO_EVIDENCE`.
|
|
231
240
|
* - `PAYMENT_REFERENCED_BY_DEPOSIT` · 409 — the payment link (fresh, over an
|
|
232
241
|
* entry the order already holds, or a credit upgrade) of a payment whose id a
|
|
233
242
|
* seña on the order already carries as its `reference`: the same money would
|
|
@@ -435,6 +444,19 @@ export const orderFulfilmentProgress = (order) => {
|
|
|
435
444
|
* for another store or user session (`ORDER_ATTEMPT_SCOPE_TAG_LENGTH`). Mint
|
|
436
445
|
* a new key in this session. Nothing was written.
|
|
437
446
|
*
|
|
447
|
+
* ⚠️ **A money write's attempt identity is its `Idempotency-Key`, never its
|
|
448
|
+
* `reference`.** Mint one key per tender, seña or account payment and keep it
|
|
449
|
+
* for every retry. `reference` is a duplicate-money guard with its own
|
|
450
|
+
* horizon, not an outcome, and it runs across keys: a tender absorbs a repeat
|
|
451
|
+
* leg identical on money, method and source that carries the same `reference`
|
|
452
|
+
* indefinitely, or — neither side carrying one — was recorded by the same user
|
|
453
|
+
* within 30 seconds; a seña absorbs a same-user or same-`reference` repeat
|
|
454
|
+
* only inside its 30-second replay window, and an older lookalike is
|
|
455
|
+
* `409 DEPOSIT_DUPLICATE_REFERENCE`. To learn whether a keyed
|
|
456
|
+
* write landed after a lost reply, `IDEMPOTENCY_IN_PROGRESS` or
|
|
457
|
+
* `IDEMPOTENCY_KEY_FAILED`, ask `POST /orders { mode: 'collection-attempt' }`
|
|
458
|
+
* (`OrderCollectionAttemptVerdict`) — never resend it under a new key first.
|
|
459
|
+
*
|
|
438
460
|
* Idempotency answers, all six from `withIdempotency`:
|
|
439
461
|
* - `INVALID_IDEMPOTENCY_KEY` · 400 · `IDEMPOTENCY_KEY_REUSE` · 422 (the same
|
|
440
462
|
* key with a different body)
|
|
@@ -514,6 +536,8 @@ export const ORDER_ERROR_CODES = [
|
|
|
514
536
|
'DEPOSIT_INVOICED_ORDER_MUST_FREEZE',
|
|
515
537
|
'DEPOSIT_REFERENCE_IS_LINKED_PAYMENT',
|
|
516
538
|
'DEPOSIT_DUPLICATE_REFERENCE',
|
|
539
|
+
// Till money on an undelivered order with no seña — `mode: 'deposit'` takes it.
|
|
540
|
+
'TILL_TENDER_REQUIRES_DEPOSIT',
|
|
517
541
|
// The payment link of a payment a seña on the order already references.
|
|
518
542
|
'PAYMENT_REFERENCED_BY_DEPOSIT',
|
|
519
543
|
// Customer cancellation of an order still holding a seña.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order-collection attempt lookup — the read a client runs when a money write
|
|
3
|
+
* on an order (a till tender, a seña, an account payment) left it without an
|
|
4
|
+
* answer: a lost reply, a timeout, or one of the two uncertain refusals
|
|
5
|
+
* `409 IDEMPOTENCY_IN_PROGRESS` and `409 IDEMPOTENCY_KEY_FAILED`.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ **The attempt's identity is its `Idempotency-Key`, never its `reference`.**
|
|
8
|
+
* Mint ONE key per collection attempt and keep it for every retry of that
|
|
9
|
+
* attempt. `reference` keeps its own, narrower meaning as a duplicate-money
|
|
10
|
+
* guard, and that guard runs ACROSS keys: a tender absorbs a repeat leg
|
|
11
|
+
* identical on money, method and source that carries the same `reference`
|
|
12
|
+
* however much later it arrives, OR — neither side carrying a reference — was
|
|
13
|
+
* recorded by the same user within 30 seconds; a seña absorbs a same-user or
|
|
14
|
+
* same-`reference` repeat only inside its 30-second replay window (an older
|
|
15
|
+
* lookalike is `409 DEPOSIT_DUPLICATE_REFERENCE`). So a NEW key is a new
|
|
16
|
+
* attempt, not a guaranteed new leg: two genuine reference-less handoffs of the
|
|
17
|
+
* same amount by one cashier inside 30 s land as ONE leg, and the second key
|
|
18
|
+
* answers `committed` all the same. Give each physical payment its own
|
|
19
|
+
* `reference` when that distinction matters. Neither guard is an outcome
|
|
20
|
+
* protocol.
|
|
21
|
+
*
|
|
22
|
+
* `POST /orders { mode: 'collection-attempt', operation, orderId, idempotencyKey }`
|
|
23
|
+
* answers from the write's own idempotency marker, scoped to the CALLER's store
|
|
24
|
+
* and user. It never claims, replays, reclaims or seals the marker, never
|
|
25
|
+
* writes the marker or any business row, and calls no provider; a claim it
|
|
26
|
+
* finds stranded past 60 s logs one deduped operator row.
|
|
27
|
+
*/
|
|
28
|
+
declare global {
|
|
29
|
+
/** One of {@link ORDER_COLLECTION_ATTEMPT_OPERATIONS}. */
|
|
30
|
+
type OrderCollectionAttemptOperation = (typeof ORDER_COLLECTION_ATTEMPT_OPERATIONS)[number];
|
|
31
|
+
/** One of {@link ORDER_COLLECTION_ATTEMPT_VERDICTS}. */
|
|
32
|
+
type OrderCollectionAttemptVerdictKind = (typeof ORDER_COLLECTION_ATTEMPT_VERDICTS)[number];
|
|
33
|
+
/** Why a lookup could not decide — one of {@link ORDER_COLLECTION_ATTEMPT_UNKNOWN_REASONS}. */
|
|
34
|
+
type OrderCollectionAttemptUnknownReason = (typeof ORDER_COLLECTION_ATTEMPT_UNKNOWN_REASONS)[number];
|
|
35
|
+
/**
|
|
36
|
+
* `POST /orders` body for the lookup.
|
|
37
|
+
*
|
|
38
|
+
* - `operation` — which write the key was sent with: `tender`
|
|
39
|
+
* (`POST /orders { mode: 'tender' }`), `deposit`
|
|
40
|
+
* (`POST /orders { mode: 'deposit' }`) or `account` (`POST /account`, a
|
|
41
|
+
* customer-account movement sent with this `orderId`).
|
|
42
|
+
* - `orderId` — the order that write named.
|
|
43
|
+
* - `idempotencyKey` — the exact `Idempotency-Key` header the write carried.
|
|
44
|
+
*
|
|
45
|
+
* The store and user are the caller's session, never a body or header
|
|
46
|
+
* field. The lookup is not itself idempotency-keyed and is safe to repeat;
|
|
47
|
+
* an `in-progress` answer is a moment in time, so ask again after
|
|
48
|
+
* `retryAfterMs` and never cache it.
|
|
49
|
+
*/
|
|
50
|
+
interface OrderCollectionAttemptLookupRequest {
|
|
51
|
+
mode: 'collection-attempt';
|
|
52
|
+
operation: OrderCollectionAttemptOperation;
|
|
53
|
+
orderId: string;
|
|
54
|
+
idempotencyKey: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `data` of a `200` from `POST /orders { mode: 'collection-attempt' }`.
|
|
58
|
+
*
|
|
59
|
+
* - `committed` — the write answered `2xx` (`statusCode`). `body` is the
|
|
60
|
+
* exact JSON envelope it answered — for a tender or a seña the order's
|
|
61
|
+
* post-commit echo with the updated settlement (a repeat the dedupe guard
|
|
62
|
+
* absorbed appended no leg, and the echo shows the ledger as it stands);
|
|
63
|
+
* for an account movement its ACCOUNT row. `body` is `null` when no usable
|
|
64
|
+
* envelope was cached (a body too large to keep, or not JSON);
|
|
65
|
+
* `statusCode` is `null` only on a marker that cached no response at all.
|
|
66
|
+
* `claimedAt` is the instant the write was admitted (epoch ms), at or
|
|
67
|
+
* before its commit. Do not collect again.
|
|
68
|
+
* - `in-progress` — the write is still running. Ask again after
|
|
69
|
+
* `retryAfterMs`.
|
|
70
|
+
* - `sealed` — the write may or may not have landed and can never be
|
|
71
|
+
* re-run under this key. `abandoned: false` — the key is sealed: by a
|
|
72
|
+
* throw after the transaction may have committed, or by a later same-key
|
|
73
|
+
* retry that found the claim stranded. `abandoned: true` — the claim is
|
|
74
|
+
* still stranded `in_progress` past 60 s: the invocation died mid-flight
|
|
75
|
+
* and nothing has sealed it yet. Show "unresolved — check", never
|
|
76
|
+
* "pay again", and verify where the operation writes before collecting
|
|
77
|
+
* under a NEW key: the order's `tenders` for `tender` and `deposit`; the
|
|
78
|
+
* customer's account movements carrying this `orderId` for `account`,
|
|
79
|
+
* which never writes the order.
|
|
80
|
+
* - `unknown` — the server holds no evidence for this attempt; `reason`
|
|
81
|
+
* says why. Never read it as "not written": a write released before it
|
|
82
|
+
* committed leaves no evidence, and so does one STILL IN FLIGHT that has
|
|
83
|
+
* not claimed its key yet (`NO_EVIDENCE`). The safe remedy, in this order:
|
|
84
|
+
* 1. FIRST resend the identical request — the same body under the SAME
|
|
85
|
+
* `Idempotency-Key`, as the SAME user. The write's claim is a
|
|
86
|
+
* conditional put (`attribute_not_exists`), so the resend serialises
|
|
87
|
+
* against any copy still in flight: it replays the cached `2xx` of a
|
|
88
|
+
* write that committed, or runs exactly once. A
|
|
89
|
+
* `409 IDEMPOTENCY_IN_PROGRESS` means a copy holds the claim — ask the
|
|
90
|
+
* lookup again after a pause; a `409 IDEMPOTENCY_KEY_FAILED` means the
|
|
91
|
+
* key is sealed — handle it as `sealed`. Neither is a second charge.
|
|
92
|
+
* "Identical" is what the write fingerprints: a changed body answers
|
|
93
|
+
* `422 IDEMPOTENCY_KEY_REUSE` (for `POST /account` a fresh
|
|
94
|
+
* `elevationToken` is not a change).
|
|
95
|
+
* 2. Collect under a NEW key only after the operator has verified where
|
|
96
|
+
* the operation writes (as for `sealed`) AND at least 30 seconds have
|
|
97
|
+
* passed since the last send of this key — API Gateway stops waiting at
|
|
98
|
+
* 29 s, and the writing Lambdas time out at 25 s (`tender`, `deposit`)
|
|
99
|
+
* and 20 s (`account`).
|
|
100
|
+
* This holds inside {@link ORDER_COLLECTION_ATTEMPT_RETENTION_MS} of the
|
|
101
|
+
* attempt's first send. Past it a same-key resend is no longer a safe
|
|
102
|
+
* probe (see `EVIDENCE_EXPIRED`): verify first.
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ A verdict only ever describes the caller's OWN key: another user's or
|
|
105
|
+
* another store's key answers `unknown` / `NO_EVIDENCE`, never their verdict.
|
|
106
|
+
*/
|
|
107
|
+
type OrderCollectionAttemptVerdict = {
|
|
108
|
+
verdict: Extract<OrderCollectionAttemptVerdictKind, 'committed'>;
|
|
109
|
+
claimedAt: number | null;
|
|
110
|
+
statusCode: number | null;
|
|
111
|
+
body: Record<string, unknown> | null;
|
|
112
|
+
} | {
|
|
113
|
+
verdict: Extract<OrderCollectionAttemptVerdictKind, 'in-progress'>;
|
|
114
|
+
retryAfterMs: number;
|
|
115
|
+
claimedAt: number;
|
|
116
|
+
} | {
|
|
117
|
+
verdict: Extract<OrderCollectionAttemptVerdictKind, 'sealed'>;
|
|
118
|
+
claimedAt: number | null;
|
|
119
|
+
abandoned: boolean;
|
|
120
|
+
} | {
|
|
121
|
+
verdict: Extract<OrderCollectionAttemptVerdictKind, 'unknown'>;
|
|
122
|
+
reason: OrderCollectionAttemptUnknownReason;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Every `operation` the lookup answers for — see `OrderCollectionAttemptLookupRequest`. */
|
|
126
|
+
export declare const ORDER_COLLECTION_ATTEMPT_OPERATIONS: readonly ["tender", "deposit", "account"];
|
|
127
|
+
/** Every `verdict` the lookup answers — see `OrderCollectionAttemptVerdict`. */
|
|
128
|
+
export declare const ORDER_COLLECTION_ATTEMPT_VERDICTS: readonly ["committed", "in-progress", "sealed", "unknown"];
|
|
129
|
+
/**
|
|
130
|
+
* Every `reason` on an `unknown` verdict:
|
|
131
|
+
*
|
|
132
|
+
* - `NO_EVIDENCE` — no marker under this key for the caller: never sent, sent
|
|
133
|
+
* by another user or store, refused or released before it committed,
|
|
134
|
+
* already deleted by its retention expiry — or STILL IN FLIGHT and not yet
|
|
135
|
+
* claimed. A write holds no marker until its claim lands, and before that
|
|
136
|
+
* it can be queued at the gateway, in a cold start, or (a tender) running
|
|
137
|
+
* its pre-claim reads. So it is never "not collected": follow the
|
|
138
|
+
* `unknown` remedy on `OrderCollectionAttemptVerdict` — resend the same
|
|
139
|
+
* body under the same key first.
|
|
140
|
+
* - `EVIDENCE_EXPIRED` — the marker outlived
|
|
141
|
+
* {@link ORDER_COLLECTION_ATTEMPT_RETENTION_MS} and is awaiting deletion,
|
|
142
|
+
* which DynamoDB does lazily (up to days late). That is the LOOKUP's
|
|
143
|
+
* horizon, not the writer's: the write never reads the expiry, so while
|
|
144
|
+
* the row survives a same-key resend still replays a committed `200` (or
|
|
145
|
+
* refuses a sealed or stranded key) and does NOT run again. Once DynamoDB
|
|
146
|
+
* deletes the row the same resend runs as a new write — so past the
|
|
147
|
+
* horizon verify where the operation writes before resending at all.
|
|
148
|
+
* - `UNBOUND_ATTEMPT` — the key belongs to a write that is not a collection
|
|
149
|
+
* attempt (another order mode), or to one admitted before the server
|
|
150
|
+
* started recording which collection a key was for.
|
|
151
|
+
* - `ATTEMPT_MISMATCH` — the key was sent for a different `operation` or
|
|
152
|
+
* `orderId` than the lookup names.
|
|
153
|
+
* - `REFUSED` — the write's cached answer was a refusal (non-`2xx`): nothing
|
|
154
|
+
* moved under this key. No collection write caches a refusal today; this
|
|
155
|
+
* keeps one that ever does from reading as `committed`.
|
|
156
|
+
*/
|
|
157
|
+
export declare const ORDER_COLLECTION_ATTEMPT_UNKNOWN_REASONS: readonly ["NO_EVIDENCE", "EVIDENCE_EXPIRED", "UNBOUND_ATTEMPT", "ATTEMPT_MISMATCH", "REFUSED"];
|
|
158
|
+
/**
|
|
159
|
+
* How long the server keeps a collection attempt's evidence: 24 hours — the
|
|
160
|
+
* write's own idempotency retention. Past it the lookup answers `unknown`.
|
|
161
|
+
*/
|
|
162
|
+
export declare const ORDER_COLLECTION_ATTEMPT_RETENTION_MS = 86400000;
|
|
163
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order-collection attempt lookup — the read a client runs when a money write
|
|
3
|
+
* on an order (a till tender, a seña, an account payment) left it without an
|
|
4
|
+
* answer: a lost reply, a timeout, or one of the two uncertain refusals
|
|
5
|
+
* `409 IDEMPOTENCY_IN_PROGRESS` and `409 IDEMPOTENCY_KEY_FAILED`.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ **The attempt's identity is its `Idempotency-Key`, never its `reference`.**
|
|
8
|
+
* Mint ONE key per collection attempt and keep it for every retry of that
|
|
9
|
+
* attempt. `reference` keeps its own, narrower meaning as a duplicate-money
|
|
10
|
+
* guard, and that guard runs ACROSS keys: a tender absorbs a repeat leg
|
|
11
|
+
* identical on money, method and source that carries the same `reference`
|
|
12
|
+
* however much later it arrives, OR — neither side carrying a reference — was
|
|
13
|
+
* recorded by the same user within 30 seconds; a seña absorbs a same-user or
|
|
14
|
+
* same-`reference` repeat only inside its 30-second replay window (an older
|
|
15
|
+
* lookalike is `409 DEPOSIT_DUPLICATE_REFERENCE`). So a NEW key is a new
|
|
16
|
+
* attempt, not a guaranteed new leg: two genuine reference-less handoffs of the
|
|
17
|
+
* same amount by one cashier inside 30 s land as ONE leg, and the second key
|
|
18
|
+
* answers `committed` all the same. Give each physical payment its own
|
|
19
|
+
* `reference` when that distinction matters. Neither guard is an outcome
|
|
20
|
+
* protocol.
|
|
21
|
+
*
|
|
22
|
+
* `POST /orders { mode: 'collection-attempt', operation, orderId, idempotencyKey }`
|
|
23
|
+
* answers from the write's own idempotency marker, scoped to the CALLER's store
|
|
24
|
+
* and user. It never claims, replays, reclaims or seals the marker, never
|
|
25
|
+
* writes the marker or any business row, and calls no provider; a claim it
|
|
26
|
+
* finds stranded past 60 s logs one deduped operator row.
|
|
27
|
+
*/
|
|
28
|
+
/** Every `operation` the lookup answers for — see `OrderCollectionAttemptLookupRequest`. */
|
|
29
|
+
export const ORDER_COLLECTION_ATTEMPT_OPERATIONS = ['tender', 'deposit', 'account'];
|
|
30
|
+
/** Every `verdict` the lookup answers — see `OrderCollectionAttemptVerdict`. */
|
|
31
|
+
export const ORDER_COLLECTION_ATTEMPT_VERDICTS = ['committed', 'in-progress', 'sealed', 'unknown'];
|
|
32
|
+
/**
|
|
33
|
+
* Every `reason` on an `unknown` verdict:
|
|
34
|
+
*
|
|
35
|
+
* - `NO_EVIDENCE` — no marker under this key for the caller: never sent, sent
|
|
36
|
+
* by another user or store, refused or released before it committed,
|
|
37
|
+
* already deleted by its retention expiry — or STILL IN FLIGHT and not yet
|
|
38
|
+
* claimed. A write holds no marker until its claim lands, and before that
|
|
39
|
+
* it can be queued at the gateway, in a cold start, or (a tender) running
|
|
40
|
+
* its pre-claim reads. So it is never "not collected": follow the
|
|
41
|
+
* `unknown` remedy on `OrderCollectionAttemptVerdict` — resend the same
|
|
42
|
+
* body under the same key first.
|
|
43
|
+
* - `EVIDENCE_EXPIRED` — the marker outlived
|
|
44
|
+
* {@link ORDER_COLLECTION_ATTEMPT_RETENTION_MS} and is awaiting deletion,
|
|
45
|
+
* which DynamoDB does lazily (up to days late). That is the LOOKUP's
|
|
46
|
+
* horizon, not the writer's: the write never reads the expiry, so while
|
|
47
|
+
* the row survives a same-key resend still replays a committed `200` (or
|
|
48
|
+
* refuses a sealed or stranded key) and does NOT run again. Once DynamoDB
|
|
49
|
+
* deletes the row the same resend runs as a new write — so past the
|
|
50
|
+
* horizon verify where the operation writes before resending at all.
|
|
51
|
+
* - `UNBOUND_ATTEMPT` — the key belongs to a write that is not a collection
|
|
52
|
+
* attempt (another order mode), or to one admitted before the server
|
|
53
|
+
* started recording which collection a key was for.
|
|
54
|
+
* - `ATTEMPT_MISMATCH` — the key was sent for a different `operation` or
|
|
55
|
+
* `orderId` than the lookup names.
|
|
56
|
+
* - `REFUSED` — the write's cached answer was a refusal (non-`2xx`): nothing
|
|
57
|
+
* moved under this key. No collection write caches a refusal today; this
|
|
58
|
+
* keeps one that ever does from reading as `committed`.
|
|
59
|
+
*/
|
|
60
|
+
export const ORDER_COLLECTION_ATTEMPT_UNKNOWN_REASONS = [
|
|
61
|
+
'NO_EVIDENCE',
|
|
62
|
+
'EVIDENCE_EXPIRED',
|
|
63
|
+
'UNBOUND_ATTEMPT',
|
|
64
|
+
'ATTEMPT_MISMATCH',
|
|
65
|
+
'REFUSED',
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* How long the server keeps a collection attempt's evidence: 24 hours — the
|
|
69
|
+
* write's own idempotency retention. Past it the lookup answers `unknown`.
|
|
70
|
+
*/
|
|
71
|
+
export const ORDER_COLLECTION_ATTEMPT_RETENTION_MS = 86_400_000;
|
package/dist/stock.d.ts
CHANGED
|
@@ -506,6 +506,50 @@ declare global {
|
|
|
506
506
|
/** Hydrated movement returned by readers after deriving ids from DynamoDB keys. */
|
|
507
507
|
interface StockSale extends StockBase, StockSaleWrite {
|
|
508
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* `data` of `GET /products?mode=stock&productId=…` — one product's ledger
|
|
511
|
+
* TOTALS, never its movement rows.
|
|
512
|
+
*
|
|
513
|
+
* - `incomes` — `Σ quantity` over the product's `INCOME#` rows.
|
|
514
|
+
* - `sales` — `Σ quantity` over the product's `SALE#` rows.
|
|
515
|
+
* - `stock` — on-hand, `incomes − sales`.
|
|
516
|
+
*
|
|
517
|
+
* Rows carrying `skip` are excluded from both sums.
|
|
518
|
+
*
|
|
519
|
+
* ⚠️ **`stock` is ABSENT exactly when the read truncated** — when either
|
|
520
|
+
* leg's paged read stopped short, which the envelope reports as top-level
|
|
521
|
+
* `truncated: true` ({@link ProductStockTotalsResponse}). It is never `0`
|
|
522
|
+
* or `null` in that case: a short read of either leg makes the difference
|
|
523
|
+
* WRONG, in either direction and by any amount, so absence is the signal.
|
|
524
|
+
* Read `stock === undefined` as "not computable from this read", never as
|
|
525
|
+
* zero. `incomes` and `sales` are still present then, but each is only the
|
|
526
|
+
* sum over the rows that were read — NOT a bound on the true leg total,
|
|
527
|
+
* because ledger quantities are signed (a service-part restore writes a
|
|
528
|
+
* negative-quantity `SALE#` row).
|
|
529
|
+
*
|
|
530
|
+
* A leg with no rows sums to `0`, never absent: a product with an empty
|
|
531
|
+
* ledger answers `incomes: 0, sales: 0, stock: 0` with `truncated: false`.
|
|
532
|
+
* The handler does not check that the product exists, so an unknown
|
|
533
|
+
* `productId` answers the same zeros rather than a 404.
|
|
534
|
+
*/
|
|
535
|
+
interface ProductStockTotals {
|
|
536
|
+
incomes: number;
|
|
537
|
+
sales: number;
|
|
538
|
+
stock?: number;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The success envelope of `GET /products?mode=stock&productId=…`.
|
|
542
|
+
*
|
|
543
|
+
* `truncated` is always present: `true` exactly when `data.stock` is absent
|
|
544
|
+
* (see {@link ProductStockTotals}). There is no resume cursor — a truncated
|
|
545
|
+
* read means the on-hand figure is not computable from this endpoint, not
|
|
546
|
+
* that a next page exists.
|
|
547
|
+
*/
|
|
548
|
+
interface ProductStockTotalsResponse {
|
|
549
|
+
message: string;
|
|
550
|
+
data: ProductStockTotals;
|
|
551
|
+
truncated: boolean;
|
|
552
|
+
}
|
|
509
553
|
/**
|
|
510
554
|
* The body of one nightly stock-drift reconciliation run
|
|
511
555
|
* (`stacks/lambdas/stockReconciliationCron/index.ts`).
|