@fantastic.dev/better-stripe 0.10.2 → 0.12.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 (140) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +220 -4
  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.d.ts +115 -0
  20. package/dist/client/costRecovery.d.ts.map +1 -0
  21. package/dist/client/costRecovery.js +50 -0
  22. package/dist/client/costRecovery.js.map +1 -0
  23. package/dist/client/index.d.ts +28 -64
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +25 -0
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/products/prices.d.ts +4 -2
  28. package/dist/client/products/prices.d.ts.map +1 -1
  29. package/dist/client/products/prices.js +5 -4
  30. package/dist/client/products/prices.js.map +1 -1
  31. package/dist/client/products/products.d.ts +4 -2
  32. package/dist/client/products/products.d.ts.map +1 -1
  33. package/dist/client/products/products.js +7 -5
  34. package/dist/client/products/products.js.map +1 -1
  35. package/dist/client/settlement/processingCosts.d.ts +56 -0
  36. package/dist/client/settlement/processingCosts.d.ts.map +1 -0
  37. package/dist/client/settlement/processingCosts.js +173 -0
  38. package/dist/client/settlement/processingCosts.js.map +1 -0
  39. package/dist/client/utils/webhookEndpoints.d.ts +1 -1
  40. package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
  41. package/dist/client/utils/webhookEndpoints.js +1 -0
  42. package/dist/client/utils/webhookEndpoints.js.map +1 -1
  43. package/dist/client/webhooks/processors.d.ts.map +1 -1
  44. package/dist/client/webhooks/processors.js +1 -0
  45. package/dist/client/webhooks/processors.js.map +1 -1
  46. package/dist/component/_generated/component.d.ts +244 -0
  47. package/dist/component/_generated/component.d.ts.map +1 -1
  48. package/dist/component/costRecovery/claims.d.ts +68 -0
  49. package/dist/component/costRecovery/claims.d.ts.map +1 -0
  50. package/dist/component/costRecovery/claims.js +85 -0
  51. package/dist/component/costRecovery/claims.js.map +1 -0
  52. package/dist/component/costRecovery/completion.d.ts +33 -0
  53. package/dist/component/costRecovery/completion.d.ts.map +1 -0
  54. package/dist/component/costRecovery/completion.js +69 -0
  55. package/dist/component/costRecovery/completion.js.map +1 -0
  56. package/dist/component/costRecovery/helpers.d.ts +62 -0
  57. package/dist/component/costRecovery/helpers.d.ts.map +1 -0
  58. package/dist/component/costRecovery/helpers.js +71 -0
  59. package/dist/component/costRecovery/helpers.js.map +1 -0
  60. package/dist/component/costRecovery/mutations.d.ts +138 -0
  61. package/dist/component/costRecovery/mutations.d.ts.map +1 -0
  62. package/dist/component/costRecovery/mutations.js +36 -0
  63. package/dist/component/costRecovery/mutations.js.map +1 -0
  64. package/dist/component/costRecovery/queries.d.ts +102 -0
  65. package/dist/component/costRecovery/queries.d.ts.map +1 -0
  66. package/dist/component/costRecovery/queries.js +73 -0
  67. package/dist/component/costRecovery/queries.js.map +1 -0
  68. package/dist/component/costRecovery/reservations.d.ts +83 -0
  69. package/dist/component/costRecovery/reservations.d.ts.map +1 -0
  70. package/dist/component/costRecovery/reservations.js +134 -0
  71. package/dist/component/costRecovery/reservations.js.map +1 -0
  72. package/dist/component/costRecovery/schema.d.ts +340 -0
  73. package/dist/component/costRecovery/schema.d.ts.map +1 -0
  74. package/dist/component/costRecovery/schema.js +109 -0
  75. package/dist/component/costRecovery/schema.js.map +1 -0
  76. package/dist/component/schema.d.ts +119 -0
  77. package/dist/component/schema.js +5 -0
  78. package/dist/component/schema.js.map +1 -1
  79. package/dist/react/components/PriceCard.d.ts +3 -1
  80. package/dist/react/components/PriceCard.d.ts.map +1 -1
  81. package/dist/react/components/PriceCard.js +2 -2
  82. package/dist/react/components/PriceCard.js.map +1 -1
  83. package/dist/react/index.d.ts +1 -0
  84. package/dist/react/index.d.ts.map +1 -1
  85. package/dist/react/index.js +1 -0
  86. package/dist/react/index.js.map +1 -1
  87. package/dist/react/lib/price-helpers.d.ts +4 -6
  88. package/dist/react/lib/price-helpers.d.ts.map +1 -1
  89. package/dist/react/lib/price-helpers.js +16 -9
  90. package/dist/react/lib/price-helpers.js.map +1 -1
  91. package/dist/react/workflows/PlanPricingEditor.d.ts +4 -0
  92. package/dist/react/workflows/PlanPricingEditor.d.ts.map +1 -1
  93. package/dist/react/workflows/PlanPricingEditor.js +12 -9
  94. package/dist/react/workflows/PlanPricingEditor.js.map +1 -1
  95. package/docs/react-workflows.md +91 -0
  96. package/package.json +2 -2
  97. package/src/client/billing/balanceFunding.test.ts +257 -0
  98. package/src/client/billing/balanceFunding.ts +183 -0
  99. package/src/client/billing/balanceFundingTransport.test.ts +89 -0
  100. package/src/client/billing/invoicePreview.test.ts +123 -0
  101. package/src/client/billing/invoicePreview.ts +94 -0
  102. package/src/client/billing/invoicePreviewTransport.test.ts +70 -0
  103. package/src/client/charge-currency-format.ts +29 -0
  104. package/src/client/charge-currency.test.ts +113 -0
  105. package/src/client/charge-currency.ts +119 -0
  106. package/src/client/costRecovery.test.ts +76 -0
  107. package/src/client/costRecovery.ts +149 -0
  108. package/src/client/index.ts +76 -73
  109. package/src/client/products/catalog-idempotency.test.ts +116 -0
  110. package/src/client/products/prices.ts +18 -11
  111. package/src/client/products/products.ts +17 -11
  112. package/src/client/settlement/processingCosts.test.ts +349 -0
  113. package/src/client/settlement/processingCosts.ts +315 -0
  114. package/src/client/settlement/processingCostsTransport.test.ts +135 -0
  115. package/src/client/utils/webhookEndpoints.test.ts +5 -0
  116. package/src/client/utils/webhookEndpoints.ts +1 -0
  117. package/src/client/webhooks/hooks.test.ts +9 -0
  118. package/src/client/webhooks/payout-canceled.integration.test.ts +192 -0
  119. package/src/client/webhooks/processors.ts +1 -0
  120. package/src/component/_generated/api.ts +12 -0
  121. package/src/component/_generated/component.ts +308 -0
  122. package/src/component/cost-recovery.test.ts +344 -0
  123. package/src/component/costRecovery/claims.ts +109 -0
  124. package/src/component/costRecovery/completion.ts +92 -0
  125. package/src/component/costRecovery/helpers.ts +83 -0
  126. package/src/component/costRecovery/mutations.ts +43 -0
  127. package/src/component/costRecovery/queries.ts +88 -0
  128. package/src/component/costRecovery/reservations.ts +164 -0
  129. package/src/component/costRecovery/schema.ts +127 -0
  130. package/src/component/schema.ts +10 -0
  131. package/src/react/components/PayoutSchedule.render.test.tsx +15 -0
  132. package/src/react/components/PriceCard.render.test.tsx +37 -0
  133. package/src/react/components/PriceCard.tsx +9 -1
  134. package/src/react/index.test.ts +5 -0
  135. package/src/react/index.ts +7 -0
  136. package/src/react/lib/price-helpers.test.ts +15 -2
  137. package/src/react/lib/price-helpers.ts +25 -8
  138. package/src/react/workflows/Billing.test.tsx +1 -1
  139. package/src/react/workflows/PlanPricingEditor.currency.test.tsx +164 -0
  140. package/src/react/workflows/PlanPricingEditor.tsx +26 -10
package/CHANGELOG.md CHANGED
@@ -14,6 +14,29 @@
14
14
  - Remove internal tracker references from public documentation and add a
15
15
  regression check that prevents their reintroduction.
16
16
 
17
+ ## [0.12.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.11.0...v0.12.0) (2026-10-15)
18
+
19
+ ### Features
20
+
21
+ - **billing:** add consented balance funding operations ([#149](https://github.com/FantasticDevHQ/better-stripe/issues/149)) ([16434c4](https://github.com/FantasticDevHQ/better-stripe/commit/16434c4eeef343045b4ba66516221232dfa57e06))
22
+ - **billing:** expose complete upcoming invoice previews ([#147](https://github.com/FantasticDevHQ/better-stripe/issues/147)) ([6d81cec](https://github.com/FantasticDevHQ/better-stripe/commit/6d81cec1b458fadbe4aeb69b9611d6ba208d87ce))
23
+ - **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))
24
+ - **settlement:** expose complete processing cost context ([#150](https://github.com/FantasticDevHQ/better-stripe/issues/150)) ([3933d6b](https://github.com/FantasticDevHQ/better-stripe/commit/3933d6bf3c54181e8f7c27b35e6eb6802fb7c2d8))
25
+
26
+ ### Bug Fixes
27
+
28
+ - **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))
29
+ - **react:** preserve plan pricing currency units ([#156](https://github.com/FantasticDevHQ/better-stripe/issues/156)) ([f65e712](https://github.com/FantasticDevHQ/better-stripe/commit/f65e712a91dd55e6979ccea540627d4c437dfc6c))
30
+ - **release:** keep generated notes public ([#153](https://github.com/FantasticDevHQ/better-stripe/issues/153)) ([d67f21a](https://github.com/FantasticDevHQ/better-stripe/commit/d67f21a6315c0498db7358b6b37669520037fefa))
31
+ - **release:** preserve historical code examples ([#154](https://github.com/FantasticDevHQ/better-stripe/issues/154)) ([1c3c9f0](https://github.com/FantasticDevHQ/better-stripe/commit/1c3c9f0a63e7c5eb77e685dc0bb3963a4e7dc935))
32
+ - **webhooks:** reconcile dedicated payout cancellations ([#152](https://github.com/FantasticDevHQ/better-stripe/issues/152)) ([9dd84e2](https://github.com/FantasticDevHQ/better-stripe/commit/9dd84e22473a35445a0445a82e8fcdf2eb9ace23))
33
+
34
+ ## [0.11.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.10.2...v0.11.0) (2026-10-14)
35
+
36
+ ### Features
37
+
38
+ - add retry-safe catalog creation request keys ([#145](https://github.com/FantasticDevHQ/better-stripe/issues/145)) ([45b0d52](https://github.com/FantasticDevHQ/better-stripe/commit/45b0d52dfcb6577cf5df339721ed9eed34a83a1d))
39
+
17
40
  ## [0.10.2](https://github.com/FantasticDevHQ/better-stripe/compare/v0.10.1...v0.10.2) (2026-10-14)
18
41
 
19
42
  ### Bug Fixes
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.10.2` <!-- x-release-please-version --> (pre-1.0). API may change between minor versions until 1.0.
41
+ **Version**: `0.12.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,201 @@ 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 bookkeeping client. It uses the Convex
341
+ component. Its calls do not use Stripe credentials; normal component installation
342
+ configuration still applies. It never moves money. Your host authenticates the
343
+ caller, approves which costs are owed, authorizes the account and recovery scope,
344
+ and verifies provider outcomes before confirming collection. A recovery scope is
345
+ an application-defined liability boundary, such as one store. Amounts are signed
346
+ integer minor units and currencies never mix.
347
+
348
+ ```ts
349
+ const recovery = stripe.costRecovery;
350
+ const scope = {
351
+ accountId: sellerAccountId,
352
+ scopeKey: storeId,
353
+ currency: "usd",
354
+ };
355
+ await recovery.recordClaim(ctx, {
356
+ ...scope,
357
+ claimKey: `cost:${chargeId}`,
358
+ sourceChargeId: chargeId,
359
+ sourceGross: originalGross,
360
+ sourceProcessingFee: originalProviderFee,
361
+ amountDue: approvedCumulativeCost,
362
+ revision: durableObservationRevision,
363
+ });
364
+ const result = await recovery.reserve(ctx, {
365
+ ...scope,
366
+ groupKey: `settlement:${fundingChargeId}`,
367
+ operationKey: `settlement:${fundingChargeId}:batch:1`,
368
+ sourceKind: "settlement",
369
+ sourceId: fundingChargeId,
370
+ sourceGross: fundingGross,
371
+ sourceProcessingFee: fundingProviderFee,
372
+ maxDebit: availableSellerEarnings,
373
+ maxCredit: approvedCreditBudget,
374
+ });
375
+ ```
376
+
377
+ Claims retain their original charge, account, scope, currency, gross and provider
378
+ fee. Replays of the same revision must have the same amount; lower revisions are
379
+ ignored. The host must assign durable, monotonically increasing observation
380
+ revisions from authoritative reconciliation, not an unordered event arrival
381
+ counter. Use `getClaim(ctx, { claimKey })` to capture the prior revision before
382
+ reading provider state, then propose the next revision. On a conflicting revision,
383
+ retry the full provider reconciliation, rather than incrementing the revision of
384
+ an already-stale snapshot. Later provider observations update cumulative `amountDue`, including
385
+ signed credits, without erasing collected or reserved history. Claim keys beginning
386
+ with `rollback:` are reserved by the package.
387
+
388
+ Reservations use maintained totals across all claims, so a credit beyond the
389
+ first history page still reduces the recoverable amount. Each call allocates at
390
+ most 100 claims. Repeat with distinct, durable batch operation keys in the same
391
+ group until `seal(ctx, { groupKey })` succeeds. The group's debit/credit budgets
392
+ apply across all batches. A group identifies exactly one external money boundary;
393
+ only one group per account/scope/currency may remain pending. Retry the same batch
394
+ key to recover the same allocation after a timeout or host write failure.
395
+
396
+ Seal before any provider side effect. New claim observations can require another
397
+ batch before sealing; observations after sealing remain owed or credited for a
398
+ later group. After verifying the provider's final result, call
399
+ `finish(ctx, { groupKey, outcome: "collect" | "release" })`. Completion processes
400
+ at most ten reservations per call, each with at most 100 claims. Repeat while
401
+ `isDone` is false using separate action/job invocations when needed to respect
402
+ Convex transaction limits. Completion is idempotent; a collected group cannot be
403
+ released or reused with another outcome. Do not release an unknown provider
404
+ outcome just because a request timed out. An unpaid invoice must stay reserved
405
+ until its verified payment or cancellation policy resolves it.
406
+
407
+ For a collected settlement group whose funding sale is later returned, call
408
+ `rollback(ctx, { groupKey, returnedPrincipal, revision })` with the authorized
409
+ cumulative principal. This records a separate, source-linked rollback claim,
410
+ preserves the original costs and collection history, and rounds positive and
411
+ negative amounts symmetrically. Reinstated principal can reduce the rollback
412
+ again with a newer revision. This bookkeeping operation does not refund or
413
+ reverse a transfer. Invoice funding requires a provider-aware adapter; this
414
+ settlement rollback helper does not infer invoice payment allocations.
415
+
416
+ Use `balance(ctx, scope)` for exact maintained totals and
417
+ `listClaims(ctx, { ...scope, limit, cursor })` for paginated history. `getClaim`, `getGroup` and
418
+ `getReservation` resume durable workflows. Integrate these server callbacks with
419
+ the existing unstyled React `CostRecovery` display. These bookkeeping APIs leave
420
+ consent, provider fee policy and entitlement changes in the host; provider invoice
421
+ reconciliation and the example migration are separate work.
422
+
423
+ ### Refund and dispute processing costs
424
+
425
+ `getProcessingCostContext(ctx, { paymentIntentId, stripeAccountId? })` reads
426
+ provider costs for an authorized payment, including refunded and disputed
427
+ charges. Authorize both the payment and optional connected-account namespace in
428
+ your host. The client preserves its configured API version and provider errors;
429
+ it does not settle a sale, move money, create debt or decide which costs a seller
430
+ owes. The example's cost adapter uses this method while keeping its billing policy
431
+ in the application.
432
+
433
+ A `ready` result contains every refund and dispute, source-linked balance
434
+ transactions, signed fee adjustments and provider amounts in integer minor units.
435
+ Dates are milliseconds. `processingFee` is the original charge balance
436
+ transaction's total provider-reported fee, which can include application fees on
437
+ direct charges. Each transaction exposes `feeDetails` so your approved policy can
438
+ distinguish processor, application and other fees. A null breakdown means provider
439
+ details were unavailable; the reported total remains authoritative. Negative fee
440
+ adjustments remain credits. Failed refunds include their failure reversal instead
441
+ of counting their principal as returned.
442
+
443
+ `principalReturned` caps the combined economic refund/dispute principal at the
444
+ original gross amount. Raw signed provider transactions remain intact, including
445
+ any overlapping debits, and are not a seller debt calculation. A won dispute with
446
+ an unreinstated principal withdrawal is `not_ready`. Pending refunds, missing cost
447
+ transactions or different settlement currencies also return an explicit
448
+ `not_ready` reason instead of estimated or mixed-currency totals. Provider
449
+ pagination errors throw rather than returning partial data. Retry delayed
450
+ provider data; resolve currency conversion through an explicit host policy.
451
+
452
+ See Stripe's [balance transaction fields](https://docs.stripe.com/api/balance_transactions/object)
453
+ and [refund failure reversals](https://docs.stripe.com/api/refunds/object).
454
+
455
+ ### Optional balance-funded subscriptions
456
+
457
+ Balance funding requires an eligible connected account and Stripe's Accounts v2
458
+ balance-payment preview. It is separate from non-Connect customer-only Accounts
459
+ v2 eligibility. Keep your normal client on GA; construct a separate client with
460
+ `stripeApiVersion: STRIPE_CUSTOMER_PREVIEW_API_VERSION` for this optional feature.
461
+ `getBalanceFundingCapability()` reports whether that client is explicitly
462
+ configured. It does not claim the account has provider approval; Stripe validates
463
+ eligibility when preparing the payment method.
464
+
465
+ After authenticating the seller, authorizing the subscription, and recording
466
+ explicit recurring-debit consent in your own database:
467
+
468
+ ```ts
469
+ const preparation = await balanceStripe.prepareBalanceFunding(ctx, {
470
+ customerAccount: authorizedSellerAccountId,
471
+ stripeSubscriptionId: authorizedSubscriptionId,
472
+ consent: true,
473
+ idempotencyKey: durablePreparationRequestId,
474
+ });
475
+ // Persist this immutable server-side record BEFORE enabling balance payments.
476
+ await savePreparationAndConsent(preparation);
477
+ await balanceStripe.setBalanceFunding(ctx, {
478
+ preparation,
479
+ enabled: true,
480
+ consent: true,
481
+ idempotencyKey: durableEnableRequestId,
482
+ });
483
+ // Opt-out restores the previous default method and payment-method types.
484
+ await balanceStripe.setBalanceFunding(ctx, {
485
+ preparation: await loadPersistedPreparation(),
486
+ enabled: false,
487
+ idempotencyKey: durableDisableRequestId,
488
+ });
489
+ ```
490
+
491
+ `prepareBalanceFunding` creates/attaches a `stripe_balance` method and captures
492
+ the prior subscription settings without changing them. `setBalanceFunding`
493
+ checks subscription ownership and payment-method ownership, then changes the
494
+ settings or recognizes that the requested state already exists. Null prior
495
+ settings restore inheritance from the customer's settings. External source
496
+ changes cause an error instead of being overwritten. Provider errors are
497
+ preserved. Never accept restoration records or account IDs from an unauthenticated
498
+ browser as authority.
499
+
500
+ Serialize changes per subscription in the host. Persist the operation identity
501
+ before the provider call, reuse it after a timeout or host write failure, and use
502
+ a new identity for each later enable/disable cycle. The example's durable pending
503
+ operation prevents an opposite change from racing an unfinished operation; retry
504
+ the pending change first. Preserve the preparation through retries. Keys must be
505
+ nonempty, contain no line breaks, and be at most 247 characters because the package
506
+ adds phase suffixes. Stripe's idempotency retention window still applies.
507
+
508
+ Only eligible processing earnings can fund these subscriptions; top-ups cannot.
509
+ Insufficient available balance in the invoice currency fails payment and does not
510
+ automatically charge a saved card. Handle `invoice.payment_failed` in your host
511
+ and offer an explicit alternate-method recovery flow. These operations neither
512
+ pay an overdue invoice nor change payout schedules. See Stripe's
513
+ [balance-payment eligibility, approval and recovery guidance](https://docs.stripe.com/payments/pay-with-balance).
514
+
515
+ ### Upcoming invoice preview
516
+
517
+ Authorize the customer account and subscription in your host action, then call
518
+ `stripe.getUpcomingInvoicePreview(ctx, { customerAccount, stripeSubscriptionId })`.
519
+ The method verifies that the subscription belongs to that account and uses your
520
+ configured Stripe API version. It retrieves every invoice line, returns integer
521
+ minor-unit amounts and millisecond timestamps, and preserves Stripe errors,
522
+ including unavailable previews. Failed pagination throws instead of returning a
523
+ partial estimate. `nextBillingAt` uses Stripe's next payment attempt, trial end,
524
+ or earliest subscription-item renewal date, in that order.
525
+
526
+ Pass the result through an authenticated callback to the React `InvoiceEstimate`
527
+ component. It displays the provider `total` without summing line amounts. Totals
528
+ can include discounts, tax or credits; `amountDue` can differ from `total` because
529
+ of customer balances. Previews are estimates, not payable invoices. Application
530
+ recovery costs and credits remain host-owned policy. See Stripe's
531
+ [invoice preview documentation](https://docs.stripe.com/api/invoices/create_preview).
532
+
338
533
  ### Invoice, Payment Method, and Payout
339
534
 
340
535
  | Method | Description |
@@ -344,6 +539,7 @@ Methods named `get<Entity>` take the component document ID (exception: `getInvoi
344
539
  | `listInvoices(ctx, { stripeAccountId?, userId?, subscriptionId?, status?, limit? })` | List invoices with optional filters |
345
540
  | `listInvoicesByUser(ctx, { userId })` | List all invoices for a user |
346
541
  | `getInvoiceFromStripe(ctx, { stripeInvoiceId })` | Fetch latest invoice data directly from Stripe |
542
+ | `getUpcomingInvoicePreview(ctx, { customerAccount, stripeSubscriptionId })` | Preview the next invoice with complete lines, provider totals and millisecond dates |
347
543
  | `listPaymentMethods(ctx, { stripeCustomerId, type? })` | List saved payment methods |
348
544
  | `attachPaymentMethod(ctx, { paymentMethodId, stripeCustomerId })` | Attach a payment method to a customer account |
349
545
  | `detachPaymentMethod(ctx, { paymentMethodId })` | Detach a payment method |
@@ -493,7 +689,8 @@ The component maintains a `webhookEvents` table that tracks every event by its S
493
689
  - `customer.subscription.created`, `.updated`, `.deleted`, `.trial_will_end`
494
690
  - `invoice.created`, `.finalized`, `.paid`, `.payment_failed`
495
691
  - `payment_intent.succeeded`, `.payment_failed`, `.canceled`
496
- - `payout.created`, `.updated`, `.paid`, `.failed`
692
+ - `payout.created`, `.updated`, `.paid`, `.failed`, `.canceled`
693
+
497
694
  - `refund.created`, `.updated`, `.failed`
498
695
  - `application_fee.refunded` -- keeps `feeCollectedAmount`/`feeRefundedAmount` accurate on the linked payment
499
696
  - `charge.dispute.created`, `.updated`, `.closed`, `.funds_withdrawn`, `.funds_reinstated`
@@ -502,6 +699,18 @@ The component maintains a `webhookEvents` table that tracks every event by its S
502
699
 
503
700
  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
701
 
702
+ Dedicated payout cancellations use the same signed V1 endpoint, payout record
703
+ and connected-account association as other payout events. Generic `onEvent`
704
+ and `events["payout.canceled"]` callbacks run after reconciliation. Cancellation
705
+ does not invoke the paid/completed convenience hook. Normal verified replay is
706
+ deduplicated by the existing event ledger.
707
+
708
+ When upgrading from 0.11.0 or earlier, add `payout.canceled` to the existing V1
709
+ endpoint's selected events. New endpoints created with the corrected package's
710
+ defaults include it. Updating the library alone does not edit an existing provider
711
+ endpoint or recover already missed events. Consumers must adopt a published
712
+ version containing this correction; a merged source fix is not a package release.
713
+
505
714
  ## Trigger API
506
715
 
507
716
  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`.
@@ -1234,9 +1443,16 @@ terms, and recoverable execution through the component's existing transfer ledge
1234
1443
  explicitly bounded upcoming payout data. See [settlement and payout contracts](docs/settlement.md)
1235
1444
  for authorization, recovery, migration, and provider limitations.
1236
1445
 
1237
- Account creation (`createAccount`, `getOrCreateAccount`, `ensureCustomerAccount`)
1238
- and `createCheckoutSession` accept an optional `idempotencyKey`. Use one stable
1446
+ Account creation (`createAccount`, `getOrCreateAccount`, `ensureCustomerAccount`),
1447
+ `createCheckoutSession`, `createProduct`, and `createPrice` accept an optional `idempotencyKey`. Use one stable
1239
1448
  account-creation key per logical account and a distinct key per checkout order.
1449
+ For catalog edits, persist a request key for the logical product or immutable
1450
+ price version. Retry identical parameters with the same key after a provider or
1451
+ component persistence failure. Use a new key when the price amount or other
1452
+ creation parameters change. Creating a new Price does not migrate existing
1453
+ subscriptions; they retain their prior price until the host explicitly changes
1454
+ them. Never reuse a version key for different parameters.
1455
+
1240
1456
  React `Checkout` preserves its request ID through retries; your server must
1241
1457
  forward that identity as the SDK request option. Persist it in the host order
1242
1458
  for recovery across reloads. Omission retains the previous SDK behavior.
@@ -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"}
@@ -0,0 +1,14 @@
1
+ export type ChargeCurrencyMetadata = {
2
+ currency: string;
3
+ apiDecimals: 0 | 2;
4
+ displayDecimals: 0 | 2;
5
+ };
6
+ /** Metadata does not establish account/country/payment-method support. */
7
+ export declare function getChargeCurrencyMetadata(currency: string): ChargeCurrencyMetadata | null;
8
+ /** Parse a nonnegative ASCII decimal exactly. Invalid input is never rounded. */
9
+ export declare function parseChargeAmount(value: string, currency: string): number | null;
10
+ /** Exact ASCII decimal for charge inputs; invalid source units stay unavailable. */
11
+ export declare function chargeAmountToDecimal(amount: number, currency: string): string | null;
12
+ /** Format signed integer charge units without floating-point division. */
13
+ export declare function formatChargeAmount(amount: number, currency: string, locale?: string): string;
14
+ //# sourceMappingURL=charge-currency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charge-currency.d.ts","sourceRoot":"","sources":["../../src/client/charge-currency.ts"],"names":[],"mappings":"AAuCA,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;IACnB,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC;CACxB,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,GACf,sBAAsB,GAAG,IAAI,CAU/B;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CASf;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAef;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,SAAU,GACf,MAAM,CAiBR"}