@fantastic.dev/better-stripe 0.11.0 → 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.
- package/CHANGELOG.md +17 -0
- package/README.md +211 -2
- package/dist/client/billing/balanceFunding.d.ts +36 -0
- package/dist/client/billing/balanceFunding.d.ts.map +1 -0
- package/dist/client/billing/balanceFunding.js +107 -0
- package/dist/client/billing/balanceFunding.js.map +1 -0
- package/dist/client/billing/invoicePreview.d.ts +28 -0
- package/dist/client/billing/invoicePreview.d.ts.map +1 -0
- package/dist/client/billing/invoicePreview.js +56 -0
- package/dist/client/billing/invoicePreview.js.map +1 -0
- package/dist/client/charge-currency-format.d.ts +3 -0
- package/dist/client/charge-currency-format.d.ts.map +1 -0
- package/dist/client/charge-currency-format.js +22 -0
- package/dist/client/charge-currency-format.js.map +1 -0
- package/dist/client/charge-currency.d.ts +14 -0
- package/dist/client/charge-currency.d.ts.map +1 -0
- package/dist/client/charge-currency.js +85 -0
- package/dist/client/charge-currency.js.map +1 -0
- package/dist/client/costRecovery.d.ts +115 -0
- package/dist/client/costRecovery.d.ts.map +1 -0
- package/dist/client/costRecovery.js +50 -0
- package/dist/client/costRecovery.js.map +1 -0
- package/dist/client/index.d.ts +28 -66
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +25 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/products/prices.d.ts +3 -2
- package/dist/client/products/prices.d.ts.map +1 -1
- package/dist/client/products/prices.js +0 -3
- package/dist/client/products/prices.js.map +1 -1
- package/dist/client/products/products.d.ts +3 -2
- package/dist/client/products/products.d.ts.map +1 -1
- package/dist/client/products/products.js +0 -3
- package/dist/client/products/products.js.map +1 -1
- package/dist/client/settlement/processingCosts.d.ts +56 -0
- package/dist/client/settlement/processingCosts.d.ts.map +1 -0
- package/dist/client/settlement/processingCosts.js +173 -0
- package/dist/client/settlement/processingCosts.js.map +1 -0
- package/dist/client/utils/webhookEndpoints.d.ts +1 -1
- package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
- package/dist/client/utils/webhookEndpoints.js +1 -0
- package/dist/client/utils/webhookEndpoints.js.map +1 -1
- package/dist/client/webhooks/processors.d.ts.map +1 -1
- package/dist/client/webhooks/processors.js +1 -0
- package/dist/client/webhooks/processors.js.map +1 -1
- package/dist/component/_generated/component.d.ts +244 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/costRecovery/claims.d.ts +68 -0
- package/dist/component/costRecovery/claims.d.ts.map +1 -0
- package/dist/component/costRecovery/claims.js +85 -0
- package/dist/component/costRecovery/claims.js.map +1 -0
- package/dist/component/costRecovery/completion.d.ts +33 -0
- package/dist/component/costRecovery/completion.d.ts.map +1 -0
- package/dist/component/costRecovery/completion.js +69 -0
- package/dist/component/costRecovery/completion.js.map +1 -0
- package/dist/component/costRecovery/helpers.d.ts +62 -0
- package/dist/component/costRecovery/helpers.d.ts.map +1 -0
- package/dist/component/costRecovery/helpers.js +71 -0
- package/dist/component/costRecovery/helpers.js.map +1 -0
- package/dist/component/costRecovery/mutations.d.ts +138 -0
- package/dist/component/costRecovery/mutations.d.ts.map +1 -0
- package/dist/component/costRecovery/mutations.js +36 -0
- package/dist/component/costRecovery/mutations.js.map +1 -0
- package/dist/component/costRecovery/queries.d.ts +102 -0
- package/dist/component/costRecovery/queries.d.ts.map +1 -0
- package/dist/component/costRecovery/queries.js +73 -0
- package/dist/component/costRecovery/queries.js.map +1 -0
- package/dist/component/costRecovery/reservations.d.ts +83 -0
- package/dist/component/costRecovery/reservations.d.ts.map +1 -0
- package/dist/component/costRecovery/reservations.js +134 -0
- package/dist/component/costRecovery/reservations.js.map +1 -0
- package/dist/component/costRecovery/schema.d.ts +340 -0
- package/dist/component/costRecovery/schema.d.ts.map +1 -0
- package/dist/component/costRecovery/schema.js +109 -0
- package/dist/component/costRecovery/schema.js.map +1 -0
- package/dist/component/schema.d.ts +119 -0
- package/dist/component/schema.js +5 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/react/components/PriceCard.d.ts +3 -1
- package/dist/react/components/PriceCard.d.ts.map +1 -1
- package/dist/react/components/PriceCard.js +2 -2
- package/dist/react/components/PriceCard.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/price-helpers.d.ts +4 -6
- package/dist/react/lib/price-helpers.d.ts.map +1 -1
- package/dist/react/lib/price-helpers.js +16 -9
- package/dist/react/lib/price-helpers.js.map +1 -1
- package/dist/react/workflows/PlanPricingEditor.d.ts +4 -0
- package/dist/react/workflows/PlanPricingEditor.d.ts.map +1 -1
- package/dist/react/workflows/PlanPricingEditor.js +12 -9
- package/dist/react/workflows/PlanPricingEditor.js.map +1 -1
- package/docs/react-workflows.md +91 -0
- package/package.json +2 -2
- package/src/client/billing/balanceFunding.test.ts +257 -0
- package/src/client/billing/balanceFunding.ts +183 -0
- package/src/client/billing/balanceFundingTransport.test.ts +89 -0
- package/src/client/billing/invoicePreview.test.ts +123 -0
- package/src/client/billing/invoicePreview.ts +94 -0
- package/src/client/billing/invoicePreviewTransport.test.ts +70 -0
- package/src/client/charge-currency-format.ts +29 -0
- package/src/client/charge-currency.test.ts +113 -0
- package/src/client/charge-currency.ts +119 -0
- package/src/client/costRecovery.test.ts +76 -0
- package/src/client/costRecovery.ts +149 -0
- package/src/client/index.ts +76 -75
- package/src/client/products/prices.ts +13 -11
- package/src/client/products/products.ts +10 -10
- package/src/client/settlement/processingCosts.test.ts +349 -0
- package/src/client/settlement/processingCosts.ts +315 -0
- package/src/client/settlement/processingCostsTransport.test.ts +135 -0
- package/src/client/utils/webhookEndpoints.test.ts +5 -0
- package/src/client/utils/webhookEndpoints.ts +1 -0
- package/src/client/webhooks/hooks.test.ts +9 -0
- package/src/client/webhooks/payout-canceled.integration.test.ts +192 -0
- package/src/client/webhooks/processors.ts +1 -0
- package/src/component/_generated/api.ts +12 -0
- package/src/component/_generated/component.ts +308 -0
- package/src/component/cost-recovery.test.ts +344 -0
- package/src/component/costRecovery/claims.ts +109 -0
- package/src/component/costRecovery/completion.ts +92 -0
- package/src/component/costRecovery/helpers.ts +83 -0
- package/src/component/costRecovery/mutations.ts +43 -0
- package/src/component/costRecovery/queries.ts +88 -0
- package/src/component/costRecovery/reservations.ts +164 -0
- package/src/component/costRecovery/schema.ts +127 -0
- package/src/component/schema.ts +10 -0
- package/src/react/components/PayoutSchedule.render.test.tsx +15 -0
- package/src/react/components/PriceCard.render.test.tsx +37 -0
- package/src/react/components/PriceCard.tsx +9 -1
- package/src/react/index.test.ts +5 -0
- package/src/react/index.ts +7 -0
- package/src/react/lib/price-helpers.test.ts +15 -2
- package/src/react/lib/price-helpers.ts +25 -8
- package/src/react/workflows/Billing.test.tsx +1 -1
- package/src/react/workflows/PlanPricingEditor.currency.test.tsx +164 -0
- package/src/react/workflows/PlanPricingEditor.tsx +26 -10
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,23 @@
|
|
|
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
|
+
|
|
17
34
|
## [0.11.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.10.2...v0.11.0) (2026-10-14)
|
|
18
35
|
|
|
19
36
|
### 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.
|
|
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`.
|
|
@@ -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"}
|