@happyvertical/smrt-commerce 0.51.22 → 0.51.24

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 (42) hide show
  1. package/AGENTS.md +47 -0
  2. package/README.md +104 -0
  3. package/dist/billing/credits.d.ts +33 -0
  4. package/dist/billing/credits.d.ts.map +1 -0
  5. package/dist/billing/events.d.ts +26 -0
  6. package/dist/billing/events.d.ts.map +1 -0
  7. package/dist/billing/index.d.ts +13 -0
  8. package/dist/billing/index.d.ts.map +1 -0
  9. package/dist/billing/jobs.d.ts +31 -0
  10. package/dist/billing/jobs.d.ts.map +1 -0
  11. package/dist/billing/period-close.d.ts +41 -0
  12. package/dist/billing/period-close.d.ts.map +1 -0
  13. package/dist/billing/provider.d.ts +136 -0
  14. package/dist/billing/provider.d.ts.map +1 -0
  15. package/dist/billing/runtime.d.ts +168 -0
  16. package/dist/billing/runtime.d.ts.map +1 -0
  17. package/dist/billing/stripe.d.ts +12 -0
  18. package/dist/billing/stripe.d.ts.map +1 -0
  19. package/dist/billing/units.d.ts +31 -0
  20. package/dist/billing/units.d.ts.map +1 -0
  21. package/dist/chunks/types-CRnln577.js.map +1 -1
  22. package/dist/chunks/units--tTn2ps8.js +52 -0
  23. package/dist/chunks/units--tTn2ps8.js.map +1 -0
  24. package/dist/collections/InvoiceLineItemCollection.d.ts +2 -1
  25. package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4258 -2584
  29. package/dist/index.js.map +1 -1
  30. package/dist/manifest.json +1397 -6
  31. package/dist/models/Invoice.d.ts +13 -2
  32. package/dist/models/Invoice.d.ts.map +1 -1
  33. package/dist/models/InvoiceLineItem.d.ts +6 -2
  34. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  35. package/dist/models/billing.d.ts +117 -0
  36. package/dist/models/billing.d.ts.map +1 -0
  37. package/dist/models/index.d.ts +1 -0
  38. package/dist/models/index.d.ts.map +1 -1
  39. package/dist/smrt-knowledge.json +529 -9
  40. package/dist/types/index.d.ts +3 -1
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/package.json +10 -7
package/AGENTS.md CHANGED
@@ -23,6 +23,52 @@ E-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, pay
23
23
 
24
24
  `@happyvertical/smrt-ledgers` is a regular dependency, loaded lazily via dynamic `import()` so the coupling stays runtime-only (no hard static import; the package graph stays a DAG — see #1582). Invoice stores `arJournalId` and `revenueJournalId` as string references. `recognizeRevenue()` creates a balanced AR journal entry; `getArJournal()` returns null when no journal has been recognized yet.
25
25
 
26
+ ## Billing-period close (#3060)
27
+
28
+ `src/billing/` closes periods from smrt-subscriptions charges; see the README
29
+ for usage.
30
+
31
+ - **Port, not provider.** Period close, events, and credit checkout call only
32
+ the `BillingProvider` port (integer minor units). `stripe.ts` adapts
33
+ `@happyvertical/accounting`, which takes currency *major* units; convert with
34
+ `units.ts` (ISO 4217 exponent) and never hand-roll a Stripe call here.
35
+ `sendAndReconcile` refuses a provider invoice whose subtotal or total
36
+ disagrees with the local invoice.
37
+ - **Replay safety is identity.** Close, invoice, line, payment, allocation,
38
+ and claim ids are `deterministicId()`s; `BillingLineSource` (conflict key
39
+ `source_type, source_id`) is the only double-billing guard, so never bill a
40
+ source without claiming it, and never widen that key. Each close step is
41
+ persisted before the next; `acquireCloseLease()` serializes workers. A
42
+ close that nets to a credit keeps its claims and ends `carried_forward`;
43
+ its subtotal is a `credit_carry_forward` source for the payer's next billed
44
+ close, which marks the carried close `completed`.
45
+ - **System tables.** `BillingAccount`, `BillingPeriodClose`, and
46
+ `BillingLineSource` are not tenant-scoped and have no generated surface.
47
+ Collections stay unexported; models are root exports (#3082). Invoices,
48
+ payments, and journals are written in the seller's tenant; payer-owned
49
+ subscriptions and credit grants in a system context, only after
50
+ verification.
51
+ - **Events** use smrt-jobs' delivery inbox under provider
52
+ `<name>-billing:<sellerTenantId>` and a provider-filtered
53
+ `ForgeProjectionRuntime`, so a runtime never claims another seller's
54
+ events; any other runtime sharing the inbox must pass `providers` too. Only
55
+ events this package acts on are stored: foreign checkouts are `ignored`,
56
+ and only `smrt_*` checkout metadata is kept; it is
57
+ HMAC-signed with the webhook secret at creation (empty values are omitted:
58
+ Stripe drops them), so another integration on the same account cannot forge
59
+ a credit purchase; credit is granted only when the collected total equals
60
+ it. Rotating the webhook secret strands in-flight checkouts (up to 24 h). `observe()` re-reads provider state, so
61
+ ordering never depends on delivery order. The standing hook runs inside the
62
+ event transaction and must be idempotent.
63
+ - **`Invoice.providerTaxAmount`** is added to line-item tax; it is
64
+ server-managed (not API-writable). `toAccountingInput()` emits major units.
65
+ - **Known limits** (each tracked upstream): provider customer creation is not
66
+ idempotent (happyvertical/sdk#1268); credit checkouts are untaxed and
67
+ two-decimal only (sdk#1269); `autoTopUp` cannot charge a saved card
68
+ (sdk#1270); Stripe line discounts and uncollectible status are worked around
69
+ as negative lines and event types (sdk#1271). `RetailCharge` has no
70
+ adjustment ledger. Flat plans are monthly, in arrears, without proration.
71
+
26
72
  ## Cross-Package References
27
73
 
28
74
  - `customerId` → `@foreignKey('Customer')` (hard reference within package)
@@ -31,6 +77,7 @@ E-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, pay
31
77
  - `skuId` (on `PaymentIntent`, `LicenseSale`) → plain string to smrt-products
32
78
  - `paymentId` (on `PaymentIntent`, `Payout`, `LicenseSale`) → plain string to `Payment` (same package, but kept as plain string for cross-model consistency)
33
79
  - `vendorId` on `Payout` → `@foreignKey(Vendor)` (hard reference within package)
80
+ - Billing records reference tenants with `@crossPackageRef('@happyvertical/smrt-users:Tenant')` and charges, plan periods, and adjustments by plain `sourceId`
34
81
 
35
82
  ## Gotchas
36
83
 
package/README.md CHANGED
@@ -99,6 +99,9 @@ await payment.recordPayment({
99
99
  | `PaymentAllocation` | Payment-to-invoice allocation |
100
100
  | `Fulfillment` | Shipment/delivery tracking with carrier and address |
101
101
  | `FulfillmentLineItem` | Individual fulfillment line item |
102
+ | `BillingAccount` | A payer's account with a seller: customer, provider customer, terms, standing |
103
+ | `BillingPeriodClose` | One payer's invoice for one period and currency; also the billing job target |
104
+ | `BillingLineSource` | The claim that a charge or plan period is billed on exactly one close |
102
105
 
103
106
  ### Collections
104
107
 
@@ -138,10 +141,111 @@ Invoice and Payment integrate with `@happyvertical/smrt-ledgers` via dynamic imp
138
141
 
139
142
  Customer and Vendor link to `@happyvertical/smrt-profiles` via plain `profileId` string. Invoice and Payment reference `@happyvertical/smrt-ledgers` journals via plain string IDs (`arJournalId`, `revenueJournalId`, `journalId`). All models use `@TenantScoped({ mode: 'optional' })` with nullable `tenantId`.
140
143
 
144
+ ## Billing-period close
145
+
146
+ `BillingRuntime` turns approved `@happyvertical/smrt-subscriptions` charges into
147
+ invoices for each billing owner, pushes them to a payment provider with
148
+ provider-calculated tax, posts revenue to the ledger, and applies the
149
+ provider's webhook events. Every provider call goes through
150
+ `@happyvertical/accounting`; `createStripeBillingProvider()` adapts its Stripe
151
+ provider to the `BillingProvider` port. Money crossing the port is integer
152
+ minor units.
153
+
154
+ ```ts
155
+ import { getAccountingProvider } from '@happyvertical/accounting';
156
+ import {
157
+ BillingRuntime,
158
+ createStripeBillingProvider,
159
+ enqueueBillingPeriodClose,
160
+ registerBillingRuntime,
161
+ } from '@happyvertical/smrt-commerce';
162
+
163
+ const stripe = await getAccountingProvider({ type: 'stripe', secretKey });
164
+ const billing = await BillingRuntime.create({
165
+ db,
166
+ sellerTenantId: platformTenantId,
167
+ kind: 'provider', // or 'reseller' to bill a reseller's children
168
+ provider: createStripeBillingProvider({ stripe, webhookSecret }),
169
+ billingRelationships, // smrt-tenancy BillingRelationshipService
170
+ ledger: {
171
+ arAccountId,
172
+ revenueAccountId,
173
+ taxAccountId,
174
+ cashAccountId,
175
+ prepaidCreditAccountId,
176
+ },
177
+ onPayerStanding: async ({ payerTenantId, standing, db }) => {
178
+ // suspend or reinstate the payer; runs in the event transaction
179
+ },
180
+ });
181
+
182
+ // One account per payer: its customer, tax location, and terms.
183
+ await billing.upsertAccount({
184
+ payerTenantId,
185
+ name: 'Network Co',
186
+ email: 'billing@example.test',
187
+ billingAddress: { country: 'CA', postalCode: 'T0L 0A0' },
188
+ });
189
+
190
+ // Close last month (default) — safe to run on every schedule tick.
191
+ registerBillingRuntime('platform', billing);
192
+ await enqueueBillingPeriodClose({ runtime: 'platform' });
193
+
194
+ // Webhook route: verify, enqueue, respond 2xx.
195
+ const intake = await billing.acceptWebhook(
196
+ rawBody,
197
+ request.headers.get('stripe-signature'),
198
+ );
199
+ if (intake.type?.endsWith(':unverified_credit_purchase')) {
200
+ // alert: a paid checkout claimed to buy credit but failed verification
201
+ }
202
+ await billing.processEvents(); // or enqueueBillingEvents({ runtime: 'platform' })
203
+
204
+ // Prepaid credit through provider checkout.
205
+ const { url } = await billing.createCreditCheckout({
206
+ spendingPolicyId,
207
+ amount: 5000,
208
+ purchaseId: cartId,
209
+ successUrl,
210
+ cancelUrl,
211
+ });
212
+ ```
213
+
214
+ - **What is billed.** A `provider` runtime bills `ClientCharge`s (and their
215
+ `BillingAdjustment`s) to the payer each charge names, plus monthly flat plans
216
+ it owns, to each subscriber's billing owner. A `reseller` runtime bills the
217
+ `RetailCharge`s its children owe it plus its own flat plans. Charges approved
218
+ before the period end are billed once, whatever period that is; flat plans
219
+ are billed in arrears for a closed calendar month (trials and
220
+ provider-managed subscriptions are skipped). One invoice per payer and
221
+ currency carries both kinds of lines.
222
+ - **Replay safety.** Close, invoice, and line ids are derived from the payer,
223
+ currency, and period; each charge is claimed by exactly one close; the
224
+ provider invoice uses the close id as its idempotency key; and every step is
225
+ persisted before the next, so a retry resumes where it stopped. A lease stops
226
+ two workers advancing the same close. A payer whose charges net to a credit
227
+ gets no invoice; the credit is carried to its next invoice.
228
+ - **Tax** comes from the provider (Stripe Tax) using the account customer's
229
+ `defaultBillingAddress`; the invoice records it as `providerTaxAmount`.
230
+ - **Events** are verified, stored in smrt-jobs' durable delivery inbox, and
231
+ applied against current provider state: paid invoices record a payment and
232
+ allocation; failures and overdue notices mark the payer `past_due` and its
233
+ flat-plan subscriptions `past_due`; payment reinstates them. Dunning is the
234
+ provider's.
235
+ - **Prepaid credit** purchases credit a `period: 'balance'` spending policy
236
+ once per checkout session, paid by the policy's tenant or, for a delegated
237
+ balance, the parent that set it.
238
+
239
+ See [`AGENTS.md`](./AGENTS.md#billing-period-close-3060) for invariants and
240
+ known limits.
241
+
141
242
  ## Dependencies
142
243
 
143
244
  - `@happyvertical/smrt-core` -- ORM and code generation
144
245
  - `@happyvertical/smrt-tenancy` -- multi-tenant scoping
246
+ - `@happyvertical/smrt-subscriptions` -- charges, plans, and credit balances billed by period close
247
+ - `@happyvertical/smrt-jobs` -- period-close jobs and the provider event inbox
248
+ - `@happyvertical/accounting` -- payment-provider (Stripe) calls
145
249
  - `@happyvertical/smrt-types` -- shared type definitions
146
250
  - Peer: `@happyvertical/smrt-ledgers`, `@happyvertical/smrt-profiles`, `@happyvertical/smrt-svelte`
147
251
 
@@ -0,0 +1,33 @@
1
+ import { BillingProviderCheckoutSession } from './provider.js';
2
+ import { BillingRuntime } from './runtime.js';
3
+ export declare const CREDIT_PURCHASE_PURPOSE = "credit_purchase";
4
+ export interface CreateCreditCheckoutInput {
5
+ /** The `period: 'balance'` spending policy to credit. */
6
+ spendingPolicyId: string;
7
+ /** Credit to buy, in integer minor units of the policy currency. */
8
+ amount: number;
9
+ successUrl: string;
10
+ cancelUrl: string;
11
+ /**
12
+ * Caller-owned id for this purchase attempt (for example a cart id). A
13
+ * retried request with the same id returns the same checkout.
14
+ */
15
+ purchaseId: string;
16
+ /** Checkout line description (default `Prepaid credit`). */
17
+ description?: string;
18
+ }
19
+ /** The checkout metadata a completed purchase is settled from. */
20
+ export interface CreditPurchaseMetadata {
21
+ purpose: string;
22
+ sellerTenantId: string;
23
+ payerTenantId: string;
24
+ billingAccountId: string;
25
+ spendingPolicyId: string;
26
+ grantedByTenantId: string;
27
+ amount: number;
28
+ currency: string;
29
+ }
30
+ /** Parse checkout metadata written by {@link createCreditCheckout}. */
31
+ export declare function creditMetadata(raw: Record<string, string>): CreditPurchaseMetadata | null;
32
+ export declare function createCreditCheckout(runtime: BillingRuntime, input: CreateCreditCheckoutInput): Promise<BillingProviderCheckoutSession>;
33
+ //# sourceMappingURL=credits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credits.d.ts","sourceRoot":"","sources":["../../src/billing/credits.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQnD,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD,MAAM,WAAW,yBAAyB;IACxC,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuBD,uEAAuE;AACvE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,sBAAsB,GAAG,IAAI,CAwB/B;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,8BAA8B,CAAC,CAgEzC"}
@@ -0,0 +1,26 @@
1
+ import { ForgeProjector } from '@happyvertical/smrt-jobs';
2
+ import { BillingProviderEvent, BillingProviderInvoiceState, BillingProviderSubscriptionState } from './provider.js';
3
+ import { BillingRuntime } from './runtime.js';
4
+ type InvoiceEvent = Extract<BillingProviderEvent, {
5
+ kind: 'invoice';
6
+ }>;
7
+ type SubscriptionEvent = Extract<BillingProviderEvent, {
8
+ kind: 'subscription';
9
+ }>;
10
+ type CheckoutEvent = Extract<BillingProviderEvent, {
11
+ kind: 'checkout_completed';
12
+ }>;
13
+ type ObservedEvent = {
14
+ event: InvoiceEvent;
15
+ invoice: BillingProviderInvoiceState;
16
+ } | {
17
+ event: SubscriptionEvent;
18
+ subscription: BillingProviderSubscriptionState;
19
+ } | {
20
+ event: CheckoutEvent;
21
+ };
22
+ /** Apply up to `limit` queued events for a runtime; returns how many ran. */
23
+ export declare function processBillingEvents(runtime: BillingRuntime, limit: number): Promise<number>;
24
+ export declare function createBillingEventProjector(runtime: BillingRuntime): ForgeProjector<ObservedEvent>;
25
+ export {};
26
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/billing/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAoBlC,OAAO,KAAK,EACV,oBAAoB,EACpB,2BAA2B,EAC3B,gCAAgC,EACjC,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQnD,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACvE,KAAK,iBAAiB,GAAG,OAAO,CAC9B,oBAAoB,EACpB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CACzB,CAAC;AACF,KAAK,aAAa,GAAG,OAAO,CAC1B,oBAAoB,EACpB;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE,CAC/B,CAAC;AAEF,KAAK,aAAa,GACd;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,2BAA2B,CAAA;CAAE,GAC7D;IACE,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,EAAE,gCAAgC,CAAC;CAChD,GACD;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAI7B,6EAA6E;AAC7E,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,GACtB,cAAc,CAAC,aAAa,CAAC,CA+D/B"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Billing-period close, provider events, and prepaid credit purchases
3
+ * (#3060).
4
+ * @packageDocumentation
5
+ */
6
+ export type { CreateCreditCheckoutInput } from './credits.js';
7
+ export { type EnqueueBillingJobOptions, enqueueBillingEvents, enqueueBillingPeriodClose, getBillingRuntime, registerBillingRuntime, unregisterBillingRuntime, } from './jobs.js';
8
+ export { type BillingPeriod, BillingPeriodCloseError, type ClosePeriodInput, type PeriodCloseGroupResult, type PeriodCloseOutcome, type PeriodCloseResult, previousCalendarMonth, } from './period-close.js';
9
+ export { type BillingInvoiceEventType, type BillingProvider, type BillingProviderCheckoutInput, type BillingProviderCheckoutSession, type BillingProviderCustomerInput, type BillingProviderEvent, type BillingProviderInvoiceInput, type BillingProviderInvoiceLine, type BillingProviderInvoiceState, type BillingProviderInvoiceStatus, type BillingProviderSubscriptionState, BillingWebhookVerificationError, } from './provider.js';
10
+ export { type BillingLedgerAccounts, BillingRuntime, type BillingRuntimeOptions, type PayerStandingChange, type PayerStandingHook, type SyncedBillingAccount, type UpsertBillingAccountInput, } from './runtime.js';
11
+ export { createStripeBillingProvider, type StripeBillingProviderOptions, } from './stripe.js';
12
+ export { currencyMinorUnitExponent, majorToMinorUnits, minorToMajorUnits, } from './units.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/billing/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EACL,KAAK,wBAAwB,EAC7B,oBAAoB,EACpB,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACnC,KAAK,4BAA4B,EACjC,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,gCAAgC,EACrC,+BAA+B,GAChC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,qBAAqB,EAC1B,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,2BAA2B,EAC3B,KAAK,4BAA4B,GAClC,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { SmrtJob } from '@happyvertical/smrt-jobs';
2
+ import { PeriodCloseResult } from './period-close.js';
3
+ import { BillingRuntime } from './runtime.js';
4
+ /** Make a runtime available to billing jobs under `name`. */
5
+ export declare function registerBillingRuntime(name: string, runtime: BillingRuntime): void;
6
+ export declare function unregisterBillingRuntime(name: string): void;
7
+ export declare function getBillingRuntime(name: string): BillingRuntime;
8
+ export declare function runBillingPeriodCloseJob(args: Record<string, unknown>): Promise<PeriodCloseResult>;
9
+ export declare function runBillingEventsJob(args: Record<string, unknown>): Promise<{
10
+ processed: number;
11
+ }>;
12
+ export interface EnqueueBillingJobOptions {
13
+ /** Registered runtime name. */
14
+ runtime: string;
15
+ runAt?: Date;
16
+ queue?: string;
17
+ maxAttempts?: number;
18
+ }
19
+ /**
20
+ * Queue a period close. Without a period the job closes the previous
21
+ * calendar month when it runs, so enqueueing it daily is safe.
22
+ */
23
+ export declare function enqueueBillingPeriodClose(options: EnqueueBillingJobOptions & {
24
+ periodStart?: Date;
25
+ periodEnd?: Date;
26
+ }): Promise<SmrtJob>;
27
+ /** Queue processing of received provider events. */
28
+ export declare function enqueueBillingEvents(options: EnqueueBillingJobOptions & {
29
+ limit?: number;
30
+ }): Promise<SmrtJob>;
31
+ //# sourceMappingURL=jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/billing/jobs.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,OAAO,EAAqB,MAAM,0BAA0B,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAmBnD,6DAA6D;AAC7D,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACtB,IAAI,CAGN;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAM9D;AAkBD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAK5B;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAGhC;AAED,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA4BD;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,wBAAwB,GAAG;IAAE,WAAW,CAAC,EAAE,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,IAAI,CAAA;CAAE,GAC3E,OAAO,CAAC,OAAO,CAAC,CAelB;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,wBAAwB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,OAAO,CAAC,OAAO,CAAC,CAclB"}
@@ -0,0 +1,41 @@
1
+ import { BillingRuntime } from './runtime.js';
2
+ export interface BillingPeriod {
3
+ /** Inclusive start. */
4
+ periodStart: Date;
5
+ /** Exclusive end; charges approved before it are billable. */
6
+ periodEnd: Date;
7
+ }
8
+ export interface ClosePeriodInput {
9
+ periodStart?: Date;
10
+ periodEnd?: Date;
11
+ /** Clock override for tests; defaults to now. */
12
+ now?: Date;
13
+ }
14
+ export type PeriodCloseOutcome = 'completed' | 'empty' | 'busy' | 'carried_forward' | 'failed';
15
+ export interface PeriodCloseGroupResult {
16
+ payerTenantId: string;
17
+ currency: string;
18
+ closeId?: string;
19
+ invoiceId?: string;
20
+ providerInvoiceId?: string;
21
+ outcome: PeriodCloseOutcome;
22
+ error?: string;
23
+ }
24
+ export interface PeriodCloseResult extends BillingPeriod {
25
+ sellerTenantId: string;
26
+ groups: PeriodCloseGroupResult[];
27
+ }
28
+ /** Thrown when one or more payers could not be closed; the rest were. */
29
+ export declare class BillingPeriodCloseError extends Error {
30
+ readonly result: PeriodCloseResult;
31
+ constructor(result: PeriodCloseResult);
32
+ }
33
+ /** The previous calendar month in UTC relative to `now`. */
34
+ export declare function previousCalendarMonth(now?: Date): BillingPeriod;
35
+ export declare function periodCloseId(runtime: Pick<BillingRuntime, 'sellerTenantId' | 'kind'>, payerTenantId: string, currency: string, period: BillingPeriod): Promise<string>;
36
+ /**
37
+ * Close one billing period for a runtime's seller. Idempotent: re-running it
38
+ * for the same period resumes unfinished payers and bills nothing twice.
39
+ */
40
+ export declare function closeBillingPeriod(runtime: BillingRuntime, input?: ClosePeriodInput): Promise<PeriodCloseResult>;
41
+ //# sourceMappingURL=period-close.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"period-close.d.ts","sourceRoot":"","sources":["../../src/billing/period-close.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,8DAA8D;IAC9D,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,iDAAiD;IACjD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,OAAO,GACP,MAAM,GACN,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAED,yEAAyE;AACzE,qBAAa,uBAAwB,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB;gBAAzB,MAAM,EAAE,iBAAiB;CAW/C;AAwBD,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,GAAG,OAAa,GAAG,aAAa,CAQrE;AAoCD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,GAAG,MAAM,CAAC,EACxD,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAMD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,KAAK,GAAE,gBAAqB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CA0D5B"}
@@ -0,0 +1,136 @@
1
+ import { SubscriptionStatus } from '@happyvertical/smrt-subscriptions';
2
+ import { Address } from '../types/index.js';
3
+ export interface BillingProviderCustomerInput {
4
+ /** The local billing account id, recorded on the provider customer. */
5
+ accountId: string;
6
+ /** Existing provider customer id; omitted to create one. */
7
+ providerCustomerId?: string;
8
+ name: string;
9
+ email?: string;
10
+ /** Tax location used by provider-calculated tax. */
11
+ billingAddress?: Address;
12
+ taxExempt: boolean;
13
+ }
14
+ export interface BillingProviderInvoiceLine {
15
+ description: string;
16
+ /** Signed line amount in minor units; discounts are negative lines. */
17
+ amount: number;
18
+ periodStart?: Date;
19
+ periodEnd?: Date;
20
+ }
21
+ export interface BillingProviderInvoiceInput {
22
+ /** Local invoice id; the provider tags its invoice with it. */
23
+ invoiceId: string;
24
+ invoiceNumber: string;
25
+ providerCustomerId: string;
26
+ currency: string;
27
+ issueDate: Date;
28
+ dueDate: Date;
29
+ lines: BillingProviderInvoiceLine[];
30
+ /** Sum of `lines`, in minor units. */
31
+ subtotal: number;
32
+ /** Stable key for this logical invoice; reused on every retry. */
33
+ idempotencyKey: string;
34
+ /** Ask the provider to calculate tax from the customer's tax location. */
35
+ automaticTax: boolean;
36
+ memo?: string;
37
+ }
38
+ export type BillingProviderInvoiceStatus = 'draft' | 'open' | 'paid' | 'void';
39
+ export interface BillingProviderInvoiceState {
40
+ providerInvoiceId: string;
41
+ status: BillingProviderInvoiceStatus;
42
+ currency: string;
43
+ /** Pre-tax subtotal, minor units. */
44
+ subtotal: number;
45
+ /** Provider-calculated tax, minor units. */
46
+ taxAmount: number;
47
+ total: number;
48
+ amountPaid: number;
49
+ amountDue: number;
50
+ }
51
+ export interface BillingProviderSubscriptionState {
52
+ providerSubscriptionId: string;
53
+ status: SubscriptionStatus;
54
+ currentPeriodStart?: Date;
55
+ currentPeriodEnd?: Date;
56
+ cancelAtPeriodEnd: boolean;
57
+ canceledAt?: Date;
58
+ trialEndsAt?: Date;
59
+ }
60
+ export interface BillingProviderCheckoutInput {
61
+ /** Stable key reused when retrying the same checkout creation. */
62
+ idempotencyKey: string;
63
+ providerCustomerId?: string;
64
+ customerEmail?: string;
65
+ currency: string;
66
+ /** Amount charged, minor units. */
67
+ amount: number;
68
+ description: string;
69
+ successUrl: string;
70
+ cancelUrl: string;
71
+ /** Opaque values returned verbatim on the completion event. */
72
+ metadata: Record<string, string>;
73
+ }
74
+ export interface BillingProviderCheckoutSession {
75
+ sessionId: string;
76
+ url: string | null;
77
+ }
78
+ /** Invoice lifecycle changes this package acts on. */
79
+ export type BillingInvoiceEventType = 'paid' | 'payment_failed' | 'overdue' | 'uncollectible' | 'voided';
80
+ /** A verified provider event, normalized and stripped of customer data. */
81
+ export type BillingProviderEvent = {
82
+ kind: 'invoice';
83
+ eventId: string;
84
+ type: BillingInvoiceEventType;
85
+ providerInvoiceId: string;
86
+ } | {
87
+ kind: 'subscription';
88
+ eventId: string;
89
+ providerSubscriptionId: string;
90
+ } | {
91
+ kind: 'checkout_completed';
92
+ eventId: string;
93
+ sessionId: string;
94
+ /** Only `paid` sessions settle a credit purchase. */
95
+ paid: boolean;
96
+ currency: string;
97
+ /** Line-item amount before discounts and tax, minor units. */
98
+ amountSubtotal: number;
99
+ /** Amount actually collected (after discounts, with tax), minor units. */
100
+ amountTotal: number;
101
+ metadata: Record<string, string>;
102
+ } | {
103
+ kind: 'ignored';
104
+ eventId: string;
105
+ type: string;
106
+ };
107
+ /**
108
+ * A payment provider as period close sees it. Implementations must be
109
+ * replay-safe: `pushInvoice` with the same `idempotencyKey` returns the same
110
+ * provider invoice however often it is retried.
111
+ */
112
+ export interface BillingProvider {
113
+ /** Provider name recorded on local rows (`stripe`). */
114
+ readonly name: string;
115
+ syncCustomer(input: BillingProviderCustomerInput): Promise<{
116
+ providerCustomerId: string;
117
+ }>;
118
+ pushInvoice(input: BillingProviderInvoiceInput): Promise<{
119
+ providerInvoiceId: string;
120
+ }>;
121
+ /** Finalize (if still a draft) and send an invoice for payment. */
122
+ sendInvoice(providerInvoiceId: string): Promise<void>;
123
+ getInvoice(providerInvoiceId: string): Promise<BillingProviderInvoiceState>;
124
+ getSubscription(providerSubscriptionId: string): Promise<BillingProviderSubscriptionState>;
125
+ createCheckout(input: BillingProviderCheckoutInput): Promise<BillingProviderCheckoutSession>;
126
+ /**
127
+ * Verify a webhook signature and normalize the event. Rejects with
128
+ * {@link BillingWebhookVerificationError} when the signature is invalid;
129
+ * anything verified but not actionable is returned as `ignored`.
130
+ */
131
+ verifyWebhook(payload: string, signature: string): Promise<BillingProviderEvent>;
132
+ }
133
+ export declare class BillingWebhookVerificationError extends Error {
134
+ constructor(message?: string);
135
+ }
136
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/billing/provider.ts"],"names":[],"mappings":"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"}