@fantastic.dev/better-stripe 0.11.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +267 -2
  3. package/dist/client/billing/balanceFunding.d.ts +36 -0
  4. package/dist/client/billing/balanceFunding.d.ts.map +1 -0
  5. package/dist/client/billing/balanceFunding.js +107 -0
  6. package/dist/client/billing/balanceFunding.js.map +1 -0
  7. package/dist/client/billing/invoicePreview.d.ts +28 -0
  8. package/dist/client/billing/invoicePreview.d.ts.map +1 -0
  9. package/dist/client/billing/invoicePreview.js +56 -0
  10. package/dist/client/billing/invoicePreview.js.map +1 -0
  11. package/dist/client/charge-currency-format.d.ts +3 -0
  12. package/dist/client/charge-currency-format.d.ts.map +1 -0
  13. package/dist/client/charge-currency-format.js +22 -0
  14. package/dist/client/charge-currency-format.js.map +1 -0
  15. package/dist/client/charge-currency.d.ts +14 -0
  16. package/dist/client/charge-currency.d.ts.map +1 -0
  17. package/dist/client/charge-currency.js +85 -0
  18. package/dist/client/charge-currency.js.map +1 -0
  19. package/dist/client/costRecovery-invoices.d.ts +38 -0
  20. package/dist/client/costRecovery-invoices.d.ts.map +1 -0
  21. package/dist/client/costRecovery-invoices.js +105 -0
  22. package/dist/client/costRecovery-invoices.js.map +1 -0
  23. package/dist/client/costRecovery.d.ts +156 -0
  24. package/dist/client/costRecovery.d.ts.map +1 -0
  25. package/dist/client/costRecovery.js +117 -0
  26. package/dist/client/costRecovery.js.map +1 -0
  27. package/dist/client/index.d.ts +28 -66
  28. package/dist/client/index.d.ts.map +1 -1
  29. package/dist/client/index.js +25 -0
  30. package/dist/client/index.js.map +1 -1
  31. package/dist/client/products/prices.d.ts +3 -2
  32. package/dist/client/products/prices.d.ts.map +1 -1
  33. package/dist/client/products/prices.js +0 -3
  34. package/dist/client/products/prices.js.map +1 -1
  35. package/dist/client/products/products.d.ts +3 -2
  36. package/dist/client/products/products.d.ts.map +1 -1
  37. package/dist/client/products/products.js +0 -3
  38. package/dist/client/products/products.js.map +1 -1
  39. package/dist/client/settlement/processingCosts.d.ts +56 -0
  40. package/dist/client/settlement/processingCosts.d.ts.map +1 -0
  41. package/dist/client/settlement/processingCosts.js +173 -0
  42. package/dist/client/settlement/processingCosts.js.map +1 -0
  43. package/dist/client/utils/webhookEndpoints.d.ts +1 -1
  44. package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
  45. package/dist/client/utils/webhookEndpoints.js +1 -0
  46. package/dist/client/utils/webhookEndpoints.js.map +1 -1
  47. package/dist/client/webhooks/processors.d.ts.map +1 -1
  48. package/dist/client/webhooks/processors.js +1 -0
  49. package/dist/client/webhooks/processors.js.map +1 -1
  50. package/dist/component/_generated/component.d.ts +322 -0
  51. package/dist/component/_generated/component.d.ts.map +1 -1
  52. package/dist/component/costRecovery/claims.d.ts +68 -0
  53. package/dist/component/costRecovery/claims.d.ts.map +1 -0
  54. package/dist/component/costRecovery/claims.js +92 -0
  55. package/dist/component/costRecovery/claims.js.map +1 -0
  56. package/dist/component/costRecovery/completion.d.ts +39 -0
  57. package/dist/component/costRecovery/completion.d.ts.map +1 -0
  58. package/dist/component/costRecovery/completion.js +69 -0
  59. package/dist/component/costRecovery/completion.js.map +1 -0
  60. package/dist/component/costRecovery/helpers.d.ts +68 -0
  61. package/dist/component/costRecovery/helpers.d.ts.map +1 -0
  62. package/dist/component/costRecovery/helpers.js +71 -0
  63. package/dist/component/costRecovery/helpers.js.map +1 -0
  64. package/dist/component/costRecovery/mutations.d.ts +210 -0
  65. package/dist/component/costRecovery/mutations.d.ts.map +1 -0
  66. package/dist/component/costRecovery/mutations.js +93 -0
  67. package/dist/component/costRecovery/mutations.js.map +1 -0
  68. package/dist/component/costRecovery/queries.d.ts +108 -0
  69. package/dist/component/costRecovery/queries.d.ts.map +1 -0
  70. package/dist/component/costRecovery/queries.js +73 -0
  71. package/dist/component/costRecovery/queries.js.map +1 -0
  72. package/dist/component/costRecovery/reservations.d.ts +95 -0
  73. package/dist/component/costRecovery/reservations.d.ts.map +1 -0
  74. package/dist/component/costRecovery/reservations.js +134 -0
  75. package/dist/component/costRecovery/reservations.js.map +1 -0
  76. package/dist/component/costRecovery/schema.d.ts +382 -0
  77. package/dist/component/costRecovery/schema.d.ts.map +1 -0
  78. package/dist/component/costRecovery/schema.js +115 -0
  79. package/dist/component/costRecovery/schema.js.map +1 -0
  80. package/dist/component/schema.d.ts +135 -0
  81. package/dist/component/schema.js +5 -0
  82. package/dist/component/schema.js.map +1 -1
  83. package/dist/react/components/PriceCard.d.ts +3 -1
  84. package/dist/react/components/PriceCard.d.ts.map +1 -1
  85. package/dist/react/components/PriceCard.js +2 -2
  86. package/dist/react/components/PriceCard.js.map +1 -1
  87. package/dist/react/index.d.ts +1 -0
  88. package/dist/react/index.d.ts.map +1 -1
  89. package/dist/react/index.js +1 -0
  90. package/dist/react/index.js.map +1 -1
  91. package/dist/react/lib/price-helpers.d.ts +4 -6
  92. package/dist/react/lib/price-helpers.d.ts.map +1 -1
  93. package/dist/react/lib/price-helpers.js +16 -9
  94. package/dist/react/lib/price-helpers.js.map +1 -1
  95. package/dist/react/workflows/Collections.d.ts.map +1 -1
  96. package/dist/react/workflows/Collections.js +2 -1
  97. package/dist/react/workflows/Collections.js.map +1 -1
  98. package/dist/react/workflows/PlanPricingEditor.d.ts +4 -0
  99. package/dist/react/workflows/PlanPricingEditor.d.ts.map +1 -1
  100. package/dist/react/workflows/PlanPricingEditor.js +12 -9
  101. package/dist/react/workflows/PlanPricingEditor.js.map +1 -1
  102. package/dist/react/workflows/Refunds.d.ts +14 -3
  103. package/dist/react/workflows/Refunds.d.ts.map +1 -1
  104. package/dist/react/workflows/Refunds.js +58 -16
  105. package/dist/react/workflows/Refunds.js.map +1 -1
  106. package/dist/react/workflows/index.d.ts +3 -1
  107. package/dist/react/workflows/index.d.ts.map +1 -1
  108. package/dist/react/workflows/index.js +2 -1
  109. package/dist/react/workflows/index.js.map +1 -1
  110. package/dist/react/workflows/refund-preview.d.ts +16 -0
  111. package/dist/react/workflows/refund-preview.d.ts.map +1 -0
  112. package/dist/react/workflows/refund-preview.js +38 -0
  113. package/dist/react/workflows/refund-preview.js.map +1 -0
  114. package/docs/react-workflows.md +167 -1
  115. package/package.json +2 -2
  116. package/src/client/billing/balanceFunding.test.ts +257 -0
  117. package/src/client/billing/balanceFunding.ts +183 -0
  118. package/src/client/billing/balanceFundingTransport.test.ts +89 -0
  119. package/src/client/billing/invoicePreview.test.ts +123 -0
  120. package/src/client/billing/invoicePreview.ts +94 -0
  121. package/src/client/billing/invoicePreviewTransport.test.ts +70 -0
  122. package/src/client/charge-currency-format.ts +29 -0
  123. package/src/client/charge-currency.test.ts +113 -0
  124. package/src/client/charge-currency.ts +119 -0
  125. package/src/client/costRecovery-invoices.test.ts +401 -0
  126. package/src/client/costRecovery-invoices.transport.test.ts +107 -0
  127. package/src/client/costRecovery-invoices.ts +144 -0
  128. package/src/client/costRecovery.test.ts +76 -0
  129. package/src/client/costRecovery.ts +254 -0
  130. package/src/client/index.ts +76 -75
  131. package/src/client/products/prices.ts +13 -11
  132. package/src/client/products/products.ts +10 -10
  133. package/src/client/settlement/processingCosts.test.ts +349 -0
  134. package/src/client/settlement/processingCosts.ts +315 -0
  135. package/src/client/settlement/processingCostsTransport.test.ts +135 -0
  136. package/src/client/utils/webhookEndpoints.test.ts +5 -0
  137. package/src/client/utils/webhookEndpoints.ts +1 -0
  138. package/src/client/webhooks/hooks.test.ts +9 -0
  139. package/src/client/webhooks/payout-canceled.integration.test.ts +192 -0
  140. package/src/client/webhooks/processors.ts +1 -0
  141. package/src/component/_generated/api.ts +12 -0
  142. package/src/component/_generated/component.ts +407 -0
  143. package/src/component/cost-recovery.test.ts +344 -0
  144. package/src/component/costRecovery/claims.ts +119 -0
  145. package/src/component/costRecovery/completion.ts +92 -0
  146. package/src/component/costRecovery/helpers.ts +83 -0
  147. package/src/component/costRecovery/mutations.ts +115 -0
  148. package/src/component/costRecovery/queries.ts +88 -0
  149. package/src/component/costRecovery/reservations.ts +164 -0
  150. package/src/component/costRecovery/schema.ts +135 -0
  151. package/src/component/schema.ts +10 -0
  152. package/src/react/components/PayoutSchedule.render.test.tsx +15 -0
  153. package/src/react/components/PriceCard.render.test.tsx +37 -0
  154. package/src/react/components/PriceCard.tsx +9 -1
  155. package/src/react/index.test.ts +5 -0
  156. package/src/react/index.ts +7 -0
  157. package/src/react/lib/price-helpers.test.ts +15 -2
  158. package/src/react/lib/price-helpers.ts +25 -8
  159. package/src/react/workflows/Billing.test.tsx +1 -1
  160. package/src/react/workflows/Collections.test.tsx +36 -0
  161. package/src/react/workflows/Collections.tsx +3 -1
  162. package/src/react/workflows/PlanPricingEditor.currency.test.tsx +164 -0
  163. package/src/react/workflows/PlanPricingEditor.tsx +26 -10
  164. package/src/react/workflows/Refunds.currency.test.tsx +290 -0
  165. package/src/react/workflows/Refunds.tsx +115 -25
  166. package/src/react/workflows/index.ts +8 -1
  167. package/src/react/workflows/refund-preview.test.ts +65 -0
  168. package/src/react/workflows/refund-preview.ts +56 -0
package/CHANGELOG.md CHANGED
@@ -14,6 +14,36 @@
14
14
  - Remove internal tracker references from public documentation and add a
15
15
  regression check that prevents their reintroduction.
16
16
 
17
+ ## [0.13.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.12.0...v0.13.0) (2026-10-15)
18
+
19
+ ### Features
20
+
21
+ - **recovery:** reconcile seller cost invoice adjustments ([#159](https://github.com/FantasticDevHQ/better-stripe/issues/159)) ([d1ffdbc](https://github.com/FantasticDevHQ/better-stripe/commit/d1ffdbcb224b6c31ae958c576a4bcd6ff4a0a4dd))
22
+
23
+ ### Bug Fixes
24
+
25
+ - **example:** bind reversal inputs to original charge currency ([#160](https://github.com/FantasticDevHQ/better-stripe/issues/160)) ([6b86926](https://github.com/FantasticDevHQ/better-stripe/commit/6b86926c7d4a173babd2964d4eee086f61c48f63))
26
+ - **example:** reconcile unsettled disputed sources ([#161](https://github.com/FantasticDevHQ/better-stripe/issues/161)) ([9c732bd](https://github.com/FantasticDevHQ/better-stripe/commit/9c732bdc88555210d2172067e6bbf5d2fc513190))
27
+ - **react:** isolate refund preview amounts by currency and status ([#162](https://github.com/FantasticDevHQ/better-stripe/issues/162)) ([601d509](https://github.com/FantasticDevHQ/better-stripe/commit/601d50923dc98f212b29fd4e4e3d856dfffbda44))
28
+ - **react:** preserve original refund currency units ([#157](https://github.com/FantasticDevHQ/better-stripe/issues/157)) ([494827f](https://github.com/FantasticDevHQ/better-stripe/commit/494827f1cb35b0b0ca10b1c6da38a1095bb32500))
29
+
30
+ ## [0.12.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.11.0...v0.12.0) (2026-10-15)
31
+
32
+ ### Features
33
+
34
+ - **billing:** add consented balance funding operations ([#149](https://github.com/FantasticDevHQ/better-stripe/issues/149)) ([16434c4](https://github.com/FantasticDevHQ/better-stripe/commit/16434c4eeef343045b4ba66516221232dfa57e06))
35
+ - **billing:** expose complete upcoming invoice previews ([#147](https://github.com/FantasticDevHQ/better-stripe/issues/147)) ([6d81cec](https://github.com/FantasticDevHQ/better-stripe/commit/6d81cec1b458fadbe4aeb69b9611d6ba208d87ce))
36
+ - **recovery:** add durable cost claims and reservations ([#151](https://github.com/FantasticDevHQ/better-stripe/issues/151)) ([a958c3a](https://github.com/FantasticDevHQ/better-stripe/commit/a958c3aa12178de4f22c0c614b0b9e5d7eb37819))
37
+ - **settlement:** expose complete processing cost context ([#150](https://github.com/FantasticDevHQ/better-stripe/issues/150)) ([3933d6b](https://github.com/FantasticDevHQ/better-stripe/commit/3933d6bf3c54181e8f7c27b35e6eb6802fb7c2d8))
38
+
39
+ ### Bug Fixes
40
+
41
+ - **react:** preserve charge currency units in price cards ([#155](https://github.com/FantasticDevHQ/better-stripe/issues/155)) ([b0967ae](https://github.com/FantasticDevHQ/better-stripe/commit/b0967ae61ac8ae85adc4f2d8f375e2c71ad1464f))
42
+ - **react:** preserve plan pricing currency units ([#156](https://github.com/FantasticDevHQ/better-stripe/issues/156)) ([f65e712](https://github.com/FantasticDevHQ/better-stripe/commit/f65e712a91dd55e6979ccea540627d4c437dfc6c))
43
+ - **release:** keep generated notes public ([#153](https://github.com/FantasticDevHQ/better-stripe/issues/153)) ([d67f21a](https://github.com/FantasticDevHQ/better-stripe/commit/d67f21a6315c0498db7358b6b37669520037fefa))
44
+ - **release:** preserve historical code examples ([#154](https://github.com/FantasticDevHQ/better-stripe/issues/154)) ([1c3c9f0](https://github.com/FantasticDevHQ/better-stripe/commit/1c3c9f0a63e7c5eb77e685dc0bb3963a4e7dc935))
45
+ - **webhooks:** reconcile dedicated payout cancellations ([#152](https://github.com/FantasticDevHQ/better-stripe/issues/152)) ([9dd84e2](https://github.com/FantasticDevHQ/better-stripe/commit/9dd84e22473a35445a0445a82e8fcdf2eb9ace23))
46
+
17
47
  ## [0.11.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.10.2...v0.11.0) (2026-10-14)
18
48
 
19
49
  ### Features
package/README.md CHANGED
@@ -38,7 +38,7 @@ See the [customer preview setup and webhook migration guide](docs/accounts-v2-pr
38
38
 
39
39
  ## Status
40
40
 
41
- **Version**: `0.11.0` <!-- x-release-please-version --> (pre-1.0). API may change between minor versions until 1.0.
41
+ **Version**: `0.13.0` <!-- x-release-please-version --> (pre-1.0). API may change between minor versions until 1.0.
42
42
 
43
43
  Used in production by its authors. Webhook pipeline is covered by unit tests and a live E2E harness (`npm run e2e:webhooks`) that fires real Stripe-signed events and asserts the ledger.
44
44
 
@@ -335,6 +335,257 @@ Methods named `get<Entity>` take the component document ID (exception: `getInvoi
335
335
  | `reactivateSubscription(ctx, { stripeSubscriptionId })` | Reactivate a canceled subscription |
336
336
  | `updateSubscriptionQuantity(ctx, { stripeSubscriptionId, quantity })` | Update subscription seat quantity |
337
337
 
338
+ ### Durable recovery claims
339
+
340
+ `stripe.costRecovery` is a server-only recovery client backed by the Convex
341
+ component. Bookkeeping methods do not use Stripe credentials or move money.
342
+ Invoice methods use the configured Stripe client and must run in an action.
343
+ Your host authenticates the
344
+ caller, approves which costs are owed, authorizes the account and recovery scope,
345
+ and verifies provider outcomes before confirming collection. A recovery scope is
346
+ an application-defined liability boundary, such as one store. Amounts are signed
347
+ integer minor units and currencies never mix.
348
+
349
+ ```ts
350
+ const recovery = stripe.costRecovery;
351
+ const scope = {
352
+ accountId: sellerAccountId,
353
+ scopeKey: storeId,
354
+ currency: "usd",
355
+ };
356
+ await recovery.recordClaim(ctx, {
357
+ ...scope,
358
+ claimKey: `cost:${chargeId}`,
359
+ sourceChargeId: chargeId,
360
+ sourceGross: originalGross,
361
+ sourceProcessingFee: originalProviderFee,
362
+ amountDue: approvedCumulativeCost,
363
+ revision: durableObservationRevision,
364
+ });
365
+ const result = await recovery.reserve(ctx, {
366
+ ...scope,
367
+ groupKey: `settlement:${fundingChargeId}`,
368
+ operationKey: `settlement:${fundingChargeId}:batch:1`,
369
+ sourceKind: "settlement",
370
+ sourceId: fundingChargeId,
371
+ sourceGross: fundingGross,
372
+ sourceProcessingFee: fundingProviderFee,
373
+ maxDebit: availableSellerEarnings,
374
+ maxCredit: approvedCreditBudget,
375
+ });
376
+ ```
377
+
378
+ Claims retain their original charge, account, scope, currency, gross and provider
379
+ fee. Replays of the same revision must have the same amount; lower revisions are
380
+ ignored. The host must assign durable, monotonically increasing observation
381
+ revisions from authoritative reconciliation, not an unordered event arrival
382
+ counter. Use `getClaim(ctx, { claimKey })` to capture the prior revision before
383
+ reading provider state, then propose the next revision. On a conflicting revision,
384
+ retry the full provider reconciliation, rather than incrementing the revision of
385
+ an already-stale snapshot. Later provider observations update cumulative `amountDue`, including
386
+ signed credits, without erasing collected or reserved history. Claim keys beginning
387
+ with `rollback:` are reserved by the package.
388
+
389
+ Reservations use maintained totals across all claims, so a credit beyond the
390
+ first history page still reduces the recoverable amount. Each call allocates at
391
+ most 100 claims. Repeat with distinct, durable batch operation keys in the same
392
+ group until `seal(ctx, { groupKey })` succeeds. The group's debit/credit budgets
393
+ apply across all batches. A group identifies exactly one external money boundary;
394
+ only one group per account/scope/currency may remain pending. Retry the same batch
395
+ key to recover the same allocation after a timeout or host write failure.
396
+
397
+ Seal before any provider side effect. New claim observations can require another
398
+ batch before sealing; observations after sealing remain owed or credited for a
399
+ later group. After verifying the provider's final result, call
400
+ `finish(ctx, { groupKey, outcome: "collect" | "release" })`. Completion processes
401
+ at most ten reservations per call, each with at most 100 claims. Repeat while
402
+ `isDone` is false using separate action/job invocations when needed to respect
403
+ Convex transaction limits. Completion is idempotent; a collected group cannot be
404
+ released or reused with another outcome. Do not release an unknown provider
405
+ outcome just because a request timed out. An unpaid invoice must stay reserved
406
+ until its verified payment or cancellation policy resolves it.
407
+
408
+ For a collected settlement group whose funding sale is later returned, call
409
+ `rollback(ctx, { groupKey, returnedPrincipal, revision })` with the authorized
410
+ cumulative principal. This records a separate, source-linked rollback claim,
411
+ preserves the original costs and collection history, and rounds positive and
412
+ negative amounts symmetrically. Reinstated principal can reduce the rollback
413
+ again with a newer revision. This bookkeeping operation does not refund or
414
+ reverse a transfer. For invoice groups, use the verified funding adapter below;
415
+ do not invent invoice payment allocations.
416
+
417
+ Use `balance(ctx, scope)` for exact maintained totals and
418
+ `listClaims(ctx, { ...scope, limit, cursor })` for paginated history. `getClaim`, `getGroup` and
419
+ `getReservation` resume durable workflows. Integrate these server callbacks with
420
+ the existing unstyled React `CostRecovery` display. These bookkeeping APIs leave
421
+ consent, provider fee policy and entitlement changes in the host.
422
+
423
+ ### Invoice recovery adjustments
424
+
425
+ After authorizing the seller, invoice and liability scope, reserve with
426
+ `sourceKind: "invoice"` and `sourceId: invoiceId`. Allocate bounded batches and
427
+ seal the group before calling Stripe. Then attach exactly one signed adjustment
428
+ for the entire group:
429
+
430
+ ```ts
431
+ const receipt = await recovery.attachInvoice(ctx, { ...scope, groupKey });
432
+ // Invoke from your invoice lifecycle reconciliation, and repeat bounded completion
433
+ // until isDone after payment or release.
434
+ const result = await recovery.reconcileInvoice(ctx, {
435
+ ...scope,
436
+ groupKey,
437
+ releaseUncollectible: false,
438
+ });
439
+ ```
440
+
441
+ The invoice must belong to the authorized customer account, use the same currency,
442
+ and be draft before a new item is created. The package stores its stable group
443
+ identity before the provider call, uses that identity as the idempotency key and
444
+ item metadata, and persists the resulting item ID. Every retry scans all items
445
+ on that exact invoice, recovering a successful creation even if the subsequent
446
+ database write failed or the provider idempotency key expired. A bound item that
447
+ has been deleted, changed or duplicated requires reconciliation; the package
448
+ never silently recreates it. Negative adjustments are credits. Items are not
449
+ discountable.
450
+
451
+ Your host must durably serialize provider operations for each group, including
452
+ attachment and any calls that finish or release it. Never release a reservation
453
+ after a timeout or unknown provider outcome. Draft and open invoices retain their
454
+ reservations, blocking recovery from a second payout. A verified paid invoice
455
+ commits collection; a verified void releases the obligation for a future boundary.
456
+ Uncollectible invoices stay reserved unless your explicit host policy passes
457
+ `releaseUncollectible: true`. Do not use that policy while another process might
458
+ retry payment on the original invoice. Subscription cancellation alone does not
459
+ prove its invoices were voided. These methods never attempt card fallback or
460
+ change a payout schedule.
461
+
462
+ For a collected invoice fully funded by one PaymentIntent, call
463
+ `rollbackInvoice(ctx, { ...scope, groupKey, revision })`. It checks the paid
464
+ invoice, completely paginates its payments, verifies the funding charge and its
465
+ refund/dispute transactions, retains the original funding economics, and records
466
+ a separate proportional rollback claim. Use a durable revision captured before
467
+ provider reconciliation, and retry the full read on revision conflicts. Original
468
+ cost claims remain intact. Multiple payments, partial allocations, mixed customer
469
+ credits and non-PaymentIntent funding return
470
+ `{ status: "not_ready", reason: "invoice_funding_allocation_required" }` without
471
+ changing claims. Provider processing costs can also return their documented
472
+ not-ready reasons. Those cases need a host-approved allocation policy rather than
473
+ guessed amounts. Invoice integration in the example is a separate migration.
474
+
475
+ Provider methods preserve the configured API version and propagate Stripe errors.
476
+ See Stripe's [invoice item creation](https://docs.stripe.com/api/invoiceitems/create)
477
+ and [invoice item pagination](https://docs.stripe.com/api/invoiceitems/list).
478
+
479
+ ### Refund and dispute processing costs
480
+
481
+ `getProcessingCostContext(ctx, { paymentIntentId, stripeAccountId? })` reads
482
+ provider costs for an authorized payment, including refunded and disputed
483
+ charges. Authorize both the payment and optional connected-account namespace in
484
+ your host. The client preserves its configured API version and provider errors;
485
+ it does not settle a sale, move money, create debt or decide which costs a seller
486
+ owes. The example's cost adapter uses this method while keeping its billing policy
487
+ in the application.
488
+
489
+ A `ready` result contains every refund and dispute, source-linked balance
490
+ transactions, signed fee adjustments and provider amounts in integer minor units.
491
+ Dates are milliseconds. `processingFee` is the original charge balance
492
+ transaction's total provider-reported fee, which can include application fees on
493
+ direct charges. Each transaction exposes `feeDetails` so your approved policy can
494
+ distinguish processor, application and other fees. A null breakdown means provider
495
+ details were unavailable; the reported total remains authoritative. Negative fee
496
+ adjustments remain credits. Failed refunds include their failure reversal instead
497
+ of counting their principal as returned.
498
+
499
+ `principalReturned` caps the combined economic refund/dispute principal at the
500
+ original gross amount. Raw signed provider transactions remain intact, including
501
+ any overlapping debits, and are not a seller debt calculation. A won dispute with
502
+ an unreinstated principal withdrawal is `not_ready`. Pending refunds, missing cost
503
+ transactions or different settlement currencies also return an explicit
504
+ `not_ready` reason instead of estimated or mixed-currency totals. Provider
505
+ pagination errors throw rather than returning partial data. Retry delayed
506
+ provider data; resolve currency conversion through an explicit host policy.
507
+
508
+ See Stripe's [balance transaction fields](https://docs.stripe.com/api/balance_transactions/object)
509
+ and [refund failure reversals](https://docs.stripe.com/api/refunds/object).
510
+
511
+ ### Optional balance-funded subscriptions
512
+
513
+ Balance funding requires an eligible connected account and Stripe's Accounts v2
514
+ balance-payment preview. It is separate from non-Connect customer-only Accounts
515
+ v2 eligibility. Keep your normal client on GA; construct a separate client with
516
+ `stripeApiVersion: STRIPE_CUSTOMER_PREVIEW_API_VERSION` for this optional feature.
517
+ `getBalanceFundingCapability()` reports whether that client is explicitly
518
+ configured. It does not claim the account has provider approval; Stripe validates
519
+ eligibility when preparing the payment method.
520
+
521
+ After authenticating the seller, authorizing the subscription, and recording
522
+ explicit recurring-debit consent in your own database:
523
+
524
+ ```ts
525
+ const preparation = await balanceStripe.prepareBalanceFunding(ctx, {
526
+ customerAccount: authorizedSellerAccountId,
527
+ stripeSubscriptionId: authorizedSubscriptionId,
528
+ consent: true,
529
+ idempotencyKey: durablePreparationRequestId,
530
+ });
531
+ // Persist this immutable server-side record BEFORE enabling balance payments.
532
+ await savePreparationAndConsent(preparation);
533
+ await balanceStripe.setBalanceFunding(ctx, {
534
+ preparation,
535
+ enabled: true,
536
+ consent: true,
537
+ idempotencyKey: durableEnableRequestId,
538
+ });
539
+ // Opt-out restores the previous default method and payment-method types.
540
+ await balanceStripe.setBalanceFunding(ctx, {
541
+ preparation: await loadPersistedPreparation(),
542
+ enabled: false,
543
+ idempotencyKey: durableDisableRequestId,
544
+ });
545
+ ```
546
+
547
+ `prepareBalanceFunding` creates/attaches a `stripe_balance` method and captures
548
+ the prior subscription settings without changing them. `setBalanceFunding`
549
+ checks subscription ownership and payment-method ownership, then changes the
550
+ settings or recognizes that the requested state already exists. Null prior
551
+ settings restore inheritance from the customer's settings. External source
552
+ changes cause an error instead of being overwritten. Provider errors are
553
+ preserved. Never accept restoration records or account IDs from an unauthenticated
554
+ browser as authority.
555
+
556
+ Serialize changes per subscription in the host. Persist the operation identity
557
+ before the provider call, reuse it after a timeout or host write failure, and use
558
+ a new identity for each later enable/disable cycle. The example's durable pending
559
+ operation prevents an opposite change from racing an unfinished operation; retry
560
+ the pending change first. Preserve the preparation through retries. Keys must be
561
+ nonempty, contain no line breaks, and be at most 247 characters because the package
562
+ adds phase suffixes. Stripe's idempotency retention window still applies.
563
+
564
+ Only eligible processing earnings can fund these subscriptions; top-ups cannot.
565
+ Insufficient available balance in the invoice currency fails payment and does not
566
+ automatically charge a saved card. Handle `invoice.payment_failed` in your host
567
+ and offer an explicit alternate-method recovery flow. These operations neither
568
+ pay an overdue invoice nor change payout schedules. See Stripe's
569
+ [balance-payment eligibility, approval and recovery guidance](https://docs.stripe.com/payments/pay-with-balance).
570
+
571
+ ### Upcoming invoice preview
572
+
573
+ Authorize the customer account and subscription in your host action, then call
574
+ `stripe.getUpcomingInvoicePreview(ctx, { customerAccount, stripeSubscriptionId })`.
575
+ The method verifies that the subscription belongs to that account and uses your
576
+ configured Stripe API version. It retrieves every invoice line, returns integer
577
+ minor-unit amounts and millisecond timestamps, and preserves Stripe errors,
578
+ including unavailable previews. Failed pagination throws instead of returning a
579
+ partial estimate. `nextBillingAt` uses Stripe's next payment attempt, trial end,
580
+ or earliest subscription-item renewal date, in that order.
581
+
582
+ Pass the result through an authenticated callback to the React `InvoiceEstimate`
583
+ component. It displays the provider `total` without summing line amounts. Totals
584
+ can include discounts, tax or credits; `amountDue` can differ from `total` because
585
+ of customer balances. Previews are estimates, not payable invoices. Application
586
+ recovery costs and credits remain host-owned policy. See Stripe's
587
+ [invoice preview documentation](https://docs.stripe.com/api/invoices/create_preview).
588
+
338
589
  ### Invoice, Payment Method, and Payout
339
590
 
340
591
  | Method | Description |
@@ -344,6 +595,7 @@ Methods named `get<Entity>` take the component document ID (exception: `getInvoi
344
595
  | `listInvoices(ctx, { stripeAccountId?, userId?, subscriptionId?, status?, limit? })` | List invoices with optional filters |
345
596
  | `listInvoicesByUser(ctx, { userId })` | List all invoices for a user |
346
597
  | `getInvoiceFromStripe(ctx, { stripeInvoiceId })` | Fetch latest invoice data directly from Stripe |
598
+ | `getUpcomingInvoicePreview(ctx, { customerAccount, stripeSubscriptionId })` | Preview the next invoice with complete lines, provider totals and millisecond dates |
347
599
  | `listPaymentMethods(ctx, { stripeCustomerId, type? })` | List saved payment methods |
348
600
  | `attachPaymentMethod(ctx, { paymentMethodId, stripeCustomerId })` | Attach a payment method to a customer account |
349
601
  | `detachPaymentMethod(ctx, { paymentMethodId })` | Detach a payment method |
@@ -493,7 +745,8 @@ The component maintains a `webhookEvents` table that tracks every event by its S
493
745
  - `customer.subscription.created`, `.updated`, `.deleted`, `.trial_will_end`
494
746
  - `invoice.created`, `.finalized`, `.paid`, `.payment_failed`
495
747
  - `payment_intent.succeeded`, `.payment_failed`, `.canceled`
496
- - `payout.created`, `.updated`, `.paid`, `.failed`
748
+ - `payout.created`, `.updated`, `.paid`, `.failed`, `.canceled`
749
+
497
750
  - `refund.created`, `.updated`, `.failed`
498
751
  - `application_fee.refunded` -- keeps `feeCollectedAmount`/`feeRefundedAmount` accurate on the linked payment
499
752
  - `charge.dispute.created`, `.updated`, `.closed`, `.funds_withdrawn`, `.funds_reinstated`
@@ -502,6 +755,18 @@ The component maintains a `webhookEvents` table that tracks every event by its S
502
755
 
503
756
  Note: Subscription and invoice events use v1 Billing API event names even when the billing entity is a V2 account referenced via `customer_account`.
504
757
 
758
+ Dedicated payout cancellations use the same signed V1 endpoint, payout record
759
+ and connected-account association as other payout events. Generic `onEvent`
760
+ and `events["payout.canceled"]` callbacks run after reconciliation. Cancellation
761
+ does not invoke the paid/completed convenience hook. Normal verified replay is
762
+ deduplicated by the existing event ledger.
763
+
764
+ When upgrading from 0.11.0 or earlier, add `payout.canceled` to the existing V1
765
+ endpoint's selected events. New endpoints created with the corrected package's
766
+ defaults include it. Updating the library alone does not edit an existing provider
767
+ endpoint or recover already missed events. Consumers must adopt a published
768
+ version containing this correction; a merged source fix is not a package release.
769
+
505
770
  ## Trigger API
506
771
 
507
772
  The trigger system follows the BetterAuth pattern: define callbacks at client init time, export the `syncWebhook`/`asyncWebhook` pair via `webhookHandlers()`, and pass their function references to `registerRoutes` via `webhooks`.
@@ -0,0 +1,36 @@
1
+ import type Stripe from "stripe";
2
+ import { STRIPE_CUSTOMER_PREVIEW_API_VERSION } from "../apiVersion.js";
3
+ export type BalanceFundingCapability = {
4
+ configured: boolean;
5
+ requiredApiVersion: typeof STRIPE_CUSTOMER_PREVIEW_API_VERSION;
6
+ requiresConnectedAccount: true;
7
+ eligibility: "provider_validates_on_prepare";
8
+ };
9
+ export declare function getBalanceFundingCapability(version: string): BalanceFundingCapability;
10
+ export type BalanceFundingScope = {
11
+ customerAccount: string;
12
+ stripeSubscriptionId: string;
13
+ };
14
+ /** Persist server-side before applying; never accept this record from a browser. */
15
+ export type BalanceFundingPreparation = BalanceFundingScope & {
16
+ paymentMethodId: string;
17
+ previousPaymentMethodId: string | null;
18
+ previousPaymentMethodTypes: string[] | null;
19
+ };
20
+ export type PrepareBalanceFundingOptions = BalanceFundingScope & {
21
+ consent: true;
22
+ idempotencyKey: string;
23
+ };
24
+ export type SetBalanceFundingOptions = {
25
+ preparation: BalanceFundingPreparation;
26
+ enabled: boolean;
27
+ consent?: true;
28
+ idempotencyKey: string;
29
+ };
30
+ /** Capture restoration settings, provision and attach; does not enable billing. */
31
+ export declare function prepareBalanceFunding(stripe: Stripe, version: string, opts: PrepareBalanceFundingOptions): Promise<BalanceFundingPreparation>;
32
+ /** Apply a host-persisted preparation. Host serializes changes per subscription. */
33
+ export declare function setBalanceFunding(stripe: Stripe, version: string, opts: SetBalanceFundingOptions): Promise<{
34
+ enabled: boolean;
35
+ }>;
36
+ //# sourceMappingURL=balanceFunding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balanceFunding.d.ts","sourceRoot":"","sources":["../../../src/client/billing/balanceFunding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,mCAAmC,EAAE,MAAM,kBAAkB,CAAC;AAIvE,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,OAAO,mCAAmC,CAAC;IAC/D,wBAAwB,EAAE,IAAI,CAAC;IAC/B,WAAW,EAAE,+BAA+B,CAAC;CAC9C,CAAC;AACF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,GACd,wBAAwB,CAO1B;AACD,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AACF,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,GAAG;IAC5D,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,0BAA0B,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC7C,CAAC;AACF,MAAM,MAAM,4BAA4B,GAAG,mBAAmB,GAAG;IAC/D,OAAO,EAAE,IAAI,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,yBAAyB,CAAC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AA4BF,mFAAmF;AACnF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,4BAA4B,GACjC,OAAO,CAAC,yBAAyB,CAAC,CA0CpC;AAQD,oFAAoF;AACpF,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAsD/B"}
@@ -0,0 +1,107 @@
1
+ import { STRIPE_CUSTOMER_PREVIEW_API_VERSION } from "../apiVersion.js";
2
+ import { idempotencyOptions } from "../core/idempotency.js";
3
+ import { resolveOwnerAccount } from "../utils/owner.js";
4
+ export function getBalanceFundingCapability(version) {
5
+ return {
6
+ configured: version === STRIPE_CUSTOMER_PREVIEW_API_VERSION,
7
+ requiredApiVersion: STRIPE_CUSTOMER_PREVIEW_API_VERSION,
8
+ requiresConnectedAccount: true,
9
+ eligibility: "provider_validates_on_prepare",
10
+ };
11
+ }
12
+ function request(version, key, phase) {
13
+ if (!getBalanceFundingCapability(version).configured)
14
+ throw new Error("Balance funding requires explicit Stripe preview API configuration.");
15
+ if (typeof key !== "string")
16
+ throw new Error("An explicit idempotency key is required for balance funding.");
17
+ idempotencyOptions(key);
18
+ return idempotencyOptions(`${key}:${phase}`);
19
+ }
20
+ async function subscription(stripe, scope) {
21
+ if (!scope.customerAccount.startsWith("acct_") || !scope.stripeSubscriptionId)
22
+ throw new Error("A customer account and subscription are required.");
23
+ const sub = await stripe.subscriptions.retrieve(scope.stripeSubscriptionId);
24
+ if (resolveOwnerAccount(sub) !== scope.customerAccount)
25
+ throw new Error("Subscription does not belong to the customer account.");
26
+ if (!["active", "past_due", "trialing"].includes(sub.status))
27
+ throw new Error("This subscription cannot change payment sources.");
28
+ return sub;
29
+ }
30
+ function methodId(sub) {
31
+ const pm = sub.default_payment_method;
32
+ return typeof pm === "string" ? pm : (pm?.id ?? null);
33
+ }
34
+ /** Capture restoration settings, provision and attach; does not enable billing. */
35
+ export async function prepareBalanceFunding(stripe, version, opts) {
36
+ const createOptions = request(version, opts.idempotencyKey, "create");
37
+ const attachOptions = request(version, opts.idempotencyKey, "attach");
38
+ if (opts.consent !== true)
39
+ throw new Error("Explicit consent is required for balance funding.");
40
+ const sub = await subscription(stripe, opts);
41
+ const previous = methodId(sub);
42
+ const types = sub.payment_settings?.payment_method_types ?? null;
43
+ if (types?.includes("stripe_balance") ||
44
+ (previous &&
45
+ (await stripe.paymentMethods.retrieve(previous)).type ===
46
+ "stripe_balance"))
47
+ throw new Error("Balance funding is already configured; reuse the persisted restoration record.");
48
+ const pm = await stripe.paymentMethods.create({ type: "stripe_balance" }, createOptions);
49
+ if (pm.type !== "stripe_balance")
50
+ throw new Error("Stripe did not return a balance payment method.");
51
+ const current = await stripe.paymentMethods.retrieve(pm.id);
52
+ if (resolveOwnerAccount(current) === null) {
53
+ await stripe.paymentMethods.attach(pm.id, { customer_account: opts.customerAccount }, attachOptions);
54
+ }
55
+ else if (resolveOwnerAccount(current) !== opts.customerAccount) {
56
+ throw new Error("Balance payment method belongs to a different customer account.");
57
+ }
58
+ return {
59
+ customerAccount: opts.customerAccount,
60
+ stripeSubscriptionId: opts.stripeSubscriptionId,
61
+ paymentMethodId: pm.id,
62
+ previousPaymentMethodId: previous,
63
+ previousPaymentMethodTypes: types,
64
+ };
65
+ }
66
+ function sameTypes(a, b) {
67
+ return a === null || b === null
68
+ ? a === b
69
+ : a.length === b.length &&
70
+ [...a].sort().every((type, i) => type === [...b].sort()[i]);
71
+ }
72
+ /** Apply a host-persisted preparation. Host serializes changes per subscription. */
73
+ export async function setBalanceFunding(stripe, version, opts) {
74
+ if (typeof opts.enabled !== "boolean")
75
+ throw new Error("An explicit enabled state is required for balance funding.");
76
+ const requestOptions = request(version, opts.idempotencyKey, opts.enabled ? "enable" : "disable");
77
+ if (opts.enabled && opts.consent !== true)
78
+ throw new Error("Explicit consent is required for balance funding.");
79
+ const prep = opts.preparation;
80
+ const sub = await subscription(stripe, prep);
81
+ const pm = await stripe.paymentMethods.retrieve(prep.paymentMethodId);
82
+ if (pm.type !== "stripe_balance" ||
83
+ resolveOwnerAccount(pm) !== prep.customerAccount)
84
+ throw new Error("Balance payment method does not belong to the customer account.");
85
+ const original = methodId(sub) === prep.previousPaymentMethodId &&
86
+ sameTypes(sub.payment_settings?.payment_method_types ?? null, prep.previousPaymentMethodTypes);
87
+ const balance = methodId(sub) === prep.paymentMethodId &&
88
+ sameTypes(sub.payment_settings?.payment_method_types ?? null, [
89
+ "stripe_balance",
90
+ ]);
91
+ if (!original && !balance)
92
+ throw new Error("Subscription payment settings changed; reconcile before applying this restoration record.");
93
+ if ((opts.enabled && balance) || (!opts.enabled && original))
94
+ return { enabled: opts.enabled };
95
+ await stripe.subscriptions.update(sub.id, {
96
+ default_payment_method: opts.enabled
97
+ ? prep.paymentMethodId
98
+ : (prep.previousPaymentMethodId ?? ""),
99
+ payment_settings: {
100
+ payment_method_types: opts.enabled
101
+ ? ["stripe_balance"]
102
+ : (prep.previousPaymentMethodTypes ?? ""),
103
+ },
104
+ }, requestOptions);
105
+ return { enabled: opts.enabled };
106
+ }
107
+ //# sourceMappingURL=balanceFunding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balanceFunding.js","sourceRoot":"","sources":["../../../src/client/billing/balanceFunding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type Stripe from "stripe";
2
+ export type UpcomingInvoicePreviewOptions = {
3
+ /** Host must authorize this customer account before calling. */
4
+ customerAccount: string;
5
+ stripeSubscriptionId: string;
6
+ };
7
+ /** Provider amounts are integer minor units; all dates are milliseconds. */
8
+ export type UpcomingInvoicePreview = {
9
+ stripeInvoiceId: string;
10
+ currency: string;
11
+ total: number;
12
+ subtotal: number;
13
+ amountDue: number;
14
+ nextBillingAt: number;
15
+ periodStart: number;
16
+ periodEnd: number;
17
+ linesComplete: true;
18
+ lines: {
19
+ id: string;
20
+ description: string;
21
+ amount: number;
22
+ currency: string;
23
+ periodStart: number;
24
+ periodEnd: number;
25
+ }[];
26
+ };
27
+ export declare function getUpcomingInvoicePreview(stripe: Stripe, opts: UpcomingInvoicePreviewOptions): Promise<UpcomingInvoicePreview>;
28
+ //# sourceMappingURL=invoicePreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoicePreview.d.ts","sourceRoot":"","sources":["../../../src/client/billing/invoicePreview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,MAAM,MAAM,6BAA6B,GAAG;IAC1C,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,IAAI,CAAC;IACpB,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;KACnB,EAAE,CAAC;CACL,CAAC;AAQF,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,sBAAsB,CAAC,CAsDjC"}
@@ -0,0 +1,56 @@
1
+ import { resolveOwnerAccount } from "../utils/owner.js";
2
+ function integer(value) {
3
+ if (!Number.isSafeInteger(value))
4
+ throw new Error("Invoice preview amounts and dates must be safe integers.");
5
+ return value;
6
+ }
7
+ export async function getUpcomingInvoicePreview(stripe, opts) {
8
+ if (!opts.customerAccount.startsWith("acct_") || !opts.stripeSubscriptionId)
9
+ throw new Error("A customer account and subscription are required.");
10
+ const subscription = await stripe.subscriptions.retrieve(opts.stripeSubscriptionId);
11
+ if (resolveOwnerAccount(subscription) !== opts.customerAccount)
12
+ throw new Error("Subscription does not belong to the customer account.");
13
+ const preview = await stripe.invoices.createPreview({
14
+ customer_account: opts.customerAccount,
15
+ subscription: opts.stripeSubscriptionId,
16
+ });
17
+ const lines = [];
18
+ const append = (line) => {
19
+ if (line.currency !== preview.currency)
20
+ throw new Error("Invoice preview line currency does not match the invoice.");
21
+ lines.push({
22
+ id: line.id,
23
+ description: line.description ?? "Subscription",
24
+ amount: integer(line.amount),
25
+ currency: line.currency,
26
+ periodStart: integer(line.period.start * 1000),
27
+ periodEnd: integer(line.period.end * 1000),
28
+ });
29
+ };
30
+ if (preview.lines.has_more) {
31
+ for await (const line of stripe.invoices.listLineItems(preview.id, {
32
+ limit: 100,
33
+ }))
34
+ append(line);
35
+ }
36
+ else {
37
+ preview.lines.data.forEach(append);
38
+ }
39
+ const renewalDates = subscription.items.data.map((item) => item.current_period_end);
40
+ const nextBillingAt = preview.next_payment_attempt ??
41
+ (subscription.status === "trialing" ? subscription.trial_end : null) ??
42
+ Math.min(...renewalDates);
43
+ return {
44
+ stripeInvoiceId: preview.id,
45
+ currency: preview.currency,
46
+ total: integer(preview.total),
47
+ subtotal: integer(preview.subtotal),
48
+ amountDue: integer(preview.amount_due),
49
+ nextBillingAt: integer(nextBillingAt * 1000),
50
+ periodStart: integer(preview.period_start * 1000),
51
+ periodEnd: integer(preview.period_end * 1000),
52
+ linesComplete: true,
53
+ lines,
54
+ };
55
+ }
56
+ //# sourceMappingURL=invoicePreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoicePreview.js","sourceRoot":"","sources":["../../../src/client/billing/invoicePreview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Internal exact renderer. Callers establish units and amount eligibility. */
2
+ export declare function renderCurrencyUnits(amount: number, currency: string, apiDecimals: 0 | 2, displayDecimals: 0 | 2, locale: string): string;
3
+ //# sourceMappingURL=charge-currency-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charge-currency-format.d.ts","sourceRoot":"","sources":["../../src/client/charge-currency-format.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,CAAC,GAAG,CAAC,EAClB,eAAe,EAAE,CAAC,GAAG,CAAC,EACtB,MAAM,EAAE,MAAM,GACb,MAAM,CAqBR"}
@@ -0,0 +1,22 @@
1
+ /** Internal exact renderer. Callers establish units and amount eligibility. */
2
+ export function renderCurrencyUnits(amount, currency, apiDecimals, displayDecimals, locale) {
3
+ const scale = 10n ** BigInt(apiDecimals);
4
+ const units = BigInt(Math.abs(amount));
5
+ const whole = units / scale;
6
+ const signedWhole = amount < 0 ? (whole === 0n ? -0 : -whole) : whole;
7
+ const formatter = new Intl.NumberFormat(locale, {
8
+ style: "currency",
9
+ currency: currency.toUpperCase(),
10
+ minimumFractionDigits: displayDecimals,
11
+ maximumFractionDigits: displayDecimals,
12
+ });
13
+ const digits = new Intl.NumberFormat(locale, { useGrouping: false });
14
+ const fraction = (units % scale).toString().padStart(apiDecimals, "0");
15
+ return formatter
16
+ .formatToParts(signedWhole)
17
+ .map((part) => part.type === "fraction"
18
+ ? [...fraction].map((digit) => digits.format(Number(digit))).join("")
19
+ : part.value)
20
+ .join("");
21
+ }
22
+ //# sourceMappingURL=charge-currency-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charge-currency-format.js","sourceRoot":"","sources":["../../src/client/charge-currency-format.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,QAAgB,EAChB,WAAkB,EAClB,eAAsB,EACtB,MAAc;IAEd,MAAM,KAAK,GAAG,GAAG,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAC5B,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACtE,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;QAC9C,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;QAChC,qBAAqB,EAAE,eAAe;QACtC,qBAAqB,EAAE,eAAe;KACvC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACvE,OAAO,SAAS;SACb,aAAa,CAAC,WAAW,CAAC;SAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,IAAI,CAAC,IAAI,KAAK,UAAU;QACtB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,CAAC,CAAC,IAAI,CAAC,KAAK,CACf;SACA,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}