@fantastic.dev/better-stripe 0.12.0 → 0.13.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/CHANGELOG.md +13 -0
- package/README.md +64 -8
- package/dist/client/costRecovery-invoices.d.ts +38 -0
- package/dist/client/costRecovery-invoices.d.ts.map +1 -0
- package/dist/client/costRecovery-invoices.js +105 -0
- package/dist/client/costRecovery-invoices.js.map +1 -0
- package/dist/client/costRecovery.d.ts +43 -2
- package/dist/client/costRecovery.d.ts.map +1 -1
- package/dist/client/costRecovery.js +69 -2
- package/dist/client/costRecovery.js.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +78 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/costRecovery/claims.d.ts.map +1 -1
- package/dist/component/costRecovery/claims.js +15 -8
- package/dist/component/costRecovery/claims.js.map +1 -1
- package/dist/component/costRecovery/completion.d.ts +6 -0
- package/dist/component/costRecovery/completion.d.ts.map +1 -1
- package/dist/component/costRecovery/helpers.d.ts +6 -0
- package/dist/component/costRecovery/helpers.d.ts.map +1 -1
- package/dist/component/costRecovery/mutations.d.ts +72 -0
- package/dist/component/costRecovery/mutations.d.ts.map +1 -1
- package/dist/component/costRecovery/mutations.js +57 -0
- package/dist/component/costRecovery/mutations.js.map +1 -1
- package/dist/component/costRecovery/queries.d.ts +6 -0
- package/dist/component/costRecovery/queries.d.ts.map +1 -1
- package/dist/component/costRecovery/reservations.d.ts +12 -0
- package/dist/component/costRecovery/reservations.d.ts.map +1 -1
- package/dist/component/costRecovery/schema.d.ts +44 -2
- package/dist/component/costRecovery/schema.d.ts.map +1 -1
- package/dist/component/costRecovery/schema.js +6 -0
- package/dist/component/costRecovery/schema.js.map +1 -1
- package/dist/component/schema.d.ts +17 -1
- package/dist/react/workflows/Collections.d.ts.map +1 -1
- package/dist/react/workflows/Collections.js +2 -1
- package/dist/react/workflows/Collections.js.map +1 -1
- package/dist/react/workflows/Refunds.d.ts +14 -3
- package/dist/react/workflows/Refunds.d.ts.map +1 -1
- package/dist/react/workflows/Refunds.js +58 -16
- package/dist/react/workflows/Refunds.js.map +1 -1
- package/dist/react/workflows/index.d.ts +3 -1
- package/dist/react/workflows/index.d.ts.map +1 -1
- package/dist/react/workflows/index.js +2 -1
- package/dist/react/workflows/index.js.map +1 -1
- package/dist/react/workflows/refund-preview.d.ts +16 -0
- package/dist/react/workflows/refund-preview.d.ts.map +1 -0
- package/dist/react/workflows/refund-preview.js +38 -0
- package/dist/react/workflows/refund-preview.js.map +1 -0
- package/docs/react-workflows.md +79 -4
- package/package.json +1 -1
- package/src/client/costRecovery-invoices.test.ts +401 -0
- package/src/client/costRecovery-invoices.transport.test.ts +107 -0
- package/src/client/costRecovery-invoices.ts +144 -0
- package/src/client/costRecovery.ts +107 -2
- package/src/client/index.ts +1 -1
- package/src/component/_generated/component.ts +99 -0
- package/src/component/costRecovery/claims.ts +18 -8
- package/src/component/costRecovery/mutations.ts +72 -0
- package/src/component/costRecovery/schema.ts +8 -0
- package/src/react/workflows/Collections.test.tsx +36 -0
- package/src/react/workflows/Collections.tsx +3 -1
- package/src/react/workflows/Refunds.currency.test.tsx +290 -0
- package/src/react/workflows/Refunds.tsx +115 -25
- package/src/react/workflows/index.ts +8 -1
- package/src/react/workflows/refund-preview.test.ts +65 -0
- package/src/react/workflows/refund-preview.ts +56 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/workflows/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/workflows/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,8 +4,9 @@ export { Checkout } from "./Checkout.js";
|
|
|
4
4
|
export { CheckoutElementsForm, PaymentIntentForm, CheckoutForm, } from "./CheckoutForms.js";
|
|
5
5
|
export { Connect } from "./Connect.js";
|
|
6
6
|
export { Payouts } from "./Payouts.js";
|
|
7
|
-
export { Refunds, useRefunds, parseAmountCents } from "./Refunds.js";
|
|
7
|
+
export { Refunds, useRefunds, parseAmountCents, parseRefundAmount, } from "./Refunds.js";
|
|
8
8
|
export { Collection } from "./Collection.js";
|
|
9
|
+
export { formatRefundAmount, groupRefundPreview } from "./refund-preview.js";
|
|
9
10
|
export { Sales, Invoices, RefundHistory, ReferredSales, WebhookEvents, } from "./Collections.js";
|
|
10
11
|
export { Products, useProductManager } from "./Products.js";
|
|
11
12
|
export { Onboarding, useOnboarding } from "./Onboarding.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/workflows/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AASzC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAM5B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAMvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQvC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/workflows/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type RefundPreviewGroup = {
|
|
2
|
+
currency: string;
|
|
3
|
+
status: string;
|
|
4
|
+
/** Loaded rows only. Null means unknown/invalid units or an unsafe sum. */
|
|
5
|
+
amount: number | null;
|
|
6
|
+
count: number;
|
|
7
|
+
};
|
|
8
|
+
/** Observed refund units, including fractional ISK/UGX amounts; not input eligibility. */
|
|
9
|
+
export declare function formatRefundAmount(amount: number, currency: string, locale?: string): string | null;
|
|
10
|
+
/** Group only the supplied preview, preserving provider status and currency. */
|
|
11
|
+
export declare function groupRefundPreview(rows: readonly {
|
|
12
|
+
amount: number;
|
|
13
|
+
currency: string;
|
|
14
|
+
status: string;
|
|
15
|
+
}[]): RefundPreviewGroup[];
|
|
16
|
+
//# sourceMappingURL=refund-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refund-preview.d.ts","sourceRoot":"","sources":["../../../src/react/workflows/refund-preview.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAUF,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,SAAU,GACf,MAAM,GAAG,IAAI,CAIf;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GACpE,kBAAkB,EAAE,CAsBtB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getChargeCurrencyMetadata } from "../../client/charge-currency.js";
|
|
2
|
+
import { formatPrice } from "../lib/price-helpers.js";
|
|
3
|
+
function isRefundAmount(amount, currency) {
|
|
4
|
+
return (Number.isSafeInteger(amount) &&
|
|
5
|
+
amount >= 0 &&
|
|
6
|
+
getChargeCurrencyMetadata(currency) !== null);
|
|
7
|
+
}
|
|
8
|
+
/** Observed refund units, including fractional ISK/UGX amounts; not input eligibility. */
|
|
9
|
+
export function formatRefundAmount(amount, currency, locale = "en-US") {
|
|
10
|
+
return isRefundAmount(amount, currency)
|
|
11
|
+
? formatPrice(amount, currency, locale)
|
|
12
|
+
: null;
|
|
13
|
+
}
|
|
14
|
+
/** Group only the supplied preview, preserving provider status and currency. */
|
|
15
|
+
export function groupRefundPreview(rows) {
|
|
16
|
+
const groups = new Map();
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const currency = row.currency.toLowerCase();
|
|
19
|
+
const key = JSON.stringify([currency, row.status]);
|
|
20
|
+
const group = groups.get(key) ?? {
|
|
21
|
+
currency,
|
|
22
|
+
status: row.status,
|
|
23
|
+
amount: 0,
|
|
24
|
+
count: 0,
|
|
25
|
+
};
|
|
26
|
+
group.count++;
|
|
27
|
+
if (group.amount === null || !isRefundAmount(row.amount, currency))
|
|
28
|
+
group.amount = null;
|
|
29
|
+
else {
|
|
30
|
+
const sum = BigInt(group.amount) + BigInt(row.amount);
|
|
31
|
+
group.amount =
|
|
32
|
+
sum <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(sum) : null;
|
|
33
|
+
}
|
|
34
|
+
groups.set(key, group);
|
|
35
|
+
}
|
|
36
|
+
return [...groups.values()];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=refund-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refund-preview.js","sourceRoot":"","sources":["../../../src/react/workflows/refund-preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAUtD,SAAS,cAAc,CAAC,MAAc,EAAE,QAAgB;IACtD,OAAO,CACL,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC;QAC5B,MAAM,IAAI,CAAC;QACX,yBAAyB,CAAC,QAAQ,CAAC,KAAK,IAAI,CAC7C,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,QAAgB,EAChB,MAAM,GAAG,OAAO;IAEhB,OAAO,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC;QACrC,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC;QACvC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAChC,IAAqE;IAErE,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8B,CAAC;IACrD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI;YAC/B,QAAQ;YACR,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,CAAC;SACT,CAAC;QACF,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC;YAChE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;aACjB,CAAC;YACJ,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACtD,KAAK,CAAC,MAAM;gBACV,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChE,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9B,CAAC"}
|
package/docs/react-workflows.md
CHANGED
|
@@ -58,9 +58,8 @@ by the host's error boundary rather than presented as a fabricated amount.
|
|
|
58
58
|
Metadata describes precision only. Derive selectable presentment currencies
|
|
59
59
|
from the public `getCountrySpecs` result and validate account/payment-method
|
|
60
60
|
support on the server. Do not derive an account's supported currencies from
|
|
61
|
-
`Intl.supportedValuesOf` or this helper.
|
|
62
|
-
|
|
63
|
-
charge helpers or PlanPricingEditor integration.
|
|
61
|
+
`Intl.supportedValuesOf` or this helper. Refunds and PlanPricingEditor reuse the
|
|
62
|
+
charge helpers for original-currency inputs; neither establishes payout units.
|
|
64
63
|
|
|
65
64
|
## Bind your backend
|
|
66
65
|
|
|
@@ -207,7 +206,7 @@ widgets. Use package UI or the hosted Billing Portal for those workflows.
|
|
|
207
206
|
| Earnings | `Earnings`, `EarningsSummary` | `earningsFromBalance` |
|
|
208
207
|
| Payouts | `Payouts`, `PayoutSchedule`, `PayoutHistory`, `PayoutOverview` | `getPayoutTotals` |
|
|
209
208
|
| Sales | `Sales`, `ReferredSales` | Server-authoritative economic rows |
|
|
210
|
-
| Refunds | `Refunds`, `RefundHistory` | `useRefunds`, `parseAmountCents`
|
|
209
|
+
| Refunds | `Refunds`, `RefundHistory` | `useRefunds`, `parseRefundAmount`; legacy USD-only `parseAmountCents` |
|
|
211
210
|
| Disputes | `Disputes`, `DisputesList`, `DisputeDetail`, `EvidenceForm`, `ChargebackLedger` | `useDisputeResponse` |
|
|
212
211
|
| Onboarding | `Onboarding` | `useOnboarding` |
|
|
213
212
|
| Accounts | `Account`, `AccountConfigurations` | `useAccountManager` |
|
|
@@ -316,6 +315,82 @@ loading. The example's affiliate sales page uses this with the component query
|
|
|
316
315
|
pages scoped to the recipient account. Earnings totals still use aggregate
|
|
317
316
|
queries independently of pagination.
|
|
318
317
|
|
|
318
|
+
## Original-currency refunds and reversals
|
|
319
|
+
|
|
320
|
+
`Refunds` and `useRefunds` accept `paymentCurrency` for the selected
|
|
321
|
+
`paymentIntentId` and independent `sourceChargeCurrency` for `sourceChargeId`.
|
|
322
|
+
Derive both from authorized original records. The Vite seller refunds page
|
|
323
|
+
loads the selected payment before parsing a partial refund. Its readonly
|
|
324
|
+
`getRefundChargeContext` adapter reads the component's stored settlement allocation
|
|
325
|
+
for the selected source charge and matches the claimed persona's current account
|
|
326
|
+
to its original `store` leg. Missing allocations, unavailable accounts, foreign
|
|
327
|
+
store legs and affiliate/collaborator-only recipients return no context. Partial
|
|
328
|
+
reversals remain disabled until the selected charge has supported verified currency,
|
|
329
|
+
independently of payment currency. A charge without verified currency can still use
|
|
330
|
+
its separately authorized full operation, but cannot submit an assumed-USD partial
|
|
331
|
+
amount. This adapter adds no provider calls, writes or settlement ledger.
|
|
332
|
+
|
|
333
|
+
The example binds a client-claimed demo persona, not an authenticated identity.
|
|
334
|
+
Production hosts such as Dojo must derive an enabled user from the authenticated
|
|
335
|
+
session, authorize merchant ownership on the server and expose only the permitted
|
|
336
|
+
original-charge context. Loading context does not authorize a reversal.
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
<Refunds
|
|
340
|
+
scopeKey={authenticatedUser.id}
|
|
341
|
+
paymentIntentId={selectedPayment.stripePaymentIntentId}
|
|
342
|
+
paymentCurrency={selectedPayment.currency}
|
|
343
|
+
sourceChargeId={verifiedOriginalCharge.id}
|
|
344
|
+
sourceChargeCurrency={verifiedOriginalCharge.currency}
|
|
345
|
+
issueRefund={authorizedRefund}
|
|
346
|
+
reverseTransfers={authorizedRecoveryReversal}
|
|
347
|
+
/>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The callbacks remain application-authenticated server bindings. Their optional
|
|
351
|
+
`amountCents` property keeps its legacy name but contains exact provider units
|
|
352
|
+
in the original operation's currency, not universal hundredths. JPY 500 emits
|
|
353
|
+
500; ISK 500 emits 50000; HUF/TWD charges retain fractions. Full operations omit
|
|
354
|
+
the property entirely. Currency parsing does not authorize a refund, calculate
|
|
355
|
+
the remaining refundable balance, reverse ordinary-refund legs a second time,
|
|
356
|
+
cancel a subscription or change access.
|
|
357
|
+
|
|
358
|
+
Omitting a currency retains the legacy USD contract. Pass explicit `null` while
|
|
359
|
+
original currency is unknown; partial operations then fail safely. An explicit
|
|
360
|
+
currency requires its matching original identifier prop. Editing to another
|
|
361
|
+
identifier cannot reuse the old currency; the host must select/load its verified
|
|
362
|
+
record. Unknown currencies, unsupported precision, unsafe integers, scientific
|
|
363
|
+
notation, excess decimals and nonpositive partial amounts never reach callbacks.
|
|
364
|
+
`parseRefundAmount(value, currency)` exposes this exact positive parser through
|
|
365
|
+
`/react`. `parseAmountCents(value)` remains a USD-only compatibility wrapper.
|
|
366
|
+
|
|
367
|
+
Headless state exposes both currencies and nullable `refundInputDecimalPlaces`
|
|
368
|
+
and `reversalInputDecimalPlaces`. Use these for translated labels and input
|
|
369
|
+
precision. Fields, pending/error/result state reset by authenticated scope,
|
|
370
|
+
selected identifiers and currencies. Late completion cannot update another
|
|
371
|
+
context. Changing the view does not cancel an already-submitted server operation;
|
|
372
|
+
reconcile that intent before requesting another. Revalidate authorization,
|
|
373
|
+
original merchant/currency, positive units, remaining refundable limits and
|
|
374
|
+
stable provider idempotency on the server before moving money.
|
|
375
|
+
|
|
376
|
+
The Vite refund history is a bounded preview of up to 25 loaded rows, not an
|
|
377
|
+
authoritative account total. `groupRefundPreview(rows)` groups only supplied rows
|
|
378
|
+
by original currency and exact provider status, returning currency, status, nullable
|
|
379
|
+
amount and row count. It never adds currencies or combines pending/failed/canceled
|
|
380
|
+
requests with succeeded refunds. An empty preview has no monetary group; an explicit
|
|
381
|
+
zero remains zero. Invalid or unsupported units and unsafe sums yield an unavailable
|
|
382
|
+
group amount instead of a rounded or invented total.
|
|
383
|
+
|
|
384
|
+
`formatRefundAmount(amount, currency, locale?)` returns a nullable display string
|
|
385
|
+
using package ordinary provider-unit helpers. It preserves observed fractional
|
|
386
|
+
ISK/UGX refunds; display does not establish eligibility for a new partial input.
|
|
387
|
+
The native `RefundHistory` and the Vite render-prop table use this safe formatter.
|
|
388
|
+
Both helpers are public through `/react`, allowing Dojo to supply translated
|
|
389
|
+
Shadcn labels and empty/unavailable states without rebuilding financial arithmetic.
|
|
390
|
+
Preview grouping performs no reads, writes or provider operations and cannot prove
|
|
391
|
+
bank delivery or complete-account earnings. Authorize and bound the source read in
|
|
392
|
+
the host; keep authoritative full-account aggregates separate.
|
|
393
|
+
|
|
319
394
|
## Store-plan pricing editor
|
|
320
395
|
|
|
321
396
|
`PlanPricingEditor` accepts a `plan` with `id`, `name`, `monthlyAmount` in minor
|
package/package.json
CHANGED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { componentsGeneric, type FunctionReference } from "convex/server";
|
|
2
|
+
import type Stripe from "stripe";
|
|
3
|
+
import { expect, it, vi } from "vitest";
|
|
4
|
+
import schema from "../component/schema.js";
|
|
5
|
+
import { initConvexTest } from "./setup.test.js";
|
|
6
|
+
import { CostRecoveryClient } from "./costRecovery.js";
|
|
7
|
+
import type { Component, RunCtx } from "./helpers.js";
|
|
8
|
+
const modules = import.meta.glob("../component/**/*.{ts,js,tsx,jsx}");
|
|
9
|
+
const scope = { accountId: "acct_owner", scopeKey: "store", currency: "usd" };
|
|
10
|
+
const options = { ...scope, groupKey: "invoice:in_1" };
|
|
11
|
+
async function setup(amount = 320) {
|
|
12
|
+
const t = initConvexTest();
|
|
13
|
+
t.registerComponent("betterStripe", schema, modules);
|
|
14
|
+
const ctx = {
|
|
15
|
+
runQuery: (
|
|
16
|
+
ref: FunctionReference<"query">,
|
|
17
|
+
args: Record<string, unknown>,
|
|
18
|
+
) => t.query(ref, args),
|
|
19
|
+
runMutation: (
|
|
20
|
+
ref: FunctionReference<"mutation">,
|
|
21
|
+
args: Record<string, unknown>,
|
|
22
|
+
) => t.mutation(ref, args),
|
|
23
|
+
} as unknown as RunCtx;
|
|
24
|
+
const invoice = {
|
|
25
|
+
id: "in_1",
|
|
26
|
+
customer_account: scope.accountId,
|
|
27
|
+
currency: "usd",
|
|
28
|
+
status: "draft",
|
|
29
|
+
};
|
|
30
|
+
const items: Stripe.InvoiceItem[] = [];
|
|
31
|
+
const provider = {
|
|
32
|
+
invoices: { retrieve: vi.fn(async () => invoice) },
|
|
33
|
+
invoiceItems: {
|
|
34
|
+
list: vi.fn(() =>
|
|
35
|
+
(async function* () {
|
|
36
|
+
for (const item of items) yield item;
|
|
37
|
+
})(),
|
|
38
|
+
),
|
|
39
|
+
create: vi.fn(async (params: Record<string, unknown>) => {
|
|
40
|
+
const item = {
|
|
41
|
+
id: "ii_recovery",
|
|
42
|
+
...params,
|
|
43
|
+
} as unknown as Stripe.InvoiceItem;
|
|
44
|
+
items.push(item);
|
|
45
|
+
return item;
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const recovery = new CostRecoveryClient(
|
|
50
|
+
componentsGeneric().betterStripe as unknown as Component,
|
|
51
|
+
() => provider as unknown as Stripe,
|
|
52
|
+
);
|
|
53
|
+
await recovery.recordClaim(ctx, {
|
|
54
|
+
...scope,
|
|
55
|
+
claimKey: "fee:ch_original",
|
|
56
|
+
sourceChargeId: "ch_original",
|
|
57
|
+
sourceGross: 10000,
|
|
58
|
+
sourceProcessingFee: 320,
|
|
59
|
+
amountDue: amount,
|
|
60
|
+
revision: 1,
|
|
61
|
+
});
|
|
62
|
+
await recovery.reserve(ctx, {
|
|
63
|
+
...options,
|
|
64
|
+
operationKey: "batch1",
|
|
65
|
+
sourceKind: "invoice",
|
|
66
|
+
sourceId: invoice.id,
|
|
67
|
+
maxDebit: 10000,
|
|
68
|
+
maxCredit: 10000,
|
|
69
|
+
});
|
|
70
|
+
await recovery.seal(ctx, { groupKey: options.groupKey });
|
|
71
|
+
return { ctx, recovery, invoice, items, provider };
|
|
72
|
+
}
|
|
73
|
+
it("attaches one signed adjustment to an authorized draft invoice and keeps claims reserved", async () => {
|
|
74
|
+
const { ctx, recovery, provider } = await setup(-320);
|
|
75
|
+
const result = await recovery.attachInvoice(ctx, options);
|
|
76
|
+
expect(result.invoiceItemId).toBe("ii_recovery");
|
|
77
|
+
expect(provider.invoiceItems.create).toHaveBeenCalledWith(
|
|
78
|
+
expect.objectContaining({
|
|
79
|
+
invoice: "in_1",
|
|
80
|
+
customer_account: "acct_owner",
|
|
81
|
+
currency: "usd",
|
|
82
|
+
amount: -320,
|
|
83
|
+
discountable: false,
|
|
84
|
+
}),
|
|
85
|
+
expect.objectContaining({ idempotencyKey: expect.any(String) }),
|
|
86
|
+
);
|
|
87
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
88
|
+
reservedAmount: -320,
|
|
89
|
+
collectedAmount: 0,
|
|
90
|
+
outstanding: 0,
|
|
91
|
+
});
|
|
92
|
+
await recovery.attachInvoice(ctx, options);
|
|
93
|
+
expect(provider.invoiceItems.create).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
it("rejects invoice ownership, currency and host scope mismatches before creating items", async () => {
|
|
96
|
+
const { ctx, recovery, invoice, provider } = await setup();
|
|
97
|
+
await expect(
|
|
98
|
+
recovery.attachInvoice(ctx, { ...options, scopeKey: "other" }),
|
|
99
|
+
).rejects.toThrow(/scope/i);
|
|
100
|
+
invoice.customer_account = "acct_other";
|
|
101
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toThrow(/owner/i);
|
|
102
|
+
invoice.customer_account = scope.accountId;
|
|
103
|
+
invoice.currency = "eur";
|
|
104
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toThrow(
|
|
105
|
+
/currency/i,
|
|
106
|
+
);
|
|
107
|
+
expect(provider.invoiceItems.create).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
it("recovers provider success after persistence failure by scanning the exact invoice completely", async () => {
|
|
110
|
+
const { ctx, recovery, items, provider } = await setup();
|
|
111
|
+
const mutation = ctx.runMutation! as (
|
|
112
|
+
ref: FunctionReference<"mutation">,
|
|
113
|
+
args: Record<string, unknown>,
|
|
114
|
+
) => Promise<unknown>;
|
|
115
|
+
ctx.runMutation = vi.fn(
|
|
116
|
+
async (
|
|
117
|
+
ref: FunctionReference<"mutation">,
|
|
118
|
+
args: Record<string, unknown>,
|
|
119
|
+
) => {
|
|
120
|
+
if (items.length) throw new Error("host persistence failed");
|
|
121
|
+
return mutation(ref, args);
|
|
122
|
+
},
|
|
123
|
+
) as RunCtx["runMutation"];
|
|
124
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toThrow(
|
|
125
|
+
"host persistence failed",
|
|
126
|
+
);
|
|
127
|
+
ctx.runMutation = mutation as RunCtx["runMutation"];
|
|
128
|
+
items.unshift(
|
|
129
|
+
...Array.from(
|
|
130
|
+
{ length: 105 },
|
|
131
|
+
(_, i) => ({ id: `ii_other_${i}`, metadata: {} }) as Stripe.InvoiceItem,
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
expect((await recovery.attachInvoice(ctx, options)).invoiceItemId).toBe(
|
|
135
|
+
"ii_recovery",
|
|
136
|
+
);
|
|
137
|
+
expect(provider.invoiceItems.list).toHaveBeenCalledWith({
|
|
138
|
+
invoice: "in_1",
|
|
139
|
+
limit: 100,
|
|
140
|
+
});
|
|
141
|
+
expect(provider.invoiceItems.create).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
it("does not create new adjustments after finalization or recreate a deleted bound item", async () => {
|
|
144
|
+
const { ctx, recovery, invoice, items, provider } = await setup();
|
|
145
|
+
invoice.status = "open";
|
|
146
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toThrow(/draft/i);
|
|
147
|
+
invoice.status = "draft";
|
|
148
|
+
await recovery.attachInvoice(ctx, options);
|
|
149
|
+
items.length = 0;
|
|
150
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toThrow(
|
|
151
|
+
/missing/i,
|
|
152
|
+
);
|
|
153
|
+
expect(provider.invoiceItems.create).toHaveBeenCalledTimes(1);
|
|
154
|
+
});
|
|
155
|
+
it("preserves provider errors and keeps unknown outcomes reserved", async () => {
|
|
156
|
+
const { ctx, recovery, provider } = await setup();
|
|
157
|
+
const failure = new Error("network interrupted");
|
|
158
|
+
provider.invoiceItems.create.mockRejectedValue(failure);
|
|
159
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toBe(failure);
|
|
160
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
161
|
+
reservedAmount: 320,
|
|
162
|
+
collectedAmount: 0,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
it("collects only after verified payment and replays completion without collecting twice", async () => {
|
|
166
|
+
const { ctx, recovery, invoice } = await setup();
|
|
167
|
+
await recovery.attachInvoice(ctx, options);
|
|
168
|
+
invoice.status = "open";
|
|
169
|
+
expect((await recovery.reconcileInvoice(ctx, options)).isDone).toBe(false);
|
|
170
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
171
|
+
reservedAmount: 320,
|
|
172
|
+
collectedAmount: 0,
|
|
173
|
+
});
|
|
174
|
+
invoice.status = "paid";
|
|
175
|
+
expect((await recovery.reconcileInvoice(ctx, options)).isDone).toBe(true);
|
|
176
|
+
await recovery.reconcileInvoice(ctx, options);
|
|
177
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
178
|
+
reservedAmount: 0,
|
|
179
|
+
collectedAmount: 320,
|
|
180
|
+
outstanding: 0,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
it("releases verified void invoices, and uncollectible invoices only under explicit host policy", async () => {
|
|
184
|
+
const { ctx, recovery, invoice } = await setup();
|
|
185
|
+
await recovery.attachInvoice(ctx, options);
|
|
186
|
+
invoice.status = "uncollectible";
|
|
187
|
+
expect((await recovery.reconcileInvoice(ctx, options)).isDone).toBe(false);
|
|
188
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
189
|
+
reservedAmount: 320,
|
|
190
|
+
});
|
|
191
|
+
invoice.status = "void";
|
|
192
|
+
await recovery.reconcileInvoice(ctx, options);
|
|
193
|
+
await recovery.reconcileInvoice(ctx, options);
|
|
194
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
195
|
+
reservedAmount: 0,
|
|
196
|
+
collectedAmount: 0,
|
|
197
|
+
outstanding: 320,
|
|
198
|
+
});
|
|
199
|
+
const other = await setup();
|
|
200
|
+
await other.recovery.attachInvoice(other.ctx, options);
|
|
201
|
+
other.invoice.status = "uncollectible";
|
|
202
|
+
await other.recovery.reconcileInvoice(other.ctx, {
|
|
203
|
+
...options,
|
|
204
|
+
releaseUncollectible: true,
|
|
205
|
+
});
|
|
206
|
+
expect(await other.recovery.balance(other.ctx, scope)).toMatchObject({
|
|
207
|
+
reservedAmount: 0,
|
|
208
|
+
outstanding: 320,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it("retains source changes while an invoice is unpaid and carries the signed difference forward", async () => {
|
|
212
|
+
const { ctx, recovery, invoice } = await setup();
|
|
213
|
+
await recovery.attachInvoice(ctx, options);
|
|
214
|
+
await recovery.recordClaim(ctx, {
|
|
215
|
+
...scope,
|
|
216
|
+
claimKey: "fee:ch_original",
|
|
217
|
+
sourceChargeId: "ch_original",
|
|
218
|
+
sourceGross: 10000,
|
|
219
|
+
sourceProcessingFee: 320,
|
|
220
|
+
amountDue: 0,
|
|
221
|
+
revision: 2,
|
|
222
|
+
});
|
|
223
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
224
|
+
reservedAmount: 320,
|
|
225
|
+
outstanding: -320,
|
|
226
|
+
});
|
|
227
|
+
await expect(
|
|
228
|
+
recovery.reserve(ctx, {
|
|
229
|
+
...scope,
|
|
230
|
+
groupKey: "settlement:pi_2",
|
|
231
|
+
operationKey: "other",
|
|
232
|
+
sourceKind: "settlement",
|
|
233
|
+
sourceId: "pi_2",
|
|
234
|
+
sourceGross: 1000,
|
|
235
|
+
sourceProcessingFee: 59,
|
|
236
|
+
maxDebit: 1000,
|
|
237
|
+
maxCredit: 1000,
|
|
238
|
+
}),
|
|
239
|
+
).rejects.toThrow();
|
|
240
|
+
invoice.status = "paid";
|
|
241
|
+
await recovery.reconcileInvoice(ctx, options);
|
|
242
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
243
|
+
collectedAmount: 320,
|
|
244
|
+
reservedAmount: 0,
|
|
245
|
+
outstanding: -320,
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
it("refuses altered or duplicate adjustment items before marking any claim collected", async () => {
|
|
249
|
+
const { ctx, recovery, invoice, items } = await setup();
|
|
250
|
+
await recovery.attachInvoice(ctx, options);
|
|
251
|
+
invoice.status = "paid";
|
|
252
|
+
items[0].amount = 1;
|
|
253
|
+
await expect(recovery.reconcileInvoice(ctx, options)).rejects.toThrow(
|
|
254
|
+
/economics/i,
|
|
255
|
+
);
|
|
256
|
+
items[0].amount = 320;
|
|
257
|
+
items.push({ ...items[0], id: "ii_duplicate" });
|
|
258
|
+
await expect(recovery.reconcileInvoice(ctx, options)).rejects.toThrow(
|
|
259
|
+
/duplicate/i,
|
|
260
|
+
);
|
|
261
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
262
|
+
reservedAmount: 320,
|
|
263
|
+
collectedAmount: 0,
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
it("preserves funding-charge economics when a collected invoice payment is partially refunded", async () => {
|
|
267
|
+
const { ctx, recovery, invoice, provider } = await setup();
|
|
268
|
+
await recovery.attachInvoice(ctx, options);
|
|
269
|
+
invoice.status = "paid";
|
|
270
|
+
await recovery.reconcileInvoice(ctx, options);
|
|
271
|
+
Object.assign(invoice, { amount_paid: 10000 });
|
|
272
|
+
Object.assign(provider, {
|
|
273
|
+
invoicePayments: {
|
|
274
|
+
list: () =>
|
|
275
|
+
(async function* () {
|
|
276
|
+
yield {
|
|
277
|
+
id: "inpay_1",
|
|
278
|
+
invoice: "in_1",
|
|
279
|
+
status: "paid",
|
|
280
|
+
amount_paid: 10000,
|
|
281
|
+
currency: "usd",
|
|
282
|
+
payment: { type: "payment_intent", payment_intent: "pi_funding" },
|
|
283
|
+
};
|
|
284
|
+
})(),
|
|
285
|
+
},
|
|
286
|
+
paymentIntents: {
|
|
287
|
+
retrieve: async () => ({
|
|
288
|
+
id: "pi_funding",
|
|
289
|
+
status: "succeeded",
|
|
290
|
+
amount_received: 10000,
|
|
291
|
+
currency: "usd",
|
|
292
|
+
metadata: {},
|
|
293
|
+
latest_charge: {
|
|
294
|
+
id: "ch_funding",
|
|
295
|
+
amount: 10000,
|
|
296
|
+
amount_captured: 10000,
|
|
297
|
+
amount_refunded: 5000,
|
|
298
|
+
currency: "usd",
|
|
299
|
+
disputed: false,
|
|
300
|
+
payment_intent: "pi_funding",
|
|
301
|
+
balance_transaction: {
|
|
302
|
+
id: "txn_original",
|
|
303
|
+
amount: 10000,
|
|
304
|
+
fee: 320,
|
|
305
|
+
net: 9680,
|
|
306
|
+
currency: "usd",
|
|
307
|
+
created: 1800000000,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
}),
|
|
311
|
+
},
|
|
312
|
+
refunds: {
|
|
313
|
+
list: () =>
|
|
314
|
+
(async function* () {
|
|
315
|
+
yield {
|
|
316
|
+
id: "re_half",
|
|
317
|
+
charge: "ch_funding",
|
|
318
|
+
payment_intent: "pi_funding",
|
|
319
|
+
amount: 5000,
|
|
320
|
+
currency: "usd",
|
|
321
|
+
status: "succeeded",
|
|
322
|
+
balance_transaction: {
|
|
323
|
+
id: "txn_refund",
|
|
324
|
+
amount: -5000,
|
|
325
|
+
fee: 0,
|
|
326
|
+
net: -5000,
|
|
327
|
+
currency: "usd",
|
|
328
|
+
created: 1800000001,
|
|
329
|
+
},
|
|
330
|
+
failure_balance_transaction: null,
|
|
331
|
+
};
|
|
332
|
+
})(),
|
|
333
|
+
},
|
|
334
|
+
disputes: { list: () => (async function* () {})() },
|
|
335
|
+
});
|
|
336
|
+
const result = await recovery.rollbackInvoice(ctx, {
|
|
337
|
+
...options,
|
|
338
|
+
revision: 1,
|
|
339
|
+
});
|
|
340
|
+
expect(result).toMatchObject({
|
|
341
|
+
status: "ready",
|
|
342
|
+
claim: {
|
|
343
|
+
sourceChargeId: "ch_funding",
|
|
344
|
+
sourceGross: 10000,
|
|
345
|
+
sourceProcessingFee: 320,
|
|
346
|
+
amountDue: 160,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
await recovery.rollbackInvoice(ctx, { ...options, revision: 1 });
|
|
350
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
351
|
+
amountDue: 480,
|
|
352
|
+
collectedAmount: 320,
|
|
353
|
+
outstanding: 160,
|
|
354
|
+
});
|
|
355
|
+
expect(
|
|
356
|
+
await recovery.getClaim(ctx, { claimKey: "fee:ch_original" }),
|
|
357
|
+
).toMatchObject({ amountDue: 320, sourceChargeId: "ch_original" });
|
|
358
|
+
});
|
|
359
|
+
it("does not guess funding allocations for multi-payment invoices", async () => {
|
|
360
|
+
const { ctx, recovery, invoice, provider } = await setup();
|
|
361
|
+
await recovery.attachInvoice(ctx, options);
|
|
362
|
+
invoice.status = "paid";
|
|
363
|
+
await recovery.reconcileInvoice(ctx, options);
|
|
364
|
+
Object.assign(invoice, { amount_paid: 10000 });
|
|
365
|
+
Object.assign(provider, {
|
|
366
|
+
invoicePayments: {
|
|
367
|
+
list: () =>
|
|
368
|
+
(async function* () {
|
|
369
|
+
for (let i = 0; i < 2; i++)
|
|
370
|
+
yield {
|
|
371
|
+
id: `inpay_${i}`,
|
|
372
|
+
invoice: "in_1",
|
|
373
|
+
status: "paid",
|
|
374
|
+
amount_paid: 5000,
|
|
375
|
+
currency: "usd",
|
|
376
|
+
payment: { type: "payment_intent", payment_intent: `pi_${i}` },
|
|
377
|
+
};
|
|
378
|
+
})(),
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
expect(
|
|
382
|
+
await recovery.rollbackInvoice(ctx, { ...options, revision: 1 }),
|
|
383
|
+
).toMatchObject({
|
|
384
|
+
status: "not_ready",
|
|
385
|
+
reason: "invoice_funding_allocation_required",
|
|
386
|
+
});
|
|
387
|
+
expect(await recovery.balance(ctx, scope)).toMatchObject({
|
|
388
|
+
amountDue: 320,
|
|
389
|
+
collectedAmount: 320,
|
|
390
|
+
outstanding: 0,
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
it("never creates a new provider item for an already completed bookkeeping group", async () => {
|
|
394
|
+
const { ctx, recovery, provider } = await setup();
|
|
395
|
+
await recovery.finish(ctx, {
|
|
396
|
+
groupKey: options.groupKey,
|
|
397
|
+
outcome: "collect",
|
|
398
|
+
});
|
|
399
|
+
await expect(recovery.attachInvoice(ctx, options)).rejects.toThrow(/sealed/i);
|
|
400
|
+
expect(provider.invoiceItems.create).not.toHaveBeenCalled();
|
|
401
|
+
});
|