@happyvertical/smrt-commerce 0.30.0
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/AGENTS.md +44 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +146 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/collections/ContractCollection.d.ts +87 -0
- package/dist/collections/ContractCollection.d.ts.map +1 -0
- package/dist/collections/CustomerCollection.d.ts +58 -0
- package/dist/collections/CustomerCollection.d.ts.map +1 -0
- package/dist/collections/FulfillmentCollection.d.ts +75 -0
- package/dist/collections/FulfillmentCollection.d.ts.map +1 -0
- package/dist/collections/InvoiceCollection.d.ts +162 -0
- package/dist/collections/InvoiceCollection.d.ts.map +1 -0
- package/dist/collections/InvoiceLineItemCollection.d.ts +90 -0
- package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -0
- package/dist/collections/PaymentAllocationCollection.d.ts +86 -0
- package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -0
- package/dist/collections/PaymentCollection.d.ts +96 -0
- package/dist/collections/PaymentCollection.d.ts.map +1 -0
- package/dist/collections/PaymentIntentCollection.d.ts +66 -0
- package/dist/collections/PaymentIntentCollection.d.ts.map +1 -0
- package/dist/collections/PayoutCollection.d.ts +47 -0
- package/dist/collections/PayoutCollection.d.ts.map +1 -0
- package/dist/collections/VendorCollection.d.ts +59 -0
- package/dist/collections/VendorCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +15 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5308 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +13852 -0
- package/dist/models/Contract.d.ts +425 -0
- package/dist/models/Contract.d.ts.map +1 -0
- package/dist/models/ContractLineItem.d.ts +92 -0
- package/dist/models/ContractLineItem.d.ts.map +1 -0
- package/dist/models/Customer.d.ts +98 -0
- package/dist/models/Customer.d.ts.map +1 -0
- package/dist/models/Fulfillment.d.ts +99 -0
- package/dist/models/Fulfillment.d.ts.map +1 -0
- package/dist/models/FulfillmentLineItem.d.ts +42 -0
- package/dist/models/FulfillmentLineItem.d.ts.map +1 -0
- package/dist/models/Invoice.d.ts +326 -0
- package/dist/models/Invoice.d.ts.map +1 -0
- package/dist/models/InvoiceLineItem.d.ts +120 -0
- package/dist/models/InvoiceLineItem.d.ts.map +1 -0
- package/dist/models/Payment.d.ts +269 -0
- package/dist/models/Payment.d.ts.map +1 -0
- package/dist/models/PaymentAllocation.d.ts +93 -0
- package/dist/models/PaymentAllocation.d.ts.map +1 -0
- package/dist/models/PaymentIntent.d.ts +341 -0
- package/dist/models/PaymentIntent.d.ts.map +1 -0
- package/dist/models/Payout.d.ts +200 -0
- package/dist/models/Payout.d.ts.map +1 -0
- package/dist/models/Vendor.d.ts +153 -0
- package/dist/models/Vendor.d.ts.map +1 -0
- package/dist/models/index.d.ts +17 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +108 -0
- package/dist/playground.js.map +1 -0
- package/dist/smrt-knowledge.json +5494 -0
- package/dist/svelte/components/InvoiceActions.svelte +191 -0
- package/dist/svelte/components/InvoiceActions.svelte.d.ts +26 -0
- package/dist/svelte/components/InvoiceActions.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceCard.svelte +233 -0
- package/dist/svelte/components/InvoiceCard.svelte.d.ts +16 -0
- package/dist/svelte/components/InvoiceCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceHeader.svelte +258 -0
- package/dist/svelte/components/InvoiceHeader.svelte.d.ts +26 -0
- package/dist/svelte/components/InvoiceHeader.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceLineItems.svelte +322 -0
- package/dist/svelte/components/InvoiceLineItems.svelte.d.ts +24 -0
- package/dist/svelte/components/InvoiceLineItems.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceTotals.svelte +193 -0
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts +27 -0
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UnbilledItems.svelte +355 -0
- package/dist/svelte/components/UnbilledItems.svelte.d.ts +18 -0
- package/dist/svelte/components/UnbilledItems.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +19 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +19 -0
- package/dist/svelte/index.d.ts +40 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +43 -0
- package/dist/svelte/playground.d.ts +103 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +103 -0
- package/dist/svelte/types.d.ts +47 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +4 -0
- package/dist/types/index.d.ts +234 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +85 -0
- package/dist/ui.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { PaymentIntentStatus, PaymentOption } from '../types/index.js';
|
|
3
|
+
export declare class PaymentIntent extends SmrtObject {
|
|
4
|
+
/**
|
|
5
|
+
* Tenant ID for multi-tenant isolation. Nullable so the same model
|
|
6
|
+
* can serve both per-tenant SaaS deployments and single-tenant /
|
|
7
|
+
* global setups.
|
|
8
|
+
*/
|
|
9
|
+
tenantId: string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Plain string reference to the upstream `Sku` (from
|
|
12
|
+
* `@happyvertical/smrt-products`) being purchased. Cross-package
|
|
13
|
+
* reference — plain string, not `@foreignKey()`, to avoid the
|
|
14
|
+
* circular dependency the framework warns against.
|
|
15
|
+
*
|
|
16
|
+
* Required for the marketplace flow; other consumers can leave it
|
|
17
|
+
* empty if they don't model a catalog.
|
|
18
|
+
*/
|
|
19
|
+
skuId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Caller-supplied scope for the idempotency-key natural key. The
|
|
22
|
+
* marketplace sets this to a `Sku` id, but the field is intentionally
|
|
23
|
+
* abstract so other consumers (subscription tiers, donation tiers,
|
|
24
|
+
* tipping flows) can scope idempotency by whatever granularity makes
|
|
25
|
+
* sense.
|
|
26
|
+
*
|
|
27
|
+
* Empty string is permitted but disables the natural-key dedup —
|
|
28
|
+
* every retry then creates a fresh row.
|
|
29
|
+
*/
|
|
30
|
+
offeringRef: string;
|
|
31
|
+
/**
|
|
32
|
+
* The buyer's email address. The licensee on any downstream
|
|
33
|
+
* `LicenseSale` (or similar rights-issuance row) is identified by
|
|
34
|
+
* this email — high-tier purchases also create a `Customer` record
|
|
35
|
+
* and set {@link customerId}, but most purchases get away with email
|
|
36
|
+
* alone.
|
|
37
|
+
*/
|
|
38
|
+
licenseeEmail: string;
|
|
39
|
+
/**
|
|
40
|
+
* Optional link to a `Customer` row for purchases that warrant a
|
|
41
|
+
* full customer record (high-tier licensing, recurring buyers,
|
|
42
|
+
* etc.). Cross-model reference is kept as a plain string to match
|
|
43
|
+
* the rest of the package's cross-package convention.
|
|
44
|
+
*/
|
|
45
|
+
customerId: string;
|
|
46
|
+
/**
|
|
47
|
+
* The payment rails the buyer can choose between to satisfy this
|
|
48
|
+
* intent. Stored as a JSON column. See {@link PaymentOption} for
|
|
49
|
+
* the per-option shape. Empty array means the intent is invalid /
|
|
50
|
+
* mis-built; callers should reject save in that case.
|
|
51
|
+
*/
|
|
52
|
+
paymentOptions: PaymentOption[];
|
|
53
|
+
/**
|
|
54
|
+
* USD price locked at quote time. Stored at decimal precision.
|
|
55
|
+
* Independent of any specific option's native-currency amount;
|
|
56
|
+
* `usdPriceLocked` is the canonical "what this costs in USD"
|
|
57
|
+
* number used downstream for reporting, tax, and drift accounting.
|
|
58
|
+
*/
|
|
59
|
+
usdPriceLocked: number;
|
|
60
|
+
/**
|
|
61
|
+
* Length of the price-lock window in milliseconds. Defaults to 15
|
|
62
|
+
* minutes; consumers can override per-intent. Stored so a later
|
|
63
|
+
* audit can see the original window even after {@link priceLockExpiresAt}
|
|
64
|
+
* has passed.
|
|
65
|
+
*/
|
|
66
|
+
priceLockWindowMs: number;
|
|
67
|
+
/**
|
|
68
|
+
* Wall-clock time at which the price lock expires. Set by the
|
|
69
|
+
* constructor (or by `expire()` if you want to short-circuit a
|
|
70
|
+
* specific intent). Once `Date.now()` passes this value and the
|
|
71
|
+
* status is still `AWAITING_PAYMENT`, callers should treat the
|
|
72
|
+
* intent as expired and refuse to record a payment against it.
|
|
73
|
+
*/
|
|
74
|
+
priceLockExpiresAt: Date | null;
|
|
75
|
+
/**
|
|
76
|
+
* Current status — see {@link PaymentIntentStatus} for the state
|
|
77
|
+
* machine. Mutate via the dedicated `markPaid` / `markIssued` /
|
|
78
|
+
* `expire` / `cancel` / `retire` helpers rather than assigning
|
|
79
|
+
* directly, so the helpers' invariant checks run.
|
|
80
|
+
*/
|
|
81
|
+
status: PaymentIntentStatus;
|
|
82
|
+
/**
|
|
83
|
+
* Caller-supplied idempotency key. Combined with `tenantId`,
|
|
84
|
+
* `offeringRef`, and `licenseeEmail`, this forms the natural key
|
|
85
|
+
* registered in `conflictColumns` — a retried `create` with the
|
|
86
|
+
* same tuple upserts the existing row instead of creating a
|
|
87
|
+
* duplicate.
|
|
88
|
+
*
|
|
89
|
+
* Empty string disables natural-key dedup (every retry creates a
|
|
90
|
+
* fresh row).
|
|
91
|
+
*/
|
|
92
|
+
idempotencyKey: string;
|
|
93
|
+
/**
|
|
94
|
+
* `backendId` of the option that satisfied the intent. Set by
|
|
95
|
+
* {@link markPaid}; remains empty for non-paid intents. Used by
|
|
96
|
+
* {@link isOptionRetired} to flag inbound funds on the other
|
|
97
|
+
* options as "needs refund".
|
|
98
|
+
*/
|
|
99
|
+
paidOptionBackendId: string;
|
|
100
|
+
/**
|
|
101
|
+
* Plain string reference to the `Payment` row that satisfied the
|
|
102
|
+
* intent. Cross-model reference matches the rest of the
|
|
103
|
+
* package's convention.
|
|
104
|
+
*/
|
|
105
|
+
paymentId: string;
|
|
106
|
+
/**
|
|
107
|
+
* When the intent transitioned to `PAID`.
|
|
108
|
+
*/
|
|
109
|
+
paidAt: Date | null;
|
|
110
|
+
/**
|
|
111
|
+
* When the intent transitioned to `ISSUED`.
|
|
112
|
+
*/
|
|
113
|
+
issuedAt: Date | null;
|
|
114
|
+
/**
|
|
115
|
+
* When the intent transitioned to `EXPIRED`.
|
|
116
|
+
*/
|
|
117
|
+
expiredAt: Date | null;
|
|
118
|
+
/**
|
|
119
|
+
* When the intent transitioned to `CANCELLED`.
|
|
120
|
+
*/
|
|
121
|
+
cancelledAt: Date | null;
|
|
122
|
+
/**
|
|
123
|
+
* When the intent transitioned to `RETIRED`.
|
|
124
|
+
*/
|
|
125
|
+
retiredAt: Date | null;
|
|
126
|
+
/**
|
|
127
|
+
* Optional human-readable notes (e.g., support tickets, refund
|
|
128
|
+
* memos, cancellation reasons). Append-only by convention.
|
|
129
|
+
*/
|
|
130
|
+
notes: string;
|
|
131
|
+
constructor(options?: any);
|
|
132
|
+
/**
|
|
133
|
+
* Re-normalize `paymentOptions` after the framework's
|
|
134
|
+
* `initializePropertiesFromOptions` pass has overwritten the
|
|
135
|
+
* constructor-set values with the raw cloned input, and stamp a
|
|
136
|
+
* default `priceLockExpiresAt` so newly-created intents have a
|
|
137
|
+
* concrete expiry without callers needing to compute it.
|
|
138
|
+
*/
|
|
139
|
+
initialize(): Promise<this>;
|
|
140
|
+
/**
|
|
141
|
+
* Save-time state-machine guard (S5 audit #1390 round 2).
|
|
142
|
+
*
|
|
143
|
+
* `status`, `paymentId`, and `paidOptionBackendId` are all mass-assignable on
|
|
144
|
+
* the generated update route, and the sync `markPaid` helper trusts its
|
|
145
|
+
* arguments. Two distinct attacks follow:
|
|
146
|
+
*
|
|
147
|
+
* 1. **Forge a PAID intent** — set `status: 'paid'` (or call bare `markPaid`)
|
|
148
|
+
* against a bogus / pending / non-matching Payment. This falsifies
|
|
149
|
+
* downstream "this was paid for" rights issuance.
|
|
150
|
+
* 2. **Repoint an already-PAID intent** — leave `status: 'paid'` but swap
|
|
151
|
+
* `paymentId` / `paidOptionBackendId` to a bogus Payment after the fact.
|
|
152
|
+
* Round 1 only verified the *transition into* PAID, so an already-PAID
|
|
153
|
+
* row could be silently repointed with no re-verify. Likewise an intent
|
|
154
|
+
* forced straight to `ISSUED` (the terminal happy-path that gates rights
|
|
155
|
+
* issuance) was never required to have been backed by a real Payment.
|
|
156
|
+
*
|
|
157
|
+
* This guard therefore:
|
|
158
|
+
* - validates the status transition is legal (no `awaiting_payment → issued`
|
|
159
|
+
* skips, no reviving terminal states);
|
|
160
|
+
* - re-verifies the backing Payment on **every** save where the persisted
|
|
161
|
+
* status is PAID or ISSUED — not just the transition edge — so a repoint
|
|
162
|
+
* of the paid backing fields is caught.
|
|
163
|
+
*
|
|
164
|
+
* Re-verifying an unchanged PAID/ISSUED row is cheap (one Payment load) and
|
|
165
|
+
* closes the repoint hole; freezing the backing fields would instead block
|
|
166
|
+
* legitimate corrections, so we re-verify.
|
|
167
|
+
*/
|
|
168
|
+
save(): Promise<this>;
|
|
169
|
+
/**
|
|
170
|
+
* Load the authoritative persisted row for this intent (S5 audit #1390 round
|
|
171
|
+
* 4). Returns `undefined` when the intent has no `id` or no row exists yet
|
|
172
|
+
* (truly new). Reading the DB directly — rather than trusting the
|
|
173
|
+
* {@link loadedIntentStatus} WeakMap, which is only populated when
|
|
174
|
+
* {@link initialize} hydrated the row — defeats the poisonable-prior-state
|
|
175
|
+
* vector where `create({ id: <existing>, _skipLoad: true })` produces an
|
|
176
|
+
* un-hydrated instance whose WeakMap entry is missing. A create-onto-existing
|
|
177
|
+
* is thus correctly treated as an update.
|
|
178
|
+
*/
|
|
179
|
+
private loadPersistedRow;
|
|
180
|
+
/**
|
|
181
|
+
* Reject any change to the settled backing fields of an already-PAID/ISSUED
|
|
182
|
+
* intent (codex HIGH#2). `paymentOptions` is compared structurally because
|
|
183
|
+
* the winning option's `nativeAmount` is exactly what the reconciliation in
|
|
184
|
+
* {@link assertBackedByCompletedPayment} binds against — letting it drift
|
|
185
|
+
* would let a repointed `paymentId` match a doctored option.
|
|
186
|
+
*/
|
|
187
|
+
private assertBackingFieldsUnchanged;
|
|
188
|
+
/**
|
|
189
|
+
* Reject an illegal status flip done via raw field assignment. Compares the
|
|
190
|
+
* about-to-be-written status against the status the row was loaded with.
|
|
191
|
+
* No-op re-saves (status unchanged) and brand-new rows are allowed (the
|
|
192
|
+
* backing-Payment verification still runs separately for PAID/ISSUED).
|
|
193
|
+
*/
|
|
194
|
+
private assertStatusTransition;
|
|
195
|
+
/**
|
|
196
|
+
* Verify the intent's `paidOptionBackendId` / `paymentId` reference a real,
|
|
197
|
+
* COMPLETED, amount-matching Payment. Throws otherwise. Shared by
|
|
198
|
+
* {@link verifyAndMarkPaid} (pre-transition) and the save-time guard
|
|
199
|
+
* (catch-all for raw mass-assignment).
|
|
200
|
+
*/
|
|
201
|
+
private assertBackedByCompletedPayment;
|
|
202
|
+
/**
|
|
203
|
+
* Reconcile a COMPLETED Payment against the winning {@link PaymentOption}
|
|
204
|
+
* (S5 audit #1390). Beyond the amount check, the Payment must have arrived on
|
|
205
|
+
* the SAME rail and currency the option quoted — otherwise an unrelated
|
|
206
|
+
* completed payment that merely shares a numeric amount (e.g. a USD 199
|
|
207
|
+
* payment) could satisfy a different option (e.g. a `base-usdc` 199 option),
|
|
208
|
+
* marking the intent paid on the wrong rail with no matching funds. Compares
|
|
209
|
+
* the Payment's `backendId` (rail) and native currency (falling back to its
|
|
210
|
+
* settlement currency) against the option's `backendId` / `currency`, then
|
|
211
|
+
* the native amount.
|
|
212
|
+
*
|
|
213
|
+
* Shared by {@link verifyAndMarkPaid} (pre-transition) and
|
|
214
|
+
* {@link assertBackedByCompletedPayment} (save-time catch-all) so both gates
|
|
215
|
+
* enforce the identical invariant.
|
|
216
|
+
*/
|
|
217
|
+
private reconcilePaymentWithOption;
|
|
218
|
+
isAwaitingPayment(): boolean;
|
|
219
|
+
isPaid(): boolean;
|
|
220
|
+
isIssued(): boolean;
|
|
221
|
+
isCancelled(): boolean;
|
|
222
|
+
isRetired(): boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Terminal-state predicate: any status other than `AWAITING_PAYMENT`
|
|
225
|
+
* is terminal (the intent will not accept further state changes
|
|
226
|
+
* except the explicit `markIssued` after `PAID`).
|
|
227
|
+
*/
|
|
228
|
+
isTerminal(): boolean;
|
|
229
|
+
/**
|
|
230
|
+
* `Date.now()`-based predicate: has the price lock window passed?
|
|
231
|
+
* Independent of `status` so callers can detect a stale-but-not-
|
|
232
|
+
* yet-marked-EXPIRED intent (and call `expire()` to advance it).
|
|
233
|
+
*/
|
|
234
|
+
isExpired(): boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Transition the intent to `PAID`, naming which option satisfied it
|
|
237
|
+
* and which `Payment` row carries the funds. Implicitly retires the
|
|
238
|
+
* other options — callers can check {@link isOptionRetired} to flag
|
|
239
|
+
* later inbound funds for refund.
|
|
240
|
+
*
|
|
241
|
+
* Throws when called on a non-`AWAITING_PAYMENT` intent so a stale
|
|
242
|
+
* caller can't accidentally overwrite a winning option with a
|
|
243
|
+
* losing one.
|
|
244
|
+
*/
|
|
245
|
+
markPaid(args: {
|
|
246
|
+
backendId: string;
|
|
247
|
+
paymentId: string;
|
|
248
|
+
}): void;
|
|
249
|
+
/**
|
|
250
|
+
* Verify-then-transition variant of {@link markPaid} (S5 audit #1390).
|
|
251
|
+
*
|
|
252
|
+
* `markPaid` trusts the caller-supplied `paymentId` / `backendId` without
|
|
253
|
+
* checking the referenced `Payment` actually exists, is `COMPLETED`, or
|
|
254
|
+
* carries the amount the winning option quoted. That lets a caller mark an
|
|
255
|
+
* intent PAID against a non-existent or still-pending payment. This method
|
|
256
|
+
* loads the `Payment` row and enforces:
|
|
257
|
+
*
|
|
258
|
+
* - the Payment exists,
|
|
259
|
+
* - it is `COMPLETED` (not pending / failed / cancelled / refunded),
|
|
260
|
+
* - it arrived on the same rail (`backendId`) and currency the option
|
|
261
|
+
* quoted (so an unrelated completed payment that merely shares a numeric
|
|
262
|
+
* amount can't satisfy a different option),
|
|
263
|
+
* - and its amount reconciles with the winning option's `nativeAmount`
|
|
264
|
+
* (within sub-cent tolerance), falling back to the option vs. the
|
|
265
|
+
* Payment's settlement `amount` when no native rail is recorded.
|
|
266
|
+
*
|
|
267
|
+
* Only after those pass does it delegate to the sync `markPaid` for the
|
|
268
|
+
* status-machine invariants.
|
|
269
|
+
*
|
|
270
|
+
* @param args.backendId backendId of the option that was satisfied
|
|
271
|
+
* @param args.paymentId id of the Payment row carrying the funds (required)
|
|
272
|
+
*/
|
|
273
|
+
verifyAndMarkPaid(args: {
|
|
274
|
+
backendId: string;
|
|
275
|
+
paymentId: string;
|
|
276
|
+
}): Promise<void>;
|
|
277
|
+
/**
|
|
278
|
+
* Transition a `PAID` intent to `ISSUED` once the downstream rights
|
|
279
|
+
* / contract / fulfillment have been created. Idempotent — calling
|
|
280
|
+
* twice is a no-op so callers don't have to guard against retries.
|
|
281
|
+
*/
|
|
282
|
+
markIssued(): void;
|
|
283
|
+
/**
|
|
284
|
+
* Move an `AWAITING_PAYMENT` intent to `EXPIRED`. Safe to call on
|
|
285
|
+
* an already-expired intent (no-op). Throws on terminal non-
|
|
286
|
+
* expired states so a confused caller can't undo a paid / issued
|
|
287
|
+
* intent.
|
|
288
|
+
*/
|
|
289
|
+
expire(): void;
|
|
290
|
+
/**
|
|
291
|
+
* Cancel an open intent. Only valid from `AWAITING_PAYMENT` —
|
|
292
|
+
* a paid / issued intent is no longer the buyer's to cancel; that
|
|
293
|
+
* path is a refund, modelled separately on `Payment`.
|
|
294
|
+
*/
|
|
295
|
+
cancel(reason?: string): void;
|
|
296
|
+
/**
|
|
297
|
+
* Mark a paid intent as retired — used when a previously-recorded
|
|
298
|
+
* payment was reversed (chargeback, refund) and the intent should
|
|
299
|
+
* not be considered "satisfied" anymore. Distinct from `cancel()`
|
|
300
|
+
* which only applies to never-paid intents.
|
|
301
|
+
*/
|
|
302
|
+
retire(reason?: string): void;
|
|
303
|
+
/**
|
|
304
|
+
* Return the option whose `backendId` matches, or `undefined` if
|
|
305
|
+
* no such option is on this intent. Useful for routing inbound
|
|
306
|
+
* payments to the right option's `nativeAmount` / `payTo` for
|
|
307
|
+
* verification.
|
|
308
|
+
*/
|
|
309
|
+
getOption(backendId: string): PaymentOption | undefined;
|
|
310
|
+
/**
|
|
311
|
+
* `true` once the intent is paid and the given option was NOT the
|
|
312
|
+
* winning one. Consumers use this to flag inbound funds to retired
|
|
313
|
+
* options for refund. Returns `false` for the winning option, for
|
|
314
|
+
* unpaid intents, and for an unknown `backendId`.
|
|
315
|
+
*/
|
|
316
|
+
isOptionRetired(backendId: string): boolean;
|
|
317
|
+
/**
|
|
318
|
+
* The options that were NOT selected at payment time. Returns an
|
|
319
|
+
* empty array for unpaid intents so callers can iterate without
|
|
320
|
+
* special-casing.
|
|
321
|
+
*/
|
|
322
|
+
getRetiredOptions(): PaymentOption[];
|
|
323
|
+
/**
|
|
324
|
+
* Defensive coercion for `paymentOptions` input. Accepts either an
|
|
325
|
+
* already-parsed array or a JSON string (e.g. from a row whose
|
|
326
|
+
* column was hand-edited or migrated from a different schema).
|
|
327
|
+
* Drops entries that don't carry the required `backendId` /
|
|
328
|
+
* `currency` / `payTo` / `nativeAmount` quartet so downstream
|
|
329
|
+
* routing code can rely on the invariant.
|
|
330
|
+
*/
|
|
331
|
+
private static normalizePaymentOptions;
|
|
332
|
+
/**
|
|
333
|
+
* Coerce a Date-ish input (Date / number / ISO string / null /
|
|
334
|
+
* undefined) into a `Date | null`. The framework hands us strings
|
|
335
|
+
* when hydrating from SQLite, numbers when round-tripping through
|
|
336
|
+
* JSON, and Date instances from the application.
|
|
337
|
+
*/
|
|
338
|
+
private static coerceDate;
|
|
339
|
+
}
|
|
340
|
+
export default PaymentIntent;
|
|
341
|
+
//# sourceMappingURL=PaymentIntent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaymentIntent.d.ts","sourceRoot":"","sources":["../../src/models/PaymentIntent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { PayoutStatus } from '../types/index.js';
|
|
3
|
+
export declare class Payout extends SmrtObject {
|
|
4
|
+
/**
|
|
5
|
+
* Tenant ID for multi-tenant isolation. Nullable so global / single-
|
|
6
|
+
* tenant deployments work too.
|
|
7
|
+
*/
|
|
8
|
+
tenantId: string | null;
|
|
9
|
+
/**
|
|
10
|
+
* The source {@link Payment} that funded this payout. Plain string
|
|
11
|
+
* reference (FK convention: `@foreignKey('ModelName')` would be
|
|
12
|
+
* fine within the package, but `Payment` lives in this same
|
|
13
|
+
* package — staying with a string keeps the dependency graph
|
|
14
|
+
* clean and matches how `PaymentIntent` references `Payment`).
|
|
15
|
+
*/
|
|
16
|
+
paymentId: string;
|
|
17
|
+
/**
|
|
18
|
+
* The destination {@link Vendor}. Foreign-key constrained because
|
|
19
|
+
* Vendor lives in this same package and a hard reference catches
|
|
20
|
+
* dangling payouts at insert time.
|
|
21
|
+
*/
|
|
22
|
+
vendorId: string;
|
|
23
|
+
/**
|
|
24
|
+
* Total funds moved, in the originating payment's native currency.
|
|
25
|
+
* Stored at decimal precision (matches `Payment.amount` /
|
|
26
|
+
* `Payment.nativeAmount` convention).
|
|
27
|
+
*/
|
|
28
|
+
grossAmount: number;
|
|
29
|
+
/**
|
|
30
|
+
* Operator's take from `grossAmount`. Must equal
|
|
31
|
+
* `grossAmount - supplierNet` at save time — the model enforces
|
|
32
|
+
* the invariant via `validateAmounts()`.
|
|
33
|
+
*/
|
|
34
|
+
operatorFee: number;
|
|
35
|
+
/**
|
|
36
|
+
* Net funds the supplier receives. Must equal
|
|
37
|
+
* `grossAmount - operatorFee` at save time.
|
|
38
|
+
*/
|
|
39
|
+
supplierNet: number;
|
|
40
|
+
/**
|
|
41
|
+
* Native currency the funds are denominated in — payout-rail-
|
|
42
|
+
* qualified, matching `Payment.nativeCurrency` and
|
|
43
|
+
* `Vendor.payoutAddresses` keys (`USDC-base`, `BTC`, `USD-stripe`,
|
|
44
|
+
* etc.).
|
|
45
|
+
*/
|
|
46
|
+
currency: string;
|
|
47
|
+
/**
|
|
48
|
+
* The `PaymentBackend` adapter id used to send the payout. For a
|
|
49
|
+
* marketplace this typically matches the source `Payment.backendId`
|
|
50
|
+
* (currency-match settlement, no conversion); other consumers can
|
|
51
|
+
* use a different backend if they bridge currencies elsewhere.
|
|
52
|
+
*/
|
|
53
|
+
backendId: string;
|
|
54
|
+
/**
|
|
55
|
+
* Outgoing chain transaction hash, gateway settlement id, or
|
|
56
|
+
* (for not-yet-broadcast BTC payouts) a PSBT identifier. Set when
|
|
57
|
+
* the payout transitions to `SENT`. Empty until then.
|
|
58
|
+
*/
|
|
59
|
+
backendTxRef: string;
|
|
60
|
+
/**
|
|
61
|
+
* Current status — see {@link PayoutStatus} for the state machine.
|
|
62
|
+
* Mutate via `markSent` / `markConfirmed` / `markFailed` /
|
|
63
|
+
* `resetFromFailed` rather than direct assignment so the
|
|
64
|
+
* transition invariants run.
|
|
65
|
+
*/
|
|
66
|
+
status: PayoutStatus;
|
|
67
|
+
/**
|
|
68
|
+
* When the payout transitioned to `SENT`.
|
|
69
|
+
*/
|
|
70
|
+
sentAt: Date | null;
|
|
71
|
+
/**
|
|
72
|
+
* When the payout transitioned to `CONFIRMED`.
|
|
73
|
+
*/
|
|
74
|
+
confirmedAt: Date | null;
|
|
75
|
+
/**
|
|
76
|
+
* When the payout transitioned to `FAILED`.
|
|
77
|
+
*/
|
|
78
|
+
failedAt: Date | null;
|
|
79
|
+
/**
|
|
80
|
+
* Caller-supplied human-readable failure reason. Empty for non-
|
|
81
|
+
* failed payouts. Cleared by `resetFromFailed()`.
|
|
82
|
+
*/
|
|
83
|
+
failureReason: string;
|
|
84
|
+
/**
|
|
85
|
+
* Append-only operator notes — chargeback memos, manual-retry
|
|
86
|
+
* justifications, etc.
|
|
87
|
+
*/
|
|
88
|
+
notes: string;
|
|
89
|
+
constructor(options?: any);
|
|
90
|
+
/**
|
|
91
|
+
* Re-coerce timestamp fields after the framework reapplies raw option
|
|
92
|
+
* values during initialization.
|
|
93
|
+
*/
|
|
94
|
+
initialize(): Promise<this>;
|
|
95
|
+
isPending(): boolean;
|
|
96
|
+
isSent(): boolean;
|
|
97
|
+
isConfirmed(): boolean;
|
|
98
|
+
isFailed(): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Transition `PENDING → SENT`. Requires `backendTxRef` — without a
|
|
101
|
+
* way to identify the outgoing transaction the payout cannot be
|
|
102
|
+
* tracked further. Throws on any other source status so a confused
|
|
103
|
+
* caller can't accidentally roll back a confirmed payout.
|
|
104
|
+
*/
|
|
105
|
+
markSent(backendTxRef: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Transition `SENT → CONFIRMED`. Throws if called from any other
|
|
108
|
+
* status — confirmations on a pending payout would mean a chain
|
|
109
|
+
* confirmation arrived before the application even recorded the
|
|
110
|
+
* outgoing tx, which is a code bug worth surfacing rather than
|
|
111
|
+
* silently fixing.
|
|
112
|
+
*/
|
|
113
|
+
markConfirmed(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Move the payout to `FAILED`. Valid from `PENDING` (couldn't even
|
|
116
|
+
* broadcast) or `SENT` (broadcast but chain rejected). Confirmed
|
|
117
|
+
* payouts cannot fail — that path is a refund, modelled separately.
|
|
118
|
+
*/
|
|
119
|
+
markFailed(reason: string): void;
|
|
120
|
+
/**
|
|
121
|
+
* Operator-driven reset: move a `FAILED` payout back to `PENDING`
|
|
122
|
+
* after fixing whatever broke. Clears the failure metadata and the
|
|
123
|
+
* old `backendTxRef` (the next attempt will have a new one).
|
|
124
|
+
* Distinct from `markFailed` reflexivity — the issue spec says
|
|
125
|
+
* "failed is terminal but allows manual reset via explicit method".
|
|
126
|
+
*/
|
|
127
|
+
resetFromFailed(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Throws if the gross/fee/net invariant doesn't hold. Tolerates a
|
|
130
|
+
* sub-cent rounding fuzz (`EPSILON = 0.01`) to match the rest of
|
|
131
|
+
* the smrt-ledgers package's tolerance.
|
|
132
|
+
*/
|
|
133
|
+
validateAmounts(): void;
|
|
134
|
+
/**
|
|
135
|
+
* Save-time state-machine + settlement guard (S5 audit #1390 round 2).
|
|
136
|
+
*
|
|
137
|
+
* `status`, `backendTxRef`, and the amount triple are all mass-assignable on
|
|
138
|
+
* the generated create/update routes. The amount invariant alone (round 1)
|
|
139
|
+
* still let a caller raw-flip a persisted payout to `CONFIRMED` / `SENT`
|
|
140
|
+
* (skipping the chain steps) or remit a `grossAmount` larger than the source
|
|
141
|
+
* Payment actually brought in — money that never arrived.
|
|
142
|
+
*
|
|
143
|
+
* This guard adds:
|
|
144
|
+
* - **Transitions on an existing row must be legal** per
|
|
145
|
+
* {@link PAYOUT_STATUS_TRANSITIONS} (no `pending → confirmed` skip on a
|
|
146
|
+
* raw update, no reviving a CONFIRMED payout). Brand-new rows may be
|
|
147
|
+
* created in any status (collection/import/query fixtures), but advancing
|
|
148
|
+
* a persisted row is constrained to the legal edges the helpers enforce.
|
|
149
|
+
* - **SENT requires a backendTxRef** (matches `markSent`'s invariant) so a
|
|
150
|
+
* sent payout is always traceable, regardless of how the status was set.
|
|
151
|
+
* - **grossAmount is capped by the source Payment** when that Payment
|
|
152
|
+
* resolves: a payout can never remit more than the funding Payment
|
|
153
|
+
* settled. A `paymentId` that doesn't resolve (synthetic id, Payment-less
|
|
154
|
+
* deployment) skips the cap — the source Payment is a plain-string,
|
|
155
|
+
* cross-model reference by design and may legitimately be a manually
|
|
156
|
+
* recorded (non-COMPLETED) row, so the cap is best-effort rather than a
|
|
157
|
+
* hard existence requirement.
|
|
158
|
+
*
|
|
159
|
+
* The amount-invariant check (`validateAmounts`) still runs first as the
|
|
160
|
+
* arithmetic floor.
|
|
161
|
+
*/
|
|
162
|
+
save(): Promise<this>;
|
|
163
|
+
/**
|
|
164
|
+
* Resolve the AUTHORITATIVE prior status from the database (S5 audit #1390
|
|
165
|
+
* round 4). The {@link loadedPayoutStatus} WeakMap is only populated when
|
|
166
|
+
* {@link initialize} hydrated the row, so a `create({ id: <existing>,
|
|
167
|
+
* _skipLoad: true })` upsert produces an instance with a missing WeakMap
|
|
168
|
+
* entry — trusting it would treat the write as a brand-new row and skip the
|
|
169
|
+
* transition + CONFIRMED-genesis guards. Reading the persisted row directly
|
|
170
|
+
* makes a create-onto-existing behave as an update. `undefined` means no row
|
|
171
|
+
* exists (genuinely new).
|
|
172
|
+
*/
|
|
173
|
+
private resolvePriorStatus;
|
|
174
|
+
/**
|
|
175
|
+
* Reject an illegal status flip on an existing row. Brand-new payouts (no
|
|
176
|
+
* prior) may start in any status — collection imports and query fixtures
|
|
177
|
+
* legitimately seed SENT / CONFIRMED rows — but advancing a *persisted* row
|
|
178
|
+
* is constrained to the legal edges the helpers enforce, so a raw update
|
|
179
|
+
* can't skip `pending → confirmed` or revive a terminal CONFIRMED.
|
|
180
|
+
*/
|
|
181
|
+
private assertStatusTransition;
|
|
182
|
+
/**
|
|
183
|
+
* Cap `grossAmount` by the source {@link Payment}'s settled funds. A payout
|
|
184
|
+
* that remits more than the Payment that funded it brought in is money the
|
|
185
|
+
* operator never received.
|
|
186
|
+
*
|
|
187
|
+
* HARD requirement (S5 audit #1390 round 4, codex HIGH#3): the source Payment
|
|
188
|
+
* MUST resolve. Round 3 made the cap best-effort — a `paymentId` that didn't
|
|
189
|
+
* resolve silently skipped the cap, so a caller could remit an arbitrary
|
|
190
|
+
* `grossAmount` simply by pointing at a non-existent (or empty) payment.
|
|
191
|
+
* A payout's whole purpose is to remit funds that *arrived* via a Payment;
|
|
192
|
+
* without a resolvable source there is no funded amount to cap against, so we
|
|
193
|
+
* reject rather than skip. `PayoutCollection.createFromPayment()` always wires
|
|
194
|
+
* a real `paymentId`, so this never blocks the supported creation path.
|
|
195
|
+
*/
|
|
196
|
+
private assertCappedBySourcePayment;
|
|
197
|
+
private static coerceDate;
|
|
198
|
+
}
|
|
199
|
+
export default Payout;
|
|
200
|
+
//# sourceMappingURL=Payout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Payout.d.ts","sourceRoot":"","sources":["../../src/models/Payout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAmCjD,qBAsBa,MAAO,SAAQ,UAAU;IACpC;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAM;IAEvB;;;;OAIG;IAEH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAO;IAE1B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAO;IAE1B;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAO;IAE1B;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAM;IAEvB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAM;IAE1B;;;;;OAKG;IACH,MAAM,EAAE,YAAY,CAAwB;IAE5C;;OAEG;IACH,MAAM,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE3B;;OAEG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEhC;;OAEG;IACH,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE7B;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAM;IAE3B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IA2B7B;;;OAGG;IACY,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAa1C,SAAS,IAAI,OAAO;IAIpB,MAAM,IAAI,OAAO;IAIjB,WAAW,IAAI,OAAO;IAItB,QAAQ,IAAI,OAAO;IAInB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAcpC;;;;;;OAMG;IACH,aAAa,IAAI,IAAI;IAUrB;;;;OAIG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAchC;;;;;;OAMG;IACH,eAAe,IAAI,IAAI;IAevB;;;;OAIG;IACH,eAAe,IAAI,IAAI;IA0BvB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwCpC;;;;;;;;;OASG;YACW,kBAAkB;IAchC;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAa9B;;;;;;;;;;;;;OAaG;YACW,2BAA2B;IAoCzC,OAAO,CAAC,MAAM,CAAC,UAAU;CAS1B;AAED,eAAe,MAAM,CAAC"}
|