@happyvertical/smrt-commerce 0.53.3 → 0.53.5

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 (158) hide show
  1. package/AGENTS.md +19 -3
  2. package/README.md +29 -1
  3. package/agents/commercial-ui.md +78 -0
  4. package/agents/customer-vendor-ui.md +76 -0
  5. package/agents/invoice-calculations.md +17 -0
  6. package/agents/invoice-preparation.md +86 -0
  7. package/agents/invoice-quantity.md +46 -0
  8. package/agents/pricing-versions.md +28 -0
  9. package/agents/purchasing-ui.md +82 -0
  10. package/agents/quote-ui.md +82 -0
  11. package/dist/billing/units.d.ts +1 -7
  12. package/dist/billing/units.d.ts.map +1 -1
  13. package/dist/chunks/calculations-DPuldr1J.js +242 -0
  14. package/dist/chunks/calculations-DPuldr1J.js.map +1 -0
  15. package/dist/chunks/types-CRnln577.js.map +1 -1
  16. package/dist/chunks/{units-CpBepqG1.js → units-CJcAOQm1.js} +3 -39
  17. package/dist/chunks/units-CJcAOQm1.js.map +1 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +164 -44
  21. package/dist/index.js.map +1 -1
  22. package/dist/invoices.d.ts +3 -0
  23. package/dist/invoices.d.ts.map +1 -0
  24. package/dist/invoices.js +2 -0
  25. package/dist/manifest.json +66 -8
  26. package/dist/migrations/invoiceEditorStorage.d.ts +39 -0
  27. package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -0
  28. package/dist/models/ContractLineItem.d.ts +1 -1
  29. package/dist/models/InvoiceLineItem.d.ts +18 -36
  30. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  31. package/dist/playground.js +201 -3
  32. package/dist/playground.js.map +1 -1
  33. package/dist/smrt-knowledge.json +109 -7
  34. package/dist/svelte/components/CustomerDetail.svelte +150 -0
  35. package/dist/svelte/components/CustomerDetail.svelte.d.ts +28 -0
  36. package/dist/svelte/components/CustomerDetail.svelte.d.ts.map +1 -0
  37. package/dist/svelte/components/CustomerDirectory.svelte +177 -0
  38. package/dist/svelte/components/CustomerDirectory.svelte.d.ts +42 -0
  39. package/dist/svelte/components/CustomerDirectory.svelte.d.ts.map +1 -0
  40. package/dist/svelte/components/CustomerForm.svelte +234 -0
  41. package/dist/svelte/components/CustomerForm.svelte.d.ts +44 -0
  42. package/dist/svelte/components/CustomerForm.svelte.d.ts.map +1 -0
  43. package/dist/svelte/components/InvoiceAllocationFields.svelte +72 -0
  44. package/dist/svelte/components/InvoiceAllocationFields.svelte.d.ts +8 -0
  45. package/dist/svelte/components/InvoiceAllocationFields.svelte.d.ts.map +1 -0
  46. package/dist/svelte/components/InvoiceCard.svelte +2 -2
  47. package/dist/svelte/components/InvoiceEditor.svelte +134 -0
  48. package/dist/svelte/components/InvoiceEditor.svelte.d.ts +8 -0
  49. package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -0
  50. package/dist/svelte/components/InvoiceLineEditor.svelte +85 -0
  51. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +8 -0
  52. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +1 -0
  53. package/dist/svelte/components/InvoiceReview.svelte +23 -0
  54. package/dist/svelte/components/InvoiceReview.svelte.d.ts +10 -0
  55. package/dist/svelte/components/InvoiceReview.svelte.d.ts.map +1 -0
  56. package/dist/svelte/components/PartyAddressFields.svelte +95 -0
  57. package/dist/svelte/components/PartyAddressFields.svelte.d.ts +25 -0
  58. package/dist/svelte/components/PartyAddressFields.svelte.d.ts.map +1 -0
  59. package/dist/svelte/components/PartyContactFields.svelte +153 -0
  60. package/dist/svelte/components/PartyContactFields.svelte.d.ts +28 -0
  61. package/dist/svelte/components/PartyContactFields.svelte.d.ts.map +1 -0
  62. package/dist/svelte/components/PricingVersionDecision.svelte +88 -0
  63. package/dist/svelte/components/PricingVersionDecision.svelte.d.ts +35 -0
  64. package/dist/svelte/components/PricingVersionDecision.svelte.d.ts.map +1 -0
  65. package/dist/svelte/components/PricingVersionSummary.svelte +46 -0
  66. package/dist/svelte/components/PricingVersionSummary.svelte.d.ts +9 -0
  67. package/dist/svelte/components/PricingVersionSummary.svelte.d.ts.map +1 -0
  68. package/dist/svelte/components/PurchaseOrderEditor.svelte +210 -0
  69. package/dist/svelte/components/PurchaseOrderEditor.svelte.d.ts +79 -0
  70. package/dist/svelte/components/PurchaseOrderEditor.svelte.d.ts.map +1 -0
  71. package/dist/svelte/components/PurchaseSourceSelector.svelte +56 -0
  72. package/dist/svelte/components/PurchaseSourceSelector.svelte.d.ts +30 -0
  73. package/dist/svelte/components/PurchaseSourceSelector.svelte.d.ts.map +1 -0
  74. package/dist/svelte/components/PurchasingPlayground.svelte +133 -0
  75. package/dist/svelte/components/PurchasingPlayground.svelte.d.ts +9 -0
  76. package/dist/svelte/components/PurchasingPlayground.svelte.d.ts.map +1 -0
  77. package/dist/svelte/components/QuoteEditor.svelte +177 -0
  78. package/dist/svelte/components/QuoteEditor.svelte.d.ts +76 -0
  79. package/dist/svelte/components/QuoteEditor.svelte.d.ts.map +1 -0
  80. package/dist/svelte/components/QuotePlayground.svelte +108 -0
  81. package/dist/svelte/components/QuotePlayground.svelte.d.ts +10 -0
  82. package/dist/svelte/components/QuotePlayground.svelte.d.ts.map +1 -0
  83. package/dist/svelte/components/QuoteRevisionComparison.svelte +44 -0
  84. package/dist/svelte/components/QuoteRevisionComparison.svelte.d.ts +14 -0
  85. package/dist/svelte/components/QuoteRevisionComparison.svelte.d.ts.map +1 -0
  86. package/dist/svelte/components/QuoteRevisionHistory.svelte +55 -0
  87. package/dist/svelte/components/QuoteRevisionHistory.svelte.d.ts +15 -0
  88. package/dist/svelte/components/QuoteRevisionHistory.svelte.d.ts.map +1 -0
  89. package/dist/svelte/components/VendorDetail.svelte +134 -0
  90. package/dist/svelte/components/VendorDetail.svelte.d.ts +26 -0
  91. package/dist/svelte/components/VendorDetail.svelte.d.ts.map +1 -0
  92. package/dist/svelte/components/VendorDirectory.svelte +178 -0
  93. package/dist/svelte/components/VendorDirectory.svelte.d.ts +42 -0
  94. package/dist/svelte/components/VendorDirectory.svelte.d.ts.map +1 -0
  95. package/dist/svelte/components/VendorForm.svelte +224 -0
  96. package/dist/svelte/components/VendorForm.svelte.d.ts +42 -0
  97. package/dist/svelte/components/VendorForm.svelte.d.ts.map +1 -0
  98. package/dist/svelte/i18n.d.ts +102 -0
  99. package/dist/svelte/i18n.d.ts.map +1 -1
  100. package/dist/svelte/i18n.js +102 -0
  101. package/dist/svelte/index.d.ts +52 -0
  102. package/dist/svelte/index.d.ts.map +1 -1
  103. package/dist/svelte/index.js +49 -0
  104. package/dist/svelte/invoices/calculations.d.ts +121 -0
  105. package/dist/svelte/invoices/calculations.d.ts.map +1 -0
  106. package/dist/svelte/invoices/calculations.js +259 -0
  107. package/dist/svelte/invoices/currency.d.ts +9 -0
  108. package/dist/svelte/invoices/currency.d.ts.map +1 -0
  109. package/dist/svelte/invoices/currency.js +55 -0
  110. package/dist/svelte/invoices/fields.d.ts +6 -0
  111. package/dist/svelte/invoices/fields.d.ts.map +1 -0
  112. package/dist/svelte/invoices/fields.js +22 -0
  113. package/dist/svelte/invoices/messages.d.ts +53 -0
  114. package/dist/svelte/invoices/messages.d.ts.map +1 -0
  115. package/dist/svelte/invoices/messages.js +53 -0
  116. package/dist/svelte/invoices/types.d.ts +194 -0
  117. package/dist/svelte/invoices/types.d.ts.map +1 -0
  118. package/dist/svelte/invoices/types.js +1 -0
  119. package/dist/svelte/party-types.d.ts +203 -0
  120. package/dist/svelte/party-types.d.ts.map +1 -0
  121. package/dist/svelte/party-types.js +82 -0
  122. package/dist/svelte/playground/InvoicePreparationPreview.svelte +116 -0
  123. package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts +4 -0
  124. package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -0
  125. package/dist/svelte/playground/PartyFormPreview.svelte +211 -0
  126. package/dist/svelte/playground/PartyFormPreview.svelte.d.ts +8 -0
  127. package/dist/svelte/playground/PartyFormPreview.svelte.d.ts.map +1 -0
  128. package/dist/svelte/playground/PricingVersionPreview.svelte +55 -0
  129. package/dist/svelte/playground/PricingVersionPreview.svelte.d.ts +4 -0
  130. package/dist/svelte/playground/PricingVersionPreview.svelte.d.ts.map +1 -0
  131. package/dist/svelte/playground.d.ts +442 -3
  132. package/dist/svelte/playground.d.ts.map +1 -1
  133. package/dist/svelte/playground.js +199 -3
  134. package/dist/svelte/pricing-i18n.d.ts +10 -0
  135. package/dist/svelte/pricing-i18n.d.ts.map +1 -0
  136. package/dist/svelte/pricing-i18n.js +10 -0
  137. package/dist/svelte/pricing-types.d.ts +38 -0
  138. package/dist/svelte/pricing-types.d.ts.map +1 -0
  139. package/dist/svelte/pricing-types.js +1 -0
  140. package/dist/svelte/purchasing-i18n.d.ts +53 -0
  141. package/dist/svelte/purchasing-i18n.d.ts.map +1 -0
  142. package/dist/svelte/purchasing-i18n.js +53 -0
  143. package/dist/svelte/purchasing-types.d.ts +91 -0
  144. package/dist/svelte/purchasing-types.d.ts.map +1 -0
  145. package/dist/svelte/purchasing-types.js +13 -0
  146. package/dist/svelte/quote-i18n.d.ts +49 -0
  147. package/dist/svelte/quote-i18n.d.ts.map +1 -0
  148. package/dist/svelte/quote-i18n.js +49 -0
  149. package/dist/svelte/quote-types.d.ts +81 -0
  150. package/dist/svelte/quote-types.d.ts.map +1 -0
  151. package/dist/svelte/quote-types.js +51 -0
  152. package/dist/types/index.d.ts +13 -3
  153. package/dist/types/index.d.ts.map +1 -1
  154. package/dist/ui.d.ts.map +1 -1
  155. package/dist/ui.js +150 -6
  156. package/dist/ui.js.map +1 -1
  157. package/package.json +25 -13
  158. package/dist/chunks/units-CpBepqG1.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["requested","sequence","seq","payment"],"sources":["../src/__smrt-register__.ts","../src/billing/provider.ts","../src/models/Customer.ts","../src/collections/CustomerCollection.ts","../src/models/PaymentInstrument.ts","../src/collections/PaymentInstrumentCollection.ts","../src/billing/cards.ts","../src/billing/credits.ts","../src/models/Payment.ts","../src/collections/PaymentCollection.ts","../src/billing/top-up.ts","../src/billing/stripe.ts","../src/billing/crypto.ts","../src/billing/btcpay.ts","../src/billing/cycles.ts","../src/models/PaymentAllocation.ts","../src/collections/PaymentAllocationCollection.ts","../src/billing/invoice-payments.ts","../src/billing/jobs.ts","../src/models/Invoice.ts","../src/collections/InvoiceCollection.ts","../src/models/billing.ts","../src/billing/payment-attempts.ts","../src/billing/period-close.ts","../src/models/InvoiceLineItem.ts","../src/collections/InvoiceLineItemCollection.ts","../src/billing/events.ts","../src/billing/runtime.ts","../src/models/Vendor.ts","../src/models/Contract.ts","../src/collections/ContractCollection.ts","../src/models/ContractLineItem.ts","../src/collections/ContractLineItemCollection.ts","../src/models/Fulfillment.ts","../src/collections/FulfillmentCollection.ts","../src/models/FulfillmentLineItem.ts","../src/collections/FulfillmentLineItemCollection.ts","../src/models/PaymentIntent.ts","../src/collections/PaymentIntentCollection.ts","../src/models/Payout.ts","../src/collections/PayoutCollection.ts","../src/collections/VendorCollection.ts","../src/migrations/moneyMinorUnits.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * The provider-neutral port period close, webhook intake, and credit\n * purchases talk to (#3060).\n *\n * Every amount crossing this port is **integer minor units** of the named\n * currency, like the rest of commerce. Adapters (see\n * {@link createStripeBillingProvider}) own the conversion to the provider's\n * representation and every provider call; this package never calls a payment\n * provider directly.\n */\nimport type { SubscriptionStatus } from '@happyvertical/smrt-subscriptions';\nimport type { Address } from '../types/index.js';\n\nexport interface BillingProviderCustomerInput {\n /** The local billing account id, recorded on the provider customer. */\n accountId: string;\n /** Existing provider customer id; omitted to create one. */\n providerCustomerId?: string;\n name: string;\n email?: string;\n /** Tax location used by provider-calculated tax. */\n billingAddress?: Address;\n taxExempt: boolean;\n}\n\nexport interface BillingProviderInvoiceLine {\n description: string;\n /** Signed line amount in minor units; discounts are negative lines. */\n amount: number;\n periodStart?: Date;\n periodEnd?: Date;\n /**\n * Product tax code for provider-calculated tax (Stripe Tax `txcd_...`,\n * #3194). Omitted: the provider account's default.\n */\n taxCode?: string;\n}\n\n/**\n * How the provider collects an invoice. `charge_automatically` charges the\n * payer's saved default payment method after the invoice is sent, on the\n * provider's own schedule; the outcome arrives as a `paid` or\n * `payment_failed` event. A provider that cannot pull funds from a saved\n * method must reject it, never fall back to `send_invoice`.\n */\nexport type BillingCollectionMethod = 'send_invoice' | 'charge_automatically';\n\nexport interface BillingProviderInvoiceInput {\n /** Local invoice id; the provider tags its invoice with it. */\n invoiceId: string;\n invoiceNumber: string;\n providerCustomerId: string;\n currency: string;\n issueDate: Date;\n dueDate: Date;\n lines: BillingProviderInvoiceLine[];\n /** Sum of `lines`, in minor units. */\n subtotal: number;\n /** Stable key for this logical invoice; reused on every retry. */\n idempotencyKey: string;\n /** Ask the provider to calculate tax from the customer's tax location. */\n automaticTax: boolean;\n /** Default `send_invoice` (#3139). */\n collectionMethod?: BillingCollectionMethod;\n memo?: string;\n}\n\n/**\n * `uncollectible` is an open balance the seller wrote off (#3139); it can\n * still be paid or voided later.\n */\nexport type BillingProviderInvoiceStatus =\n | 'draft'\n | 'open'\n | 'paid'\n | 'void'\n | 'uncollectible';\n\nexport interface BillingProviderInvoiceState {\n providerInvoiceId: string;\n status: BillingProviderInvoiceStatus;\n /**\n * The invoice was closed as paid outside the provider (for example by a\n * crypto payment rail, #3138). The rail that took the money records it; the\n * provider's own `paid` event must not record a second payment.\n */\n paidOutOfBand?: boolean;\n currency: string;\n /** Pre-tax subtotal, minor units. */\n subtotal: number;\n /** Provider-calculated tax, minor units. */\n taxAmount: number;\n total: number;\n amountPaid: number;\n amountDue: number;\n}\n\nexport interface BillingProviderSubscriptionState {\n providerSubscriptionId: string;\n status: SubscriptionStatus;\n currentPeriodStart?: Date;\n currentPeriodEnd?: Date;\n cancelAtPeriodEnd: boolean;\n canceledAt?: Date;\n trialEndsAt?: Date;\n}\n\nexport interface BillingProviderCheckoutInput {\n /** Stable key reused when retrying the same checkout creation. */\n idempotencyKey: string;\n providerCustomerId?: string;\n customerEmail?: string;\n currency: string;\n /** Amount charged, minor units. */\n amount: number;\n description: string;\n successUrl: string;\n cancelUrl: string;\n /** Opaque values returned verbatim on the completion event. */\n metadata: Record<string, string>;\n /**\n * Charge provider-calculated tax on top of `amount` (#3139). The buyer's\n * tax location is collected at checkout when the customer has none.\n */\n automaticTax?: boolean;\n /**\n * Product tax code for the checkout line (Stripe Tax `txcd_...`, #3194).\n * Omitted: the provider account's default.\n */\n taxCode?: string;\n /**\n * Also save the payment method for later off-session charges (for example\n * automatic top-ups). Requires `providerCustomerId`.\n */\n savePaymentMethod?: boolean;\n}\n\nexport interface BillingProviderCheckoutSession {\n sessionId: string;\n url: string | null;\n}\n\n/** A checkout that saves a payment method without charging (#3139). */\nexport interface BillingProviderSetupCheckoutInput {\n /** Stable key reused when retrying the same checkout creation. */\n idempotencyKey: string;\n providerCustomerId: string;\n /** The currency the saved method will be charged in. */\n currency: string;\n successUrl: string;\n cancelUrl: string;\n /** Collect the billing address (tax location) and save it to the customer. */\n collectBillingAddress: boolean;\n /** Opaque values returned verbatim on the completion event. */\n metadata: Record<string, string>;\n}\n\n/** A checkout session as the provider reports it now (#3139). */\nexport interface BillingProviderCheckoutState {\n sessionId: string;\n mode: 'payment' | 'setup' | 'other';\n /** The session completed (paid, or a payment method was saved). */\n complete: boolean;\n /** A payment-mode session collected its money. */\n paid: boolean;\n providerCustomerId?: string;\n /** The payment method the session collected. */\n paymentMethodId?: string;\n currency?: string;\n /** Line amount before discounts and tax, minor units. */\n amountSubtotal?: number;\n /** Provider-calculated tax, minor units. */\n amountTax?: number;\n /** Amount collected, minor units. */\n amountTotal?: number;\n /**\n * The customer's billing address after a completed session that was asked\n * to collect one.\n */\n billingAddress?: Address;\n}\n\n/**\n * The outcome of an off-session charge (#3139). `processing` settles later\n * through a `payment` event; `requires_action` (the issuer wants the payer\n * present), `failed`, and `canceled` charged nothing.\n */\nexport type BillingChargeStatus =\n | 'succeeded'\n | 'processing'\n | 'requires_action'\n | 'failed'\n | 'canceled';\n\nexport interface BillingProviderChargeInput {\n providerCustomerId: string;\n /** Positive integer minor units. */\n amount: number;\n currency: string;\n /**\n * Stable key for this logical charge. Every retry with it returns the\n * original charge; a new attempt needs a new key.\n */\n idempotencyKey: string;\n description?: string;\n metadata?: Record<string, string>;\n /**\n * Add provider-calculated tax on top of `amount` from the customer's tax\n * location (#3194): `amount` is then the pre-tax subtotal. A customer whose\n * tax location cannot be determined yields a `failed` result with\n * `failureCode: 'customer_tax_location_invalid'`, charging nothing. A\n * provider that cannot calculate tax must throw, never charge untaxed.\n * Every retry of a charge key must pass the same value.\n */\n automaticTax?: boolean;\n /** Product tax code (requires `automaticTax`). */\n taxCode?: string;\n}\n\nexport interface BillingProviderChargeResult {\n status: BillingChargeStatus;\n /** The provider payment id, when one was created. */\n providerPaymentId?: string;\n /** Minor units charged, including tax for an `automaticTax` charge. */\n amount: number;\n currency: string;\n /** `automaticTax` charges: the pre-tax amount, minor units. */\n subtotal?: number;\n /** `automaticTax` charges: the provider-calculated tax, minor units. */\n tax?: number;\n failureCode?: string;\n failureMessage?: string;\n}\n\n/** Invoice lifecycle changes this package acts on. */\nexport type BillingInvoiceEventType =\n | 'paid'\n | 'payment_failed'\n | 'overdue'\n | 'uncollectible'\n | 'voided';\n\n/** A verified provider event, normalized and stripped of customer data. */\nexport type BillingProviderEvent =\n | {\n kind: 'invoice';\n eventId: string;\n type: BillingInvoiceEventType;\n providerInvoiceId: string;\n }\n | {\n kind: 'subscription';\n eventId: string;\n providerSubscriptionId: string;\n }\n | {\n kind: 'checkout_completed';\n eventId: string;\n sessionId: string;\n /** `payment` (credit purchase) or `setup` (card on file, #3139). */\n mode?: 'payment' | 'setup';\n /** Only `paid` sessions settle a credit purchase. */\n paid: boolean;\n currency: string;\n /**\n * Line-item amount before discounts and tax, minor units. Omitted by a\n * provider whose event amounts are not minor units; `observe()` then\n * re-reads the session with `getCheckout()`.\n */\n amountSubtotal?: number;\n /** Provider-calculated tax, minor units. */\n amountTax?: number;\n /** Amount actually collected (after discounts, with tax), minor units. */\n amountTotal?: number;\n metadata: Record<string, string>;\n }\n | {\n /** An off-session charge made by `chargeSavedPaymentMethod` (#3139). */\n kind: 'payment';\n eventId: string;\n /** The `idempotencyKey` the charge was made with. */\n chargeKey: string;\n status: BillingChargeStatus;\n providerPaymentId: string;\n providerCustomerId?: string;\n /**\n * Minor units charged (with tax for an `automaticTax` charge), when the\n * provider amount converts exactly.\n */\n amount?: number;\n currency?: string;\n /** `automaticTax` charges: the pre-tax amount, minor units (#3194). */\n subtotal?: number;\n /** `automaticTax` charges: the provider-calculated tax, minor units. */\n tax?: number;\n failureCode?: string;\n }\n | {\n /**\n * A payment rail's checkout changed (#3138). The event carries ids\n * only; `observe()` re-reads the attempt with `getPaymentAttempt()`.\n */\n kind: 'payment_attempt';\n eventId: string;\n checkoutId: string;\n }\n | { kind: 'ignored'; eventId: string; type: string };\n\n/** What a provider can do beyond the core port (#3138). */\nexport interface BillingProviderCapabilities {\n /**\n * The provider issues, taxes, and sends invoices (`pushInvoice` …). A\n * payment rail (for example a crypto checkout) does not, and cannot be a\n * runtime's issuing provider.\n */\n issuesInvoices: boolean;\n /** The provider reports checkouts through `getPaymentAttempt()`. */\n paymentAttempts: boolean;\n}\n\n/** A rail checkout's lifecycle, normalized by the provider (#3138). */\nexport type BillingPaymentAttemptStatus =\n | 'open'\n | 'confirming'\n | 'settled'\n | 'expired'\n | 'invalid';\n\nexport type BillingPaymentAttemptException =\n | 'none'\n | 'underpaid'\n | 'overpaid'\n | 'paid_late'\n | 'manually_marked';\n\nexport interface BillingPaymentAttemptPayment {\n id: string;\n /** `onchain`, `lightning`, … */\n rail: string;\n /** The asset paid (`BTC`). */\n asset: string;\n /** Amount in the asset's minor units (satoshis for BTC). */\n amount: number;\n /** Fee in the asset's minor units, when reported. */\n fee?: number;\n status: 'confirming' | 'settled' | 'invalid';\n transactionId?: string;\n receivedAt?: string;\n}\n\n/**\n * A rail checkout as the provider reports it now. Amounts are minor units;\n * `metadata` holds only `smrt_*` keys whose signature verified, and is empty\n * for a checkout this package did not create.\n */\nexport interface BillingPaymentAttemptState {\n checkoutId: string;\n /** The rail order id the checkout was created for. */\n orderId?: string;\n status: BillingPaymentAttemptStatus;\n exception: BillingPaymentAttemptException;\n /** The locked fiat price. */\n amount: number;\n currency: string;\n /** Fiat value received at the locked rate, rounded down. */\n amountPaid: number;\n metadata: Record<string, string>;\n /** The asset the price was quoted in (`BTC`). */\n nativeCurrency?: string;\n /** Asset minor units (satoshis). */\n nativeAmountDue?: number;\n nativeAmountPaid?: number;\n /** Decimal price of one unit of `nativeCurrency` in `currency`. */\n rate?: string;\n rateSource?: string;\n checkoutUrl?: string;\n expiresAt?: Date;\n payments: BillingPaymentAttemptPayment[];\n}\n\n/**\n * A payment provider as period close sees it. Implementations must be\n * replay-safe: `pushInvoice` with the same `idempotencyKey` returns the same\n * provider invoice however often it is retried.\n */\nexport interface BillingProvider {\n /** Provider name recorded on local rows (`stripe`). */\n readonly name: string;\n /**\n * Defaults to an issuing provider without payment attempts (Stripe's\n * shape) when omitted.\n */\n readonly capabilities?: BillingProviderCapabilities;\n syncCustomer(\n input: BillingProviderCustomerInput,\n ): Promise<{ providerCustomerId: string }>;\n pushInvoice(\n input: BillingProviderInvoiceInput,\n ): Promise<{ providerInvoiceId: string }>;\n /** Finalize (if still a draft) and send an invoice for payment. */\n sendInvoice(providerInvoiceId: string): Promise<void>;\n getInvoice(providerInvoiceId: string): Promise<BillingProviderInvoiceState>;\n getSubscription(\n providerSubscriptionId: string,\n ): Promise<BillingProviderSubscriptionState>;\n createCheckout(\n input: BillingProviderCheckoutInput,\n ): Promise<BillingProviderCheckoutSession>;\n /**\n * Verify a webhook signature and normalize the event. Rejects with\n * {@link BillingWebhookVerificationError} when the signature is invalid;\n * anything verified but not actionable is returned as `ignored`.\n */\n verifyWebhook(\n payload: string,\n signature: string,\n /** The request headers, for providers that sign with their own header. */\n headers?: Headers | Record<string, string | undefined>,\n ): Promise<BillingProviderEvent>;\n\n // Optional capabilities (#3139). A provider that cannot do one omits the\n // method, and callers detect the capability by its presence.\n\n /**\n * Charge the customer's saved default payment method off-session,\n * idempotently by `input.idempotencyKey`. A decline or an authentication\n * requirement is a result, not a throw.\n */\n chargeSavedPaymentMethod?(\n input: BillingProviderChargeInput,\n ): Promise<BillingProviderChargeResult>;\n /** Start a checkout that saves a payment method without charging. */\n createSetupCheckout?(\n input: BillingProviderSetupCheckoutInput,\n ): Promise<BillingProviderCheckoutSession>;\n /** Re-read a checkout session, with amounts in minor units. */\n getCheckout?(sessionId: string): Promise<BillingProviderCheckoutState>;\n /** Make a saved payment method the customer's default. Idempotent. */\n setDefaultPaymentMethod?(\n providerCustomerId: string,\n paymentMethodId: string,\n ): Promise<void>;\n /** Write an open invoice off as uncollectible. Idempotent. */\n markInvoiceUncollectible?(providerInvoiceId: string): Promise<void>;\n /**\n * Close an issued invoice as paid outside the provider (#3138). Must be\n * idempotent: an invoice already closed this way is a no-op. Callers check\n * `getInvoice()` first and never close an invoice the provider collected\n * itself (a provider may throw for one).\n */\n markInvoicePaidOutOfBand?(providerInvoiceId: string): Promise<void>;\n /**\n * Read a rail checkout's current state (#3138). Returns null for a\n * checkout this provider did not create.\n */\n getPaymentAttempt?(\n checkoutId: string,\n ): Promise<BillingPaymentAttemptState | null>;\n}\n\n/** The provider's capabilities, defaulted for providers that omit them. */\nexport function providerCapabilities(\n provider: BillingProvider,\n): BillingProviderCapabilities {\n return (\n provider.capabilities ?? { issuesInvoices: true, paymentAttempts: false }\n );\n}\n\n/** A provider was asked for something it does not do (#3138). */\nexport class BillingProviderUnsupportedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BillingProviderUnsupportedError';\n }\n}\n\n/**\n * Why a payer's payment request was refused (#3185). New codes may be added\n * in a minor release; treat an unknown code like `not_payable`.\n *\n * - `not_found`: no such invoice for this payer, or no active balance policy.\n * - `not_payable`: the invoice exists but cannot be paid now (not sent and\n * unpaid, or not issued by this runtime's provider).\n * - `payment_in_progress`: another payment for the invoice is still open.\n * - `nothing_due`: the invoice has no amount left to pay.\n * - `invalid_amount`: the requested amount is not positive minor units.\n * - `below_minimum`: the amount is under the rail's minimum.\n * - `no_account`: the payer has no billing account with this seller.\n */\nexport type BillingPaymentRefusalCode =\n | 'not_found'\n | 'not_payable'\n | 'payment_in_progress'\n | 'nothing_due'\n | 'invalid_amount'\n | 'below_minimum'\n | 'no_account';\n\n/**\n * A payment request refused for a reason the payer can act on (#3185): show\n * the message as a 4xx. Anything else thrown by the same call (provider,\n * database, or configuration failures) is a server error.\n * `TenantIsolationError` (a caller who is not the payer) stays separate.\n */\nexport class BillingPaymentRefusedError extends Error {\n readonly code: BillingPaymentRefusalCode;\n\n constructor(code: BillingPaymentRefusalCode, message: string) {\n super(message);\n this.name = 'BillingPaymentRefusedError';\n this.code = code;\n }\n}\n\nexport class BillingWebhookVerificationError extends Error {\n constructor(message = 'Billing webhook signature verification failed.') {\n super(message);\n this.name = 'BillingWebhookVerificationError';\n }\n}\n","/**\n * Customer model - links to Profile with customer-specific data\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type Address,\n type CustomerOptions,\n CustomerStatus,\n CustomerType,\n} from '../types/index.js';\n\n/**\n * Customer represents the customer role for a Profile.\n *\n * One Profile can have multiple Customer records (e.g., different business contexts).\n * Customer stores customer-specific data like credit limits and payment terms.\n *\n * @example\n * ```typescript\n * const customer = await customers.create({\n * profileId: 'profile-uuid',\n * creditLimit: 10000,\n * paymentTerms: 'Net 30'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Customer extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global customers\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Reference to smrt-profiles Profile\n * Plain string for cross-package reference\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n profileId: string = '';\n\n /**\n * Maximum credit extended to this customer, in **integer minor units**\n * (cents, satoshis).\n *\n * Converts with the rest of commerce because\n * {@link Customer.hasAvailableCredit} compares it directly against a money\n * amount; a dollars-vs-cents split there would approve every request off by\n * a factor of 100 (#2401).\n */\n creditLimit: number = 0;\n\n /**\n * Payment terms (e.g., \"Net 30\", \"Due on receipt\", \"2/10 Net 30\")\n */\n paymentTerms: string = '';\n\n /**\n * Whether customer is exempt from tax\n */\n taxExempt: boolean = false;\n\n /**\n * Tax identification number\n *\n * Sensitive (#1540): PII excluded from generated API/MCP responses and\n * rejected as a `where` filter key.\n */\n @field({ sensitive: true })\n taxId: string = '';\n\n /**\n * Default shipping address\n */\n defaultShippingAddress: Address = {};\n\n /**\n * Default billing address\n */\n defaultBillingAddress: Address = {};\n\n /**\n * Customer status\n */\n status: CustomerStatus = CustomerStatus.ACTIVE;\n\n /**\n * Customer classification — DTC, wholesale, or retail.\n *\n * Drives channel access (wholesale portal vs. storefront vs. POS), default\n * price tier, and credit terms. Defaults to DTC for backwards compatibility.\n */\n customerType: CustomerType = CustomerType.DTC;\n\n /**\n * Internal notes about this customer\n */\n notes: string = '';\n\n constructor(options: CustomerOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.creditLimit !== undefined)\n this.creditLimit = options.creditLimit;\n if (options.paymentTerms !== undefined)\n this.paymentTerms = options.paymentTerms;\n if (options.taxExempt !== undefined) this.taxExempt = options.taxExempt;\n if (options.taxId !== undefined) this.taxId = options.taxId;\n if (options.defaultShippingAddress !== undefined)\n this.defaultShippingAddress = options.defaultShippingAddress;\n if (options.defaultBillingAddress !== undefined)\n this.defaultBillingAddress = options.defaultBillingAddress;\n if (options.status !== undefined) this.status = options.status;\n // Treat `null` the same as `undefined` here. Customers loaded from\n // rows written before the `customer_type` column existed will\n // hydrate with the field as SQL `NULL`; without this guard the\n // SmrtObject loader would overwrite the `CustomerType.DTC`\n // initializer with `null`, breaking the documented\n // backwards-compatible default. Explicit string values (including\n // any future custom flavour) still take precedence.\n if (options.customerType !== undefined && options.customerType !== null) {\n this.customerType = options.customerType;\n }\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Convenience predicate: is this a wholesale customer?\n */\n isWholesale(): boolean {\n return this.customerType === CustomerType.WHOLESALE;\n }\n\n /**\n * Check if customer is active\n */\n isActive(): boolean {\n return this.status === CustomerStatus.ACTIVE;\n }\n\n /**\n * Check if customer has available credit\n */\n hasAvailableCredit(amount: number): boolean {\n // In a full implementation, this would check against outstanding balances\n return this.creditLimit >= amount;\n }\n\n /**\n * Get the profile ID for external lookup.\n *\n * To get the actual Profile object, use the ProfileCollection from smrt-profiles:\n * ```typescript\n * const profiles = await ProfileCollection.create(options);\n * const profile = await profiles.get({ id: customer.profileId });\n * ```\n *\n * @returns The profile ID or empty string if not set\n */\n getProfileId(): string {\n return this.profileId;\n }\n}\n\nexport default Customer;\n","/**\n * CustomerCollection - Collection manager for Customer objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Customer } from '../models/Customer.js';\nimport { CustomerStatus } from '../types/index.js';\n\nexport class CustomerCollection extends SmrtCollection<Customer> {\n static readonly _itemClass = Customer;\n\n /**\n * Find customers by profile ID\n *\n * @param profileId - Profile ID from smrt-profiles\n * @returns Array of customers linked to this profile\n */\n async findByProfile(profileId: string): Promise<Customer[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all active customers\n *\n * @returns Array of active customers\n */\n async findActive(): Promise<Customer[]> {\n return await this.list({\n where: { status: CustomerStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find customers by status\n *\n * @param status - Customer status\n * @returns Array of customers\n */\n async findByStatus(status: CustomerStatus): Promise<Customer[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get or create a customer for a profile\n *\n * @param profileId - Profile ID\n * @param defaults - Default values if creating\n * @returns Customer\n */\n async getOrCreateForProfile(\n profileId: string,\n defaults: Partial<{\n creditLimit: number;\n paymentTerms: string;\n }> = {},\n ): Promise<Customer> {\n const existing = await this.findByProfile(profileId);\n if (existing.length > 0) {\n return existing[0];\n }\n\n const customer = await this.create({\n profileId,\n creditLimit: defaults.creditLimit ?? 0,\n paymentTerms: defaults.paymentTerms ?? '',\n status: CustomerStatus.ACTIVE,\n });\n await customer.save();\n return customer;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all customers belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of customers for the tenant\n */\n async findByTenant(tenantId: string): Promise<Customer[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global customers (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global customers\n */\n async findGlobal(): Promise<Customer[]> {\n return queryGlobal<Customer>(this);\n }\n\n /**\n * Find customers for a tenant including global customers.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global customers\n */\n async findWithGlobals(tenantId: string): Promise<Customer[]> {\n return queryWithGlobals<Customer>(\n this,\n tenantId,\n 'Customer.findWithGlobals',\n );\n }\n}\n","/**\n * PaymentInstrument model - a reusable saved payment method (\"card on file\").\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type PaymentInstrumentOptions,\n PaymentInstrumentStatus,\n} from '../types/index.js';\n\n/**\n * PaymentInstrument stores a reusable payment method a customer has saved with\n * a payment backend (e.g. a card vaulted by Stripe), so it can be charged later\n * without the customer present.\n *\n * It holds only **references and non-sensitive display metadata** — the\n * provider customer id (`cus_...`), the provider payment-method id (`pm_...`),\n * and card display fields (brand / last4 / expiry). The actual card number is\n * vaulted by the payment backend and is never stored here.\n *\n * @example\n * ```typescript\n * const instrument = await instruments.create({\n * customerId: customer.id,\n * backendId: 'stripe',\n * providerCustomerId: 'cus_123',\n * providerPaymentMethodId: 'pm_123',\n * brand: 'visa',\n * last4: '4242',\n * expMonth: 12,\n * expYear: 2030,\n * isDefault: true,\n * });\n * await instrument.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: {\n include: ['list', 'get', 'create', 'update'],\n // `isDefault` is deliberately EXCLUDED from the generated write surface:\n // the single-default-per-customer invariant is enforced by\n // {@link PaymentInstrumentCollection.setDefaultForCustomer}, which clears\n // the flag on the customer's other instruments. Letting a generated\n // create/update route set `isDefault` directly would let a client mark\n // several instruments default (or flip one without clearing the others).\n writable: [\n 'customerId',\n 'backendId',\n 'providerCustomerId',\n 'providerPaymentMethodId',\n 'type',\n 'brand',\n 'last4',\n 'expMonth',\n 'expYear',\n 'status',\n ],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class PaymentInstrument extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable to support both\n * tenant-scoped and global instruments.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Customer this saved method belongs to.\n */\n @foreignKey('Customer')\n customerId: string = '';\n\n /**\n * Stable identifier of the `PaymentBackend` adapter that vaulted the method —\n * e.g. `stripe`. Mirrors {@link Payment.backendId}.\n */\n backendId: string = '';\n\n /**\n * Provider customer reference (e.g. a Stripe `cus_...`). A reference, not\n * card data.\n */\n providerCustomerId: string = '';\n\n /**\n * Provider payment-method reference (e.g. a Stripe `pm_...`). Used, together\n * with {@link providerCustomerId}, to charge the saved method off-session. A\n * reference, not card data.\n */\n providerPaymentMethodId: string = '';\n\n /**\n * Instrument type as reported by the backend — e.g. `card`,\n * `us_bank_account`.\n */\n type: string = 'card';\n\n /**\n * Card brand for display (e.g. `visa`). Display-only, not PCI-sensitive.\n */\n brand: string = '';\n\n /**\n * Last four digits for display. Not the full card number.\n */\n last4: string = '';\n\n /**\n * Card expiry month (1–12). `0` when unknown / not a card.\n */\n expMonth: number = 0;\n\n /**\n * Card expiry year (four digits). `0` when unknown / not a card.\n */\n expYear: number = 0;\n\n /**\n * Whether this is the customer's default instrument. Use\n * {@link PaymentInstrumentCollection.setDefaultForCustomer} to flip the\n * default so only one instrument per customer is marked default.\n */\n isDefault: boolean = false;\n\n /**\n * Lifecycle status — `active` while chargeable, `expired` once the card has\n * lapsed, `removed` when the customer detaches it.\n */\n status: PaymentInstrumentStatus = PaymentInstrumentStatus.ACTIVE;\n\n constructor(options: PaymentInstrumentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.backendId !== undefined) this.backendId = options.backendId;\n if (options.providerCustomerId !== undefined)\n this.providerCustomerId = options.providerCustomerId;\n if (options.providerPaymentMethodId !== undefined)\n this.providerPaymentMethodId = options.providerPaymentMethodId;\n if (options.type !== undefined) this.type = options.type;\n if (options.brand !== undefined) this.brand = options.brand;\n if (options.last4 !== undefined) this.last4 = options.last4;\n if (options.expMonth !== undefined) this.expMonth = options.expMonth;\n if (options.expYear !== undefined) this.expYear = options.expYear;\n if (options.isDefault !== undefined) this.isDefault = options.isDefault;\n if (options.status !== undefined) this.status = options.status;\n }\n\n /**\n * True while the instrument is active (chargeable).\n */\n isActive(): boolean {\n return this.status === PaymentInstrumentStatus.ACTIVE;\n }\n\n /**\n * True once the instrument has been marked expired.\n */\n isExpired(): boolean {\n return this.status === PaymentInstrumentStatus.EXPIRED;\n }\n\n /**\n * True once the instrument has been removed/detached.\n */\n isRemoved(): boolean {\n return this.status === PaymentInstrumentStatus.REMOVED;\n }\n\n /**\n * Mark the instrument expired (e.g. the card lapsed). A removed instrument\n * is not revived by this.\n */\n markExpired(): void {\n if (this.status === PaymentInstrumentStatus.REMOVED) return;\n this.status = PaymentInstrumentStatus.EXPIRED;\n }\n\n /**\n * Mark the instrument removed/detached and clear its default flag.\n */\n markRemoved(): void {\n this.status = PaymentInstrumentStatus.REMOVED;\n this.isDefault = false;\n }\n}\n\nexport default PaymentInstrument;\n","/**\n * PaymentInstrumentCollection - Collection manager for PaymentInstrument objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { PaymentInstrument } from '../models/PaymentInstrument.js';\nimport { PaymentInstrumentStatus } from '../types/index.js';\n\nexport class PaymentInstrumentCollection extends SmrtCollection<PaymentInstrument> {\n static readonly _itemClass = PaymentInstrument;\n\n /**\n * Find all instruments saved by a customer, newest first.\n *\n * @param customerId - Customer ID\n * @returns Array of instruments\n */\n async findByCustomer(customerId: string): Promise<PaymentInstrument[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find a customer's active instruments, newest first.\n *\n * @param customerId - Customer ID\n * @returns Array of active instruments\n */\n async findActiveByCustomer(customerId: string): Promise<PaymentInstrument[]> {\n return await this.list({\n where: { customerId, status: PaymentInstrumentStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find a customer's default instrument, or null if none is set.\n *\n * @param customerId - Customer ID\n * @returns The default instrument or null\n */\n async findDefaultForCustomer(\n customerId: string,\n ): Promise<PaymentInstrument | null> {\n const results = await this.list({\n where: { customerId, isDefault: true },\n orderBy: 'created_at DESC',\n });\n // Skip any instrument left flagged default after removal, and pick\n // deterministically (newest) if more than one row is somehow marked.\n return results.find((instrument) => !instrument.isRemoved()) ?? null;\n }\n\n /**\n * Find an instrument by its provider payment-method reference (e.g. a Stripe\n * `pm_...`).\n *\n * @param providerPaymentMethodId - Provider payment-method id\n * @returns The instrument or null\n */\n async findByProviderPaymentMethodId(\n providerPaymentMethodId: string,\n ): Promise<PaymentInstrument | null> {\n const results = await this.list({\n where: { providerPaymentMethodId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Make one of a customer's instruments the default, clearing the flag on the\n * others so only a single default is ever set. Only rows whose flag actually\n * changes are re-saved.\n *\n * @param customerId - Customer ID\n * @param instrumentId - The instrument to make default\n */\n async setDefaultForCustomer(\n customerId: string,\n instrumentId: string,\n ): Promise<void> {\n const instruments = await this.findByCustomer(customerId);\n const target = instruments.find(\n (instrument) => instrument.id === instrumentId,\n );\n // Fail fast rather than clearing every default — leaving the customer with\n // none — when the target isn't one of this customer's instruments.\n if (!target) {\n throw new Error(\n `PaymentInstrument ${instrumentId} not found for customer ${customerId}`,\n );\n }\n if (target.isRemoved()) {\n throw new Error(\n `PaymentInstrument ${instrumentId} is removed and cannot be made default`,\n );\n }\n for (const instrument of instruments) {\n const shouldBeDefault = instrument.id === instrumentId;\n if (instrument.isDefault !== shouldBeDefault) {\n instrument.isDefault = shouldBeDefault;\n await instrument.save();\n }\n }\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all instruments belonging to a specific tenant.\n *\n * @param tenantId - Tenant ID\n * @returns Array of instruments for the tenant\n */\n async findByTenant(tenantId: string): Promise<PaymentInstrument[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global instruments (not associated with any tenant). Routes\n * through the shared tenant-global helper so it does not throw under an\n * active tenant context. (#1600)\n *\n * @returns Array of global instruments\n */\n async findGlobal(): Promise<PaymentInstrument[]> {\n return queryGlobal<PaymentInstrument>(this);\n }\n\n /**\n * Find instruments for a tenant including global instruments. Fails closed if\n * an active tenant context requests a different tenant's rows. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global instruments\n */\n async findWithGlobals(tenantId: string): Promise<PaymentInstrument[]> {\n return queryWithGlobals<PaymentInstrument>(\n this,\n tenantId,\n 'PaymentInstrument.findWithGlobals',\n );\n }\n}\n","/**\n * Card on file (#3139): a provider checkout that saves a payment method\n * without charging, and the settlement that makes it the payer's default.\n *\n * A completed setup (or a credit purchase that saved its card) is applied\n * from the provider's checkout event: `observe()` re-reads the session and\n * makes the saved method the provider customer's default; `project()`\n * records it as the payer customer's default commerce `PaymentInstrument`\n * and adopts the billing address collected at checkout as the tax location.\n */\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport {\n getTenantId,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport { CustomerCollection } from '../collections/CustomerCollection.js';\nimport { PaymentInstrumentCollection } from '../collections/PaymentInstrumentCollection.js';\nimport type { BillingAccount } from '../models/billing.js';\nimport type { PaymentInstrument } from '../models/PaymentInstrument.js';\nimport { PaymentInstrumentStatus } from '../types/index.js';\nimport type {\n BillingProviderCheckoutSession,\n BillingProviderCheckoutState,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n canonicalTenantId,\n deterministicId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport const CARD_SETUP_PURPOSE = 'card_setup';\n/** Checkout metadata flag: the session saves its payment method. */\nexport const SAVE_CARD_METADATA = 'smrt_save_card';\n/**\n * Checkout metadata flag: the session collects the billing address, which is\n * adopted as the payer's tax location when the card is saved.\n */\nexport const COLLECT_ADDRESS_METADATA = 'smrt_collect_address';\n\nexport interface CreateCardSetupCheckoutInput {\n payerTenantId: string;\n /** The currency the saved card will be charged in. */\n currency: string;\n successUrl: string;\n cancelUrl: string;\n /**\n * Caller-owned id for this setup attempt. A retried request with the same\n * id returns the same checkout; a new attempt (for example after a declined\n * card) needs a new id.\n */\n setupId: string;\n /**\n * Collect the billing address at checkout and adopt it as the payer's tax\n * location. Default: when the account is taxed and has no tax location.\n */\n collectBillingAddress?: boolean;\n}\n\n/** Only the payer (or a system/operator context) manages its card. */\nexport function assertPayerContext(payerTenantId: string): void {\n if (\n !isSystemContext() &&\n !isSuperAdminBypass() &&\n tenantKey(getTenantId()) !== payerTenantId\n ) {\n throw new TenantIsolationError(\n 'Only the payer can manage its payment methods.',\n );\n }\n}\n\nexport async function createCardSetupCheckout(\n runtime: BillingRuntime,\n input: CreateCardSetupCheckoutInput,\n): Promise<BillingProviderCheckoutSession> {\n const createSetupCheckout = runtime.provider.createSetupCheckout;\n if (!createSetupCheckout) {\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot save a payment method.`,\n );\n }\n assertCanSaveCards(runtime);\n if (!input.setupId) throw new Error('setupId is required.');\n const payer = canonicalTenantId(input.payerTenantId, 'payerTenantId');\n assertPayerContext(payer);\n const account = await runtime.getAccount(payer);\n if (!account?.id) throw new Error(`No billing account for payer ${payer}.`);\n // The address may be collected at checkout, so the tax location is not\n // required yet; period close still requires one before invoicing.\n const synced = await runtime.ensureProviderCustomer(account, {\n requireTaxLocation: false,\n });\n const key = await deterministicId([\n 'billing-card-setup',\n runtime.sellerTenantId,\n String(account.id),\n input.setupId,\n ]);\n const collectBillingAddress =\n input.collectBillingAddress ??\n (synced.automaticTax && !synced.hasTaxLocation);\n return createSetupCheckout.call(runtime.provider, {\n idempotencyKey: `smrt-card-setup:${key}`,\n providerCustomerId: synced.providerCustomerId,\n currency: normalizeCurrency(input.currency),\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n collectBillingAddress,\n metadata: {\n smrt_purpose: CARD_SETUP_PURPOSE,\n smrt_seller: runtime.sellerTenantId,\n smrt_payer: payer,\n smrt_account: String(account.id),\n ...(collectBillingAddress ? { [COLLECT_ADDRESS_METADATA]: '1' } : {}),\n },\n });\n}\n\n/**\n * What a completed checkout saved for one of this seller's accounts: a card\n * (setup, or a purchase with `savePaymentMethod`) and/or a billing address it\n * was asked to collect. Validated against the account.\n */\nexport interface SavedCard {\n account: BillingAccount;\n providerCustomerId: string;\n paymentMethodId?: string;\n billingAddress?: BillingProviderCheckoutState['billingAddress'];\n}\n\n/** Whether the provider can apply a saved card from a checkout event. */\nexport function assertCanSaveCards(runtime: BillingRuntime): void {\n if (\n !runtime.provider.getCheckout ||\n !runtime.provider.setDefaultPaymentMethod\n ) {\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot apply a saved payment method (getCheckout and setDefaultPaymentMethod are required).`,\n );\n }\n}\n\n/**\n * The card and/or collected address a completed checkout saved for one of\n * this seller's accounts, or null when it saved neither. Throws when the\n * session and its (verified) metadata disagree, so the event dead-letters\n * visibly.\n */\nexport async function savedCardFromCheckout(\n runtime: BillingRuntime,\n metadata: Record<string, string>,\n state: BillingProviderCheckoutState,\n savesCard: boolean,\n): Promise<SavedCard | null> {\n if (!state.complete) return null;\n const paymentMethodId = savesCard ? state.paymentMethodId : undefined;\n // Only an address the checkout was asked to collect replaces the local\n // tax location; otherwise the provider's copy is the one synced from it.\n const billingAddress =\n metadata[COLLECT_ADDRESS_METADATA] === '1' && state.billingAddress?.country\n ? state.billingAddress\n : undefined;\n if (!paymentMethodId && !billingAddress) return null;\n if (tenantKey(metadata.smrt_seller) !== runtime.sellerTenantId) return null;\n const account = await runtime.accounts.get(metadata.smrt_account ?? '');\n if (\n !account?.id ||\n tenantKey(account.sellerTenantId) !== runtime.sellerTenantId ||\n tenantKey(account.payerTenantId) !== tenantKey(metadata.smrt_payer)\n ) {\n throw new Error(\n `Checkout ${state.sessionId} does not belong to the billing account in its metadata.`,\n );\n }\n if (\n account.provider !== runtime.provider.name ||\n !account.providerCustomerId ||\n state.providerCustomerId !== account.providerCustomerId\n ) {\n // The account's provider customer changed while the checkout was open\n // (#3152). A saved card would attach to the wrong customer, so that\n // fails visibly; an address collected for a credit purchase is skipped\n // (the next checkout or card setup collects it again) so the credit the\n // payer paid for still settles.\n if (!paymentMethodId) return null;\n throw new Error(\n `Checkout ${state.sessionId} saved a card for provider customer ${state.providerCustomerId ?? '(none)'}, not its billing account's.`,\n );\n }\n return {\n account,\n providerCustomerId: account.providerCustomerId,\n paymentMethodId,\n billingAddress,\n };\n}\n\n/**\n * Record a saved card as the customer's default instrument, and adopt a\n * collected billing address. Idempotent; runs in the event transaction.\n */\nexport async function recordSavedCard(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n card: SavedCard,\n): Promise<void> {\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n if (card.paymentMethodId) await recordInstrument(runtime, db, card);\n if (card.billingAddress?.country) {\n const customers = await CustomerCollection.create({ db });\n const customer = await customers.get(card.account.customerId);\n if (customer) {\n customer.defaultBillingAddress = card.billingAddress;\n await customer.save();\n }\n }\n });\n}\n\nasync function recordInstrument(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n card: SavedCard,\n): Promise<void> {\n const paymentMethodId = String(card.paymentMethodId);\n const instruments = await PaymentInstrumentCollection.create({ db });\n const id = await deterministicId([\n 'billing-payment-instrument',\n runtime.provider.name,\n runtime.sellerTenantId,\n paymentMethodId,\n ]);\n let instrument = await instruments.get(id);\n if (!instrument) {\n instrument = await instruments.create({\n id,\n tenantId: runtime.sellerTenantId,\n customerId: card.account.customerId,\n backendId: runtime.provider.name,\n providerCustomerId: card.providerCustomerId,\n providerPaymentMethodId: paymentMethodId,\n type: 'card',\n status: PaymentInstrumentStatus.ACTIVE,\n _insertOnly: true,\n });\n } else if (instrument.customerId !== card.account.customerId) {\n throw new Error(\n `Payment method ${paymentMethodId} is already saved for another customer.`,\n );\n } else if (!instrument.isActive()) {\n instrument.status = PaymentInstrumentStatus.ACTIVE;\n await instrument.save();\n }\n await instruments.setDefaultForCustomer(\n card.account.customerId,\n String(instrument.id),\n );\n}\n\n/**\n * The payer's default card when it is with this runtime's provider customer,\n * else null: what `autoChargeInvoices` bills and automatic top-ups charge.\n */\nexport async function defaultCardInstrument(\n runtime: BillingRuntime,\n account: BillingAccount,\n providerCustomerId: string,\n): Promise<PaymentInstrument | null> {\n const instrument = await withTenant(\n { tenantId: runtime.sellerTenantId },\n () => runtime.instruments.findDefaultForCustomer(account.customerId),\n );\n return instrument?.isActive() &&\n instrument.backendId === runtime.provider.name &&\n instrument.providerCustomerId === providerCustomerId\n ? instrument\n : null;\n}\n\n/** Whether {@link defaultCardInstrument} finds a card. */\nexport async function defaultCardFor(\n runtime: BillingRuntime,\n account: BillingAccount,\n providerCustomerId: string,\n): Promise<boolean> {\n return Boolean(\n await defaultCardInstrument(runtime, account, providerCustomerId),\n );\n}\n\n/** Whether a payer has a card on file, and since when (#3187). */\nexport interface BillingCardOnFile {\n /**\n * The payer has a default card with this runtime's provider customer: the\n * card automatically charged invoices bill and automatic top-ups charge.\n */\n onFile: boolean;\n /** When that card was first recorded. */\n savedAt?: Date;\n}\n\n/** See {@link BillingRuntime.cardOnFile}. */\nexport async function cardOnFile(\n runtime: BillingRuntime,\n payerTenantId: string,\n): Promise<BillingCardOnFile> {\n const payer = canonicalTenantId(payerTenantId, 'payerTenantId');\n assertPayerContext(payer);\n const account = await runtime.getAccount(payer);\n if (\n !account?.id ||\n account.provider !== runtime.provider.name ||\n !account.providerCustomerId\n ) {\n return { onFile: false };\n }\n const instrument = await defaultCardInstrument(\n runtime,\n account,\n account.providerCustomerId,\n );\n if (!instrument) return { onFile: false };\n const created = instrument.created_at;\n const savedAt =\n created instanceof Date ? created : created ? new Date(created) : null;\n return savedAt && Number.isFinite(savedAt.getTime())\n ? { onFile: true, savedAt }\n : { onFile: true };\n}\n","/**\n * Prepaid credit purchases through provider checkout (#3060). A paid\n * checkout credits the balance policy through smrt-subscriptions'\n * `grantCredit()`, keyed by the checkout session so it is credited once.\n * Provider-calculated tax is charged on top of the credit (#3139).\n */\nimport {\n getTenantId,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withSystemContext,\n} from '@happyvertical/smrt-tenancy';\nimport {\n assertCanSaveCards,\n COLLECT_ADDRESS_METADATA,\n SAVE_CARD_METADATA,\n} from './cards.js';\nimport {\n BillingPaymentRefusedError,\n type BillingProviderCheckoutSession,\n providerCapabilities,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n canonicalTenantId,\n deterministicId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport const CREDIT_PURCHASE_PURPOSE = 'credit_purchase';\n\nexport interface CreateCreditCheckoutInput {\n /** The `period: 'balance'` spending policy to credit. */\n spendingPolicyId: string;\n /** Credit to buy, in integer minor units of the policy currency. */\n amount: number;\n successUrl: string;\n cancelUrl: string;\n /**\n * Caller-owned id for this purchase attempt (for example a cart id). A\n * retried request with the same id returns the same checkout.\n */\n purchaseId: string;\n /** Checkout line description (default `Prepaid credit`). */\n description?: string;\n /**\n * Charge provider-calculated tax on top of the credit (#3139). Default:\n * the payer's account setting (`automaticTax`, off for tax-exempt\n * customers). The credit granted is always `amount`; tax is booked to the\n * tax account.\n */\n automaticTax?: boolean;\n /**\n * Also save the card as the payer's default for automatic top-ups and\n * automatically charged invoices (#3139).\n */\n savePaymentMethod?: boolean;\n /**\n * The payment rail to pay with (a provider name from the runtime's\n * `provider` or `paymentProviders`; default the issuing provider, #3138).\n */\n provider?: string;\n}\n\n/** The checkout metadata a completed purchase is settled from. */\nexport interface CreditPurchaseMetadata {\n purpose: string;\n sellerTenantId: string;\n payerTenantId: string;\n billingAccountId: string;\n spendingPolicyId: string;\n grantedByTenantId: string;\n amount: number;\n currency: string;\n}\n\nfunction encodeMetadata(\n metadata: CreditPurchaseMetadata,\n): Record<string, string> {\n const encoded: Record<string, string> = {\n smrt_purpose: metadata.purpose,\n smrt_seller: metadata.sellerTenantId,\n smrt_payer: metadata.payerTenantId,\n smrt_account: metadata.billingAccountId,\n smrt_policy: metadata.spendingPolicyId,\n smrt_granted_by: metadata.grantedByTenantId,\n smrt_amount: String(metadata.amount),\n smrt_currency: metadata.currency,\n };\n // Providers drop empty metadata values (Stripe treats \"\" as unset), so an\n // empty key is omitted rather than signed and then lost in transit.\n for (const [key, value] of Object.entries(encoded)) {\n if (!value) delete encoded[key];\n }\n return encoded;\n}\n\n/** Parse checkout metadata written by {@link createCreditCheckout}. */\nexport function creditMetadata(\n raw: Record<string, string>,\n): CreditPurchaseMetadata | null {\n if (raw.smrt_purpose !== CREDIT_PURCHASE_PURPOSE) return null;\n const amount = Number(raw.smrt_amount);\n if (\n !raw.smrt_seller ||\n !raw.smrt_payer ||\n !raw.smrt_account ||\n !raw.smrt_policy ||\n !raw.smrt_currency ||\n !Number.isSafeInteger(amount) ||\n amount <= 0\n ) {\n throw new Error('Credit purchase metadata is incomplete.');\n }\n return {\n purpose: raw.smrt_purpose,\n sellerTenantId: tenantKey(raw.smrt_seller),\n payerTenantId: tenantKey(raw.smrt_payer),\n billingAccountId: raw.smrt_account,\n spendingPolicyId: raw.smrt_policy,\n grantedByTenantId: tenantKey(raw.smrt_granted_by),\n amount,\n currency: normalizeCurrency(raw.smrt_currency),\n };\n}\n\nexport async function createCreditCheckout(\n runtime: BillingRuntime,\n input: CreateCreditCheckoutInput,\n): Promise<BillingProviderCheckoutSession> {\n if (!Number.isSafeInteger(input.amount) || input.amount <= 0) {\n throw new BillingPaymentRefusedError(\n 'invalid_amount',\n 'Credit amount must be positive integer minor units.',\n );\n }\n if (!input.purchaseId) throw new Error('purchaseId is required.');\n const policy = await withSystemContext(() =>\n runtime.policies.get(input.spendingPolicyId),\n );\n if (!policy?.id || policy.period !== 'balance' || !policy.active) {\n throw new BillingPaymentRefusedError(\n // One code whether it is missing or inactive: this runs before the\n // caller is known to be the payer.\n 'not_found',\n `Spending policy ${input.spendingPolicyId} is not an active balance policy.`,\n );\n }\n // A delegated balance is funded only by the parent that set it.\n const grantedBy = tenantKey(policy.setByTenantId);\n const payer = canonicalTenantId(\n grantedBy || String(policy.tenantId),\n 'payerTenantId',\n );\n if (\n !isSystemContext() &&\n !isSuperAdminBypass() &&\n tenantKey(getTenantId()) !== payer\n ) {\n throw new TenantIsolationError(\n 'Only the payer of a balance policy can buy credit for it.',\n );\n }\n const account = await runtime.getAccount(payer);\n if (!account?.id) {\n throw new BillingPaymentRefusedError(\n 'no_account',\n `No billing account for payer ${payer}.`,\n );\n }\n const currency = normalizeCurrency(policy.currency);\n const provider = runtime.providerFor(input.provider);\n const issuing = provider === runtime.provider;\n if (!issuing && input.savePaymentMethod) {\n throw new Error(`The ${provider.name} payment rail cannot save a card.`);\n }\n // Saving a card needs a provider customer to attach it to; the address may\n // still be collected at checkout.\n // Saving a card is settled from the checkout event, so refuse a provider\n // that could take the money but not record the card.\n if (input.savePaymentMethod) assertCanSaveCards(runtime);\n const synced = input.savePaymentMethod\n ? await runtime.ensureProviderCustomer(account, {\n requireTaxLocation: false,\n })\n : null;\n const metadata = encodeMetadata({\n purpose: CREDIT_PURCHASE_PURPOSE,\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId: payer,\n billingAccountId: String(account.id),\n spendingPolicyId: String(policy.id),\n grantedByTenantId: grantedBy,\n amount: input.amount,\n currency,\n });\n const key = await deterministicId([\n 'billing-credit-checkout',\n runtime.sellerTenantId,\n String(policy.id),\n input.purchaseId,\n // The issuing provider keeps the pre-#3138 key, so retries of existing\n // purchases still return their checkout.\n ...(issuing ? [] : [provider.name]),\n ]);\n const providerCustomerId =\n synced?.providerCustomerId ??\n (account.provider === provider.name ? account.providerCustomerId : '');\n const automaticTax =\n input.automaticTax ?? (await runtime.accountIsTaxed(account));\n if (!issuing && automaticTax) {\n // A payment rail cannot calculate tax (#3138). Selling untaxed credit on\n // it is the host's explicit decision: pass `automaticTax: false`.\n throw new Error(\n `The ${provider.name} payment rail cannot charge tax; pass automaticTax: false to sell this payer untaxed credit on it.`,\n );\n }\n if (synced) metadata[SAVE_CARD_METADATA] = '1';\n // A taxed checkout for a provider customer collects the address onto that\n // customer; with no local tax location yet, it becomes the payer's (as a\n // card setup does), so period close can tax the payer too. A checkout\n // without a provider customer keeps no customer to read it from.\n if (\n automaticTax &&\n providerCustomerId &&\n provider.getCheckout &&\n !(await runtime.hasTaxLocation(account))\n ) {\n metadata[COLLECT_ADDRESS_METADATA] = '1';\n }\n const session = await provider.createCheckout({\n idempotencyKey: `smrt-credit-checkout:${key}`,\n providerCustomerId: providerCustomerId || undefined,\n customerEmail: providerCustomerId ? undefined : account.email || undefined,\n currency,\n amount: input.amount,\n description: input.description || 'Prepaid credit',\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n metadata,\n automaticTax,\n ...(automaticTax && runtime.taxCodes.credit\n ? { taxCode: runtime.taxCodes.credit }\n : {}),\n ...(input.savePaymentMethod ? { savePaymentMethod: true } : {}),\n });\n if (providerCapabilities(provider).paymentAttempts) {\n await runtime.recordPaymentAttemptStart({\n orderId: `smrt-credit-checkout:${key}`,\n provider: provider.name,\n checkoutId: session.sessionId,\n checkoutUrl: session.url ?? '',\n purpose: 'credit_purchase',\n payerTenantId: payer,\n billingAccountId: String(account.id),\n spendingPolicyId: String(policy.id),\n amount: input.amount,\n currency,\n });\n }\n return session;\n}\n","/**\n * Payment model - tracks payments with ledger integration\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Journal } from '@happyvertical/smrt-ledgers';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport {\n PaymentMethod,\n type PaymentOptions,\n PaymentStatus,\n type RecordPaymentOptions,\n} from '../types/index.js';\n\n/**\n * Legal status transitions for a Payment, keyed by the prior persisted status.\n * A status mapping to itself (no-op re-save) is always permitted and handled\n * separately. Transitioning *into* COMPLETED is additionally gated on the\n * verified `recordPayment()` settlement path (see {@link Payment.save}) — it is\n * never reachable by raw mass-assignment even though it appears here as a\n * structurally legal edge out of PENDING.\n *\n * Forward path: PENDING → COMPLETED, with FAILED / CANCELLED as alternate\n * terminal exits from PENDING and REFUNDED reachable from COMPLETED.\n */\nconst PAYMENT_STATUS_TRANSITIONS: Record<PaymentStatus, PaymentStatus[]> = {\n [PaymentStatus.PENDING]: [\n PaymentStatus.COMPLETED,\n PaymentStatus.FAILED,\n PaymentStatus.CANCELLED,\n ],\n // A completed payment can only be reversed via refund.\n [PaymentStatus.COMPLETED]: [PaymentStatus.REFUNDED],\n // Terminal states.\n [PaymentStatus.FAILED]: [],\n [PaymentStatus.REFUNDED]: [],\n [PaymentStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each Payment instance was loaded with,\n * so the save-time transition guard can compare the prior persisted status\n * against the one being written without adding a persisted column. WeakMap\n * keeps it out of the schema and GCs with the instance — same pattern as the\n * other commerce models.\n */\nconst loadedPaymentStatus = new WeakMap<Payment, PaymentStatus>();\n\n/**\n * Marks instances whose transition into COMPLETED is being driven by the\n * verified `recordPayment()` settlement path (which posts a balanced journal\n * before flipping the status). The save-time guard consults this set so that\n * COMPLETED can only be reached through that path, never via raw\n * mass-assignment on the generated update route. Cleared after the save runs.\n */\nconst settlementInProgress = new WeakSet<Payment>();\n\n/**\n * Payment represents a financial transaction against a contract.\n *\n * Payments can be integrated with smrt-ledgers to automatically\n * create balanced journal entries for proper accounting.\n *\n * @example\n * ```typescript\n * // Create a payment\n * const payment = await payments.create({\n * contractId: order.id,\n * customerId: customer.id,\n * amount: 1500.00,\n * method: PaymentMethod.CREDIT_CARD,\n * transactionId: 'stripe_pi_123456'\n * });\n *\n * // Record with ledger integration\n * await payment.recordPayment({\n * ledgerId: ledger.id,\n * receivablesAccountId: arAccount.id,\n * cashAccountId: bankAccount.id\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): restrict the generated create/update\n // write surface so privileged / settlement-derived fields can NEVER be set\n // by a generated REST/MCP route — closing the status-mass-assignment vector\n // at the surface rather than relying solely on the save()-time guard.\n //\n // `writable` is an allowlist: only these fields survive applyWritablePolicy()\n // on BOTH create and update (#1540). Deliberately EXCLUDED:\n // - `status` — a COMPLETED Payment is settlement proof downstream\n // (PaymentIntent's PAID verification trusts it). It is only ever set by\n // the verified `recordPayment()` settlement path, never by a caller. This\n // closes the \"forged COMPLETED Payment via api.create\" vector (codex HIGH#1).\n // - `journalId` / `paidAt` — settlement-derived, written by recordPayment().\n // - `syncedAt` — provider-sync bookkeeping, written by the sync path.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'contractId',\n 'customerId',\n 'amount',\n 'currency',\n 'method',\n 'transactionId',\n 'reference',\n 'notes',\n 'externalId',\n 'externalProvider',\n 'backendId',\n 'backendTxRef',\n 'nativeAmount',\n 'nativeCurrency',\n 'usdAtQuote',\n 'usdAtConfirmation',\n ],\n },\n // NOTE: `recordPayment` is intentionally NOT exposed over MCP. It posts a\n // balanced journal into smrt-ledgers (moves money in the books) and flips\n // the payment to COMPLETED — not safe as an unguarded MCP tool that carries\n // no authz. Financial mutations must go through application code that\n // enforces permissions. The MCP create/update tools honor the same\n // `api.writable` allowlist above, so `status` is unsettable over MCP too.\n // See S5 audit #1390.\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Payment extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global payments\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Contract this payment is for\n */\n @foreignKey('Contract')\n contractId: string | null = null;\n\n /**\n * Customer who made the payment\n */\n @foreignKey('Customer')\n customerId: string | null = null;\n\n /**\n * Payment amount, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. This is the figure\n * `PaymentAllocation.save()` caps allocations against and `Invoice.amountPaid`\n * is summed from, so it must be in the same exact units as both (#2401).\n */\n amount: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n currency: string = 'USD';\n\n /**\n * Payment method used\n */\n method: PaymentMethod = PaymentMethod.BANK_TRANSFER;\n\n /**\n * Current payment status\n */\n status: PaymentStatus = PaymentStatus.PENDING;\n\n /**\n * External transaction ID (from payment processor)\n */\n transactionId: string = '';\n\n /**\n * Internal reference number\n */\n reference: string = '';\n\n /**\n * Link to smrt-ledgers Journal (created by recordPayment)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n journalId: string = '';\n\n /**\n * When the payment was completed\n */\n paidAt: Date | null = null;\n\n /**\n * Notes about the payment\n */\n notes: string = '';\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * External ID in accounting provider (e.g., QBO payment ID)\n */\n externalId: string = '';\n\n /**\n * Accounting provider name ('quickbooks' | 'stripe' | 'paypal' | etc.)\n */\n externalProvider: string = '';\n\n /**\n * When payment was last synced to provider\n */\n syncedAt: Date | null = null;\n\n // ============================================================================\n // Payment Backend Identity\n // ============================================================================\n //\n // When a `PaymentBackend` adapter (Stripe, x402, BTC RPC, etc.) brokers the\n // payment rather than a human recording it manually, we capture the\n // backend identity, its native-currency amount, and the USD valuation at\n // both quote and confirmation time. The classic `amount` / `currency`\n // pair stays as the canonical settlement number; the backend fields\n // describe *how* the funds arrived and let downstream accounting close\n // the loop on volatile-currency drift.\n //\n // All fields default to empty / zero so existing Payment consumers that\n // pre-date the marketplace adapter machinery are unaffected.\n\n /**\n * Stable identifier of the `PaymentBackend` adapter that served this\n * payment — e.g. `base-usdc`, `solana-usdc`, `btc`, `stripe`, `paypal`.\n *\n * Distinct from {@link externalProvider}: `backendId` names the SMRT\n * payment-rail adapter, while `externalProvider` names a downstream\n * accounting destination (QuickBooks, Stripe-the-accounting-source).\n * The same Stripe payment will have `backendId: 'stripe'` AND\n * `externalProvider: 'stripe'`; a crypto payment synced to QBO will\n * have `backendId: 'base-usdc'` and `externalProvider: 'quickbooks'`.\n */\n backendId: string = '';\n\n /**\n * Chain transaction hash or backend aggregator's reference id. For\n * on-chain payments this is the tx hash (`0x...` on EVM, base58 sig on\n * Solana, txid on Bitcoin); for fiat-rail backends it's the gateway's\n * own reference. Kept distinct from {@link transactionId} so consumers\n * that already populate `transactionId` with a provider-internal id\n * don't have to overload it.\n */\n backendTxRef: string = '';\n\n /**\n * The amount the backend actually moved, in the native currency's own\n * **integer minor units** — satoshis for BTC, cents for a fiat rail. For\n * stablecoin rails this typically equals `amount`; for volatile-currency\n * rails it's the satoshi/wei figure the chain recorded, independent of any\n * USD valuation.\n *\n * Fresh PostgreSQL and DuckDB INTEGER columns materialize as BIGINT. SMRT\n * rejects values outside JavaScript's safe-integer range during hydration;\n * existing PostgreSQL `int4` columns require the explicit widening in #2424.\n */\n nativeAmount: number = 0;\n\n /**\n * Code identifying the native currency `nativeAmount` is denominated\n * in. Mirrors the `backendId` namespacing convention — `USDC-base`,\n * `BTC`, `ETH`, `USD-stripe`. Empty string means \"use {@link currency}\"\n * (i.e. the payment was already quoted and settled in the same\n * currency).\n */\n nativeCurrency: string = '';\n\n /**\n * USD valuation of the payment at the moment the price was quoted to\n * the buyer (typically the moment a `PaymentIntent` was issued), in\n * **integer minor units** (US cents). A default of `0` means no USD-quote\n * snapshot was taken (the payment was already USD-denominated or the\n * backend doesn't require drift accounting).\n *\n * This is money describing the same funds as {@link Payment.amount}, so it\n * converts with it — a dollars-here / cents-there split would make\n * {@link Payment.usdDrift} meaningless (#2401).\n */\n usdAtQuote: number = 0;\n\n /**\n * USD valuation of the payment at the moment it was confirmed on the\n * backend (chain confirmation, gateway settlement, etc.), in integer minor\n * units (US cents). The delta between {@link usdAtQuote} and\n * `usdAtConfirmation` is the USD drift the operator absorbs (positive or\n * negative) when accepting payment in a volatile native currency (#2401).\n */\n usdAtConfirmation: number = 0;\n\n constructor(options: PaymentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.method !== undefined) this.method = options.method;\n if (options.status !== undefined) this.status = options.status;\n if (options.transactionId !== undefined)\n this.transactionId = options.transactionId;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.journalId !== undefined) this.journalId = options.journalId;\n if (options.paidAt !== undefined) this.paidAt = options.paidAt;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.externalProvider !== undefined)\n this.externalProvider = options.externalProvider;\n if (options.syncedAt !== undefined) this.syncedAt = options.syncedAt;\n if (options.backendId !== undefined) this.backendId = options.backendId;\n if (options.backendTxRef !== undefined)\n this.backendTxRef = options.backendTxRef;\n if (options.nativeAmount !== undefined)\n this.nativeAmount = options.nativeAmount;\n if (options.nativeCurrency !== undefined)\n this.nativeCurrency = options.nativeCurrency;\n if (options.usdAtQuote !== undefined) this.usdAtQuote = options.usdAtQuote;\n if (options.usdAtConfirmation !== undefined)\n this.usdAtConfirmation = options.usdAtConfirmation;\n }\n\n /**\n * Capture the persisted status the row was loaded with, so the save-time\n * transition guard can reject illegal status flips made via raw field\n * assignment (mass-assignment on the generated update route, a stale caller,\n * etc.). Freshly-constructed (not-yet-saved) payments have no prior status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedPaymentStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Save-time state-machine guard (S5 audit #1390).\n *\n * `status` is mass-assignable on the generated update/create routes, and a\n * COMPLETED Payment is treated as settlement proof downstream — e.g.\n * {@link PaymentIntent}'s PAID verification trusts a COMPLETED Payment row.\n * A forged `status: 'completed'` (with arbitrary amounts and no journal)\n * would therefore satisfy that check without any money having moved.\n *\n * This guard enforces two things:\n * - **Transitions must be legal** per {@link PAYMENT_STATUS_TRANSITIONS}.\n * - **Reaching COMPLETED requires the verified settlement path — on\n * creation AND on update.** Only `recordPayment()` (which posts a\n * balanced journal and links `journalId`) may write a Payment into\n * COMPLETED; it announces itself via {@link settlementInProgress}. A raw\n * `status: 'completed'` is rejected whether the row is brand-new (a\n * GENESIS `create({ status: 'completed' })`) or already persisted — that\n * is the exact path a forged COMPLETED would take to satisfy\n * PaymentIntent's PAID verification without any money having moved (codex\n * HIGH#1, #1390 round 5). There is deliberately NO import/fixture\n * carve-out: a COMPLETED Payment is settlement proof downstream, so it is\n * only ever reachable through `recordPayment()`, on every surface\n * (REST/MCP/CLI/direct). Fixtures/migrations that need a completed payment\n * must drive it through `recordPayment()` (or start non-COMPLETED).\n */\n override async save(): Promise<this> {\n // Whole minor units on every surface (#2401). `amount` is what\n // `PaymentAllocation.save()` and `Payout.assertCappedBySourcePayment()` cap\n // against with exact integer comparisons, so a fractional major-unit write\n // here would silently break both.\n assertIntegerMinorUnits('Payment', this.id || '<new>', [\n ['amount', this.amount],\n ['nativeAmount', this.nativeAmount],\n ['usdAtQuote', this.usdAtQuote],\n ['usdAtConfirmation', this.usdAtConfirmation],\n ]);\n\n const priorRow = await this.loadPersistedRow();\n const prior =\n priorRow && priorRow.status != null\n ? (priorRow.status as PaymentStatus)\n : loadedPaymentStatus.get(this);\n this.assertStatusTransition(prior);\n\n // Reaching COMPLETED is only legal via the verified settlement path,\n // regardless of whether this is a create (no persisted prior) or an\n // update of an existing row. A GENESIS create with status: COMPLETED would\n // otherwise forge settlement proof, so we close it here at the model level\n // — this covers EVERY surface (REST/MCP/CLI/direct), independent of any\n // writable-allowlist gap on a given surface.\n const reachingCompleted =\n this.status === PaymentStatus.COMPLETED &&\n prior !== PaymentStatus.COMPLETED;\n if (reachingCompleted && !settlementInProgress.has(this)) {\n throw new Error(\n `Payment ${this.id || '<new>'}: cannot set status to COMPLETED via ` +\n 'raw assignment — a Payment is only completed through ' +\n 'recordPayment(), which posts a balanced settlement journal. Use ' +\n 'recordPayment() instead of setting status directly.',\n );\n }\n\n // Freeze the settled monetary fields once the row is already COMPLETED in\n // the database (S5 audit #1390 round 6 — codex ROOT INSIGHT; mirrors the\n // PaymentIntent PAID backing-field freeze). A COMPLETED Payment is\n // settlement proof: its amount was reconciled against a balanced journal\n // and is trusted downstream — PaymentIntent's PAID verification binds the\n // winning option's `nativeAmount` to `Payment.amount` / `nativeAmount`. The\n // only legal exit from COMPLETED is COMPLETED → REFUNDED, and a refund must\n // NOT alter the settled figures. Closing this at the model level covers\n // EVERY surface (REST/MCP/CLI/direct), independent of the per-surface\n // writable allowlist (`amount`/`currency`/`nativeAmount`/... are writable\n // on a *new* PENDING payment, but must be immutable once settled).\n if (priorRow && priorRow.status === PaymentStatus.COMPLETED) {\n this.assertSettledAmountsUnchanged(priorRow);\n }\n\n try {\n const result = (await super.save()) as this;\n loadedPaymentStatus.set(this, this.status);\n return result;\n } finally {\n settlementInProgress.delete(this);\n }\n }\n\n /**\n * Reject an illegal status flip. Compares the about-to-be-written status\n * against the status the row was loaded with. No-op transitions (status\n * unchanged) and brand-new rows (no prior) are always allowed — the\n * COMPLETED-specific settlement requirement is enforced separately in\n * {@link save}.\n */\n private assertStatusTransition(prior: PaymentStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status (subject to the COMPLETED gate)\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYMENT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Payment ${this.id}: illegal status transition '${prior}' → '${this.status}'. ` +\n 'Use the guarded helpers (recordPayment / markFailed / cancel).',\n );\n }\n }\n\n /**\n * Load the AUTHORITATIVE persisted row (S5 audit #1390 round 4, codex HIGH#1;\n * extended round 6 to carry the full row, not just `status`). The WeakMap is\n * only populated when {@link initialize} loaded the row from the DB; it is\n * empty for an instance built via\n * `collection.create({ id: <existing>, _skipLoad: true })` — the upsert path\n * that lets a caller write onto an existing row without hydrating it. Trusting\n * an empty WeakMap there would treat the write as a brand-new row and skip the\n * transition guard entirely (a poisonable prior-state).\n *\n * So when this instance carries an `id`, read the persisted row straight from\n * the database and use it as the prior — a create-onto-existing is an update.\n * Returns `undefined` when no row exists (truly new), so callers fall back to\n * the WeakMap (which is also empty then). Settled-amount columns are\n * snake_case (`native_amount`, etc.); the `status` column is single-word.\n */\n private async loadPersistedRow(): Promise<\n Record<string, unknown> | undefined\n > {\n if (!this.id) return undefined;\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n return row ?? undefined;\n } catch {\n // DB not ready / table absent — treat as new (in-memory fallback in save).\n return undefined;\n }\n }\n\n /**\n * Reject any change to the settled monetary fields of an already-COMPLETED\n * Payment (S5 audit #1390 round 6 — codex ROOT INSIGHT). Mirrors the\n * PaymentIntent PAID backing-field freeze. A COMPLETED Payment's economic\n * identity is settled: the `amount` was posted into a balanced journal and is\n * the figure PaymentIntent's PAID verification reconciles against, so it (and\n * the native/USD valuation fields that describe the same money) must not\n * drift. The only legal status move out of COMPLETED is REFUNDED, which\n * reverses the funds without rewriting how much they were. Compared against\n * the AUTHORITATIVE persisted row so the freeze holds on every surface, even\n * for an un-hydrated create-onto-existing upsert.\n */\n private assertSettledAmountsUnchanged(\n priorRow: Record<string, unknown>,\n ): void {\n const frozen: Array<[string, number | string, number | string]> = [\n ['amount', Number(priorRow.amount ?? 0), Number(this.amount ?? 0)],\n [\n 'currency',\n String(priorRow.currency ?? ''),\n String(this.currency ?? ''),\n ],\n [\n 'nativeAmount',\n Number(priorRow.native_amount ?? 0),\n Number(this.nativeAmount ?? 0),\n ],\n [\n 'nativeCurrency',\n String(priorRow.native_currency ?? ''),\n String(this.nativeCurrency ?? ''),\n ],\n [\n 'usdAtQuote',\n Number(priorRow.usd_at_quote ?? 0),\n Number(this.usdAtQuote ?? 0),\n ],\n [\n 'usdAtConfirmation',\n Number(priorRow.usd_at_confirmation ?? 0),\n Number(this.usdAtConfirmation ?? 0),\n ],\n ];\n for (const [field, prior, next] of frozen) {\n if (prior !== next) {\n throw new Error(\n `Payment ${this.id}: '${field}' is frozen once the payment is ` +\n `COMPLETED (was '${prior}', got '${next}'). A settled payment's ` +\n 'monetary fields cannot be mutated — issue a refund instead.',\n );\n }\n }\n }\n\n /**\n * USD drift between quote time and confirmation time — what the\n * operator gained (positive) or lost (negative) by accepting a\n * volatile-currency payment. Returns `0` when either side of the\n * comparison is missing or zero, so callers don't have to special-case\n * fiat-rail / stablecoin payments.\n */\n usdDrift(): number {\n if (!this.usdAtQuote || !this.usdAtConfirmation) return 0;\n return this.usdAtConfirmation - this.usdAtQuote;\n }\n\n /**\n * Check if payment is pending\n */\n isPending(): boolean {\n return this.status === PaymentStatus.PENDING;\n }\n\n /**\n * Check if payment is completed\n */\n isCompleted(): boolean {\n return this.status === PaymentStatus.COMPLETED;\n }\n\n /**\n * Check if payment is refunded\n */\n isRefunded(): boolean {\n return this.status === PaymentStatus.REFUNDED;\n }\n\n /**\n * Record the payment and create a balanced journal entry.\n *\n * Creates a journal entry in smrt-ledgers:\n * - Debit: Cash/Bank account (assets increase)\n * - Credit: Accounts Receivable (receivables decrease)\n *\n * Both sides carry `this.amount`, so the journal posted from commerce is\n * denominated in **integer minor units** like the rest of this package\n * (#2401). smrt-ledgers is unit-agnostic — its `BALANCE_EPSILON` only checks\n * that debits equal credits — and integer entries balance exactly, so this is\n * strictly tighter than the major-unit journals commerce used to post.\n *\n * @param options - Ledger and account configuration\n * @returns The created journal\n *\n * @example\n * ```typescript\n * const journal = await payment.recordPayment({\n * ledgerId: ledger.id,\n * receivablesAccountId: arAccount.id,\n * cashAccountId: bankAccount.id\n * });\n * console.log(`Created journal: ${journal.number}`);\n * ```\n */\n async recordPayment(options: RecordPaymentOptions): Promise<Journal> {\n if (this.status === PaymentStatus.COMPLETED) {\n throw new Error('Payment already recorded');\n }\n\n if (this.amount <= 0) {\n throw new Error('Payment amount must be positive');\n }\n\n // Dynamic import to avoid hard dependency on smrt-ledgers\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n\n // Create the journal collection\n const journalCollection = await JournalCollection.create(this.options);\n\n // Create a new journal for this payment\n const journal = await journalCollection.create({\n date: new Date(),\n description: `Payment received for contract ${this.contractId}`,\n sourceModule: 'smrt-commerce',\n sourceRef: this.id,\n });\n await journal.save();\n\n // Add balanced entries:\n // Debit Cash/Bank (assets increase)\n await journal.addEntry({\n accountId: options.cashAccountId,\n debit: this.amount,\n memo: `Payment ${this.reference || this.id}`,\n });\n\n // Credit Accounts Receivable (receivables decrease)\n await journal.addEntry({\n accountId: options.receivablesAccountId,\n credit: this.amount,\n memo: `Payment ${this.reference || this.id}`,\n });\n\n // Post the journal (validates balance and finalizes)\n await journal.post();\n\n // Update payment record. Announce the verified settlement to the save-time\n // guard so the COMPLETED transition is accepted (the guard rejects any\n // other route into COMPLETED). A posted journal always carries an id;\n // fail fast rather than persist an empty-string FK if that invariant breaks.\n if (!journal.id) {\n throw new Error(\n 'Payment.recordPayment: journal was posted but has no id',\n );\n }\n this.journalId = journal.id;\n this.status = PaymentStatus.COMPLETED;\n this.paidAt = new Date();\n settlementInProgress.add(this);\n await this.save();\n\n return journal;\n }\n\n /**\n * Get the linked journal entry (if recorded)\n */\n async getJournal(): Promise<Journal | null> {\n if (!this.journalId) return null;\n\n try {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const collection = await JournalCollection.create(this.options);\n return await collection.get({ id: this.journalId });\n } catch {\n // smrt-ledgers not available\n return null;\n }\n }\n\n /**\n * Mark payment as failed\n */\n markFailed(reason?: string): void {\n this.status = PaymentStatus.FAILED;\n if (reason) {\n this.notes = `${this.notes ? `${this.notes}\\n` : ''}Failed: ${reason}`;\n }\n }\n\n /**\n * Cancel the payment\n */\n cancel(): void {\n if (this.status === PaymentStatus.COMPLETED) {\n throw new Error('Cannot cancel a completed payment. Use refund instead.');\n }\n this.status = PaymentStatus.CANCELLED;\n }\n}\n\nexport default Payment;\n","/**\n * PaymentCollection - Collection manager for Payment objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Payment } from '../models/Payment.js';\nimport { type PaymentMethod, PaymentStatus } from '../types/index.js';\n\nexport class PaymentCollection extends SmrtCollection<Payment> {\n static readonly _itemClass = Payment;\n\n /**\n * Find payments by contract\n *\n * @param contractId - Contract ID\n * @returns Array of payments\n */\n async findByContract(contractId: string): Promise<Payment[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find payments by customer\n *\n * @param customerId - Customer ID\n * @returns Array of payments\n */\n async findByCustomer(customerId: string): Promise<Payment[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find payments by status\n *\n * @param status - Payment status\n * @returns Array of payments\n */\n async findByStatus(status: PaymentStatus): Promise<Payment[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find payments by method\n *\n * @param method - Payment method\n * @returns Array of payments\n */\n async findByMethod(method: PaymentMethod): Promise<Payment[]> {\n return await this.list({\n where: { method },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all pending payments\n *\n * @returns Array of pending payments\n */\n async findPending(): Promise<Payment[]> {\n return await this.findByStatus(PaymentStatus.PENDING);\n }\n\n /**\n * Find all completed payments\n *\n * @returns Array of completed payments\n */\n async findCompleted(): Promise<Payment[]> {\n return await this.findByStatus(PaymentStatus.COMPLETED);\n }\n\n /**\n * Find payment by transaction ID\n *\n * @param transactionId - External transaction ID\n * @returns Payment or null\n */\n async findByTransactionId(transactionId: string): Promise<Payment | null> {\n const results = await this.list({\n where: { transactionId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find payments in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of payments\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Payment[]> {\n return await this.list({\n where: {\n 'paidAt >=': startDate.toISOString(),\n 'paidAt <=': endDate.toISOString(),\n },\n orderBy: 'paidAt DESC',\n });\n }\n\n /**\n * Calculate total payments for a contract\n *\n * @param contractId - Contract ID\n * @returns Total amount paid\n */\n async getTotalForContract(contractId: string): Promise<number> {\n const payments = await this.list({\n where: {\n contractId,\n status: PaymentStatus.COMPLETED,\n },\n });\n\n return payments.reduce((sum, payment) => sum + payment.amount, 0);\n }\n\n /**\n * Find payments linked to a ledger journal\n *\n * @param journalId - Journal ID from smrt-ledgers\n * @returns Payment or null\n */\n async findByJournal(journalId: string): Promise<Payment | null> {\n const results = await this.list({\n where: { journalId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all payments belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of payments for the tenant\n */\n async findByTenant(tenantId: string): Promise<Payment[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payments (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global payments\n */\n async findGlobal(): Promise<Payment[]> {\n return queryGlobal<Payment>(this);\n }\n\n /**\n * Find payments for a tenant including global payments.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global payments\n */\n async findWithGlobals(tenantId: string): Promise<Payment[]> {\n return queryWithGlobals<Payment>(this, tenantId, 'Payment.findWithGlobals');\n }\n}\n","/**\n * Automatic prepaid-credit top-ups through an off-session charge of the\n * payer's saved payment method (#3139).\n *\n * smrt-subscriptions' `SpendingPolicyEvaluator` calls an `autoTopUp` hook\n * when a pending charge would exhaust a balance policy; it never calls a\n * payment provider itself. {@link createAutoTopUpHook} is that hook for a\n * {@link BillingRuntime} whose provider can charge a saved method.\n *\n * Exactly once:\n * - Each attempt is a `PENDING` commerce `Payment` in the seller's books whose\n * id is derived from the policy and the attempt's sequence number, inserted\n * before the provider is called. Two evaluations racing for the same top-up\n * derive the same id; only one insert wins, and only the winner charges.\n * - The charge's idempotency key is derived from that payment id, so a retry\n * (a re-drive of an unsettled attempt) returns the original charge.\n * - The credit grant is keyed by the charge key, so the synchronous result and\n * the provider's later `payment` webhook credit the balance once.\n */\nimport { isUniqueViolationError } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport {\n type AutoTopUpGrant,\n type AutoTopUpHook,\n type AutoTopUpRequest,\n SpendingPolicyEvaluator,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { PaymentCollection } from '../collections/PaymentCollection.js';\nimport type { Payment } from '../models/Payment.js';\nimport { PaymentMethod, PaymentStatus } from '../types/index.js';\nimport { defaultCardFor } from './cards.js';\nimport type { BillingChargeStatus } from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n canonicalTenantId,\n deterministicId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\n/** Prefix of every auto top-up charge key (the provider idempotency key). */\nexport const AUTO_TOP_UP_CHARGE_PREFIX = 'smrt-auto-top-up:';\n/** `Payment.reference` of an auto top-up attempt: prefix + policy id. */\nexport const AUTO_TOP_UP_REFERENCE_PREFIX = 'auto-top-up:';\n\nconst DEFAULT_RETRY_AFTER_MS = 60 * 60 * 1000;\nconst DEFAULT_RECHECK_AFTER_MS = 60 * 1000;\n\n/** A top-up charge that did not succeed. Nothing was credited. */\nexport interface AutoTopUpFailure {\n sellerTenantId: string;\n payerTenantId: string;\n spendingPolicyId: string;\n /** The attempt's commerce `Payment` id. */\n paymentId: string;\n /**\n * `requires_action`: the issuer wants the payer present (re-save the card\n * with a card setup checkout); `failed`: declined or no usable card;\n * `canceled`: the provider canceled it.\n */\n status: Exclude<BillingChargeStatus, 'succeeded' | 'processing'>;\n failureCode?: string;\n amount: number;\n currency: string;\n /**\n * The settlement transaction. Host writes through it commit or roll back\n * with the attempt's closing.\n */\n db: DatabaseInterface;\n}\n\n/**\n * Host hook for a failed top-up, for example to ask the payer to update their\n * card. It runs inside the transaction that closes the attempt, once per\n * attempt, but may run again if that transaction is retried, so it must be\n * idempotent.\n */\nexport type AutoTopUpFailureHook = (\n failure: AutoTopUpFailure,\n) => void | Promise<void>;\n\n/**\n * Why a needed top-up will not be charged (#3189). New reasons may be added in\n * a minor release; treat an unknown reason like `no_card`.\n *\n * - `taxed_account`: the payer's account is taxed and the hook was built with\n * `taxedAccounts: 'skip'`.\n * - `no_card`: the payer has no default card with this provider customer\n * (see `BillingRuntime.cardOnFile`).\n * - `no_account`: the payer has no billing account or provider customer yet.\n * - `tax_location_invalid`: a taxed payer has no tax location (billing\n * address country), so tax cannot be calculated (#3194). A location the\n * provider rejects instead fails the charge with `failureCode:\n * 'customer_tax_location_invalid'` through `onAutoTopUpFailed`.\n */\nexport type AutoTopUpSkipReason =\n | 'taxed_account'\n | 'no_card'\n | 'no_account'\n | 'tax_location_invalid';\n\n/** A top-up the balance needed that will not be charged. */\nexport interface AutoTopUpSkip {\n sellerTenantId: string;\n /** Who would have been charged (the delegating parent, if delegated). */\n payerTenantId: string;\n spendingPolicyId: string;\n currency: string;\n /** The shortfall still to cover, recomputed at the skip, minor units. */\n shortfall: number;\n reason: AutoTopUpSkipReason;\n}\n\n/**\n * Host hook for a skipped top-up, for example to ask the payer to top up by\n * hand. It runs on the spending evaluation's path, on every evaluation that\n * needed a top-up and was skipped, so dedupe notifications and keep it\n * cheap; an error it throws fails the evaluation.\n */\nexport type AutoTopUpSkipHook = (skip: AutoTopUpSkip) => void | Promise<void>;\n\nexport interface AutoTopUpHookOptions {\n /**\n * How much to charge, in integer minor units of the policy currency.\n * Default: the request's shortfall, recomputed against the current balance. Return 0 or nothing to skip the top-up\n * (the policy's own behavior then applies).\n */\n amount?: (\n request: AutoTopUpRequest,\n ) => number | null | undefined | Promise<number | null | undefined>;\n /**\n * After a failed charge, wait this long before charging the policy again\n * (default one hour), so a declined card is not retried on every request.\n */\n retryAfterMs?: number;\n /**\n * An attempt still `processing` (or interrupted before its outcome was\n * saved) is re-read from the provider at most this often (default one\n * minute); its outcome also arrives as a `payment` webhook.\n */\n recheckAfterMs?: number;\n /** Charge description shown to the payer where supported. */\n description?: string;\n /**\n * How a payer whose account is taxed (`automaticTax`, not tax-exempt) is\n * topped up (#3194). `'charge_taxed'` (the default) adds\n * provider-calculated tax on top of the credit, as a checkout purchase of\n * the same credit does: the payer is charged credit plus tax, the balance\n * is credited the credit, and the tax is booked to tax payable.\n * `'skip'` does not top them up (reported as `taxed_account`);\n * `'charge_untaxed'` charges them without tax, for sellers that account\n * for tax on this credit elsewhere. Untaxed accounts are never taxed.\n */\n taxedAccounts?: 'charge_taxed' | 'skip' | 'charge_untaxed';\n /**\n * Called when no charge will be made for a needed top-up because of the\n * payer's account (#3189): see {@link AutoTopUpSkipReason}. It is not\n * called while an attempt is still processing, while a declined card waits\n * out `retryAfterMs` (`onAutoTopUpFailed` already reported it), when the\n * recomputed shortfall is already covered, or when `amount` returns 0\n * (which is evaluated first, as for a charge).\n */\n onAutoTopUpSkipped?: AutoTopUpSkipHook;\n}\n\n/** The outcome of a charge, from the provider's result or its webhook. */\nexport interface AutoTopUpChargeOutcome {\n status: BillingChargeStatus;\n providerPaymentId?: string;\n providerCustomerId?: string;\n /** Minor units charged (with tax for a taxed charge), when reported. */\n amount?: number;\n currency?: string;\n /** A taxed charge's pre-tax amount: the credit (#3194). */\n subtotal?: number;\n /** A taxed charge's provider-calculated tax (#3194). */\n tax?: number;\n failureCode?: string;\n}\n\n/**\n * Marks an attempt charged with provider tax (#3194) in its `notes`, so a\n * re-drive of the same charge key repeats the same request whatever the\n * account's tax status is by then.\n */\nconst TAXED_ATTEMPT_NOTE = ' (automatic tax)';\n\nexport function autoTopUpChargeKey(paymentId: string): string {\n return `${AUTO_TOP_UP_CHARGE_PREFIX}${paymentId}`;\n}\n\n/** The attempt payment id in a charge key, or null for any other key. */\nexport function autoTopUpPaymentId(chargeKey: string): string | null {\n if (!chargeKey.startsWith(AUTO_TOP_UP_CHARGE_PREFIX)) return null;\n return chargeKey.slice(AUTO_TOP_UP_CHARGE_PREFIX.length) || null;\n}\n\nfunction positiveMs(value: number | undefined, fallback: number): number {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value) || value < 0) {\n throw new Error('Auto top-up intervals must be non-negative numbers.');\n }\n return value;\n}\n\n/**\n * Build the `autoTopUp` hook for `SpendingPolicyEvaluator.create()`.\n *\n * The hook returns a grant only when the charge `succeeded`. A `processing`\n * charge returns nothing now and is credited when its `payment` webhook is\n * processed; `requires_action`, `failed`, and `canceled` credit nothing. A\n * provider without `chargeSavedPaymentMethod` (a push-payment rail) never\n * tops up. Provider errors other than a charge outcome propagate.\n */\nexport function createAutoTopUpHook(\n runtime: BillingRuntime,\n options: AutoTopUpHookOptions = {},\n): AutoTopUpHook {\n const retryAfterMs = positiveMs(options.retryAfterMs, DEFAULT_RETRY_AFTER_MS);\n const recheckAfterMs = positiveMs(\n options.recheckAfterMs,\n DEFAULT_RECHECK_AFTER_MS,\n );\n return async (request) => {\n const charge = runtime.provider.chargeSavedPaymentMethod;\n if (!charge) return null;\n const attempt = await withSystemContext(() =>\n claimAttempt(runtime, request, options, retryAfterMs, recheckAfterMs),\n );\n if (attempt && 'skip' in attempt) {\n await options.onAutoTopUpSkipped?.(attempt.skip);\n return null;\n }\n if (!attempt) return null;\n const { payment, payerTenantId, providerCustomerId } = attempt;\n const paymentId = String(payment.id);\n const automaticTax = isTaxedAttempt(payment);\n const result = await charge.call(runtime.provider, {\n providerCustomerId,\n amount: payment.amount,\n currency: payment.currency,\n idempotencyKey: autoTopUpChargeKey(paymentId),\n description: options.description || 'Prepaid credit top-up',\n metadata: {\n smrt_purpose: 'auto_top_up',\n smrt_seller: runtime.sellerTenantId,\n smrt_payer: payerTenantId,\n smrt_policy: request.policyId,\n },\n ...(automaticTax\n ? {\n automaticTax: true,\n ...(runtime.taxCodes.credit\n ? { taxCode: runtime.taxCodes.credit }\n : {}),\n }\n : {}),\n });\n const outcome: AutoTopUpChargeOutcome = {\n status: result.status,\n providerPaymentId: result.providerPaymentId,\n providerCustomerId,\n amount: result.amount,\n currency: result.currency,\n subtotal: result.subtotal,\n tax: result.tax,\n failureCode: result.failureCode,\n };\n // Settle in the seller's books, like event processing does.\n const settled = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n inTransaction(runtime.db, (tx) =>\n applyAutoTopUpOutcome(runtime, tx, paymentId, outcome),\n ),\n );\n return settled.grant;\n };\n}\n\ninterface ClaimedAttempt {\n payment: Payment;\n payerTenantId: string;\n providerCustomerId: string;\n}\n\ninterface SkippedAttempt {\n skip: AutoTopUpSkip;\n}\n\nasync function claimAttempt(\n runtime: BillingRuntime,\n request: AutoTopUpRequest,\n options: AutoTopUpHookOptions,\n retryAfterMs: number,\n recheckAfterMs: number,\n): Promise<ClaimedAttempt | SkippedAttempt | null> {\n const policy = await runtime.policies.get(request.policyId);\n if (!policy?.id || policy.period !== 'balance' || !policy.active) {\n return null;\n }\n const currency = normalizeCurrency(policy.currency);\n if (normalizeCurrency(request.currency) !== currency) return null;\n // A delegated balance is funded only by the parent that set it.\n const payerTenantId = canonicalTenantId(\n tenantKey(policy.setByTenantId) || String(policy.tenantId),\n 'payerTenantId',\n );\n // What a top-up would charge now, or null when none is needed: another\n // evaluation may have topped the balance up since this one read it (its\n // attempt is completed, so its grant is visible), and `amount` may decline.\n // A skip is reported only for a top-up that is still needed (#3189).\n const needed = async (): Promise<{\n shortfall: number;\n requested: number;\n } | null> => {\n const shortfall = request.currentShortfall\n ? await request.currentShortfall()\n : request.shortfall;\n if (shortfall <= 0) return null;\n const requested = options.amount\n ? await options.amount({ ...request, shortfall })\n : shortfall;\n if (!requested) return null;\n if (!Number.isSafeInteger(requested) || requested <= 0) {\n throw new Error(\n 'Auto top-up amount must be positive integer minor units.',\n );\n }\n return { shortfall, requested };\n };\n const skipped = (\n reason: AutoTopUpSkipReason,\n shortfall: number,\n ): SkippedAttempt => ({\n skip: {\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId,\n spendingPolicyId: String(policy.id),\n currency,\n shortfall,\n reason,\n },\n });\n const payments = await PaymentCollection.create({ db: runtime.db });\n const where = {\n tenantId: runtime.sellerTenantId,\n reference: `${AUTO_TOP_UP_REFERENCE_PREFIX}${policy.id}`,\n externalProvider: runtime.provider.name,\n };\n // Attempts are numbered 0, 1, 2, … and a number is inserted only after the\n // count reached it (rows are never deleted), so the count is the next\n // number and the previous attempt is `sequence - 1`. Everything below is\n // decided from that one read: two evaluations that read the same count\n // derive the same id and only one insert wins; one that reads a later\n // count sees the earlier attempt.\n const attemptId = (sequence: number) =>\n deterministicId([\n 'billing-auto-top-up',\n runtime.provider.name,\n runtime.sellerTenantId,\n String(policy.id),\n String(sequence),\n ]);\n const sequence = await payments.count({ where });\n const latest =\n sequence > 0 ? await payments.get(await attemptId(sequence - 1)) : null;\n if (sequence > 0 && !latest) {\n throw new Error(`Auto top-up attempt ${sequence - 1} is missing.`);\n }\n const now = Date.now();\n const account = await runtime.getAccount(payerTenantId);\n const providerCustomerId =\n account?.id && account.provider === runtime.provider.name\n ? account.providerCustomerId\n : '';\n if (latest?.status === PaymentStatus.PENDING) {\n // One live attempt per policy: re-drive it (same key, same amount) only\n // when it has not been checked recently. Never a reported skip (#3189).\n if (now - timeOf(latest) < recheckAfterMs || !providerCustomerId) {\n return null;\n }\n return { payment: latest, payerTenantId, providerCustomerId };\n }\n if (\n latest?.status === PaymentStatus.FAILED &&\n now - timeOf(latest) < retryAfterMs\n ) {\n return null;\n }\n\n const need = await needed();\n if (!need) return null;\n if (!account?.id || !providerCustomerId) {\n // No provider customer, so no saved card to charge.\n return skipped('no_account', need.shortfall);\n }\n // New charges only (an attempt already charged is re-driven above with\n // the tax decision it was made with).\n const taxedAccounts = options.taxedAccounts ?? 'charge_taxed';\n const taxed = await runtime.accountIsTaxed(account);\n if (taxed && taxedAccounts === 'skip') {\n return skipped('taxed_account', need.shortfall);\n }\n // Only a payer with a card on file is charged (no failed attempt per\n // shortfall for payers who never saved one).\n if (!(await defaultCardFor(runtime, account, providerCustomerId))) {\n return skipped('no_card', need.shortfall);\n }\n const automaticTax = taxed && taxedAccounts === 'charge_taxed';\n // Without a tax location the provider cannot tax the charge: no attempt\n // (and no failed charge per shortfall) until the payer adds one.\n if (automaticTax && !(await runtime.hasTaxLocation(account))) {\n return skipped('tax_location_invalid', need.shortfall);\n }\n const requested = need.requested;\n const id = await attemptId(sequence);\n try {\n const payment = await payments.create({\n id,\n tenantId: runtime.sellerTenantId,\n customerId: account.customerId,\n amount: requested,\n currency,\n method: PaymentMethod.CREDIT_CARD,\n status: PaymentStatus.PENDING,\n reference: where.reference,\n externalProvider: runtime.provider.name,\n backendId: runtime.provider.name,\n notes: `attempt ${sequence}${automaticTax ? TAXED_ATTEMPT_NOTE : ''}`,\n _insertOnly: true,\n });\n return { payment, payerTenantId, providerCustomerId };\n } catch (error) {\n // Another evaluation claimed this attempt and is charging it.\n if (isUniqueViolationError(error) || (await payments.get(id))) return null;\n throw error;\n }\n}\n\nfunction inTransaction<T>(\n db: DatabaseInterface,\n operation: (tx: DatabaseInterface) => Promise<T>,\n): Promise<T> {\n if (!db.transaction) {\n throw new Error(\n 'Auto top-up requires a database adapter with transaction().',\n );\n }\n let result: T | undefined;\n return db\n .transaction(async (tx) => {\n result = await operation(tx);\n })\n .then(() => result as T);\n}\n\nfunction isTaxedAttempt(payment: Payment): boolean {\n return (\n payment.notes.startsWith('attempt ') &&\n payment.notes.includes(TAXED_ATTEMPT_NOTE)\n );\n}\n\nfunction timeOf(payment: Payment): number {\n const at = payment.updated_at ?? payment.created_at;\n return at instanceof Date ? at.getTime() : new Date(String(at)).getTime();\n}\n\n/**\n * Apply a charge outcome to its attempt: credit and record the payment when it\n * succeeded, close the attempt when it failed. Idempotent, and safe to run\n * concurrently from the hook and the `payment` webhook: `db` must be a\n * transaction (the event transaction, or one the hook opens), and the first\n * statement takes the attempt's row lock with a conditional update, so only\n * one caller settles a pending attempt and the rest see it settled. Runs in\n * the seller's tenant context (payments and journals are the seller's).\n */\nexport async function applyAutoTopUpOutcome(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n paymentId: string,\n outcome: AutoTopUpChargeOutcome,\n): Promise<{ grant: AutoTopUpGrant | null }> {\n const payments = await PaymentCollection.create({ db });\n // Lock a pending attempt for this transaction. A concurrent settler blocks\n // here until this one commits, then matches nothing (PostgreSQL re-checks\n // the status); SQLite serializes the transactions.\n // Reviewed raw SQL on a tenant-scoped table: filtered to the seller's\n // tenant by hand, and routed through the collection so its cache is busted.\n const locked = await payments.query(\n `UPDATE ${payments.tableName}\n SET updated_at = ?\n WHERE id = ? AND tenant_id = ? AND status = ?\n RETURNING id`,\n [\n new Date().toISOString(),\n paymentId,\n runtime.sellerTenantId,\n PaymentStatus.PENDING,\n ],\n { allowRawOnTenantScoped: true },\n );\n const pending = locked.length === 1;\n const payment = await payments.get(paymentId);\n if (\n !payment?.id ||\n tenantKey(payment.tenantId) !== runtime.sellerTenantId ||\n payment.externalProvider !== runtime.provider.name ||\n !payment.reference.startsWith(AUTO_TOP_UP_REFERENCE_PREFIX)\n ) {\n throw new Error(`Auto top-up attempt ${paymentId} was not found.`);\n }\n const policyId = payment.reference.slice(AUTO_TOP_UP_REFERENCE_PREFIX.length);\n // The policy and account belong to the payer: a reviewed system read.\n const policy = await withSystemContext(() => runtime.policies.get(policyId));\n if (!policy?.id) {\n throw new Error(`Auto top-up policy ${policyId} was not found.`);\n }\n const setBy = tenantKey(policy.setByTenantId);\n const payerTenantId = canonicalTenantId(\n setBy || String(policy.tenantId),\n 'payerTenantId',\n );\n const account = await withSystemContext(() =>\n runtime.getAccount(payerTenantId),\n );\n // The attempt row holds the credit: a taxed charge is compared by its\n // pre-tax subtotal, and its total must be that plus the tax (#3194). An\n // untaxed attempt never accepts tax (the payer was charged more than the\n // credit it asked for).\n const tax = outcome.tax ?? 0;\n const taxedAttempt = isTaxedAttempt(payment);\n if (\n !Number.isSafeInteger(tax) ||\n tax < 0 ||\n // The total must be exact minor units, never a rounded float sum.\n !Number.isSafeInteger(payment.amount + tax) ||\n (tax > 0 && !taxedAttempt) ||\n (outcome.subtotal !== undefined && outcome.subtotal !== payment.amount) ||\n (outcome.amount !== undefined && outcome.amount !== payment.amount + tax) ||\n (outcome.currency !== undefined &&\n normalizeCurrency(outcome.currency) !==\n normalizeCurrency(payment.currency)) ||\n (outcome.providerCustomerId !== undefined &&\n outcome.providerCustomerId !== account?.providerCustomerId)\n ) {\n throw new Error(\n `Charge for auto top-up ${paymentId} does not match it: ` +\n `${outcome.amount} ${outcome.currency} (tax ${tax}) for ${payment.amount} ${payment.currency}.`,\n );\n }\n\n if (outcome.status === 'succeeded') {\n if (taxedAttempt && outcome.tax === undefined) {\n // A taxed charge that reports no tax cannot be booked correctly: fail\n // visibly rather than leave the collected tax out of the ledger.\n throw new Error(\n `Taxed auto top-up ${paymentId} succeeded without a reported tax amount.`,\n );\n }\n if (payment.status === PaymentStatus.FAILED) {\n // The attempt was closed as failed, then the provider collected: an\n // operator must reconcile it (credit or refund).\n throw new Error(\n `Auto top-up ${paymentId} was recorded as failed but its charge succeeded.`,\n );\n }\n const grant: AutoTopUpGrant = {\n amount: payment.amount,\n source: `${runtime.provider.name}-auto-top-up`,\n sourceId: autoTopUpChargeKey(paymentId),\n reason: 'Automatic prepaid credit top-up',\n };\n if (pending) {\n // Keyed by the charge: the evaluator recording the returned grant\n // again finds this one.\n await withSystemContext(async () => {\n const evaluator = await SpendingPolicyEvaluator.create({ db });\n await evaluator.grantCredit({\n spendingPolicyId: policyId,\n amount: grant.amount,\n reason: grant.reason,\n source: grant.source,\n sourceId: grant.sourceId,\n ...(setBy ? { grantedByTenantId: setBy } : {}),\n });\n });\n if (outcome.providerPaymentId) {\n payment.externalId = outcome.providerPaymentId;\n }\n if (tax > 0) payment.notes = `${payment.notes}; tax ${tax}`;\n await payment.recordPayment({\n ledgerId: '',\n cashAccountId: runtime.ledger.cashAccountId,\n receivablesAccountId: runtime.ledger.prepaidCreditAccountId,\n });\n if (tax > 0) {\n // The payer paid credit plus tax: the tax is the seller's to remit,\n // booked once with the settlement it belongs to.\n const journals = await JournalCollection.create({ db });\n const journal = await journals.create({\n date: new Date(),\n description: `Tax collected on automatic top-up ${paymentId}`,\n sourceModule: 'smrt-commerce',\n sourceRef: `${paymentId}:tax`,\n });\n await journal.save();\n await journal.addEntry({\n accountId: runtime.ledger.cashAccountId,\n debit: tax,\n memo: `Tax on top-up ${policyId}`,\n });\n await journal.addEntry({\n accountId: runtime.ledger.taxAccountId,\n credit: tax,\n memo: `Tax on top-up ${policyId}`,\n });\n await journal.post();\n }\n }\n return { grant };\n }\n\n if (!pending) return { grant: null }; // Already settled.\n\n if (outcome.status === 'processing') {\n // The lock's update refreshed `updated_at`, which paces re-drives.\n if (outcome.providerPaymentId && !payment.externalId) {\n payment.externalId = outcome.providerPaymentId;\n await payment.save();\n }\n return { grant: null };\n }\n\n // requires_action, failed, canceled: nothing was charged.\n payment.status = PaymentStatus.FAILED;\n if (outcome.providerPaymentId && !payment.externalId) {\n payment.externalId = outcome.providerPaymentId;\n }\n // Appended, never replaced: the notes carry the attempt's tax decision,\n // which a later webhook for the same charge is checked against.\n payment.notes = `${payment.notes}; ${outcome.status}${outcome.failureCode ? `: ${outcome.failureCode}` : ''}`;\n await payment.save();\n await runtime.onAutoTopUpFailed?.({\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId,\n spendingPolicyId: policyId,\n paymentId,\n status: outcome.status,\n failureCode: outcome.failureCode,\n amount: payment.amount,\n currency: normalizeCurrency(payment.currency),\n db,\n });\n return { grant: null };\n}\n","/**\n * Stripe adapter for the {@link BillingProvider} port (#3060).\n *\n * Every provider call goes through `@happyvertical/accounting`'s Stripe\n * provider; this module only maps between commerce's integer minor units and\n * the SDK's major-unit contract, and normalizes verified webhook events. The\n * SDK's `*Minor` fields (checkout prices, session totals, off-session charges)\n * are already ISO minor units and pass through unconverted (#3139).\n */\nimport type {\n StripeAccountingProvider,\n StripeSubscriptionStatus,\n WebhookEvent,\n} from '@happyvertical/accounting';\nimport type { SubscriptionStatus } from '@happyvertical/smrt-subscriptions';\nimport { CARD_SETUP_PURPOSE, COLLECT_ADDRESS_METADATA } from './cards.js';\nimport { CREDIT_PURCHASE_PURPOSE } from './credits.js';\nimport {\n type BillingChargeStatus,\n type BillingInvoiceEventType,\n type BillingProvider,\n type BillingProviderChargeInput,\n type BillingProviderChargeResult,\n type BillingProviderCheckoutInput,\n type BillingProviderCheckoutSession,\n type BillingProviderCheckoutState,\n type BillingProviderCustomerInput,\n type BillingProviderEvent,\n type BillingProviderInvoiceInput,\n type BillingProviderInvoiceState,\n type BillingProviderInvoiceStatus,\n type BillingProviderSetupCheckoutInput,\n type BillingProviderSubscriptionState,\n BillingWebhookVerificationError,\n} from './provider.js';\nimport { AUTO_TOP_UP_CHARGE_PREFIX } from './top-up.js';\nimport {\n majorToMinorUnits,\n minorToMajorUnits,\n normalizeCurrency,\n} from './units.js';\n\nexport interface StripeBillingProviderOptions {\n /** A Stripe provider from `getAccountingProvider({ type: 'stripe', … })`. */\n stripe: StripeAccountingProvider;\n /** Webhook endpoint signing secret (`whsec_…`). */\n webhookSecret: string;\n}\n\nconst INVOICE_EVENTS: Record<string, BillingInvoiceEventType> = {\n 'invoice.paid': 'paid',\n 'invoice.payment_succeeded': 'paid',\n 'invoice.payment_failed': 'payment_failed',\n 'invoice.overdue': 'overdue',\n 'invoice.marked_uncollectible': 'uncollectible',\n 'invoice.voided': 'voided',\n};\n\nconst SUBSCRIPTION_EVENTS = new Set([\n 'customer.subscription.created',\n 'customer.subscription.updated',\n 'customer.subscription.deleted',\n 'customer.subscription.paused',\n 'customer.subscription.resumed',\n]);\n\nconst CHECKOUT_EVENTS = new Set([\n 'checkout.session.completed',\n 'checkout.session.async_payment_succeeded',\n]);\n\nconst PAYMENT_EVENTS = new Set([\n 'payment_intent.succeeded',\n 'payment_intent.processing',\n 'payment_intent.payment_failed',\n 'payment_intent.canceled',\n]);\n\nconst CHARGE_STATUSES = new Set<BillingChargeStatus>([\n 'succeeded',\n 'processing',\n 'requires_action',\n 'failed',\n 'canceled',\n]);\n\nconst INVOICE_STATUSES: Record<string, BillingProviderInvoiceStatus> = {\n draft: 'draft',\n paid: 'paid',\n voided: 'void',\n uncollectible: 'uncollectible',\n};\n\nconst SUBSCRIPTION_STATUSES: Record<\n StripeSubscriptionStatus,\n SubscriptionStatus\n> = {\n active: 'active',\n trialing: 'trialing',\n past_due: 'past_due',\n unpaid: 'unpaid',\n canceled: 'canceled',\n incomplete: 'incomplete',\n incomplete_expired: 'canceled',\n paused: 'unpaid',\n};\n\nasync function sha256Hex(value: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(value),\n );\n return Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n}\n\nexport function createStripeBillingProvider(\n options: StripeBillingProviderOptions,\n): BillingProvider {\n const { stripe, webhookSecret } = options;\n if (!webhookSecret) {\n throw new Error('A Stripe webhook signing secret is required.');\n }\n // Checkout amounts are settled from a re-read session (Stripe's webhook\n // amounts are in Stripe's unit, not ISO minor units), so the SDK must be\n // able to retrieve one (@happyvertical/accounting >= 0.92).\n const retrieveCheckoutSession = stripe.billing.retrieveCheckoutSession?.bind(\n stripe.billing,\n );\n if (!retrieveCheckoutSession) {\n throw new Error(\n 'The Stripe provider cannot retrieve checkout sessions; upgrade @happyvertical/accounting to 0.92 or later.',\n );\n }\n const chargeSaved = stripe.payments.chargeSavedPaymentMethod?.bind(\n stripe.payments,\n );\n const setDefault = stripe.billing.setDefaultPaymentMethod?.bind(\n stripe.billing,\n );\n const markUncollectible = stripe.invoices.markUncollectible?.bind(\n stripe.invoices,\n );\n const markPaidOutOfBand = stripe.invoices.markPaidOutOfBand?.bind(\n stripe.invoices,\n );\n\n const provider: BillingProvider = {\n name: 'stripe',\n\n async syncCustomer(input: BillingProviderCustomerInput) {\n const customer = {\n id: input.accountId,\n externalId: input.providerCustomerId || undefined,\n name: input.name,\n email: input.email || undefined,\n billingAddress: input.billingAddress,\n taxExempt: input.taxExempt,\n };\n if (customer.externalId) {\n const result = await stripe.customers.sync(customer);\n return { providerCustomerId: result.externalId };\n }\n // Creation is idempotent per account (sdk#1268): a retry returns the\n // customer an earlier attempt created, found by Stripe's key replay or,\n // after its window, by the account id the SDK tags on the customer. The\n // key also covers the contents, so a retry with edited details is a new\n // request (Stripe refuses a reused key with other parameters) that the\n // SDK still resolves to the first customer by that tag.\n const digest = await sha256Hex(JSON.stringify(customer));\n const created = await stripe.customers.sync({\n ...customer,\n idempotencyKey: `smrt-billing-customer:${input.accountId}:${digest.slice(0, 32)}`,\n });\n // A replayed or found customer carries the first attempt's details;\n // write the current ones.\n await stripe.customers.sync({\n ...customer,\n externalId: created.externalId,\n });\n return { providerCustomerId: created.externalId };\n },\n\n async pushInvoice(input: BillingProviderInvoiceInput) {\n const currency = normalizeCurrency(input.currency);\n const subtotal = minorToMajorUnits(input.subtotal, currency);\n const result = await stripe.invoices.push({\n id: input.invoiceId,\n invoiceNumber: input.invoiceNumber,\n customerId: input.providerCustomerId,\n customerExternalId: input.providerCustomerId,\n issueDate: input.issueDate,\n dueDate: input.dueDate,\n lineItems: input.lines.map((line) => ({\n description: line.description,\n quantity: 1,\n unitPrice: minorToMajorUnits(line.amount, currency),\n periodStart: line.periodStart,\n periodEnd: line.periodEnd,\n ...(line.taxCode ? { taxCode: line.taxCode } : {}),\n })),\n subtotal,\n taxAmount: 0,\n totalAmount: subtotal,\n currency: currency.toLowerCase(),\n memo: input.memo,\n idempotencyKey: input.idempotencyKey,\n automaticTax: input.automaticTax,\n collectionMethod: input.collectionMethod ?? 'send_invoice',\n });\n return { providerInvoiceId: result.externalId };\n },\n\n async sendInvoice(providerInvoiceId: string) {\n await stripe.invoices.send(providerInvoiceId);\n },\n\n async getInvoice(\n providerInvoiceId: string,\n ): Promise<BillingProviderInvoiceState> {\n const invoice = await stripe.invoices.pull(providerInvoiceId);\n const currency = normalizeCurrency(invoice.currency);\n // sent, viewed, and overdue are all open (unpaid) at the provider.\n const status: BillingProviderInvoiceStatus =\n INVOICE_STATUSES[invoice.status] ?? 'open';\n return {\n providerInvoiceId: invoice.externalId,\n status,\n currency,\n subtotal: majorToMinorUnits(invoice.subtotal, currency),\n taxAmount: majorToMinorUnits(invoice.taxAmount, currency),\n total: majorToMinorUnits(invoice.totalAmount, currency),\n // Closed out of band, Stripe collected nothing; the invoice is\n // settled in full by whoever did (#3138).\n amountPaid:\n invoice.paidOutOfBand && status === 'paid'\n ? majorToMinorUnits(invoice.totalAmount, currency)\n : majorToMinorUnits(invoice.amountPaid, currency),\n amountDue:\n invoice.paidOutOfBand && status === 'paid'\n ? 0\n : majorToMinorUnits(invoice.balance, currency),\n ...(invoice.paidOutOfBand ? { paidOutOfBand: true } : {}),\n };\n },\n\n async getSubscription(\n providerSubscriptionId: string,\n ): Promise<BillingProviderSubscriptionState> {\n const subscription = await stripe.billing.retrieveSubscriptionStatus(\n providerSubscriptionId,\n );\n const status = SUBSCRIPTION_STATUSES[subscription.status];\n if (!status) {\n throw new Error(\n `Unknown Stripe subscription status '${subscription.status}'.`,\n );\n }\n return {\n providerSubscriptionId: subscription.externalId,\n status,\n currentPeriodStart: subscription.currentPeriodStart,\n currentPeriodEnd: subscription.currentPeriodEnd,\n cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,\n canceledAt: subscription.canceledAt,\n trialEndsAt: subscription.trialEnd,\n };\n },\n\n async createCheckout(\n input: BillingProviderCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n const currency = normalizeCurrency(input.currency);\n if (!Number.isSafeInteger(input.amount) || input.amount <= 0) {\n throw new Error(\n 'Checkout amount must be positive integer minor units.',\n );\n }\n const customerExternalId = input.providerCustomerId || undefined;\n if (input.savePaymentMethod && !customerExternalId) {\n throw new Error(\n 'Saving a payment method requires a provider customer.',\n );\n }\n const session = await stripe.billing.createCheckoutSession({\n mode: 'payment',\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n customerExternalId,\n customerEmail: customerExternalId\n ? undefined\n : input.customerEmail || undefined,\n lineItems: [\n {\n quantity: 1,\n priceData: {\n currency: currency.toLowerCase(),\n // ISO minor units; the SDK converts to Stripe's unit (sdk#1269).\n unitAmountMinor: input.amount,\n productName: input.description,\n ...(input.taxCode ? { taxCode: input.taxCode } : {}),\n },\n },\n ],\n ...(input.automaticTax\n ? {\n automaticTax: true,\n // Stripe Tax needs the buyer's location: Checkout collects it\n // and saves it to an existing customer.\n ...(customerExternalId\n ? { customerUpdate: { address: 'auto' as const } }\n : { billingAddressCollection: 'required' as const }),\n }\n : {}),\n ...(input.savePaymentMethod\n ? { setupFutureUsage: 'off_session' as const }\n : {}),\n metadata: await signMetadata(input.metadata, webhookSecret),\n idempotencyKey: input.idempotencyKey,\n });\n return { sessionId: session.externalId, url: session.url };\n },\n\n async createSetupCheckout(\n input: BillingProviderSetupCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n if (!input.providerCustomerId) {\n throw new Error('A setup checkout requires a provider customer.');\n }\n const session = await stripe.billing.createCheckoutSession({\n mode: 'setup',\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n customerExternalId: input.providerCustomerId,\n currency: normalizeCurrency(input.currency).toLowerCase(),\n ...(input.collectBillingAddress\n ? {\n billingAddressCollection: 'required' as const,\n customerUpdate: { address: 'auto' as const },\n }\n : {}),\n metadata: await signMetadata(input.metadata, webhookSecret),\n idempotencyKey: input.idempotencyKey,\n });\n return { sessionId: session.externalId, url: session.url };\n },\n\n async getCheckout(\n sessionId: string,\n ): Promise<BillingProviderCheckoutState> {\n const session = await retrieveCheckoutSession(sessionId);\n const mode =\n session.mode === 'payment' || session.mode === 'setup'\n ? session.mode\n : 'other';\n const complete = session.status === 'complete';\n const state: BillingProviderCheckoutState = {\n sessionId: session.externalId,\n mode,\n complete,\n paid: session.paymentStatus === 'paid',\n providerCustomerId: session.customerExternalId,\n paymentMethodId: session.paymentMethodExternalId,\n currency: session.currency,\n amountSubtotal: session.amountSubtotalMinor,\n amountTax: session.amountTaxMinor,\n amountTotal: session.amountTotalMinor,\n };\n if (\n complete &&\n session.customerExternalId &&\n session.metadata[COLLECT_ADDRESS_METADATA] === '1'\n ) {\n // A session asked to collect the address saved it to the customer\n // (customer_update[address]=auto). Only those pay for this read.\n const customer = await stripe.customers.pull(\n session.customerExternalId,\n );\n if (customer.billingAddress?.country) {\n state.billingAddress = customer.billingAddress;\n }\n }\n return state;\n },\n\n async verifyWebhook(\n payload: string,\n signature: string,\n ): Promise<BillingProviderEvent> {\n if (!stripe.webhooks.verify(payload, signature, webhookSecret)) {\n throw new BillingWebhookVerificationError();\n }\n return normalizeStripeEvent(\n stripe.webhooks.parse(payload),\n webhookSecret,\n );\n },\n };\n\n if (chargeSaved) {\n provider.chargeSavedPaymentMethod = async (\n input: BillingProviderChargeInput,\n ): Promise<BillingProviderChargeResult> => {\n const result = await chargeSaved({\n customerExternalId: input.providerCustomerId,\n amountMinor: input.amount,\n currency: normalizeCurrency(input.currency),\n idempotencyKey: input.idempotencyKey,\n description: input.description,\n metadata: input.metadata,\n ...(input.automaticTax\n ? {\n automaticTax: true,\n ...(input.taxCode ? { taxCode: input.taxCode } : {}),\n }\n : {}),\n });\n return {\n status: result.status,\n providerPaymentId: result.paymentExternalId,\n amount: result.amountMinor,\n currency: result.currency,\n ...(result.subtotalMinor !== undefined\n ? { subtotal: result.subtotalMinor }\n : {}),\n ...(result.taxMinor !== undefined ? { tax: result.taxMinor } : {}),\n failureCode: result.failureCode,\n failureMessage: result.failureMessage,\n };\n };\n }\n if (setDefault) {\n provider.setDefaultPaymentMethod = (providerCustomerId, paymentMethodId) =>\n setDefault(providerCustomerId, paymentMethodId);\n }\n if (markUncollectible) {\n provider.markInvoiceUncollectible = (providerInvoiceId) =>\n markUncollectible(providerInvoiceId);\n }\n if (markPaidOutOfBand) {\n // Closes an invoice a payment rail collected (#3138): Stripe stops its\n // emails, dunning, and automatic charges.\n provider.markInvoicePaidOutOfBand = (providerInvoiceId) =>\n markPaidOutOfBand(providerInvoiceId);\n }\n return provider;\n}\n\n/** Normalize a verified, parsed Stripe event. Exported for tests. */\nexport async function normalizeStripeEvent(\n event: WebhookEvent,\n webhookSecret: string,\n): Promise<BillingProviderEvent> {\n const eventId = event.id;\n if (!eventId) {\n throw new BillingWebhookVerificationError(\n 'Stripe webhook event has no id; it cannot be deduplicated.',\n );\n }\n // After verification, anything this package cannot act on is `ignored`\n // (acknowledged, not stored) rather than thrown: a throw here would fail\n // the host's webhook response for an event that is not ours.\n const ignored = { kind: 'ignored' as const, eventId, type: event.type };\n const invoiceType = INVOICE_EVENTS[event.type];\n if (invoiceType) {\n if (!event.resourceId) return ignored;\n return {\n kind: 'invoice',\n eventId,\n type: invoiceType,\n providerInvoiceId: event.resourceId,\n };\n }\n if (SUBSCRIPTION_EVENTS.has(event.type)) {\n const id = stripeObject(event).id;\n if (typeof id !== 'string' || !id) return ignored;\n return { kind: 'subscription', eventId, providerSubscriptionId: id };\n }\n if (CHECKOUT_EVENTS.has(event.type)) {\n const session = stripeObject(event);\n // Only sessions this package created are stored, and only their smrt_*\n // metadata: other integrations' checkouts never enter the inbox. The\n // metadata is signed with the endpoint secret at creation, so another\n // integration on the same account cannot forge a credit purchase or\n // attach a card to another payer.\n const metadata = smrtMetadata(session.metadata);\n const id = session.id;\n const mode = session.mode;\n const expected =\n (mode === 'payment' &&\n metadata.smrt_purpose === CREDIT_PURCHASE_PURPOSE) ||\n (mode === 'setup' && metadata.smrt_purpose === CARD_SETUP_PURPOSE);\n if (\n !expected ||\n !(await metadataSignatureValid(metadata, webhookSecret)) ||\n typeof id !== 'string' ||\n !id ||\n (mode === 'payment' && typeof session.currency !== 'string')\n ) {\n // A session that claims to be ours but fails these checks is reported\n // distinctly so the host can alert on it.\n if (!expected) return ignored;\n return {\n ...ignored,\n type:\n mode === 'payment'\n ? `${event.type}:unverified_credit_purchase`\n : `${event.type}:unverified_card_setup`,\n };\n }\n // Amounts are not taken from the event: Stripe reports them in its own\n // unit, which differs from ISO minor units for some currencies (ISK,\n // UGX). `observe()` re-reads the session through the SDK, which converts.\n return {\n kind: 'checkout_completed',\n eventId,\n sessionId: id,\n mode,\n paid: session.payment_status === 'paid',\n // Validated when the event is applied, so a bad value dead-letters\n // visibly instead of failing intake.\n currency:\n typeof session.currency === 'string'\n ? session.currency.toUpperCase()\n : '',\n metadata,\n };\n }\n if (PAYMENT_EVENTS.has(event.type)) {\n // Only off-session charges this package made (#3139); the charge key is\n // the SDK's `hv_charge_key` metadata. Settlement checks it against the\n // local attempt row, amount, currency, and customer.\n const payment = event.payment;\n const chargeKey = payment?.chargeKey;\n if (\n !payment ||\n !chargeKey?.startsWith(AUTO_TOP_UP_CHARGE_PREFIX) ||\n !CHARGE_STATUSES.has(payment.status) ||\n !payment.paymentExternalId\n ) {\n return ignored;\n }\n return {\n kind: 'payment',\n eventId,\n chargeKey,\n status: payment.status,\n providerPaymentId: payment.paymentExternalId,\n providerCustomerId: payment.customerExternalId,\n amount: payment.amountMinor,\n currency: payment.currency,\n ...(payment.subtotalMinor !== undefined\n ? { subtotal: payment.subtotalMinor }\n : {}),\n ...(payment.taxMinor !== undefined ? { tax: payment.taxMinor } : {}),\n failureCode: payment.failureCode,\n };\n }\n return ignored;\n}\n\nfunction stripeObject(event: WebhookEvent): Record<string, unknown> {\n const payload = event.payload as { data?: { object?: unknown } } | null;\n const object = payload?.data?.object;\n return object && typeof object === 'object'\n ? (object as Record<string, unknown>)\n : {};\n}\n\n/** Metadata key holding the `smrt_*` HMAC (shared with crypto rails, #3138). */\nexport const SIGNATURE_KEY = 'smrt_sig';\n\nasync function hmacHex(secret: string, message: string): Promise<string> {\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const digest = await crypto.subtle.sign('HMAC', key, encoder.encode(message));\n return Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n}\n\nfunction canonicalMetadata(metadata: Record<string, string>): string {\n return JSON.stringify(\n Object.entries(metadata)\n // Empty values are dropped by the provider, so they are never signed.\n .filter(\n ([key, value]) =>\n key.startsWith('smrt_') && key !== SIGNATURE_KEY && value !== '',\n )\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n );\n}\n\nexport async function signMetadata(\n metadata: Record<string, string>,\n secret: string,\n): Promise<Record<string, string>> {\n return {\n ...metadata,\n [SIGNATURE_KEY]: await hmacHex(\n `smrt-checkout:${secret}`,\n canonicalMetadata(metadata),\n ),\n };\n}\n\nexport async function metadataSignatureValid(\n metadata: Record<string, string>,\n secret: string,\n): Promise<boolean> {\n const given = metadata[SIGNATURE_KEY];\n if (!given) return false;\n const expected = await hmacHex(\n `smrt-checkout:${secret}`,\n canonicalMetadata(metadata),\n );\n if (given.length !== expected.length) return false;\n let diff = 0;\n for (let index = 0; index < given.length; index += 1) {\n diff |= given.charCodeAt(index) ^ expected.charCodeAt(index);\n }\n return diff === 0;\n}\n\nexport function smrtMetadata(value: unknown): Record<string, string> {\n const result: Record<string, string> = {};\n if (!value || typeof value !== 'object') return result;\n for (const [key, entry] of Object.entries(value)) {\n if (key.startsWith('smrt_') && typeof entry === 'string') {\n result[key] = entry;\n }\n }\n return result;\n}\n","/**\n * A crypto payment rail for the {@link BillingProvider} port (#3138).\n *\n * The rail runs short-lived, fiat-priced checkouts through a\n * `CryptoCheckoutGateway` from `@happyvertical/payments` — BTCPay at launch,\n * any other implementation of the port later. It never issues invoices: the\n * runtime's issuing provider (Stripe) does, and this rail pays them or buys\n * prepaid credit. Settlement is the gateway's decision (its confirmation\n * policy); this package never counts confirmations.\n */\nimport {\n type CryptoCheckout,\n type CryptoCheckoutGateway,\n CryptoCheckoutNotOwnedError,\n PaymentVerificationError,\n} from '@happyvertical/payments';\nimport {\n type BillingPaymentAttemptPayment,\n type BillingPaymentAttemptState,\n BillingPaymentRefusedError,\n type BillingProvider,\n type BillingProviderCheckoutInput,\n type BillingProviderCheckoutSession,\n type BillingProviderEvent,\n BillingProviderUnsupportedError,\n BillingWebhookVerificationError,\n} from './provider.js';\nimport {\n metadataSignatureValid,\n SIGNATURE_KEY,\n signMetadata,\n smrtMetadata,\n} from './stripe.js';\nimport { currencyMinorUnitExponent, normalizeCurrency } from './units.js';\n\nexport interface CryptoBillingProviderOptions {\n /** A checkout gateway, for example `createBtcpayCheckoutGateway()`. */\n gateway: CryptoCheckoutGateway;\n /**\n * Secret the `smrt_*` checkout metadata is signed with. Rotating it\n * strands checkouts created before the rotation.\n */\n metadataSecret: string;\n /** Provider name on local rows and in the inbox (default the gateway id). */\n name?: string;\n /**\n * Smallest checkout accepted, per currency, in minor units (fee and dust\n * economics). A currency without an entry has no minimum.\n */\n minimumAmount?: Record<string, number>;\n}\n\n/** Minor-unit exponents of the settlement assets this rail records. */\nconst ASSET_EXPONENTS: Record<string, number> = { BTC: 8 };\n\nexport function createCryptoBillingProvider(\n options: CryptoBillingProviderOptions,\n): BillingProvider {\n const { gateway, metadataSecret } = options;\n if (!gateway) throw new Error('A crypto checkout gateway is required.');\n if (!metadataSecret) {\n throw new Error('A crypto rail metadata secret is required.');\n }\n const name = options.name ?? gateway.id;\n if (!/^[a-z][a-z0-9-]*$/.test(name)) {\n throw new Error(`Invalid crypto rail name '${name}'.`);\n }\n const minimums = new Map<string, number>();\n for (const [currency, amount] of Object.entries(\n options.minimumAmount ?? {},\n )) {\n if (!Number.isSafeInteger(amount) || amount < 0) {\n throw new Error(`minimumAmount for ${currency} must be minor units.`);\n }\n minimums.set(normalizeCurrency(currency), amount);\n }\n const unsupported = (what: string) => async () => {\n throw new BillingProviderUnsupportedError(\n `The ${name} payment rail does not ${what}; the runtime's issuing provider does.`,\n );\n };\n\n return {\n name,\n capabilities: { issuesInvoices: false, paymentAttempts: true },\n\n async syncCustomer(input) {\n // No provider customer exists; the account id identifies the payer.\n return { providerCustomerId: `${name}:${input.accountId}` };\n },\n pushInvoice: unsupported('issue invoices'),\n sendInvoice: unsupported('send invoices'),\n getInvoice: unsupported('issue invoices'),\n getSubscription: unsupported('manage subscriptions'),\n\n async createCheckout(\n input: BillingProviderCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n const currency = normalizeCurrency(input.currency);\n if (!Number.isSafeInteger(input.amount) || input.amount <= 0) {\n throw new Error(\n 'Checkout amount must be positive integer minor units.',\n );\n }\n if (input.automaticTax || input.savePaymentMethod) {\n throw new BillingProviderUnsupportedError(\n `The ${name} payment rail cannot charge tax or save a card.`,\n );\n }\n const minimum = minimums.get(currency) ?? 0;\n if (input.amount < minimum) {\n throw new BillingPaymentRefusedError(\n 'below_minimum',\n `The ${name} payment rail accepts at least ${minimum} ${currency} minor units.`,\n );\n }\n const checkout = await gateway.createCheckout({\n orderId: input.idempotencyKey,\n amount: input.amount,\n currency,\n description: input.description,\n buyerEmail: input.customerEmail || undefined,\n metadata: await signMetadata(\n smrtMetadata(input.metadata),\n metadataSecret,\n ),\n redirectUrl: input.successUrl,\n });\n return { sessionId: checkout.id, url: checkout.checkoutUrl ?? null };\n },\n\n async verifyWebhook(\n payload: string,\n _signature: string,\n headers?: Headers | Record<string, string | undefined>,\n ): Promise<BillingProviderEvent> {\n let event: ReturnType<CryptoCheckoutGateway['verifyWebhook']>;\n try {\n event = gateway.verifyWebhook(payload, headers ?? {});\n } catch (error) {\n if (error instanceof PaymentVerificationError) {\n throw new BillingWebhookVerificationError(error.message);\n }\n throw error;\n }\n if (!event.checkoutId) {\n return { kind: 'ignored', eventId: event.eventId, type: event.type };\n }\n return {\n kind: 'payment_attempt',\n eventId: event.eventId,\n checkoutId: event.checkoutId,\n };\n },\n\n async getPaymentAttempt(\n checkoutId: string,\n ): Promise<BillingPaymentAttemptState | null> {\n let checkout: CryptoCheckout;\n try {\n checkout = await gateway.getCheckout(checkoutId);\n } catch (error) {\n if (error instanceof CryptoCheckoutNotOwnedError) return null;\n throw error;\n }\n const metadata = smrtMetadata(checkout.metadata);\n const verified =\n Boolean(metadata[SIGNATURE_KEY]) &&\n (await metadataSignatureValid(metadata, metadataSecret));\n // A checkout without our signed metadata is not ours to record, even\n // if it settles in an asset this rail cannot map.\n if (!verified) return null;\n return toAttemptState(checkout, metadata);\n },\n };\n}\n\nfunction toAttemptState(\n checkout: CryptoCheckout,\n metadata: Record<string, string>,\n): BillingPaymentAttemptState {\n const currency = normalizeCurrency(checkout.currency);\n // Validates the fiat currency's exponent (throws for unknown codes).\n currencyMinorUnitExponent(currency);\n const asset = checkout.settlementAsset?.toUpperCase();\n const exponent = asset ? ASSET_EXPONENTS[asset] : undefined;\n if (asset && exponent === undefined) {\n throw new Error(\n `Checkout ${checkout.id} settles in ${asset}, which this rail cannot record.`,\n );\n }\n const native = (value: string | undefined) =>\n value === undefined || exponent === undefined\n ? undefined\n : decimalToAtomic(value, exponent);\n const payments: BillingPaymentAttemptPayment[] = checkout.payments.map(\n (payment) => {\n const paymentExponent = ASSET_EXPONENTS[payment.asset.toUpperCase()];\n if (paymentExponent === undefined) {\n throw new Error(\n `Checkout ${checkout.id} has a ${payment.asset} payment this rail cannot record.`,\n );\n }\n return {\n id: payment.id,\n rail: payment.rail,\n asset: payment.asset.toUpperCase(),\n amount: decimalToAtomic(payment.amount, paymentExponent),\n ...(payment.fee !== undefined\n ? { fee: decimalToAtomic(payment.fee, paymentExponent) }\n : {}),\n status: payment.status,\n ...(payment.transactionId\n ? { transactionId: payment.transactionId }\n : {}),\n ...(payment.receivedAt\n ? { receivedAt: payment.receivedAt.toISOString() }\n : {}),\n };\n },\n );\n return {\n checkoutId: checkout.id,\n orderId: checkout.orderId,\n status: checkout.status,\n exception: checkout.exception,\n amount: checkout.amount,\n currency,\n amountPaid: checkout.amountPaid,\n metadata,\n nativeCurrency: asset,\n nativeAmountDue: native(checkout.nativeAmountDue),\n nativeAmountPaid: native(checkout.nativeAmountPaid),\n rate: checkout.rate,\n rateSource: checkout.rateSource,\n checkoutUrl: checkout.checkoutUrl,\n expiresAt: checkout.expiresAt,\n payments,\n };\n}\n\n/** Exact decimal string → integer atomic units; rejects excess precision. */\nexport function decimalToAtomic(value: string, exponent: number): number {\n const match = /^(\\d+)(?:\\.(\\d+))?$/.exec(String(value).trim());\n if (!match) throw new Error(`Invalid decimal amount '${value}'.`);\n const fraction = (match[2] ?? '').replace(/0+$/, '');\n if (fraction.length > exponent) {\n throw new Error(`Amount ${value} has more than ${exponent} decimals.`);\n }\n const atomic = BigInt(`${match[1]}${fraction.padEnd(exponent, '0')}`);\n if (atomic > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new Error(`Amount ${value} exceeds the safe integer range.`);\n }\n return Number(atomic);\n}\n","/**\n * BTCPay Server as a crypto payment rail (#3138): sugar over\n * {@link createCryptoBillingProvider} with the SDK's BTCPay checkout gateway.\n * One BTCPay store per seller; keys and secrets come from the host's secret\n * store, never from this package.\n */\nimport {\n BtcpayClient,\n type BtcpayClientOptions,\n type BtcpaySpeedPolicy,\n createBtcpayCheckoutGateway,\n} from '@happyvertical/payments/btcpay';\nimport { createCryptoBillingProvider } from './crypto.js';\nimport type { BillingProvider } from './provider.js';\n\nexport interface BtcPayBillingProviderOptions {\n /** Server origin, for example `https://btcpay.example.com`. */\n baseUrl: string;\n /** Store-scoped key: `canviewinvoices` + `cancreateinvoice` suffice. */\n apiKey: string;\n storeId: string;\n /** The store webhook's secret. */\n webhookSecret: string;\n /** Secret signing the `smrt_*` checkout metadata. */\n metadataSecret: string;\n /**\n * Confirmations before BTCPay settles on-chain: `HighSpeed` 0,\n * `MediumSpeed` 1, `LowMediumSpeed` 2 (default, the owner's settlement\n * policy), `LowSpeed` 6.\n */\n speedPolicy?: BtcpaySpeedPolicy;\n /** Default `['BTC-CHAIN']`; add `BTC-LN` once the store has Lightning. */\n paymentMethods?: string[];\n expirationMinutes?: number;\n monitoringMinutes?: number;\n /** BTCPay's own underpayment tolerance, percent (default 0). */\n paymentTolerance?: number;\n /** Label of the store's rate source, recorded on every payment. */\n rateSource?: string;\n /** Minimum checkout per currency, minor units. */\n minimumAmount?: Record<string, number>;\n /** Provider name (default `btcpay`). */\n name?: string;\n fetch?: BtcpayClientOptions['fetch'];\n}\n\nexport function createBtcPayBillingProvider(\n options: BtcPayBillingProviderOptions,\n): BillingProvider {\n const gateway = createBtcpayCheckoutGateway({\n client: new BtcpayClient({\n baseUrl: options.baseUrl,\n apiKey: options.apiKey,\n storeId: options.storeId,\n fetch: options.fetch,\n }),\n webhookSecret: options.webhookSecret,\n speedPolicy: options.speedPolicy ?? 'LowMediumSpeed',\n paymentMethods: options.paymentMethods,\n expirationMinutes: options.expirationMinutes,\n monitoringMinutes: options.monitoringMinutes,\n paymentTolerance: options.paymentTolerance,\n rateSource: options.rateSource,\n });\n return createCryptoBillingProvider({\n gateway,\n metadataSecret: options.metadataSecret,\n name: options.name ?? 'btcpay',\n minimumAmount: options.minimumAmount,\n });\n}\n","/**\n * Billing-cycle schedule and proration math (#3116).\n *\n * A payer's billing periods are a pure function of its account's\n * `billingAnchorAt`:\n *\n * - no anchor: UTC calendar months (the default, and the only schedule before\n * #3116);\n * - an anchor `A`: monthly periods `[A + k months, A + (k + 1) months)` at\n * `A`'s UTC time of day. A day past the end of a shorter month is clamped to\n * its last day, always computed from `A` itself, so an anchor on the 31st\n * runs Jan 31 → Feb 28 (29) → Mar 31 without drifting. Before `A` the\n * schedule stays calendar months, and the month `A` falls in ends early at\n * `A` (a *stub* period), so an account that moves from calendar months to an\n * anchor has no gap and no overlap.\n *\n * Every period carries the full cycle it belongs to (`cycleStart`/`cycleEnd`);\n * that cycle's length is the proration denominator, so a stub is billed as the\n * fraction of its calendar month it covers.\n */\nimport type { BillingPeriod } from './period-close.js';\n\n/** A period of a payer's schedule and the full cycle it prorates against. */\nexport interface ScheduledBillingPeriod extends BillingPeriod {\n /** Start of the full monthly cycle (equals `periodStart` except for a stub). */\n cycleStart: Date;\n /** End of the full monthly cycle (equals `periodEnd` except for a stub). */\n cycleEnd: Date;\n}\n\nfunction daysInMonth(year: number, month: number): number {\n return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();\n}\n\n/**\n * `anchor` plus `months` calendar months at the anchor's UTC time of day,\n * with the day clamped to the target month's last day.\n */\nexport function addBillingMonths(anchor: Date, months: number): Date {\n const total = anchor.getUTCFullYear() * 12 + anchor.getUTCMonth() + months;\n const year = Math.floor(total / 12);\n const month = total - year * 12;\n const day = Math.min(anchor.getUTCDate(), daysInMonth(year, month));\n return new Date(\n Date.UTC(\n year,\n month,\n day,\n anchor.getUTCHours(),\n anchor.getUTCMinutes(),\n anchor.getUTCSeconds(),\n anchor.getUTCMilliseconds(),\n ),\n );\n}\n\nfunction monthStart(date: Date): Date {\n return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));\n}\n\nfunction calendarMonthContaining(at: Date): ScheduledBillingPeriod {\n const periodStart = monthStart(at);\n const periodEnd = new Date(\n Date.UTC(periodStart.getUTCFullYear(), periodStart.getUTCMonth() + 1, 1),\n );\n return {\n periodStart,\n periodEnd,\n cycleStart: periodStart,\n cycleEnd: periodEnd,\n };\n}\n\nfunction assertValidDate(date: Date, name: string): void {\n if (!(date instanceof Date) || !Number.isFinite(date.getTime())) {\n throw new Error(`${name} must be a valid date.`);\n }\n}\n\n/**\n * The period of a schedule (calendar months when `anchor` is null) that\n * contains `at`.\n */\nexport function billingPeriodContaining(\n anchor: Date | null | undefined,\n at: Date,\n): ScheduledBillingPeriod {\n assertValidDate(at, 'at');\n if (!anchor) return calendarMonthContaining(at);\n assertValidDate(anchor, 'billingAnchorAt');\n const time = at.getTime();\n if (time < anchor.getTime()) {\n const anchorMonth = monthStart(anchor);\n if (time >= anchorMonth.getTime()) {\n // The stub between the anchor's month start and the anchor itself.\n const cycle = calendarMonthContaining(anchorMonth);\n return {\n periodStart: anchorMonth,\n periodEnd: anchor,\n cycleStart: cycle.cycleStart,\n cycleEnd: cycle.cycleEnd,\n };\n }\n return calendarMonthContaining(at);\n }\n let k =\n (at.getUTCFullYear() - anchor.getUTCFullYear()) * 12 +\n (at.getUTCMonth() - anchor.getUTCMonth());\n while (k > 0 && addBillingMonths(anchor, k).getTime() > time) k -= 1;\n while (addBillingMonths(anchor, k + 1).getTime() <= time) k += 1;\n const periodStart = addBillingMonths(anchor, k);\n const periodEnd = addBillingMonths(anchor, k + 1);\n return {\n periodStart,\n periodEnd,\n cycleStart: periodStart,\n cycleEnd: periodEnd,\n };\n}\n\n/**\n * The most recent period of a schedule that has ended by `now` — what a\n * period close with no explicit period bills. With no anchor this is the\n * previous calendar month.\n */\nexport function lastEndedBillingPeriod(\n anchor: Date | null | undefined,\n now: Date = new Date(),\n): ScheduledBillingPeriod {\n const current = billingPeriodContaining(anchor, now);\n return billingPeriodContaining(\n anchor,\n new Date(current.periodStart.getTime() - 1),\n );\n}\n\n/**\n * `amount × coveredMs / cycleMs` in integer minor units, rounded half up\n * (exact integer arithmetic, no floating point). Coverage of the whole cycle\n * or more returns `amount` unchanged; no coverage returns 0.\n */\nexport function prorateMinorUnits(\n amount: number,\n coveredMs: number,\n cycleMs: number,\n): number {\n if (!Number.isSafeInteger(amount) || amount < 0) {\n throw new Error('A prorated amount must be a non-negative safe integer.');\n }\n if (!Number.isSafeInteger(cycleMs) || cycleMs <= 0) {\n throw new Error('A billing cycle must have a positive length.');\n }\n if (!Number.isSafeInteger(coveredMs) || coveredMs <= 0) return 0;\n if (coveredMs >= cycleMs) return amount;\n const numerator = BigInt(amount) * BigInt(coveredMs) * 2n + BigInt(cycleMs);\n return Number(numerator / (BigInt(cycleMs) * 2n));\n}\n","/**\n * PaymentAllocation model - maps payments to invoices\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport type { PaymentAllocationOptions } from '../types/index.js';\n\n/*\n * There is deliberately no `ALLOCATION_EPSILON` here any more (#2401).\n *\n * `Payment.amount`, `Invoice.totalAmount` and this row's `amount` are all\n * integer minor units, so \"does this allocation over-apply the payment\" is an\n * exact integer question. The old `0.01` tolerance let an allocation exceed its\n * payment by up to a cent — real money, not rounding fuzz — and only existed\n * because the payment side was a float.\n */\n\n/**\n * PaymentAllocation tracks how payments are applied to invoices.\n *\n * This enables:\n * - Partial payments (one payment partially covering an invoice)\n * - Split payments (one payment split across multiple invoices)\n * - Payment history per invoice\n *\n * **Note**: This model does not automatically validate that allocations\n * don't exceed the payment amount or invoice amount due. Use\n * `PaymentAllocationCollection.getUnallocatedFromPayment()` before creating\n * allocations to ensure sufficient funds are available.\n *\n * @example\n * ```typescript\n * import { PaymentAllocationCollection } from '@happyvertical/smrt-commerce';\n *\n * // Get the collection\n * const allocations = await PaymentAllocationCollection.create(options);\n *\n * // Check available funds before allocating (all amounts are minor units)\n * const available = await allocations.getUnallocatedFromPayment(\n * payment.id,\n * payment.amount\n * );\n *\n * if (available >= amountToAllocate) {\n * const allocation = await allocations.create({\n * paymentId: payment.id,\n * invoiceId: invoice.id,\n * amount: amountToAllocate,\n * allocatedBy: 'user-uuid'\n * });\n *\n * // Update invoice payment status\n * const totalAllocated = await allocations.getTotalAllocatedToInvoice(invoice.id);\n * invoice.updatePaymentStatus(totalAllocated);\n * await invoice.save();\n * }\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // NOTE: `create` and `delete` are intentionally NOT exposed over the\n // generated REST/MCP surface (S5 audit #1390). Allocation rows directly\n // determine an invoice's amountPaid/status and a payment's remaining funds;\n // a generated `create` lets a caller forge an allocation that over-applies a\n // payment, and a generated `delete` lets a caller silently un-apply funds —\n // both falsify balances with no authz. Allocations must be created/removed\n // through application code that re-derives invoice status and respects the\n // payment-amount cap (the cap is also enforced in `save()` as defence in\n // depth).\n //\n // CLI parity (round 6, codex ROOT INSIGHT): the CLI is an independently\n // configured write surface. `cli: true` would still generate create/update/\n // delete commands that forge or un-apply allocations, re-opening the exact\n // vector closed on api/mcp. The CLI is locked to the same read-only surface.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class PaymentAllocation extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global payment allocations\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Payment being allocated\n */\n @foreignKey('Payment')\n paymentId: string = '';\n\n /**\n * Invoice receiving the allocation\n */\n @foreignKey('Invoice')\n invoiceId: string = '';\n\n /**\n * Amount allocated from payment to invoice, in **integer minor units**\n * (cents, satoshis).\n *\n * Matches `Invoice.amountPaid` (derived by summing these rows) and\n * `Payment.amount` (which `save()` caps against). All three converted\n * together in #2401 — the cap is a subtraction across the pair, so a\n * dollars-vs-cents split there would reject every allocation as\n * over-applying.\n */\n amount: number = 0;\n\n /**\n * When the allocation was made\n */\n allocatedAt: Date = new Date();\n\n /**\n * User/agent who made the allocation\n */\n allocatedBy: string = '';\n\n /**\n * Notes about the allocation\n */\n notes: string = '';\n\n constructor(options: PaymentAllocationOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.invoiceId !== undefined) this.invoiceId = options.invoiceId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.allocatedAt !== undefined)\n this.allocatedAt = options.allocatedAt;\n if (options.allocatedBy !== undefined)\n this.allocatedBy = options.allocatedBy;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Save-time integrity guard (S5 audit #1390 + follow-up):\n * - allocation `amount` must be a finite, positive number,\n * - the sum of all allocations against the referenced Payment (this row\n * included) must not exceed the Payment's amount — over-applying a\n * payment across invoices would falsify both payment and invoice\n * balances, and\n * - the sum of all allocations against the referenced Invoice (this row\n * included) must not exceed the Invoice's `totalAmount`.\n *\n * The Payment-amount cap is enforced against the persisted Payment row. An\n * allocation always carries a `@foreignKey('Payment')` paymentId, so a\n * `paymentId` that doesn't resolve to a real Payment is a **hard error** (S5\n * audit #1390 round 2): previously a missing Payment silently skipped the cap\n * entirely, letting a caller over-apply (or fabricate) funds simply by\n * pointing at a non-existent payment. An empty `paymentId` is still rejected\n * by the underlying FK requirement; the positivity check always applies.\n *\n * The Invoice-total cap (follow-up) closes a complementary hole: the\n * per-Payment cap lets allocations from *different* payments each pass their\n * own check while jointly summing above the invoice total. The next\n * `Invoice.save()` would then recompute `amountPaid` from these allocations,\n * trip `assertNonNegativeAmounts` (amountPaid > totalAmount), and leave the\n * invoice permanently unsaveable while the over-allocations persist. Capping\n * here keeps allocations from ever exceeding what the invoice owes. The cap\n * is skipped only when the invoice row can't be resolved (e.g. ledger-less /\n * not-yet-persisted) so it never blocks an otherwise-valid allocation.\n */\n override async save(): Promise<this> {\n // Whole minor units first — every cap below is an exact integer comparison\n // and is only sound if the value really is an integer (#2401).\n assertIntegerMinorUnits('PaymentAllocation', this.id ?? '<new>', [\n ['amount', this.amount],\n ]);\n if (this.amount <= 0) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: amount must be a positive number (got ${this.amount}).`,\n );\n }\n\n if (this.paymentId) {\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: this.paymentId });\n if (!payment) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: referenced Payment ` +\n `'${this.paymentId}' does not exist — refusing to allocate against a ` +\n 'missing payment (the payment-amount cap cannot be enforced otherwise).',\n );\n }\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocations = await PaymentAllocationCollection.create(\n this.options,\n );\n const existing = await allocations.findByPayment(this.paymentId);\n const otherTotal = existing.reduce(\n (sum: number, alloc: PaymentAllocation) =>\n alloc.id === this.id ? sum : sum + alloc.amount,\n 0,\n );\n if (otherTotal + this.amount > payment.amount) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: allocating ${this.amount} would over-apply ` +\n `payment '${this.paymentId}' — already allocated ${otherTotal} of ${payment.amount}.`,\n );\n }\n }\n\n if (this.invoiceId) {\n const { InvoiceCollection } = await import(\n '../collections/InvoiceCollection.js'\n );\n const invoices = await InvoiceCollection.create(this.options);\n const invoice = await invoices.get({ id: this.invoiceId });\n // Only enforce when the invoice resolves and carries a real positive\n // total — a missing/zero-total invoice (not yet persisted, ledger-less\n // test fixture) skips the cap rather than blocking a valid allocation.\n // `Number.isFinite(0)` is `true`, so an `isFinite` guard here wrongly\n // capped a freshly-created total=0 invoice at 0 and rejected every\n // allocation against it; gate on `> 0` to match the documented\n // \"zero/missing total skips the cap\" intent.\n if (invoice && invoice.totalAmount > 0) {\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocations = await PaymentAllocationCollection.create(\n this.options,\n );\n const existingForInvoice = await allocations.findByInvoice(\n this.invoiceId,\n );\n const otherInvoiceTotal = existingForInvoice.reduce(\n (sum: number, alloc: PaymentAllocation) =>\n alloc.id === this.id ? sum : sum + alloc.amount,\n 0,\n );\n if (otherInvoiceTotal + this.amount > invoice.totalAmount) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: allocating ${this.amount} would over-pay ` +\n `invoice '${this.invoiceId}' — already allocated ${otherInvoiceTotal} of ` +\n `${invoice.totalAmount}.`,\n );\n }\n }\n }\n\n return super.save() as Promise<this>;\n }\n}\n\nexport default PaymentAllocation;\n","/**\n * PaymentAllocationCollection - Collection manager for PaymentAllocation objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { PaymentAllocation } from '../models/PaymentAllocation.js';\n\nexport class PaymentAllocationCollection extends SmrtCollection<PaymentAllocation> {\n static readonly _itemClass = PaymentAllocation;\n\n /**\n * Find allocations by payment\n *\n * @param paymentId - Payment ID\n * @returns Array of allocations\n */\n async findByPayment(paymentId: string): Promise<PaymentAllocation[]> {\n return await this.list({\n where: { paymentId },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n /**\n * Find allocations by invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Array of allocations\n */\n async findByInvoice(invoiceId: string): Promise<PaymentAllocation[]> {\n return await this.list({\n where: { invoiceId },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n /**\n * Get total amount allocated to an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Total allocated amount\n */\n async getTotalAllocatedToInvoice(invoiceId: string): Promise<number> {\n const allocations = await this.findByInvoice(invoiceId);\n return allocations.reduce((sum, alloc) => sum + alloc.amount, 0);\n }\n\n /**\n * Get total amount allocated from a payment\n *\n * @param paymentId - Payment ID\n * @returns Total allocated amount\n */\n async getTotalAllocatedFromPayment(paymentId: string): Promise<number> {\n const allocations = await this.findByPayment(paymentId);\n return allocations.reduce((sum, alloc) => sum + alloc.amount, 0);\n }\n\n /**\n * Get remaining unallocated amount from a payment.\n *\n * @param paymentId - Payment ID\n * @param paymentAmount - Total payment amount\n * @returns Unallocated amount\n * @throws Error if payment is over-allocated (indicates data integrity issue)\n */\n async getUnallocatedFromPayment(\n paymentId: string,\n paymentAmount: number,\n ): Promise<number> {\n const allocated = await this.getTotalAllocatedFromPayment(paymentId);\n const remaining = paymentAmount - allocated;\n\n if (remaining < 0) {\n throw new Error(\n `Over-allocated payment detected for paymentId=${paymentId}: ` +\n `allocated amount (${allocated}) exceeds payment amount (${paymentAmount})`,\n );\n }\n\n return remaining;\n }\n\n /**\n * Find allocation by payment and invoice\n *\n * @param paymentId - Payment ID\n * @param invoiceId - Invoice ID\n * @returns Allocation or null\n */\n async findByPaymentAndInvoice(\n paymentId: string,\n invoiceId: string,\n ): Promise<PaymentAllocation | null> {\n const results = await this.list({\n where: { paymentId, invoiceId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find allocations by allocator\n *\n * @param allocatedBy - User/agent ID who made the allocation\n * @returns Array of allocations\n */\n async findByAllocator(allocatedBy: string): Promise<PaymentAllocation[]> {\n return await this.list({\n where: { allocatedBy },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n /**\n * Find allocations in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of allocations\n */\n async findByDateRange(\n startDate: Date,\n endDate: Date,\n ): Promise<PaymentAllocation[]> {\n return await this.list({\n where: {\n 'allocatedAt >=': startDate.toISOString(),\n 'allocatedAt <=': endDate.toISOString(),\n },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all payment allocations belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of payment allocations for the tenant\n */\n async findByTenant(tenantId: string): Promise<PaymentAllocation[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payment allocations (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global payment allocations\n */\n async findGlobal(): Promise<PaymentAllocation[]> {\n return queryGlobal<PaymentAllocation>(this);\n }\n\n /**\n * Find payment allocations for a tenant including global allocations.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global payment allocations\n */\n async findWithGlobals(tenantId: string): Promise<PaymentAllocation[]> {\n return queryWithGlobals<PaymentAllocation>(\n this,\n tenantId,\n 'PaymentAllocation.findWithGlobals',\n );\n }\n}\n","/**\n * Paying an issued invoice on another payment rail (#3138).\n *\n * The issuing provider (Stripe) issues, taxes, and emails the invoice. A payer\n * may instead pay it on a payment rail such as a crypto checkout: this opens a\n * rail checkout for the invoice's amount due, and settlement records the\n * payment locally and closes the issuing provider's invoice as paid out of\n * band so it stops collecting.\n */\nimport {\n getTenantId,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport { PaymentAllocationCollection } from '../collections/PaymentAllocationCollection.js';\nimport { InvoiceStatus } from '../types/index.js';\nimport {\n BillingPaymentRefusedError,\n type BillingProviderCheckoutSession,\n providerCapabilities,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n deterministicId,\n isUuid,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport const INVOICE_PAYMENT_PURPOSE = 'invoice_payment';\n\n/**\n * Invoices a rail payment may be opened for. A written-off invoice is still\n * payable, as it is at the issuer (#3188): settlement records the payment,\n * closes the issuer's invoice out of band, and returns the payer to\n * `current`; the invoice keeps its `WRITTEN_OFF` status.\n */\nconst PAYABLE = new Set<string>([\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.WRITTEN_OFF,\n]);\n\nexport interface CreateInvoicePaymentInput {\n invoiceId: string;\n /** The payment rail (a provider name from `paymentProviders`). */\n provider: string;\n /**\n * Caller-owned id for this attempt; a retry with the same id (and the same\n * amount due) returns the same checkout.\n */\n purchaseId: string;\n successUrl: string;\n cancelUrl: string;\n description?: string;\n}\n\nexport interface InvoicePaymentMetadata {\n purpose: string;\n sellerTenantId: string;\n payerTenantId: string;\n billingAccountId: string;\n invoiceId: string;\n amount: number;\n currency: string;\n}\n\n/** Parse checkout metadata written by {@link createInvoicePayment}. */\nexport function invoicePaymentMetadata(\n raw: Record<string, string>,\n): InvoicePaymentMetadata | null {\n if (raw.smrt_purpose !== INVOICE_PAYMENT_PURPOSE) return null;\n const amount = Number(raw.smrt_amount);\n if (\n !raw.smrt_seller ||\n !raw.smrt_payer ||\n !raw.smrt_account ||\n !raw.smrt_invoice ||\n !raw.smrt_currency ||\n !Number.isSafeInteger(amount) ||\n amount <= 0\n ) {\n throw new Error('Invoice payment metadata is incomplete.');\n }\n return {\n purpose: raw.smrt_purpose,\n sellerTenantId: tenantKey(raw.smrt_seller),\n payerTenantId: tenantKey(raw.smrt_payer),\n billingAccountId: raw.smrt_account,\n invoiceId: raw.smrt_invoice,\n amount,\n currency: normalizeCurrency(raw.smrt_currency),\n };\n}\n\nexport async function createInvoicePayment(\n runtime: BillingRuntime,\n input: CreateInvoicePaymentInput,\n): Promise<BillingProviderCheckoutSession> {\n if (!input.purchaseId) throw new Error('purchaseId is required.');\n const rail = runtime.providerFor(input.provider);\n if (!providerCapabilities(rail).paymentAttempts) {\n throw new Error(\n `Provider ${rail.name} cannot take payments for issued invoices.`,\n );\n }\n // Authorize before anything about the invoice is revealed: a payer may\n // only learn about, and pay, its own invoices.\n const notFound = () =>\n new BillingPaymentRefusedError(\n 'not_found',\n `Invoice ${input.invoiceId} was not found for this payer.`,\n );\n // Billing invoice ids are UUIDs: anything else cannot be one, and must not\n // reach a UUID column as a failed cast. A failed lookup is a server error,\n // never reported to the payer as \"not found\" (#3185).\n if (!isUuid(input.invoiceId)) throw notFound();\n const invoice = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n runtime.invoices.get(input.invoiceId),\n );\n if (!invoice?.id) throw notFound();\n const [close] = await runtime.closes.list({\n where: {\n invoiceId: String(invoice.id),\n sellerTenantId: runtime.sellerTenantId,\n },\n limit: 1,\n });\n const account = close?.billingAccountId\n ? await runtime.accounts.get(close.billingAccountId)\n : null;\n if (!account?.id) throw notFound();\n const payer = tenantKey(account.payerTenantId);\n if (\n !isSystemContext() &&\n !isSuperAdminBypass() &&\n tenantKey(getTenantId()) !== payer\n ) {\n throw new TenantIsolationError('Only the payer of an invoice can pay it.');\n }\n if (!PAYABLE.has(invoice.status)) {\n throw new BillingPaymentRefusedError(\n 'not_payable',\n `Invoice ${invoice.invoiceNumber} is ${invoice.status}; only sent, unpaid invoices can be paid.`,\n );\n }\n if (invoice.externalProvider !== runtime.provider.name) {\n throw new BillingPaymentRefusedError(\n 'not_payable',\n `Invoice ${invoice.invoiceNumber} was not issued by ${runtime.provider.name}.`,\n );\n }\n // Fail closed: without it a paid invoice would stay open at the issuer.\n if (!runtime.provider.markInvoicePaidOutOfBand) {\n throw new Error(\n `${runtime.provider.name} cannot close an invoice paid on another rail.`,\n );\n }\n const allocated = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () =>\n (\n await PaymentAllocationCollection.create({ db: runtime.db })\n ).getTotalAllocatedToInvoice(String(invoice.id)),\n );\n const amountDue = invoice.totalAmount - allocated;\n if (!Number.isSafeInteger(amountDue) || amountDue <= 0) {\n throw new BillingPaymentRefusedError(\n 'nothing_due',\n `Invoice ${invoice.invoiceNumber} has nothing due.`,\n );\n }\n const currency = normalizeCurrency(invoice.currency);\n const key = await deterministicId([\n 'billing-invoice-payment',\n runtime.sellerTenantId,\n rail.name,\n String(invoice.id),\n String(amountDue),\n input.purchaseId,\n ]);\n const orderId = `smrt-invoice-payment:${key}`;\n // One live payment per invoice: a second checkout could be paid too and\n // collect the invoice twice. The payer finishes (or lets expire) the one\n // already open; retrying the same purchase returns it.\n const live = (\n await runtime.listPaymentAttempts({ invoiceId: String(invoice.id) })\n ).filter(\n (row) =>\n (row.status === 'open' || row.status === 'confirming') && !row.settledAt,\n );\n // Rows without an order id (created before it was recorded) are left to\n // the gateway, whose creation is idempotent by order id.\n const busy = live.find((row) => row.orderId && row.orderId !== orderId);\n if (busy) {\n throw new BillingPaymentRefusedError(\n 'payment_in_progress',\n `Invoice ${invoice.invoiceNumber} already has a payment in progress on ${busy.provider}.`,\n );\n }\n const metadata: Record<string, string> = {\n smrt_purpose: INVOICE_PAYMENT_PURPOSE,\n smrt_seller: runtime.sellerTenantId,\n smrt_payer: payer,\n smrt_account: String(account.id),\n smrt_invoice: String(invoice.id),\n smrt_amount: String(amountDue),\n smrt_currency: currency,\n };\n const session = await rail.createCheckout({\n idempotencyKey: orderId,\n customerEmail: account.email || undefined,\n currency,\n amount: amountDue,\n description: input.description || `Invoice ${invoice.invoiceNumber}`,\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n metadata,\n });\n await runtime.recordPaymentAttemptStart({\n orderId,\n provider: rail.name,\n checkoutId: session.sessionId,\n checkoutUrl: session.url ?? '',\n purpose: 'invoice_payment',\n payerTenantId: payer,\n billingAccountId: String(account.id),\n invoiceId: String(invoice.id),\n amount: amountDue,\n currency,\n });\n return session;\n}\n","/**\n * smrt-jobs integration for billing (#3060).\n *\n * A runtime holds live dependencies (the payment provider, the relationship\n * reader) that cannot be serialized into a job row, so jobs name a runtime\n * registered in this process and carry only plain arguments.\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { type SmrtJob, SmrtJobCollection } from '@happyvertical/smrt-jobs';\nimport type { PeriodCloseResult } from './period-close.js';\nimport type { BillingRuntime } from './runtime.js';\n\nconst REGISTRY_KEY = Symbol.for(\n '@happyvertical/smrt-commerce:billing-runtimes',\n);\n\nfunction registry(): Map<string, BillingRuntime> {\n const holder = globalThis as unknown as Record<\n symbol,\n Map<string, BillingRuntime> | undefined\n >;\n let runtimes = holder[REGISTRY_KEY];\n if (!runtimes) {\n runtimes = new Map();\n holder[REGISTRY_KEY] = runtimes;\n }\n return runtimes;\n}\n\n/** Make a runtime available to billing jobs under `name`. */\nexport function registerBillingRuntime(\n name: string,\n runtime: BillingRuntime,\n): void {\n if (!name) throw new Error('A billing runtime name is required.');\n registry().set(name, runtime);\n}\n\nexport function unregisterBillingRuntime(name: string): void {\n registry().delete(name);\n}\n\nexport function getBillingRuntime(name: string): BillingRuntime {\n const runtime = registry().get(name);\n if (!runtime) {\n throw new Error(`No billing runtime is registered as '${name}'.`);\n }\n return runtime;\n}\n\nfunction runtimeFrom(args: Record<string, unknown>): BillingRuntime {\n if (typeof args.runtime !== 'string') {\n throw new Error('Billing jobs require a runtime name.');\n }\n return getBillingRuntime(args.runtime);\n}\n\nfunction optionalDate(value: unknown, name: string): Date | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n const date = new Date(String(value));\n if (!Number.isFinite(date.getTime())) {\n throw new Error(`${name} must be an ISO date.`);\n }\n return date;\n}\n\nexport async function runBillingPeriodCloseJob(\n args: Record<string, unknown>,\n): Promise<PeriodCloseResult> {\n return runtimeFrom(args).closePeriod({\n periodStart: optionalDate(args.periodStart, 'periodStart'),\n periodEnd: optionalDate(args.periodEnd, 'periodEnd'),\n });\n}\n\nexport async function runBillingEventsJob(\n args: Record<string, unknown>,\n): Promise<{ processed: number }> {\n const limit = args.limit === undefined ? 25 : Number(args.limit);\n return { processed: await runtimeFrom(args).processEvents(limit) };\n}\n\nexport interface EnqueueBillingJobOptions {\n /** Registered runtime name. */\n runtime: string;\n runAt?: Date;\n queue?: string;\n maxAttempts?: number;\n}\n\nfunction jobObjectType(): string {\n return (\n ObjectRegistry.getClass('BillingPeriodClose')?.qualifiedName ??\n '@happyvertical/smrt-commerce:BillingPeriodClose'\n );\n}\n\nasync function enqueue(\n runtime: BillingRuntime,\n method: string,\n args: Record<string, unknown>,\n options: EnqueueBillingJobOptions,\n): Promise<SmrtJob> {\n const jobs = await SmrtJobCollection.create({ db: runtime.db });\n return jobs.enqueueJob({\n tenantId: null,\n queue: options.queue ?? 'billing',\n objectType: jobObjectType(),\n objectId: null,\n method,\n args: { runtime: options.runtime, ...args },\n runAt: options.runAt,\n maxAttempts: options.maxAttempts ?? 5,\n });\n}\n\n/**\n * Queue a period close. Without a period the job closes each payer's last\n * ended period on its own schedule when it runs (the previous calendar month,\n * or the last anchor-to-anchor period for an anchored account, #3116), so\n * enqueueing it daily is safe.\n */\nexport function enqueueBillingPeriodClose(\n options: EnqueueBillingJobOptions & { periodStart?: Date; periodEnd?: Date },\n): Promise<SmrtJob> {\n const runtime = getBillingRuntime(options.runtime);\n return enqueue(\n runtime,\n 'runPeriodClose',\n {\n ...(options.periodStart\n ? { periodStart: options.periodStart.toISOString() }\n : {}),\n ...(options.periodEnd\n ? { periodEnd: options.periodEnd.toISOString() }\n : {}),\n },\n options,\n );\n}\n\n/** Queue processing of received provider events. */\nexport function enqueueBillingEvents(\n options: EnqueueBillingJobOptions & { limit?: number },\n): Promise<SmrtJob> {\n if (\n options.limit !== undefined &&\n (!Number.isSafeInteger(options.limit) || options.limit <= 0)\n ) {\n return Promise.reject(new Error('limit must be a positive integer.'));\n }\n const runtime = getBillingRuntime(options.runtime);\n return enqueue(\n runtime,\n 'processBillingEvents',\n options.limit !== undefined ? { limit: options.limit } : {},\n options,\n );\n}\n","/**\n * Invoice model - billing document sent to customers\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Journal, JournalEntryData } from '@happyvertical/smrt-ledgers';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { minorToMajorUnits } from '../billing/units.js';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport {\n type AccountingInvoiceInput,\n type InvoiceCollectionMethod,\n type InvoiceOptions,\n InvoiceStatus,\n type RecognizeRevenueOptions,\n} from '../types/index.js';\nimport type { InvoiceLineItem } from './InvoiceLineItem.js';\n\n/*\n * There is deliberately no `INVOICE_EPSILON` here any more (#2401).\n *\n * Every amount on this model is integer minor units, so `subtotal + taxAmount`\n * is exact, the sum of allocations is exact, and every comparison below is a\n * plain integer comparison. The old `0.01` tolerance existed because the values\n * were floats; keeping it against integers would silently forgive a genuine\n * one-cent discrepancy, and the \"snap `totalAmount` to `subtotal + taxAmount`\n * afterwards\" workaround it required (to stay inside the tighter smrt-ledgers\n * `BALANCE_EPSILON`) is gone with it — exact arithmetic needs no snap.\n */\n\n/**\n * Legal status transitions for an Invoice. Keyed by the prior (persisted)\n * status; the value is the set of statuses it may move to. A status mapping\n * to itself (no-op re-save) is always permitted and handled separately.\n *\n * Forward path: DRAFT → SENT → VIEWED → PARTIAL → PAID, with OVERDUE reachable\n * from any open status and CANCELLED / WRITTEN_OFF as terminal exits. Payment\n * progress (SENT/VIEWED ↔ PARTIAL ↔ PAID) is driven by `updatePaymentStatus`,\n * which can move both forward and backward as allocations change, so those\n * edges are bidirectional here.\n */\nconst INVOICE_STATUS_TRANSITIONS: Record<InvoiceStatus, InvoiceStatus[]> = {\n [InvoiceStatus.DRAFT]: [\n InvoiceStatus.SENT,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.SENT]: [\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.VIEWED]: [\n InvoiceStatus.SENT,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.PARTIAL]: [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.OVERDUE]: [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n // PAID can fall back to PARTIAL / VIEWED / SENT when a payment allocation is\n // removed or reversed (handled by updatePaymentStatus). It can also be\n // written off as bad debt in unusual reconciliation flows.\n [InvoiceStatus.PAID]: [\n InvoiceStatus.PARTIAL,\n InvoiceStatus.VIEWED,\n InvoiceStatus.SENT,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.WRITTEN_OFF,\n ],\n // Terminal states: no outbound transitions.\n [InvoiceStatus.CANCELLED]: [],\n [InvoiceStatus.WRITTEN_OFF]: [],\n};\n\n/**\n * Module-scoped record of the status each Invoice instance was loaded with.\n * Used by the save-time status-transition guard to compare the prior\n * persisted status against the one being written, without adding a\n * persisted column. A WeakMap keyed by the instance keeps it out of the\n * schema and garbage-collects with the instance — same pattern LicenseSale\n * uses for its rights snapshot.\n */\nconst loadedInvoiceStatus = new WeakMap<Invoice, InvoiceStatus>();\n\n/**\n * Invoice represents a bill sent to a customer for goods or services.\n *\n * Invoices are distinct from Contracts - a Contract is an agreement,\n * while an Invoice is the billing document requesting payment.\n *\n * Invoices integrate with:\n * - `@happyvertical/smrt-ledgers` for revenue recognition (double-entry accounting)\n * - `@happyvertical/accounting` SDK for syncing with external providers (QBO, Stripe)\n *\n * @example\n * ```typescript\n * // Create an invoice\n * const invoice = await invoices.create({\n * customerId: customer.id,\n * invoiceNumber: await invoices.generateInvoiceNumber(),\n * issueDate: new Date(),\n * dueDate: addDays(new Date(), 30),\n * subtotal: 1000,\n * taxAmount: 50,\n * totalAmount: 1050,\n * currency: 'CAD'\n * });\n *\n * // Recognize revenue in ledger\n * await invoice.recognizeRevenue({\n * arAccountId: arAccount.id,\n * revenueAccountId: revenueAccount.id,\n * taxAccountId: taxAccount.id\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): the generated create/update surface may\n // only set the billing-document descriptors (who/when/identifiers). The\n // derived/integrity amounts (`subtotal`, `taxAmount`, `totalAmount`,\n // `amountPaid`) and `status` are EXCLUDED — totals are derived from line\n // items, amountPaid from PaymentAllocations, and status from\n // updatePaymentStatus()/the lifecycle helpers. A caller can therefore create\n // a DRAFT invoice (customer / dueDate / line-item refs) but can never forge a\n // total, an amountPaid, or a PAID status through a generated route. Ledger\n // journal refs and communication timestamps are server-managed too.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'customerId',\n 'contractId',\n 'invoiceNumber',\n 'reference',\n 'issueDate',\n 'dueDate',\n 'currency',\n 'notes',\n 'customerNotes',\n 'terms',\n 'externalId',\n 'customerExternalId',\n 'externalProvider',\n ],\n },\n // NOTE: `send` and `recognizeRevenue` are intentionally NOT exposed over\n // MCP. `recognizeRevenue` posts a balanced journal into smrt-ledgers\n // (i.e. moves money in the books) and `send` transmits a billing document\n // to a customer — neither is safe as an unauthenticated/unguarded MCP tool.\n // Generated MCP tools carry no authz; financial mutations must go through\n // application code that enforces permissions. MCP create/update honor the\n // same `api.writable` allowlist, so status/amounts are unsettable over MCP\n // too. See S5 audit #1390.\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Invoice extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global invoices\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Customer this invoice is for\n */\n @foreignKey('Customer')\n customerId: string = '';\n\n /**\n * Optional link to Contract (cross-package reference)\n */\n @foreignKey('Contract')\n contractId: string = '';\n\n // ============================================================================\n // Identification\n // ============================================================================\n\n /**\n * Invoice number (e.g., INV-2025-0001)\n * Generated via InvoiceCollection.generateInvoiceNumber()\n */\n invoiceNumber: string = '';\n\n /**\n * External reference (e.g., customer PO number)\n */\n reference: string = '';\n\n // ============================================================================\n // Dates\n // ============================================================================\n\n /**\n * Date invoice was issued\n */\n issueDate: Date = new Date();\n\n /**\n * Payment due date\n */\n dueDate: Date = new Date();\n\n /**\n * Date invoice was fully paid\n */\n paidDate: Date | null = null;\n\n // ============================================================================\n // Amounts\n // ============================================================================\n\n /**\n * Subtotal before tax, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so it is stored\n * as minor units and never as a float — `$19.99` is `1999`, not `19.99`.\n * Writing a fractional major-unit value here is the bug; PostgreSQL rejects\n * it with `22P02` while SQLite's affinity silently stores it (#2361).\n */\n subtotal: number = 0;\n\n /**\n * Tax amount, in integer minor units (#2361). With line items this is the\n * sum of their rate-based tax plus {@link providerTaxAmount}.\n */\n taxAmount: number = 0;\n\n /**\n * Tax calculated by the payment provider (for example Stripe Tax) from the\n * customer's tax location, in integer minor units (#3060). Server-managed:\n * billing-period close records it after the provider finalizes the invoice.\n * It is added to the line items' tax, so `taxAmount` and `totalAmount` stay\n * authoritative from line items plus this one provider figure.\n */\n providerTaxAmount: number = 0;\n\n /**\n * Total amount due (subtotal + tax), in integer minor units (#2361).\n */\n totalAmount: number = 0;\n\n /**\n * Amount paid (sum of PaymentAllocations), in integer minor units (#2361).\n */\n amountPaid: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n currency: string = 'CAD';\n\n // ============================================================================\n // Status\n // ============================================================================\n\n /**\n * Current invoice status\n */\n status: InvoiceStatus = InvoiceStatus.DRAFT;\n\n // ============================================================================\n // Ledger Integration\n // ============================================================================\n\n /**\n * Journal ID for AR recognition (cross-package ref to smrt-ledgers)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n arJournalId: string = '';\n\n /**\n * Journal ID for revenue recognition (cross-package ref to smrt-ledgers)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n revenueJournalId: string = '';\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * External ID in accounting provider (e.g., QBO invoice ID)\n */\n externalId: string = '';\n\n /**\n * Customer's external ID in accounting provider.\n * Used to link invoice to customer in external system.\n */\n customerExternalId: string = '';\n\n /**\n * Accounting provider name ('quickbooks' | 'stripe' | etc.)\n */\n externalProvider: string = '';\n\n /**\n * When invoice was last synced to provider\n */\n syncedAt: Date | null = null;\n\n /**\n * How the provider collects this invoice (#3190), decided once by billing\n * period close before its first push and reused on every retry, so it is\n * what the provider was asked for. Server-managed. A host must not offer\n * another payment rail on a `charge_automatically` invoice the provider is\n * still collecting.\n */\n collectionMethod: InvoiceCollectionMethod = '';\n\n // ============================================================================\n // Communication\n // ============================================================================\n\n /**\n * When invoice was sent to customer\n */\n sentAt: Date | null = null;\n\n /**\n * When customer viewed the invoice\n */\n viewedAt: Date | null = null;\n\n /**\n * Number of payment reminders sent\n */\n remindersSent: number = 0;\n\n /**\n * When last reminder was sent\n */\n lastReminderAt: Date | null = null;\n\n // ============================================================================\n // Notes\n // ============================================================================\n\n /**\n * Internal notes (not shown to customer)\n */\n notes: string = '';\n\n /**\n * Notes shown to customer on invoice\n */\n customerNotes: string = '';\n\n /**\n * Payment terms text\n */\n terms: string = '';\n\n constructor(options: InvoiceOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.invoiceNumber !== undefined)\n this.invoiceNumber = options.invoiceNumber;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.issueDate !== undefined) this.issueDate = options.issueDate;\n if (options.dueDate !== undefined) this.dueDate = options.dueDate;\n if (options.paidDate !== undefined) this.paidDate = options.paidDate;\n if (options.subtotal !== undefined) this.subtotal = options.subtotal;\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.providerTaxAmount !== undefined)\n this.providerTaxAmount = options.providerTaxAmount;\n if (options.totalAmount !== undefined)\n this.totalAmount = options.totalAmount;\n if (options.amountPaid !== undefined) this.amountPaid = options.amountPaid;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.status !== undefined) this.status = options.status;\n if (options.arJournalId !== undefined)\n this.arJournalId = options.arJournalId;\n if (options.revenueJournalId !== undefined)\n this.revenueJournalId = options.revenueJournalId;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.customerExternalId !== undefined)\n this.customerExternalId = options.customerExternalId;\n if (options.externalProvider !== undefined)\n this.externalProvider = options.externalProvider;\n if (options.syncedAt !== undefined) this.syncedAt = options.syncedAt;\n if (options.collectionMethod !== undefined)\n this.collectionMethod = options.collectionMethod;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt;\n if (options.viewedAt !== undefined) this.viewedAt = options.viewedAt;\n if (options.remindersSent !== undefined)\n this.remindersSent = options.remindersSent;\n if (options.lastReminderAt !== undefined)\n this.lastReminderAt = options.lastReminderAt;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.customerNotes !== undefined)\n this.customerNotes = options.customerNotes;\n if (options.terms !== undefined) this.terms = options.terms;\n }\n\n /**\n * Capture the persisted status the row was loaded with, so the save-time\n * transition guard can reject illegal status flips made via raw field\n * assignment (mass-assignment on a generated update route, a stale caller,\n * etc.). Only rows that already exist in the database carry a \"prior\"\n * status — freshly-constructed (not-yet-saved) invoices have no prior and\n * may start in any status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedInvoiceStatus.set(this, this.status);\n }\n return this;\n }\n\n // ============================================================================\n // Save-time financial-integrity guard (S5 audit #1390)\n // ============================================================================\n\n /**\n * Recompute and validate financial fields before every write so forged\n * totals, negative amounts, and illegal status flips can't be persisted via\n * raw mass-assignment on the generated CRUD routes.\n *\n * Behaviour:\n * - **Totals are authoritative from line items.** When the invoice is\n * persisted and has line items, `subtotal` / `taxAmount` / `totalAmount`\n * are recomputed from those items, overriding whatever the caller sent.\n * This blocks \"create real line items but claim a tiny total\" forgery.\n * - **Without line items**, the caller-supplied totals are accepted but the\n * `totalAmount === subtotal + taxAmount` arithmetic invariant is enforced.\n * - **amountPaid is derived/validated.** When persisted, it is recomputed\n * from PaymentAllocations rather than trusted from the caller. It may\n * never exceed `totalAmount`.\n * - **Whole minor units and non-negativity** are enforced on all four\n * amounts. The integer check runs FIRST (#2401), before the\n * `totalAmount === subtotal + taxAmount` invariant, so a caller who wrote\n * major units gets \"money is minor units\" rather than a confusing\n * arithmetic-mismatch error from float addition.\n * - **Status transitions** are validated against the prior persisted status.\n */\n override async save(): Promise<this> {\n const persisted = await this.isSaved();\n const prior = await this.resolvePriorStatus(persisted);\n\n this.assertAmountsAreMinorUnits();\n await this.recomputeAmountsForSave(persisted);\n this.assertNonNegativeAmounts();\n this.assertStatusTransition(prior);\n this.assertPaymentStatusConsistent();\n\n const result = (await super.save()) as this;\n // Once written, the current status becomes the new \"prior\" for the next\n // save in this instance's lifetime.\n loadedInvoiceStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status (S5 audit #1390 round 4). The\n * {@link loadedInvoiceStatus} WeakMap is only populated when {@link initialize}\n * hydrated the row, so a `create({ id: <existing>, _skipLoad: true })` upsert\n * yields an instance whose WeakMap entry is missing — trusting it would treat\n * the write as a brand-new row and skip the transition guard entirely. Read\n * the persisted row's status directly so a create-onto-existing is correctly\n * treated as an update. `undefined` means no row exists (genuinely new).\n */\n private async resolvePriorStatus(\n persisted: boolean,\n ): Promise<InvoiceStatus | undefined> {\n if (persisted && this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as InvoiceStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedInvoiceStatus.get(this);\n }\n\n /**\n * Recompute subtotal/tax/total from line items (when present) and derive\n * amountPaid from PaymentAllocations. Falls back to the arithmetic invariant\n * when the invoice has no line items. Tolerant of smrt-ledgers being absent\n * (the dynamic imports stay inside the package).\n */\n private async recomputeAmountsForSave(persisted: boolean): Promise<void> {\n if (persisted && this.id) {\n const { InvoiceLineItemCollection } = await import(\n '../collections/InvoiceLineItemCollection.js'\n );\n const lineItemCollection = await InvoiceLineItemCollection.create(\n this.options,\n );\n const lineItems = await lineItemCollection.findByInvoice(this.id);\n\n if (lineItems.length > 0) {\n const subtotal = lineItems.reduce(\n (sum: number, item: InvoiceLineItem) => sum + item.getSubtotal(),\n 0,\n );\n const taxAmount =\n lineItems.reduce(\n (sum: number, item: InvoiceLineItem) => sum + item.getTaxAmount(),\n 0,\n ) + this.providerTaxAmount;\n // Totals are AUTHORITATIVE from the line items (S5 audit #1390). Any\n // caller-supplied subtotal / tax / total is ignored and overwritten —\n // that is the security goal (a forged total can never be persisted)\n // AND it self-heals a stale stored total (e.g. one left behind after a\n // line item changed). We deliberately do NOT throw on a caller\n // mismatch: throwing blocked legitimate self-heal of stale totals, and\n // since the values are overwritten regardless, the throw added no\n // protection. Non-negativity is still enforced downstream by\n // assertNonNegativeAmounts().\n const computedTotal = subtotal + taxAmount;\n this.subtotal = subtotal;\n this.taxAmount = taxAmount;\n this.totalAmount = computedTotal;\n } else {\n this.assertTotalArithmetic();\n }\n\n // amountPaid is the sum of PaymentAllocations — never trust the caller.\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocationCollection = await PaymentAllocationCollection.create(\n this.options,\n );\n const allocated = await allocationCollection.getTotalAllocatedToInvoice(\n this.id,\n );\n this.amountPaid = allocated;\n } else {\n // Not-yet-persisted invoice: no line items / allocations exist yet, so\n // enforce the arithmetic invariant on the caller-supplied totals.\n this.assertTotalArithmetic();\n\n // A brand-new invoice cannot already be paid: there are no\n // PaymentAllocations behind it yet, so a caller-supplied amountPaid (or a\n // PAID/PARTIAL starting status) would be unbacked money. Force it to\n // start unpaid; payment progress is only ever driven by allocations via\n // updatePaymentStatus(). (Re-derive on the first persisted save once\n // allocations exist.)\n this.amountPaid = 0;\n if (\n this.status === InvoiceStatus.PAID ||\n this.status === InvoiceStatus.PARTIAL\n ) {\n throw new Error(\n `Invoice ${this.invoiceNumber || '<new>'}: a new invoice cannot start ` +\n `in status '${this.status}' — payment status is derived from ` +\n 'PaymentAllocations, not set at creation. Create the invoice unpaid ' +\n '(DRAFT / SENT) and record payments via allocations.',\n );\n }\n }\n }\n\n /**\n * Whether `amountPaid` covers `totalAmount`. This is the SINGLE source of\n * truth for the **PAID** decision — both {@link updatePaymentStatus} (which\n * decides PAID) and {@link assertPaymentStatusConsistent} (which validates\n * PAID on save) call it, so the PAID-deciding comparison and the\n * PAID-validating comparison can never drift apart (S5 audit #1390\n * follow-up).\n *\n * With integer minor units this is a plain `>=` (#2401). The float-era\n * hazard it used to guard against — a total summed from `0.1 × 3` line items\n * reading as \"not covered\" by `>=` but \"covered\" by an epsilon-tolerant\n * guard, leaving a genuinely-paid invoice unsaveable — cannot arise when both\n * sides are exact integers. That also removes the documented PAID/PARTIAL\n * asymmetry: {@link updatePaymentStatus} and {@link isPartiallyPaid} now\n * agree that any `amountPaid > 0` is a partial payment, because there is no\n * such thing as sub-cent dust.\n */\n private isFullyPaid(): boolean {\n return this.amountPaid >= this.totalAmount;\n }\n\n /**\n * Whether `amountPaid` is a non-trivial partial payment of `totalAmount`.\n * Derived from {@link isFullyPaid} so the two stay mutually exclusive.\n */\n private isPartiallyPaid(): boolean {\n return this.amountPaid > 0 && !this.isFullyPaid();\n }\n\n /**\n * After amounts are recomputed and amountPaid is re-derived from allocations,\n * assert the persisted `status` is consistent with the derived\n * amountPaid-vs-totalAmount relationship. This blocks the raw\n * `SENT → PAID with amountPaid=0` flip (and its inverse, claiming PARTIAL/SENT\n * while fully allocated) that the status-transition map alone permits because\n * SENT → PAID is a structurally legal edge.\n *\n * Only enforced for the payment-derived statuses (PAID / PARTIAL and the\n * unpaid open states SENT / VIEWED). Lifecycle statuses that aren't a\n * function of amountPaid — DRAFT, OVERDUE, CANCELLED, WRITTEN_OFF — are left\n * to their own transition rules.\n */\n private assertPaymentStatusConsistent(): void {\n const label = this.invoiceNumber || this.id || '<new>';\n const fullyPaid = this.isFullyPaid();\n const partiallyPaid = this.isPartiallyPaid();\n\n if (this.status === InvoiceStatus.PAID && !fullyPaid) {\n throw new Error(\n `Invoice ${label}: status is PAID but derived amountPaid ${this.amountPaid} ` +\n `does not cover totalAmount ${this.totalAmount}. Payment status is ` +\n 'derived from PaymentAllocations — use updatePaymentStatus().',\n );\n }\n if (this.status === InvoiceStatus.PARTIAL && !partiallyPaid) {\n throw new Error(\n `Invoice ${label}: status is PARTIAL but derived amountPaid ${this.amountPaid} ` +\n `is not a partial payment of totalAmount ${this.totalAmount}. Payment ` +\n 'status is derived from PaymentAllocations — use updatePaymentStatus().',\n );\n }\n // Claiming an unpaid open status (SENT / VIEWED) while the invoice is in\n // fact fully covered by allocations is also inconsistent.\n if (\n (this.status === InvoiceStatus.SENT ||\n this.status === InvoiceStatus.VIEWED) &&\n this.totalAmount > 0 &&\n fullyPaid\n ) {\n throw new Error(\n `Invoice ${label}: status is '${this.status}' but derived amountPaid ` +\n `${this.amountPaid} fully covers totalAmount ${this.totalAmount}. ` +\n 'Use updatePaymentStatus() so the status reflects the allocations.',\n );\n }\n }\n\n /**\n * Enforce `totalAmount === subtotal + taxAmount` exactly. Used when the\n * invoice has no line items to recompute from.\n *\n * Exact because every term is integer minor units (#2401). The float era\n * needed a `0.01` tolerance here and then had to SNAP `totalAmount` to the\n * recomputed value afterwards, because a total that passed the invoice's\n * loose tolerance could still fail smrt-ledgers' tighter `BALANCE_EPSILON`\n * and void the journal `recognizeRevenue` builds from it. Integer arithmetic\n * removes both halves of that workaround: what the caller supplied either is\n * the sum or is rejected, and any journal built from it balances exactly.\n */\n private assertTotalArithmetic(): void {\n const expected = this.subtotal + this.taxAmount;\n if (this.totalAmount !== expected) {\n throw new Error(\n `Invoice ${this.invoiceNumber || this.id || '<new>'}: totalAmount ${this.totalAmount} ` +\n `must equal subtotal + taxAmount (${expected}).`,\n );\n }\n }\n\n /**\n * Reject amounts that are not whole minor units, negative financial values,\n * and an amountPaid that exceeds the total (overpayment is modelled\n * elsewhere, not by letting amountPaid float above the invoice total).\n */\n private assertAmountsAreMinorUnits(): void {\n assertIntegerMinorUnits(\n 'Invoice',\n this.invoiceNumber || this.id || '<new>',\n [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['providerTaxAmount', this.providerTaxAmount],\n ['totalAmount', this.totalAmount],\n ['amountPaid', this.amountPaid],\n ],\n );\n }\n\n private assertNonNegativeAmounts(): void {\n const label = this.invoiceNumber || this.id || '<new>';\n const amounts = [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['providerTaxAmount', this.providerTaxAmount],\n ['totalAmount', this.totalAmount],\n ['amountPaid', this.amountPaid],\n ] as const;\n // Re-checked after the recompute: line-item sums and the allocation total\n // feed these, so integer-ness has to hold for the derived values too — it\n // is what lets every comparison below drop its epsilon.\n assertIntegerMinorUnits('Invoice', label, amounts);\n for (const [field, value] of amounts) {\n if (value < 0) {\n throw new Error(\n `Invoice ${label}: ${field} must be a non-negative number (got ${value}).`,\n );\n }\n }\n if (this.amountPaid > this.totalAmount) {\n throw new Error(\n `Invoice ${label}: amountPaid ${this.amountPaid} cannot exceed totalAmount ${this.totalAmount}.`,\n );\n }\n }\n\n /**\n * Reject an illegal status flip done via raw field assignment. Compares the\n * about-to-be-written status against the status the row was loaded with.\n * No-op transitions (status unchanged) and brand-new rows are always allowed.\n */\n private assertStatusTransition(prior: InvoiceStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = INVOICE_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Invoice ${this.invoiceNumber || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'. Use the guarded transition helpers ` +\n '(markSent / markViewed / updatePaymentStatus / cancel / writeOff).',\n );\n }\n }\n\n // ============================================================================\n // Status Helpers\n // ============================================================================\n\n /**\n * Check if invoice is a draft\n */\n isDraft(): boolean {\n return this.status === InvoiceStatus.DRAFT;\n }\n\n /**\n * Check if invoice has been sent\n */\n isSent(): boolean {\n return this.status === InvoiceStatus.SENT;\n }\n\n /**\n * Check if invoice is fully paid\n */\n isPaid(): boolean {\n return this.status === InvoiceStatus.PAID;\n }\n\n /**\n * Check if invoice is partially paid\n */\n isPartial(): boolean {\n return this.status === InvoiceStatus.PARTIAL;\n }\n\n /**\n * Check if invoice is overdue\n */\n isOverdue(): boolean {\n if (this.status === InvoiceStatus.OVERDUE) return true;\n if (this.isPaid() || this.status === InvoiceStatus.CANCELLED) return false;\n return new Date() > this.dueDate;\n }\n\n /**\n * Get remaining amount due\n */\n getAmountDue(): number {\n return Math.max(0, this.totalAmount - this.amountPaid);\n }\n\n // ============================================================================\n // Status Transitions\n // ============================================================================\n\n /**\n * Mark invoice as sent\n */\n markSent(): void {\n if (this.status !== InvoiceStatus.DRAFT) {\n throw new Error(\n `Cannot send invoice with status '${this.status}'. Only draft invoices can be sent.`,\n );\n }\n this.status = InvoiceStatus.SENT;\n this.sentAt = new Date();\n }\n\n /**\n * Record that the invoice was viewed by the customer.\n *\n * This method is idempotent - calling it multiple times will only\n * record the first view time. Status only transitions to VIEWED if\n * the current status is SENT (other statuses like PARTIAL or PAID\n * are preserved).\n */\n markViewed(): void {\n if (this.viewedAt) return; // Already viewed\n this.viewedAt = new Date();\n if (this.status === InvoiceStatus.SENT) {\n this.status = InvoiceStatus.VIEWED;\n }\n }\n\n /**\n * Update payment amount and status.\n *\n * Called when PaymentAllocations change. Handles both forward transitions\n * (SENT → PARTIAL → PAID) and reversals (PAID → PARTIAL → original status).\n *\n * Terminal statuses (CANCELLED, WRITTEN_OFF) are not modified.\n */\n updatePaymentStatus(amountPaid: number): void {\n this.amountPaid = amountPaid;\n\n // Do not change payment status for terminal states\n if (\n this.status === InvoiceStatus.CANCELLED ||\n this.status === InvoiceStatus.WRITTEN_OFF\n ) {\n return;\n }\n\n // Use the SAME epsilon-tolerant \"fully paid\" test the save-time guard\n // (assertPaymentStatusConsistent) applies, so a float-summed total paid\n // exactly (e.g. 0.1×3 line items paid 0.3) is treated as PAID by both —\n // not PARTIAL here and then rejected on save (S5 audit #1390 follow-up).\n if (this.isFullyPaid()) {\n this.status = InvoiceStatus.PAID;\n this.paidDate = new Date();\n } else if (amountPaid > 0) {\n this.status = InvoiceStatus.PARTIAL;\n this.paidDate = null; // Clear paidDate if no longer fully paid\n } else {\n // No payment remaining: revert to the appropriate pre-payment status\n this.paidDate = null;\n if (this.sentAt) {\n this.status = this.viewedAt ? InvoiceStatus.VIEWED : InvoiceStatus.SENT;\n } else {\n this.status = InvoiceStatus.DRAFT;\n }\n }\n }\n\n /**\n * Cancel the invoice\n */\n cancel(): void {\n if (this.isPaid()) {\n throw new Error(\n `Cannot cancel invoice with status '${this.status}'. Paid invoices cannot be cancelled.`,\n );\n }\n this.status = InvoiceStatus.CANCELLED;\n }\n\n /**\n * Write off the invoice (bad debt)\n */\n writeOff(): void {\n this.status = InvoiceStatus.WRITTEN_OFF;\n }\n\n // ============================================================================\n // Ledger Integration\n // ============================================================================\n\n /**\n * Recognize revenue and create AR journal entry.\n *\n * Creates a balanced journal entry in smrt-ledgers:\n * - Debit: Accounts Receivable (assets increase)\n * - Credit: Revenue (revenue increases)\n * - Credit: Tax Payable (liability increases, if taxAmount > 0)\n *\n * **Note**: This method saves the invoice after setting arJournalId.\n *\n * @param options - Account IDs for the journal entry\n * @returns The created journal\n *\n * @example\n * ```typescript\n * const journal = await invoice.recognizeRevenue({\n * arAccountId: '1120',\n * revenueAccountId: '4100',\n * taxAccountId: '2130'\n * });\n * ```\n */\n async recognizeRevenue(options: RecognizeRevenueOptions): Promise<Journal> {\n if (this.arJournalId) {\n throw new Error(\n `Revenue already recognized for invoice ${this.invoiceNumber} (journal ID: ${this.arJournalId})`,\n );\n }\n\n // Recompute/validate the invoice's authoritative totals BEFORE building any\n // journal (S5 audit #1390 round 2). Round 1 built and posted the journal\n // off stale/forged in-memory totals, then called save() — which recomputes\n // from line items and could throw, leaving an orphaned posted journal with\n // the wrong amount already in the books. Recomputing first means the\n // journal is built from the same authoritative figures save() would accept,\n // and a forged total is rejected before any ledger mutation happens.\n const persisted = await this.isSaved();\n await this.recomputeAmountsForSave(persisted);\n this.assertNonNegativeAmounts();\n\n if (this.totalAmount <= 0) {\n throw new Error(\n `Cannot recognize revenue for invoice ${this.invoiceNumber} with non-positive total: ${this.totalAmount}`,\n );\n }\n\n if (this.taxAmount > 0 && !options.taxAccountId) {\n throw new Error(\n `Invoice ${this.invoiceNumber} has taxAmount ${this.taxAmount} but no taxAccountId provided`,\n );\n }\n\n // Dynamic import to avoid hard dependency on smrt-ledgers\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n\n const journalCollection = await JournalCollection.create(this.options);\n\n // Build entries array from the now-authoritative totals.\n const entries: JournalEntryData[] = [\n // Debit AR (assets increase)\n {\n accountId: options.arAccountId,\n debit: this.totalAmount,\n memo: `Invoice ${this.invoiceNumber}`,\n },\n // Credit Revenue\n {\n accountId: options.revenueAccountId,\n credit: this.subtotal,\n memo: `Invoice ${this.invoiceNumber}`,\n },\n ];\n\n // Credit Tax Payable if there's tax\n if (this.taxAmount > 0 && options.taxAccountId) {\n entries.push({\n accountId: options.taxAccountId,\n credit: this.taxAmount,\n memo: `Tax on Invoice ${this.invoiceNumber}`,\n });\n }\n\n // Create journal with entries\n const journal = await journalCollection.createWithEntries({\n description: `Revenue: Invoice ${this.invoiceNumber}`,\n sourceModule: 'smrt-commerce',\n sourceRef: this.id,\n entries,\n });\n\n // Post the journal, then link + persist the invoice as one logical unit. If\n // the invoice save fails after the journal is posted, void the journal so\n // the books don't retain a posted-but-unlinked entry (compensating action —\n // smrt-ledgers has no cross-row transaction primitive, and a posted journal\n // is immutable except via void()).\n await journal.post();\n // A posted journal always carries an id; fail fast rather than persist an\n // empty-string FK if that invariant breaks.\n if (!journal.id) {\n throw new Error(\n 'Invoice.recognizeRevenue: journal was posted but has no id',\n );\n }\n this.arJournalId = journal.id;\n try {\n await this.save();\n } catch (err) {\n try {\n await journal.void(\n `Reverted: invoice ${this.invoiceNumber} save failed during revenue recognition`,\n );\n } catch {\n // Best-effort compensation; surface the original failure regardless.\n }\n this.arJournalId = '';\n throw err;\n }\n\n return journal;\n }\n\n /**\n * Get the AR journal entry (if revenue was recognized)\n */\n async getArJournal(): Promise<Journal | null> {\n if (!this.arJournalId) return null;\n\n try {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const collection = await JournalCollection.create(this.options);\n return await collection.get({ id: this.arJournalId });\n } catch {\n // smrt-ledgers not available\n return null;\n }\n }\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * Convert to InvoiceInput for SDK accounting provider sync.\n *\n * Fetches line items and maps all fields to the format expected\n * by @happyvertical/accounting providers. The SDK takes currency **major**\n * units (`12.34` USD), so every amount is converted from this package's\n * integer minor units with the currency's ISO 4217 exponent (#3060).\n *\n * @returns InvoiceInput compatible with @happyvertical/accounting\n *\n * @example\n * ```typescript\n * const provider = await getAccountingProvider({ type: 'quickbooks', ... });\n * const input = await invoice.toAccountingInput();\n * const result = await provider.invoices.push(input);\n * invoice.externalId = result.externalId;\n * invoice.syncedAt = result.syncedAt;\n * await invoice.save();\n * ```\n */\n async toAccountingInput(): Promise<AccountingInvoiceInput> {\n // Dynamically import to avoid circular dependency\n const { InvoiceLineItemCollection } = await import(\n '../collections/InvoiceLineItemCollection.js'\n );\n\n const lineItemCollection = await InvoiceLineItemCollection.create(\n this.options,\n );\n // A synced invoice is always persisted; fail fast rather than query line\n // items under an empty id or push an invalid invoice to the provider.\n if (!this.id) {\n throw new Error(\n 'Invoice.toAccountingInput requires a persisted invoice (missing id)',\n );\n }\n const invoiceId = this.id;\n const lineItems = await lineItemCollection.findByInvoice(invoiceId);\n\n return {\n id: invoiceId,\n externalId: this.externalId || undefined,\n invoiceNumber: this.invoiceNumber,\n customerId: this.customerId,\n customerExternalId: this.customerExternalId || undefined,\n issueDate: this.issueDate,\n dueDate: this.dueDate,\n lineItems: lineItems.map((item: InvoiceLineItem) =>\n item.toAccountingLineItem(this.currency),\n ),\n subtotal: minorToMajorUnits(this.subtotal, this.currency),\n taxAmount: minorToMajorUnits(this.taxAmount, this.currency),\n totalAmount: minorToMajorUnits(this.totalAmount, this.currency),\n currency: this.currency,\n reference: this.reference || undefined,\n memo: this.customerNotes || undefined,\n };\n }\n}\n\nexport default Invoice;\n","/**\n * InvoiceCollection - Collection manager for Invoice objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus } from '../types/index.js';\n\n/**\n * Statuses that indicate an invoice has outstanding balance.\n * Used for calculating outstanding amounts and finding unpaid invoices.\n */\nexport const UNPAID_STATUSES = [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.OVERDUE,\n] as const;\n\n/**\n * Options for generating invoice numbers\n */\nexport interface InvoiceNumberOptions {\n /** Prefix for invoice numbers (default: 'INV') */\n prefix?: string;\n /** Format: 'prefix-year-seq' or 'prefix-seq' (default: 'prefix-year-seq') */\n format?: 'prefix-year-seq' | 'prefix-seq';\n}\n\nexport class InvoiceCollection extends SmrtCollection<Invoice> {\n static readonly _itemClass = Invoice;\n\n /**\n * Find invoices by customer\n *\n * @param customerId - Customer ID\n * @returns Array of invoices\n */\n async findByCustomer(customerId: string): Promise<Invoice[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoices by contract\n *\n * @param contractId - Contract ID\n * @returns Array of invoices\n */\n async findByContract(contractId: string): Promise<Invoice[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoices by status\n *\n * @param status - Invoice status\n * @returns Array of invoices\n */\n async findByStatus(status: InvoiceStatus): Promise<Invoice[]> {\n return await this.list({\n where: { status },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find all draft invoices\n *\n * @returns Array of draft invoices\n */\n async findDrafts(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.DRAFT);\n }\n\n /**\n * Find all sent invoices\n *\n * @returns Array of sent invoices\n */\n async findSent(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.SENT);\n }\n\n /**\n * Find all paid invoices\n *\n * @returns Array of paid invoices\n */\n async findPaid(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.PAID);\n }\n\n /**\n * Find all partial invoices\n *\n * @returns Array of partially paid invoices\n */\n async findPartial(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.PARTIAL);\n }\n\n /**\n * Find overdue invoices (past due date, not paid).\n *\n * **Note**: This method finds invoices by date, but does not automatically\n * update their status to OVERDUE. Use a background job or scheduled task\n * to periodically update invoice statuses based on due dates.\n *\n * @returns Array of overdue invoices\n */\n async findOverdue(): Promise<Invoice[]> {\n const now = new Date().toISOString();\n\n // Get invoices that are sent/viewed/partial and past due\n const candidates = await this.list({\n where: {\n 'dueDate <': now,\n },\n orderBy: 'dueDate ASC',\n });\n\n return candidates.filter((inv) =>\n (UNPAID_STATUSES as readonly InvoiceStatus[]).includes(inv.status),\n );\n }\n\n /**\n * Find invoices in date range (by issue date)\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of invoices\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Invoice[]> {\n return await this.list({\n where: {\n 'issueDate >=': startDate.toISOString(),\n 'issueDate <=': endDate.toISOString(),\n },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoice by external ID (from accounting provider)\n *\n * @param externalId - External ID\n * @returns Invoice or null\n */\n async findByExternalId(externalId: string): Promise<Invoice | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find invoices needing sync to external provider\n * (no externalId or stale syncedAt)\n *\n * @returns Array of invoices needing sync\n */\n async findNeedingSync(): Promise<Invoice[]> {\n // Get all non-draft invoices without externalId\n const allInvoices = await this.list({\n orderBy: 'issueDate DESC',\n });\n\n return allInvoices.filter(\n (inv) =>\n inv.status !== InvoiceStatus.DRAFT &&\n inv.status !== InvoiceStatus.CANCELLED &&\n !inv.externalId,\n );\n }\n\n /**\n * Find invoice by invoice number\n *\n * @param invoiceNumber - Invoice number\n * @returns Invoice or null\n */\n async findByNumber(invoiceNumber: string): Promise<Invoice | null> {\n const results = await this.list({\n where: { invoiceNumber },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Count invoices for a given year\n *\n * @param year - Year to count\n * @returns Number of invoices\n */\n async countForYear(year: number): Promise<number> {\n const startDate = new Date(year, 0, 1).toISOString();\n const endDate = new Date(year + 1, 0, 1).toISOString();\n\n const invoices = await this.list({\n where: {\n 'issueDate >=': startDate,\n 'issueDate <': endDate,\n },\n });\n\n return invoices.length;\n }\n\n /**\n * Generate the next invoice number.\n *\n * **Warning**: This method has a potential race condition if multiple\n * invoices are created concurrently. For high-concurrency environments,\n * consider using a database sequence or atomic counter instead.\n *\n * @param options - Optional configuration for number format\n * @returns Generated invoice number (e.g., 'INV-2025-0001')\n *\n * @example\n * ```typescript\n * // Default format: INV-2025-0001\n * const num = await invoices.generateInvoiceNumber();\n *\n * // Custom prefix: BILL-2025-0001\n * const num = await invoices.generateInvoiceNumber({ prefix: 'BILL' });\n *\n * // Simple sequential: INV-000001\n * const num = await invoices.generateInvoiceNumber({ format: 'prefix-seq' });\n * ```\n */\n async generateInvoiceNumber(\n options: InvoiceNumberOptions = {},\n ): Promise<string> {\n const prefix = options.prefix || 'INV';\n const format = options.format || 'prefix-year-seq';\n\n const year = new Date().getFullYear();\n\n if (format === 'prefix-year-seq') {\n const count = await this.countForYear(year);\n const seq = String(count + 1).padStart(4, '0');\n return `${prefix}-${year}-${seq}`;\n }\n\n // Simple sequential format\n const allInvoices = await this.list({});\n const seq = String(allInvoices.length + 1).padStart(6, '0');\n return `${prefix}-${seq}`;\n }\n\n /**\n * Get total outstanding (unpaid) amount for a customer\n *\n * @param customerId - Customer ID\n * @returns Total amount due\n */\n async getTotalOutstandingForCustomer(customerId: string): Promise<number> {\n const invoices = await this.list({\n where: { customerId },\n });\n\n return invoices\n .filter((inv) =>\n (UNPAID_STATUSES as readonly InvoiceStatus[]).includes(inv.status),\n )\n .reduce((sum, inv) => sum + inv.getAmountDue(), 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all invoices belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of invoices for the tenant\n */\n async findByTenant(tenantId: string): Promise<Invoice[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global invoices (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global invoices\n */\n async findGlobal(): Promise<Invoice[]> {\n return queryGlobal<Invoice>(this);\n }\n\n /**\n * Find invoices for a tenant including global invoices.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global invoices\n */\n async findWithGlobals(tenantId: string): Promise<Invoice[]> {\n return queryWithGlobals<Invoice>(this, tenantId, 'Invoice.findWithGlobals');\n }\n}\n","/**\n * Billing-period close records (#3060).\n *\n * These are system tables managed by `BillingRuntime`, like smrt-tenancy's\n * `_smrt_billing_relationships`: they span a seller and its payers, so they\n * are not tenant-scoped and have no generated API, CLI, or MCP surface.\n */\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n backgroundEligible,\n type JobExecutionContext,\n} from '@happyvertical/smrt-jobs';\nimport { canonicalTenantId, normalizeCurrency } from '../billing/units.js';\n\n/**\n * Who a runtime bills. `provider` closes the platform's `ClientCharge`s,\n * their `BillingAdjustment`s, and the platform's flat plans; `reseller`\n * closes the `RetailCharge`s a reseller's children owe it and the reseller's\n * own flat plans.\n */\nexport type BillingCloseKind = 'provider' | 'reseller';\n\n/** A payer's standing with its seller, driven by provider invoice events. */\nexport type BillingStanding = 'current' | 'past_due' | 'uncollectible';\n\n/**\n * `collecting` → `invoiced` → `pushed` → `sent` → `completed`. Every step is\n * resumable: a retry continues from the persisted status. A close whose\n * claimed sources net to a credit ends `carried_forward`: its `subtotal` is\n * billed as a credit line by the payer's next close.\n */\nexport type BillingPeriodCloseStatus =\n | 'collecting'\n | 'invoiced'\n | 'pushed'\n | 'sent'\n | 'completed'\n | 'carried_forward';\n\nexport type BillingLineSourceType =\n | 'client_charge'\n | 'billing_adjustment'\n | 'retail_charge'\n | 'subscription_period'\n | 'credit_carry_forward';\n\nconst STANDINGS: readonly BillingStanding[] = [\n 'current',\n 'past_due',\n 'uncollectible',\n];\n\n/**\n * One payer's account with one seller: the commerce `Customer` invoices are\n * issued to, the provider customer they are pushed to, and the payer's\n * billing terms. The seller's tax location for the payer is the customer's\n * `defaultBillingAddress`.\n */\n@smrt({\n tableName: '_smrt_billing_accounts',\n conflictColumns: ['seller_tenant_id', 'payer_tenant_id'],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingAccount extends SmrtObject {\n /** The tenant that issues invoices (the provider or a reseller). */\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n /** The billing owner that pays them. */\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n payerTenantId: string = '';\n @foreignKey('Customer')\n customerId: string = '';\n /** Display name sent to the provider customer. */\n name: string = '';\n email: string = '';\n /** Provider name (`stripe`) once a provider customer exists. */\n provider: string = '';\n providerCustomerId: string = '';\n /** Let the provider calculate tax from the customer's tax location. */\n automaticTax: boolean = true;\n /** Discount on flat plan lines, in basis points (10000 = 100%). */\n flatDiscountBasisPoints: number = 0;\n paymentTermsDays: number = 30;\n standing: BillingStanding = 'current';\n /**\n * Billing-cycle anchor (#3116). Null bills UTC calendar months; a date bills\n * monthly periods from it (its day and UTC time of day, clamped to shorter\n * months), with calendar months before it and a stub period from its month\n * start to the anchor. Moving it never bills time twice: flat-plan claims\n * record the time they cover.\n */\n @field({ type: 'datetime', nullable: true })\n billingAnchorAt: Date | null = null;\n /**\n * Prorate flat plans to the time they were active inside a period\n * (#3116): a subscription starting, leaving trial, or canceled mid-period\n * is billed `price × covered / cycle`, rounded half up to minor units.\n * Off, a flat plan active at any point of a period is billed in full (the\n * pre-#3116 behavior).\n */\n prorateFlatPlans: boolean = false;\n\n protected async validateBeforeSave(): Promise<void> {\n await super.validateBeforeSave();\n this.sellerTenantId = canonicalTenantId(\n this.sellerTenantId,\n 'sellerTenantId',\n );\n this.payerTenantId = canonicalTenantId(this.payerTenantId, 'payerTenantId');\n if (this.sellerTenantId === this.payerTenantId) {\n throw new Error('A tenant cannot bill itself.');\n }\n if (!this.customerId) {\n throw new Error('Billing accounts require a customerId.');\n }\n if (\n !Number.isSafeInteger(this.flatDiscountBasisPoints) ||\n this.flatDiscountBasisPoints < 0 ||\n this.flatDiscountBasisPoints > 10_000\n ) {\n throw new Error(\n 'flatDiscountBasisPoints must be an integer from 0 to 10000.',\n );\n }\n if (\n !Number.isSafeInteger(this.paymentTermsDays) ||\n this.paymentTermsDays < 0 ||\n this.paymentTermsDays > 365\n ) {\n throw new Error('paymentTermsDays must be an integer from 0 to 365.');\n }\n if (!STANDINGS.includes(this.standing)) {\n throw new Error('standing must be current, past_due, or uncollectible.');\n }\n if (\n this.billingAnchorAt !== null &&\n (!(this.billingAnchorAt instanceof Date) ||\n !Number.isFinite(this.billingAnchorAt.getTime()))\n ) {\n throw new Error('billingAnchorAt must be a valid date or null.');\n }\n }\n}\n\n/**\n * The idempotency anchor for one payer's invoice in one currency for one\n * period. Its id is derived from that identity, so a retried close always\n * resumes the same row, invoice, and provider invoice.\n */\n@smrt({\n tableName: '_smrt_billing_period_closes',\n conflictColumns: [\n 'seller_tenant_id',\n 'kind',\n 'payer_tenant_id',\n 'currency',\n 'period_start',\n 'period_end',\n ],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingPeriodClose extends SmrtObject {\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n kind: BillingCloseKind = 'provider';\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n payerTenantId: string = '';\n currency: string = 'USD';\n periodStart: Date = new Date(0);\n periodEnd: Date = new Date(0);\n @foreignKey('BillingAccount')\n billingAccountId: string = '';\n status: BillingPeriodCloseStatus = 'collecting';\n @foreignKey('Invoice')\n invoiceId: string = '';\n providerInvoiceId: string = '';\n /** Invoice subtotal in integer minor units once invoiced. */\n subtotal: number = 0;\n lineCount: number = 0;\n /** When the invoice was issued; fixed at first collection. */\n issuedAt: Date = new Date(0);\n @field({ type: 'text', nullable: true })\n leaseToken: string | null = null;\n @field({ type: 'datetime', nullable: true })\n leaseExpiresAt: Date | null = null;\n attempts: number = 0;\n lastError: string = '';\n\n protected async validateBeforeSave(): Promise<void> {\n await super.validateBeforeSave();\n this.sellerTenantId = canonicalTenantId(\n this.sellerTenantId,\n 'sellerTenantId',\n );\n this.payerTenantId = canonicalTenantId(this.payerTenantId, 'payerTenantId');\n this.currency = normalizeCurrency(this.currency);\n if (this.kind !== 'provider' && this.kind !== 'reseller') {\n throw new Error('Billing close kind must be provider or reseller.');\n }\n if (!(this.periodEnd.getTime() > this.periodStart.getTime())) {\n throw new Error('A billing period must end after it starts.');\n }\n }\n\n /**\n * smrt-jobs entry point: close a period for a registered billing runtime.\n * Args: `{ runtime, periodStart?, periodEnd? }` (ISO strings); without a\n * period each payer's last ended period on its own schedule is closed\n * (#3116), so a daily schedule is safe.\n */\n @backgroundEligible()\n async runPeriodClose(\n args: Record<string, unknown> = {},\n _context?: JobExecutionContext,\n ): Promise<unknown> {\n const { runBillingPeriodCloseJob } = await import('../billing/jobs.js');\n return runBillingPeriodCloseJob(args);\n }\n\n /**\n * smrt-jobs entry point: apply queued provider webhook events for a\n * registered billing runtime. Args: `{ runtime, limit? }`.\n */\n @backgroundEligible()\n async processBillingEvents(\n args: Record<string, unknown> = {},\n _context?: JobExecutionContext,\n ): Promise<unknown> {\n const { runBillingEventsJob } = await import('../billing/jobs.js');\n return runBillingEventsJob(args);\n }\n}\n\n/**\n * A claim that one billable source — a charge, an adjustment, or a window of\n * a flat plan — is billed on exactly one period close. The id is derived\n * from `(sourceType, sourceId)`, so a source can never be claimed twice. A\n * flat plan's claims record the window they cover and are numbered per\n * subscription (`<subscriptionId>:seq:<n>`, #3116): only one claimer can take\n * the next number, so no two cover the same time. Claims written before #3116\n * are `<subscriptionId>:<periodStart>` with number 0.\n */\n@smrt({\n tableName: '_smrt_billing_line_sources',\n conflictColumns: ['source_type', 'source_id'],\n // Flat-plan claims are read per subscription line, newest number first\n // (#3116).\n indexes: [\n {\n name: '_smrt_billing_line_sources_line_key_chain_sequence_idx',\n columns: ['lineKey', 'chainSequence'],\n },\n ],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingLineSource extends SmrtObject {\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n @foreignKey('BillingPeriodClose')\n periodCloseId: string = '';\n sourceType: BillingLineSourceType = 'client_charge';\n sourceId: string = '';\n /** Groups sources into one invoice line. */\n lineKey: string = '';\n lineDescription: string = '';\n /** Source amount in integer minor units (signed for adjustments). */\n amount: number = 0;\n /** Discount on this source in integer minor units (flat plans only). */\n discount: number = 0;\n /** Metered quantity, informational; fractional like the charge's. */\n quantity: number = 0.0;\n /**\n * A flat-plan claim's number within its subscription (1, 2, …; #3116). 0\n * for other sources and for flat-plan claims written before #3116.\n */\n chainSequence: number = 0;\n currency: string = 'USD';\n /**\n * The billed window. For a flat plan this is the time the claim covers\n * (#3116), which later claims for the same subscription never overlap.\n */\n periodStart: Date | null = null;\n periodEnd: Date | null = null;\n}\n\n/** Why a payment attempt was taken (#3138). */\nexport type BillingPaymentAttemptPurpose =\n | 'credit_purchase'\n | 'invoice_payment';\n\n/**\n * Something about an attempt that an operator must resolve (#3138). Empty\n * when nothing needs attention.\n */\nexport type BillingPaymentAttemptFlag =\n | ''\n | 'overpaid'\n | 'underpaid'\n | 'paid_late'\n | 'manually_marked'\n | 'invalidated_after_settlement'\n | 'invoice_already_paid'\n | 'amount_mismatch'\n /** The issuer's invoice was closed out of band but the payment never settled. */\n | 'out_of_band_without_settlement';\n\n/** One entry of an attempt's append-only status history. */\nexport interface BillingPaymentAttemptTransition {\n at: string;\n status: string;\n exception: string;\n source: 'created' | 'webhook' | 'poll';\n}\n\n/**\n * One payment attempt on a payment rail (#3138): a short-lived provider\n * checkout (for example a BTCPay invoice) paying a credit purchase or an\n * issued invoice. It records everything the provider reported — the locked\n * fiat price, the native amounts, rate and source, each payment's txid and\n * fee — plus a timeline of status transitions and what settlement produced.\n * Its id is derived from `(provider, checkoutId)`.\n */\n@smrt({\n tableName: '_smrt_billing_payment_attempts',\n conflictColumns: ['provider', 'checkout_id'],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingPaymentAttempt extends SmrtObject {\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n payerTenantId: string = '';\n billingAccountId: string = '';\n purpose: BillingPaymentAttemptPurpose = 'credit_purchase';\n /** The invoice paid (`invoice_payment`). */\n invoiceId: string = '';\n /** The balance credited (`credit_purchase`). */\n spendingPolicyId: string = '';\n /** Provider (rail) name, for example `btcpay`. */\n provider: string = '';\n /** The rail order id (checkout idempotency key) the checkout was made for. */\n orderId: string = '';\n checkoutId: string = '';\n status: string = 'open';\n exception: string = 'none';\n flag: BillingPaymentAttemptFlag = '';\n /** Locked fiat price, integer minor units. */\n amount: number = 0;\n currency: string = 'USD';\n /** Fiat value received at the locked rate, minor units (rounded down). */\n amountPaid: number = 0;\n nativeCurrency: string = '';\n /** Native minor units (satoshis). */\n nativeAmountDue: number = 0;\n nativeAmountPaid: number = 0;\n rate: string = '';\n rateSource: string = '';\n checkoutUrl: string = '';\n @field({ type: 'datetime', nullable: true })\n expiresAt: Date | null = null;\n /** The provider's payments (JSON). */\n payments: Record<string, unknown>[] = [];\n /** Append-only status history (JSON). */\n timeline: BillingPaymentAttemptTransition[] = [];\n @field({ type: 'datetime', nullable: true })\n settledAt: Date | null = null;\n /** The commerce `Payment` settlement recorded. */\n paymentId: string = '';\n /** The credit grant settlement recorded. */\n creditGrantId: string = '';\n /** An operator's note when resolving a flag. */\n resolution: string = '';\n @field({ type: 'datetime', nullable: true })\n resolvedAt: Date | null = null;\n /**\n * Set before the issuer's invoice is closed out of band, so the issuer's\n * own `paid` event waits for this settlement instead of recording one.\n */\n @field({ type: 'datetime', nullable: true })\n outOfBandRequestedAt: Date | null = null;\n /**\n * The most severe standing an issuer event asked for while this payment\n * was confirming (dunning paused); re-applied if it ends unsettled.\n */\n pausedStanding: string = '';\n /** Fiat received above the price, booked as customer credit (minor units). */\n excessAmount: number = 0;\n /** Number of excess adjustments posted (keys each adjustment journal). */\n excessRevision: number = 0;\n /**\n * How settlement booked the price: `credit` (prepaid credit, or customer\n * credit for an invoice already paid elsewhere — refundable) or `applied`\n * (allocated to its invoice — not refundable here).\n */\n settlementOutcome: string = '';\n /** Refunds recorded by operators, total (minor units). */\n refundedAmount: number = 0;\n /** The part of `refundedAmount` taken from the price, not the excess. */\n refundedPrincipal: number = 0;\n /** Refund records (JSON): reference, amount, basis, journal, at. */\n refunds: Record<string, unknown>[] = [];\n\n protected async validateBeforeSave(): Promise<void> {\n await super.validateBeforeSave();\n this.sellerTenantId = canonicalTenantId(\n this.sellerTenantId,\n 'sellerTenantId',\n );\n this.payerTenantId = canonicalTenantId(this.payerTenantId, 'payerTenantId');\n this.currency = normalizeCurrency(this.currency);\n if (\n this.purpose !== 'credit_purchase' &&\n this.purpose !== 'invoice_payment'\n ) {\n throw new Error('Payment attempt purpose is invalid.');\n }\n if (!this.provider || !this.checkoutId) {\n throw new Error('Payment attempts require a provider and checkoutId.');\n }\n for (const value of [\n this.amount,\n this.amountPaid,\n this.nativeAmountDue,\n this.nativeAmountPaid,\n this.excessAmount,\n this.refundedAmount,\n this.refundedPrincipal,\n ]) {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new Error('Payment attempt amounts must be minor units.');\n }\n }\n }\n\n /** The timeline, tolerating a JSON string from the database. */\n get transitions(): BillingPaymentAttemptTransition[] {\n return jsonArray(this.timeline) as BillingPaymentAttemptTransition[];\n }\n\n /** The recorded payments, tolerating a JSON string from the database. */\n get paymentRecords(): Record<string, unknown>[] {\n return jsonArray(this.payments) as Record<string, unknown>[];\n }\n\n /** The recorded refunds, tolerating a JSON string from the database. */\n get refundRecords(): Record<string, unknown>[] {\n return jsonArray(this.refunds) as Record<string, unknown>[];\n }\n}\n\nfunction jsonArray(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (typeof value === 'string' && value) {\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n }\n return [];\n}\n\nexport class BillingAccountCollection extends SmrtCollection<BillingAccount> {\n static readonly _itemClass = BillingAccount;\n}\nexport class BillingPeriodCloseCollection extends SmrtCollection<BillingPeriodClose> {\n static readonly _itemClass = BillingPeriodClose;\n}\nexport class BillingLineSourceCollection extends SmrtCollection<BillingLineSource> {\n static readonly _itemClass = BillingLineSource;\n}\nexport class BillingPaymentAttemptCollection extends SmrtCollection<BillingPaymentAttempt> {\n static readonly _itemClass = BillingPaymentAttempt;\n}\n","/**\n * Payment-rail attempts (#3138): per-seller policy, settlement of a rail\n * checkout into credit or an invoice payment, and operator refund and\n * conversion journals.\n *\n * The rail's gateway decides when a checkout is settled (its confirmation\n * policy); this module never counts confirmations. It records everything the\n * rail reports on a `BillingPaymentAttempt` and settles at most once, keyed by\n * the checkout id.\n */\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport { SpendingPolicyEvaluator } from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { InvoiceCollection } from '../collections/InvoiceCollection.js';\nimport { PaymentAllocationCollection } from '../collections/PaymentAllocationCollection.js';\nimport { PaymentCollection } from '../collections/PaymentCollection.js';\nimport {\n type BillingPaymentAttempt,\n BillingPaymentAttemptCollection,\n type BillingPaymentAttemptFlag,\n type BillingPaymentAttemptPurpose,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus, PaymentMethod, PaymentStatus } from '../types/index.js';\nimport { CREDIT_PURCHASE_PURPOSE, creditMetadata } from './credits.js';\nimport {\n INVOICE_PAYMENT_PURPOSE,\n invoicePaymentMetadata,\n} from './invoice-payments.js';\nimport type { BillingPaymentAttemptState } from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport { deterministicId, normalizeCurrency, tenantKey } from './units.js';\n\n/** Per-seller rules for rail payments. Every default is conservative. */\nexport interface BillingPaymentPolicy {\n /**\n * Underpayment accepted as payment in full, in basis points of the price\n * (default 0). The shortfall is not written off in the ledger.\n */\n underpaymentToleranceBps: number;\n /** Overpayment not flagged for an operator, in basis points (default 0). */\n overpaymentToleranceBps: number;\n /**\n * A payment received after the rate lock expired: `review` (default)\n * flags it and settles nothing; `accept` settles when it covers the price.\n */\n latePaymentPolicy: 'review' | 'accept';\n /** Settle checkouts an operator marked paid at the rail (default false). */\n acceptManuallyMarked: boolean;\n /** Hold received crypto (default) or hand it to `conversionHandler`. */\n cryptoTreatment: 'hold' | 'convert';\n /**\n * Called after a settlement when `cryptoTreatment` is `convert`. It runs\n * inside the event transaction and may run again on retry: enqueue work\n * (for example a job) rather than calling an exchange directly.\n */\n conversionHandler?: (settlement: CryptoSettlement) => void | Promise<void>;\n /** Refund basis recorded when a refund does not name one. */\n refundBasis: 'original_native' | 'fiat_at_refund';\n}\n\nexport interface CryptoSettlement {\n sellerTenantId: string;\n attemptId: string;\n paymentId: string;\n provider: string;\n amount: number;\n currency: string;\n nativeCurrency: string;\n nativeAmount: number;\n db: DatabaseInterface;\n}\n\nexport function normalizePaymentPolicy(\n input: Partial<BillingPaymentPolicy> = {},\n): BillingPaymentPolicy {\n const policy: BillingPaymentPolicy = {\n underpaymentToleranceBps: input.underpaymentToleranceBps ?? 0,\n overpaymentToleranceBps: input.overpaymentToleranceBps ?? 0,\n latePaymentPolicy: input.latePaymentPolicy ?? 'review',\n acceptManuallyMarked: input.acceptManuallyMarked ?? false,\n cryptoTreatment: input.cryptoTreatment ?? 'hold',\n conversionHandler: input.conversionHandler,\n refundBasis: input.refundBasis ?? 'original_native',\n };\n for (const key of [\n 'underpaymentToleranceBps',\n 'overpaymentToleranceBps',\n ] as const) {\n const value = policy[key];\n if (!Number.isSafeInteger(value) || value < 0 || value > 10_000) {\n throw new Error(`${key} must be an integer from 0 to 10000.`);\n }\n }\n if (!['review', 'accept'].includes(policy.latePaymentPolicy)) {\n throw new Error('latePaymentPolicy must be review or accept.');\n }\n if (!['hold', 'convert'].includes(policy.cryptoTreatment)) {\n throw new Error('cryptoTreatment must be hold or convert.');\n }\n if (policy.cryptoTreatment === 'convert' && !policy.conversionHandler) {\n throw new Error('cryptoTreatment convert requires a conversionHandler.');\n }\n if (!['original_native', 'fiat_at_refund'].includes(policy.refundBasis)) {\n throw new Error('refundBasis must be original_native or fiat_at_refund.');\n }\n return policy;\n}\n\n// ---------------------------------------------------------------------------\n// Deciding\n// ---------------------------------------------------------------------------\n\nexport type AttemptDecision =\n | { action: 'settle'; flag: BillingPaymentAttemptFlag }\n | { action: 'none'; flag: BillingPaymentAttemptFlag };\n\n/**\n * What a rail checkout's state means for billing. Pure: the same state and\n * policy always decide the same way, so `observe()` and `project()` agree.\n */\nexport function decideAttempt(\n state: BillingPaymentAttemptState,\n policy: BillingPaymentPolicy,\n expected: { amount: number; currency: string },\n): AttemptDecision {\n if (\n normalizeCurrency(state.currency) !== expected.currency ||\n state.amount !== expected.amount\n ) {\n return { action: 'none', flag: 'amount_mismatch' };\n }\n const shortfall = Math.floor(\n (expected.amount * policy.underpaymentToleranceBps) / 10_000,\n );\n const excess = Math.floor(\n (expected.amount * policy.overpaymentToleranceBps) / 10_000,\n );\n const covered = state.amountPaid >= expected.amount - shortfall;\n const over: BillingPaymentAttemptFlag =\n state.amountPaid > expected.amount + excess ? 'overpaid' : '';\n // Every counted payment must itself be settled by the rail.\n const allSettled = state.payments.every(\n (payment) => payment.status !== 'confirming',\n );\n\n if (state.exception === 'manually_marked') {\n return state.status === 'settled' && policy.acceptManuallyMarked\n ? { action: 'settle', flag: '' }\n : { action: 'none', flag: 'manually_marked' };\n }\n if (state.exception === 'paid_late') {\n return policy.latePaymentPolicy === 'accept' &&\n covered &&\n allSettled &&\n (state.status === 'settled' || state.status === 'expired')\n ? { action: 'settle', flag: over }\n : { action: 'none', flag: 'paid_late' };\n }\n switch (state.status) {\n case 'settled':\n return covered\n ? { action: 'settle', flag: over }\n : { action: 'none', flag: 'underpaid' };\n case 'expired':\n if (state.exception === 'underpaid' || state.amountPaid > 0) {\n // Within tolerance and settled at the rail: treat as paid in full.\n return covered && allSettled && state.amountPaid > 0\n ? { action: 'settle', flag: over }\n : { action: 'none', flag: 'underpaid' };\n }\n return { action: 'none', flag: '' };\n default:\n return { action: 'none', flag: '' };\n }\n}\n\n/** The purpose and settlement target an attempt's verified metadata names. */\nexport interface AttemptTarget {\n purpose: BillingPaymentAttemptPurpose;\n payerTenantId: string;\n billingAccountId: string;\n amount: number;\n currency: string;\n invoiceId: string;\n spendingPolicyId: string;\n grantedByTenantId: string;\n /**\n * The issuer reported the invoice paid by its own collection (for example\n * a card charge whose event has not been applied yet): the rail's money is\n * kept as customer credit, never allocated to the invoice.\n */\n issuerCollected?: boolean;\n}\n\n/** Read the target from verified metadata; null when not this seller's. */\nexport function attemptTarget(\n runtime: BillingRuntime,\n metadata: Record<string, string>,\n): AttemptTarget | null {\n if (metadata.smrt_purpose === CREDIT_PURCHASE_PURPOSE) {\n const credit = creditMetadata(metadata);\n if (!credit || credit.sellerTenantId !== runtime.sellerTenantId) {\n return null;\n }\n return {\n purpose: 'credit_purchase',\n payerTenantId: credit.payerTenantId,\n billingAccountId: credit.billingAccountId,\n amount: credit.amount,\n currency: credit.currency,\n invoiceId: '',\n spendingPolicyId: credit.spendingPolicyId,\n grantedByTenantId: credit.grantedByTenantId,\n };\n }\n if (metadata.smrt_purpose === INVOICE_PAYMENT_PURPOSE) {\n const invoice = invoicePaymentMetadata(metadata);\n if (!invoice || invoice.sellerTenantId !== runtime.sellerTenantId) {\n return null;\n }\n return {\n purpose: 'invoice_payment',\n payerTenantId: invoice.payerTenantId,\n billingAccountId: invoice.billingAccountId,\n amount: invoice.amount,\n currency: invoice.currency,\n invoiceId: invoice.invoiceId,\n spendingPolicyId: '',\n grantedByTenantId: '',\n };\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Applying (inside the event transaction)\n// ---------------------------------------------------------------------------\n\nexport interface AttemptOutcome {\n attempt: BillingPaymentAttempt;\n /** The attempt left `confirming` without settling. */\n endedUnsettled: boolean;\n /** This application settled it. */\n settled: boolean;\n}\n\n/**\n * Record the rail's current state on the attempt and settle it once when the\n * decision says so. `invoiceHook` applies standing for invoice payments.\n */\nexport async function applyPaymentAttempt(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n providerName: string,\n state: BillingPaymentAttemptState,\n target: AttemptTarget,\n source: 'webhook' | 'poll',\n): Promise<AttemptOutcome> {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n const id = await runtime.paymentAttemptId(providerName, state.checkoutId);\n if (target.purpose === 'invoice_payment') {\n // Invoice before attempt, as the issuer's events lock them: one order\n // for every transaction touching both.\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const invoices = await InvoiceCollection.create({ db });\n await db.query(\n `UPDATE ${invoices.tableName} SET updated_at = updated_at WHERE id = ?`,\n target.invoiceId,\n );\n });\n }\n // Lock before reading, so a concurrent writer (a refund, a paused\n // standing) is never overwritten by this full-row save.\n await lockAttempt(db, attempts.tableName, id);\n const attempt =\n (await attempts.get(id)) ??\n (await attempts.create({\n id,\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId: target.payerTenantId,\n billingAccountId: target.billingAccountId,\n purpose: target.purpose,\n invoiceId: target.invoiceId,\n spendingPolicyId: target.spendingPolicyId,\n provider: providerName,\n orderId: state.orderId ?? '',\n checkoutId: state.checkoutId,\n amount: target.amount,\n currency: target.currency,\n status: 'open',\n exception: 'none',\n timeline: [],\n _insertOnly: true,\n }));\n const wasConfirming = attempt.status === 'confirming';\n if (!attempt.orderId && state.orderId) attempt.orderId = state.orderId;\n const changed =\n attempt.status !== state.status || attempt.exception !== state.exception;\n const timeline = attempt.transitions;\n if (changed || timeline.length === 0) {\n timeline.push({\n at: new Date().toISOString(),\n status: state.status,\n exception: state.exception,\n source,\n });\n }\n attempt.timeline = timeline;\n attempt.status = state.status;\n attempt.exception = state.exception;\n attempt.amountPaid = state.amountPaid;\n attempt.nativeCurrency = state.nativeCurrency ?? '';\n attempt.nativeAmountDue = state.nativeAmountDue ?? 0;\n attempt.nativeAmountPaid = state.nativeAmountPaid ?? 0;\n attempt.rate = state.rate ?? '';\n attempt.rateSource = state.rateSource ?? '';\n if (state.checkoutUrl) attempt.checkoutUrl = state.checkoutUrl;\n attempt.expiresAt = state.expiresAt ?? attempt.expiresAt;\n attempt.payments = state.payments.map((payment) => ({ ...payment }));\n\n const decision = decideAttempt(state, runtime.paymentPolicy, {\n amount: target.amount,\n currency: target.currency,\n });\n let settled = false;\n if (attempt.settledAt) {\n // Settled before; a later invalidation needs an operator, never an\n // automatic reversal.\n if (state.status === 'invalid')\n attempt.flag = 'invalidated_after_settlement';\n // Excess follows what the rail reports in both directions: money after\n // settlement is booked; a reversed payment (reorg, RBF) is unbooked.\n const excess = Math.max(0, state.amountPaid - target.amount);\n const delta = excess - attempt.excessAmount;\n if (delta !== 0 && attempt.paymentId) {\n const holdings =\n runtime.ledger.cryptoHoldingsAccountId || runtime.ledger.cashAccountId;\n const credit = runtime.ledger.prepaidCreditAccountId;\n await postOnce(\n runtime,\n db,\n `overpayment:${attempt.paymentId}:r${attempt.excessRevision + 1}`,\n {\n description: `Excess on ${providerName} checkout ${state.checkoutId}: ${attempt.excessAmount} -> ${excess}`,\n entries:\n delta > 0\n ? [\n { accountId: holdings, debit: delta },\n { accountId: credit, credit: delta },\n ]\n : [\n { accountId: credit, debit: -delta },\n { accountId: holdings, credit: -delta },\n ],\n },\n );\n attempt.excessAmount = excess;\n attempt.excessRevision += 1;\n if (delta < 0) attempt.flag = 'invalidated_after_settlement';\n else if (!attempt.flag) attempt.flag = 'overpaid';\n }\n } else if (decision.action === 'settle') {\n const result = await settleAttempt(runtime, db, attempt, state, target);\n attempt.paymentId = result.paymentId;\n attempt.creditGrantId = result.creditGrantId;\n attempt.excessAmount = result.excess;\n attempt.settlementOutcome =\n target.purpose === 'credit_purchase' ||\n result.flag === 'invoice_already_paid'\n ? 'credit'\n : 'applied';\n attempt.settledAt = new Date();\n attempt.flag = result.flag || decision.flag;\n settled = true;\n } else if (decision.flag) {\n attempt.flag = decision.flag;\n }\n\n // Ended without settling: it stopped confirming and this did not settle\n // it (expired, invalid, or reported settled but refused by the policy).\n let endedUnsettled =\n wasConfirming && !attempt.settledAt && state.status !== 'confirming';\n if (\n attempt.outOfBandRequestedAt &&\n !attempt.settledAt &&\n (state.status === 'expired' || state.status === 'invalid')\n ) {\n // The issuer was told the invoice was paid, but the money never settled:\n // an operator must reopen it at the issuer (then resolve this flag).\n // Standing is re-derived from the invoice either way.\n attempt.flag = 'out_of_band_without_settlement';\n endedUnsettled = true;\n }\n await attempt.save();\n if (\n settled &&\n runtime.paymentPolicy.cryptoTreatment === 'convert' &&\n runtime.paymentPolicy.conversionHandler\n ) {\n await runtime.paymentPolicy.conversionHandler({\n sellerTenantId: runtime.sellerTenantId,\n attemptId: String(attempt.id),\n paymentId: attempt.paymentId,\n provider: providerName,\n amount: target.amount,\n currency: target.currency,\n nativeCurrency: attempt.nativeCurrency,\n nativeAmount: attempt.nativeAmountPaid,\n db,\n });\n }\n return { attempt, endedUnsettled, settled };\n}\n\nasync function settleAttempt(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n attempt: BillingPaymentAttempt,\n state: BillingPaymentAttemptState,\n target: AttemptTarget,\n): Promise<{\n paymentId: string;\n creditGrantId: string;\n excess: number;\n flag: BillingPaymentAttemptFlag;\n}> {\n const ledger = runtime.ledger;\n const holdings = ledger.cryptoHoldingsAccountId || ledger.cashAccountId;\n const txids = [\n ...new Set(\n state.payments\n .filter((payment) => payment.status === 'settled')\n .map((payment) => payment.transactionId)\n .filter((txid): txid is string => Boolean(txid)),\n ),\n ];\n const payments = await PaymentCollection.create({ db });\n const paymentId = await deterministicId([\n 'billing-attempt-payment',\n attempt.provider,\n state.checkoutId,\n ]);\n\n let invoice: Invoice | null = null;\n let flag: BillingPaymentAttemptFlag = '';\n let receivables = ledger.prepaidCreditAccountId;\n let customerId = '';\n if (target.purpose === 'invoice_payment') {\n invoice = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () => {\n const invoices = await InvoiceCollection.create({ db });\n // Serialize settlements of one invoice: two rail payments projected\n // at once must not both read it unpaid and both allocate.\n await db.query(\n `UPDATE ${invoices.tableName} SET updated_at = updated_at WHERE id = ?`,\n target.invoiceId,\n );\n return invoices.get(target.invoiceId);\n },\n );\n if (!invoice?.id) {\n throw new Error(`Invoice ${target.invoiceId} for an attempt is missing.`);\n }\n customerId = invoice.customerId;\n // A written-off invoice keeps its status when paid (#3188), so \"paid\n // elsewhere\" is read from its allocations.\n const settledElsewhere =\n invoice.status === InvoiceStatus.WRITTEN_OFF &&\n (await withTenant({ tenantId: runtime.sellerTenantId }, async () =>\n (\n await PaymentAllocationCollection.create({ db })\n ).getTotalAllocatedToInvoice(String(invoice?.id)),\n )) >= invoice.totalAmount;\n if (\n invoice.status === InvoiceStatus.PAID ||\n target.issuerCollected ||\n settledElsewhere\n ) {\n // Paid elsewhere meanwhile: keep the money as customer credit for an\n // operator to refund or apply.\n flag = 'invoice_already_paid';\n } else {\n receivables = ledger.arAccountId;\n }\n } else {\n const account = await runtime.accounts.get(target.billingAccountId);\n if (\n !account ||\n tenantKey(account.sellerTenantId) !== runtime.sellerTenantId ||\n tenantKey(account.payerTenantId) !== target.payerTenantId\n ) {\n throw new Error(`Checkout ${state.checkoutId} has no matching account.`);\n }\n customerId = account.customerId;\n }\n\n const payment = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () => {\n const existing = await payments.get(paymentId);\n const row =\n existing ??\n (await payments.create({\n id: paymentId,\n tenantId: runtime.sellerTenantId,\n customerId,\n amount: target.amount,\n currency: target.currency,\n method: PaymentMethod.CRYPTO,\n reference:\n target.purpose === 'invoice_payment'\n ? (invoice?.invoiceNumber ?? '')\n : `credit:${target.spendingPolicyId}`,\n externalId: state.checkoutId,\n externalProvider: attempt.provider,\n backendId: (state.nativeCurrency ?? '').toLowerCase(),\n backendTxRef: txids.join(','),\n nativeAmount: state.nativeAmountPaid ?? 0,\n nativeCurrency: state.nativeCurrency ?? '',\n notes: [\n `rate ${state.rate ?? 'unknown'} ${target.currency}/${state.nativeCurrency ?? '?'}`,\n state.rateSource ? `source ${state.rateSource}` : '',\n `received ${state.amountPaid} ${target.currency} minor units`,\n ]\n .filter(Boolean)\n .join('; '),\n _insertOnly: true,\n }));\n if (row.status !== PaymentStatus.COMPLETED) {\n await row.recordPayment({\n ledgerId: '',\n cashAccountId: holdings,\n receivablesAccountId: receivables,\n });\n }\n return row;\n },\n );\n\n // Money above the price is the payer's: book it as customer credit (in the\n // prepaid-credit liability) for an operator to refund or apply.\n const excess = Math.max(0, state.amountPaid - target.amount);\n if (excess > 0) {\n await postOnce(runtime, db, `overpayment:${paymentId}`, {\n description: `Overpayment on ${attempt.provider} checkout ${state.checkoutId}`,\n entries: [\n { accountId: holdings, debit: excess },\n { accountId: ledger.prepaidCreditAccountId, credit: excess },\n ],\n });\n }\n\n let creditGrantId = '';\n if (target.purpose === 'credit_purchase') {\n const grant = await withSystemContext(async () => {\n const evaluator = await SpendingPolicyEvaluator.create({ db });\n return evaluator.grantCredit({\n spendingPolicyId: target.spendingPolicyId,\n amount: target.amount,\n reason: 'Prepaid credit purchase',\n source: `${attempt.provider}-checkout`,\n sourceId: state.checkoutId,\n ...(target.grantedByTenantId\n ? { grantedByTenantId: target.grantedByTenantId }\n : {}),\n });\n });\n creditGrantId = String(grant.id ?? '');\n } else if (invoice && flag !== 'invoice_already_paid') {\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const allocations = await PaymentAllocationCollection.create({ db });\n const already = await allocations.getTotalAllocatedToInvoice(\n String(invoice.id),\n );\n const room = Math.max(0, invoice.totalAmount - already);\n const allocationId = await deterministicId([\n 'billing-attempt-allocation',\n paymentId,\n String(invoice.id),\n ]);\n if (room > 0 && !(await allocations.get(allocationId))) {\n await allocations.create({\n id: allocationId,\n tenantId: runtime.sellerTenantId,\n paymentId,\n invoiceId: String(invoice.id),\n amount: Math.min(room, target.amount),\n allocatedBy: `${attempt.provider}-checkout`,\n _insertOnly: true,\n });\n }\n invoice.updatePaymentStatus(\n await allocations.getTotalAllocatedToInvoice(String(invoice.id)),\n );\n await invoice.save();\n });\n }\n return { paymentId: String(payment.id), creditGrantId, excess, flag };\n}\n\n// ---------------------------------------------------------------------------\n// Operator journals\n// ---------------------------------------------------------------------------\n\nexport interface ManualRefundInput {\n /** The settled rail `Payment` refunded. */\n paymentId: string;\n /** Fiat value of the refund, minor units of the payment currency. */\n fiatAmount: number;\n /** Native amount sent back (satoshis), when known. */\n nativeAmount?: number;\n /** Default: the seller's `paymentPolicy.refundBasis`. */\n basis?: 'original_native' | 'fiat_at_refund';\n /** The operator's refund reference (for example the refund txid). */\n reference: string;\n reason: string;\n}\n\nexport interface ManualRefundResult {\n journalId: string;\n creditGrantId: string;\n}\n\n/**\n * Record a refund an operator made outside billing. Refunds come from what\n * the payer is owed: first any overpayment, then — for a credit purchase, or\n * an invoice payment kept as credit because the invoice was paid elsewhere —\n * the price. An invoice payment applied to its invoice is not refundable\n * here (reverse the invoice at the issuer first). Refunds are capped\n * cumulatively per payment and are idempotent by `reference`.\n */\nexport async function recordManualRefund(\n runtime: BillingRuntime,\n input: ManualRefundInput,\n): Promise<ManualRefundResult> {\n if (!Number.isSafeInteger(input.fiatAmount) || input.fiatAmount <= 0) {\n throw new Error('fiatAmount must be positive integer minor units.');\n }\n const reference = input.reference?.trim();\n if (!reference) throw new Error('A reference is required.');\n const basis = input.basis ?? runtime.paymentPolicy.refundBasis;\n const [found] = await runtime.attempts.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n paymentId: input.paymentId,\n },\n limit: 1,\n });\n if (!found?.id) {\n throw new Error(\n `Payment ${input.paymentId} is not a settled payment-rail payment of this seller.`,\n );\n }\n const inTransaction = <T>(work: (db: DatabaseInterface) => Promise<T>) =>\n runtime.db.transaction ? runtime.db.transaction(work) : work(runtime.db);\n return withSystemContext(() =>\n inTransaction(async (db) => {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n // Serialize refunds of one payment: the cumulative cap reads, then writes.\n await lockAttempt(db, attempts.tableName, String(found.id));\n const attempt = await attempts.get(String(found.id));\n if (!attempt?.id || !attempt.settledAt) {\n throw new Error(\n `Payment ${input.paymentId} is not a settled payment-rail payment of this seller.`,\n );\n }\n const done = attempt.refundRecords.find(\n (record) => record.reference === reference,\n );\n if (done) {\n return {\n journalId: String(done.journalId ?? ''),\n creditGrantId: String(done.creditGrantId ?? ''),\n };\n }\n // The settlement's own classification, not the mutable flag.\n const principalRefundable =\n attempt.settlementOutcome === 'credit' ? attempt.amount : 0;\n const refundedExcess = attempt.refundedAmount - attempt.refundedPrincipal;\n const excessLeft = attempt.excessAmount - refundedExcess;\n const principalLeft = principalRefundable - attempt.refundedPrincipal;\n if (input.fiatAmount > excessLeft + principalLeft) {\n throw new Error(\n `A refund of ${input.fiatAmount} would exceed the ${excessLeft + principalLeft} ${attempt.currency} still refundable on this payment.`,\n );\n }\n const fromExcess = Math.min(input.fiatAmount, excessLeft);\n const fromPrincipal = input.fiatAmount - fromExcess;\n const ledger = runtime.ledger;\n const holdings = ledger.cryptoHoldingsAccountId || ledger.cashAccountId;\n const journalId = await postOnce(\n runtime,\n db,\n `refund:${attempt.id}:${reference}`,\n {\n description: `Refund ${reference} (${basis}${\n input.nativeAmount ? `, ${input.nativeAmount} native` : ''\n }): ${input.reason}`,\n entries: [\n // Excess and credit kept for an already-paid invoice both sit in\n // the prepaid-credit liability, as does purchased credit.\n {\n accountId: ledger.prepaidCreditAccountId,\n debit: input.fiatAmount,\n },\n { accountId: holdings, credit: input.fiatAmount },\n ],\n },\n );\n let creditGrantId = '';\n if (\n fromPrincipal > 0 &&\n attempt.purpose === 'credit_purchase' &&\n attempt.spendingPolicyId\n ) {\n const grant = await (\n await SpendingPolicyEvaluator.create({ db })\n ).grantCredit({\n spendingPolicyId: attempt.spendingPolicyId,\n amount: -fromPrincipal,\n reason: `Refund: ${input.reason}`,\n source: `${attempt.provider}-refund`,\n sourceId: reference,\n });\n creditGrantId = String(grant.id ?? '');\n }\n const refunds = attempt.refundRecords;\n refunds.push({\n reference,\n amount: input.fiatAmount,\n principal: fromPrincipal,\n nativeAmount: input.nativeAmount ?? null,\n basis,\n reason: input.reason,\n journalId,\n creditGrantId,\n at: new Date().toISOString(),\n });\n attempt.refunds = refunds;\n attempt.refundedAmount += input.fiatAmount;\n attempt.refundedPrincipal += fromPrincipal;\n attempt.resolution = [\n attempt.resolution,\n `refunded ${input.fiatAmount} ${attempt.currency} (${basis}) ref ${reference}`,\n ]\n .filter(Boolean)\n .join('; ');\n attempt.resolvedAt = new Date();\n await attempt.save();\n return { journalId, creditGrantId };\n }),\n );\n}\n\nexport interface CryptoConversionInput {\n /** Operator reference (for example the exchange trade id). */\n reference: string;\n currency: string;\n nativeCurrency: string;\n /** Native amount converted (satoshis). */\n nativeAmount: number;\n /** Book value of the converted holdings, minor units of `currency`. */\n carryingValue: number;\n /** Cash received, minor units. */\n proceeds: number;\n /** Fees paid, minor units (default 0). */\n fees?: number;\n}\n\nexport async function recordCryptoConversion(\n runtime: BillingRuntime,\n input: CryptoConversionInput,\n): Promise<{ journalId: string }> {\n const ledger = runtime.ledger;\n if (!ledger.cryptoHoldingsAccountId || !ledger.fxGainLossAccountId) {\n throw new Error(\n 'Conversions need cryptoHoldingsAccountId and fxGainLossAccountId ledger accounts.',\n );\n }\n const fees = input.fees ?? 0;\n for (const [key, value] of Object.entries({\n nativeAmount: input.nativeAmount,\n carryingValue: input.carryingValue,\n proceeds: input.proceeds,\n fees,\n })) {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new Error(`${key} must be non-negative integer minor units.`);\n }\n }\n if (fees > 0 && !ledger.feesAccountId) {\n throw new Error('Conversion fees need a feesAccountId ledger account.');\n }\n if (!input.reference?.trim()) throw new Error('A reference is required.');\n const gain = input.proceeds + fees - input.carryingValue;\n const entries: JournalLine[] = [\n { accountId: ledger.cashAccountId, debit: input.proceeds },\n { accountId: ledger.cryptoHoldingsAccountId, credit: input.carryingValue },\n ];\n if (fees > 0 && ledger.feesAccountId) {\n entries.push({ accountId: ledger.feesAccountId, debit: fees });\n }\n if (gain > 0) {\n entries.push({ accountId: ledger.fxGainLossAccountId, credit: gain });\n } else if (gain < 0) {\n entries.push({ accountId: ledger.fxGainLossAccountId, debit: -gain });\n }\n const inTransaction = <T>(work: (db: DatabaseInterface) => Promise<T>) =>\n runtime.db.transaction ? runtime.db.transaction(work) : work(runtime.db);\n const journalId = await inTransaction((db) =>\n postOnce(runtime, db, `conversion:${input.reference.trim()}`, {\n description: `Convert ${input.nativeAmount} ${normalizeCurrency(\n input.nativeCurrency,\n )} minor units to ${normalizeCurrency(input.currency)} (${input.reference})`,\n entries: entries.filter(\n (entry) => (entry.debit ?? 0) > 0 || (entry.credit ?? 0) > 0,\n ),\n }),\n );\n return { journalId };\n}\n\nasync function lockAttempt(\n db: DatabaseInterface,\n tableName: string,\n id: string,\n): Promise<void> {\n await db.query(\n `UPDATE ${tableName} SET updated_at = updated_at WHERE id = ?`,\n id,\n );\n}\n\ninterface JournalLine {\n accountId: string;\n debit?: number;\n credit?: number;\n}\n\n/** Post a balanced journal once per `sourceRef` in the seller's books. */\nasync function postOnce(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n sourceRef: string,\n journal: { description: string; entries: JournalLine[] },\n): Promise<string> {\n return withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const journals = await JournalCollection.create({ db });\n const [posted] = await journals.list({\n where: { sourceModule: 'smrt-commerce', sourceRef, status: 'posted' },\n limit: 1,\n });\n if (posted?.id) return String(posted.id);\n // A deterministic id makes a concurrent second writer fail on insert\n // instead of posting a duplicate journal.\n const id = await deterministicId([\n 'billing-journal',\n runtime.sellerTenantId,\n sourceRef,\n ]);\n const existing = await journals.get(id);\n if (existing) {\n if (existing.status === 'posted') return id;\n throw new Error(\n `Journal ${sourceRef} was started but not posted; an operator must complete or void it.`,\n );\n }\n let created: Awaited<ReturnType<typeof journals.create>>;\n try {\n created = await journals.create({\n id,\n date: new Date(),\n description: journal.description,\n sourceModule: 'smrt-commerce',\n sourceRef,\n _insertOnly: true,\n });\n } catch (error) {\n if (await journals.get(id)) {\n throw new Error(\n `Journal ${sourceRef} is being recorded concurrently; retry to read its result.`,\n { cause: error },\n );\n }\n throw error;\n }\n await created.save();\n for (const entry of journal.entries) {\n await created.addEntry({ ...entry, memo: sourceRef });\n }\n await created.post();\n return String(created.id);\n });\n}\n","/**\n * Billing-period close (#3060): approved charges and flat plan fees per\n * billing owner → commerce invoice → provider invoice (with provider tax) →\n * ledger.\n *\n * Replay safety comes from identity, not from a transaction spanning the\n * provider call:\n *\n * - each payer/currency/period has one {@link BillingPeriodClose} whose id is\n * derived from that identity, and a lease so two workers never advance the\n * same close at once;\n * - each billable source is claimed by one {@link BillingLineSource} whose id\n * is derived from the source, so a charge is billed at most once however\n * many periods or workers race for it; a flat plan's claims record the time\n * they cover and are numbered per subscription (#3116), so no two claims\n * for one subscription ever cover the same time, whatever schedule each\n * payer is on;\n * - the invoice and its lines have ids derived from the close, and the\n * provider invoice uses the close id as its idempotency key;\n * - every step persists its outcome before the next, so a retry resumes where\n * the last attempt stopped.\n */\nimport type {\n ClientCharge,\n RetailCharge,\n SubscriptionPlan,\n TenantSubscription,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport type {\n BillingAccount,\n BillingLineSource,\n BillingLineSourceType,\n BillingPeriodClose,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus } from '../types/index.js';\nimport { defaultCardFor } from './cards.js';\nimport {\n billingPeriodContaining,\n lastEndedBillingPeriod,\n prorateMinorUnits,\n type ScheduledBillingPeriod,\n} from './cycles.js';\nimport type { BillingProviderInvoiceLine } from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n deterministicId,\n isTenantId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport interface BillingPeriod {\n /** Inclusive start. */\n periodStart: Date;\n /** Exclusive end; charges approved before it are billable. */\n periodEnd: Date;\n}\n\n/**\n * Without a period, each payer's most recent ended period on its own schedule\n * is closed (#3116): the previous calendar month for a payer without a\n * billing anchor, the last anchor-to-anchor period for one with. With a\n * period, that exact period is closed for every calendar-schedule payer (flat\n * plans only when it is a calendar month) and for each anchored payer whose\n * schedule contains exactly that period.\n */\nexport interface ClosePeriodInput {\n periodStart?: Date;\n periodEnd?: Date;\n /** Clock override for tests; defaults to now. */\n now?: Date;\n}\n\nexport type PeriodCloseOutcome =\n | 'completed'\n | 'empty'\n | 'busy'\n | 'carried_forward'\n | 'failed';\n\nexport interface PeriodCloseGroupResult {\n payerTenantId: string;\n currency: string;\n /** The payer's period this group closed (#3116). */\n periodStart: Date;\n periodEnd: Date;\n closeId?: string;\n invoiceId?: string;\n providerInvoiceId?: string;\n outcome: PeriodCloseOutcome;\n error?: string;\n}\n\n/**\n * `periodStart`/`periodEnd` are the explicit period, or the previous calendar\n * month for a close without one; each group carries the payer's own period.\n */\nexport interface PeriodCloseResult extends BillingPeriod {\n sellerTenantId: string;\n groups: PeriodCloseGroupResult[];\n}\n\n/** Thrown when one or more payers could not be closed; the rest were. */\nexport class BillingPeriodCloseError extends Error {\n constructor(readonly result: PeriodCloseResult) {\n const failed = result.groups.filter((group) => group.outcome === 'failed');\n super(\n `Billing period close failed for ${failed.length} payer group(s): ${failed\n .map(\n (group) => `${group.payerTenantId}/${group.currency}: ${group.error}`,\n )\n .join('; ')}`,\n );\n this.name = 'BillingPeriodCloseError';\n }\n}\n\ninterface Candidate {\n sourceType: BillingLineSourceType;\n sourceId: string;\n payerTenantId: string;\n currency: string;\n lineKey: string;\n lineDescription: string;\n amount: number;\n quantity: number;\n /** Flat-plan sources take the payer account's discount at claim time. */\n discountable: boolean;\n periodStart: Date | null;\n periodEnd: Date | null;\n /** A flat plan's billable window, claimed by `claimFlatWindow()`. */\n flat?: FlatWindow;\n}\n\ninterface FlatWindow {\n subscriptionId: string;\n /** The plan's full-cycle price in minor units. */\n planAmount: number;\n /** Plan name and tenant suffix for the line description. */\n label: string;\n windowStart: Date;\n windowEnd: Date;\n cycleMs: number;\n}\n\ninterface CandidateGroup {\n payerTenantId: string;\n currency: string;\n closeId: string;\n period: ScheduledBillingPeriod;\n candidates: Candidate[];\n}\n\n/** How one payer is closed in this run. */\ninterface PayerSchedule {\n /** The period to close, or null when the payer is not closed this run. */\n period: ScheduledBillingPeriod | null;\n /** Whether flat plans are billed for that period. */\n billsFlatPlans: boolean;\n prorate: boolean;\n}\n\ntype CloseMode =\n | { kind: 'due'; now: Date }\n | { kind: 'explicit'; period: BillingPeriod };\n\n/** The previous calendar month in UTC relative to `now`. */\nexport function previousCalendarMonth(now = new Date()): BillingPeriod {\n const periodEnd = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n );\n const periodStart = new Date(\n Date.UTC(periodEnd.getUTCFullYear(), periodEnd.getUTCMonth() - 1, 1),\n );\n return { periodStart, periodEnd };\n}\n\nfunction isCalendarMonth(period: BillingPeriod): boolean {\n const { periodStart: start, periodEnd: end } = period;\n return (\n start.getUTCDate() === 1 &&\n start.getUTCHours() === 0 &&\n start.getUTCMinutes() === 0 &&\n start.getUTCSeconds() === 0 &&\n start.getUTCMilliseconds() === 0 &&\n end.getTime() ===\n Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1)\n );\n}\n\nfunction resolveMode(input: ClosePeriodInput): CloseMode {\n const now = input.now ?? new Date();\n if (!input.periodStart && !input.periodEnd) return { kind: 'due', now };\n if (!input.periodStart || !input.periodEnd) {\n throw new Error('Pass both periodStart and periodEnd, or neither.');\n }\n const period = { periodStart: input.periodStart, periodEnd: input.periodEnd };\n if (\n !Number.isFinite(period.periodStart.getTime()) ||\n !(period.periodEnd.getTime() > period.periodStart.getTime())\n ) {\n throw new Error('A billing period must end after it starts.');\n }\n if (period.periodEnd.getTime() > now.getTime()) {\n throw new Error('A billing period cannot be closed before it ends.');\n }\n return { kind: 'explicit', period };\n}\n\nexport function periodCloseId(\n runtime: Pick<BillingRuntime, 'sellerTenantId' | 'kind'>,\n payerTenantId: string,\n currency: string,\n period: BillingPeriod,\n): Promise<string> {\n return deterministicId([\n 'billing-period-close',\n runtime.sellerTenantId,\n runtime.kind,\n tenantKey(payerTenantId),\n normalizeCurrency(currency),\n period.periodStart.toISOString(),\n period.periodEnd.toISOString(),\n ]);\n}\n\nfunction sourceId(type: BillingLineSourceType, id: string): Promise<string> {\n return deterministicId(['billing-line-source', type, id]);\n}\n\nfunction samePeriod(a: BillingPeriod, b: BillingPeriod): boolean {\n return (\n a.periodStart.getTime() === b.periodStart.getTime() &&\n a.periodEnd.getTime() === b.periodEnd.getTime()\n );\n}\n\n/** Each payer's schedule for one run, read once from its billing account. */\nclass PayerSchedules {\n private readonly cache = new Map<string, Promise<PayerSchedule>>();\n\n constructor(\n private readonly runtime: BillingRuntime,\n private readonly mode: CloseMode,\n ) {}\n\n get(payerTenantId: string): Promise<PayerSchedule> {\n const key = tenantKey(payerTenantId);\n let schedule = this.cache.get(key);\n if (!schedule) {\n schedule = this.resolve(key);\n this.cache.set(key, schedule);\n }\n return schedule;\n }\n\n private async resolve(payer: string): Promise<PayerSchedule> {\n // A payer without an account keeps the calendar schedule; its close then\n // fails with a clear \"no billing account\" error, as before #3116.\n const account = isTenantId(payer)\n ? await this.runtime.getAccount(payer)\n : null;\n const anchor = account?.billingAnchorAt ?? null;\n const prorate = Boolean(account?.prorateFlatPlans);\n if (this.mode.kind === 'due') {\n return {\n period: lastEndedBillingPeriod(anchor, this.mode.now),\n billsFlatPlans: true,\n prorate,\n };\n }\n const explicit = this.mode.period;\n if (!anchor) {\n return {\n period: {\n ...explicit,\n cycleStart: explicit.periodStart,\n cycleEnd: explicit.periodEnd,\n },\n billsFlatPlans: isCalendarMonth(explicit),\n prorate,\n };\n }\n // An anchored payer is closed only for a period of its own schedule.\n const scheduled = billingPeriodContaining(anchor, explicit.periodStart);\n const matches = samePeriod(scheduled, explicit);\n return {\n period: matches ? scheduled : null,\n billsFlatPlans: matches,\n prorate,\n };\n }\n}\n\n/**\n * Close billing periods for a runtime's seller: each payer's last ended\n * period, or one explicit period (see {@link ClosePeriodInput}). Idempotent:\n * re-running it resumes unfinished payers and bills nothing twice.\n */\nexport async function closeBillingPeriod(\n runtime: BillingRuntime,\n input: ClosePeriodInput = {},\n): Promise<PeriodCloseResult> {\n const mode = resolveMode(input);\n const now = input.now ?? new Date();\n const schedules = new PayerSchedules(runtime, mode);\n const groups = await withSystemContext(() =>\n collectGroups(runtime, mode, schedules),\n );\n // Closes still in progress, including ones whose every source was already\n // claimed (a collection query no longer returns them).\n const unfinished = await runtime.closes.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n kind: runtime.kind,\n 'status !=': 'completed',\n },\n });\n const results: PeriodCloseGroupResult[] = [];\n const seen = new Set<string>();\n for (const group of groups) {\n seen.add(group.closeId);\n results.push(await closeGroup(runtime, group, now));\n }\n // Resume unfinished closes: with an explicit period, those of that period\n // whose sources were all claimed on an earlier attempt; without one, every\n // unfinished close whose period has ended, whatever the payer's schedule is\n // now — a close stranded by a re-anchor would otherwise hold its claims\n // (time later closes treat as billed) without ever invoicing them.\n for (const close of unfinished) {\n if (\n !close.id ||\n seen.has(close.id) ||\n close.status === 'completed' ||\n close.status === 'carried_forward'\n ) {\n continue;\n }\n const resumable =\n mode.kind === 'explicit'\n ? samePeriod(close, mode.period)\n : close.periodEnd.getTime() <= now.getTime();\n const period: ScheduledBillingPeriod | null = resumable\n ? {\n periodStart: close.periodStart,\n periodEnd: close.periodEnd,\n cycleStart: close.periodStart,\n cycleEnd: close.periodEnd,\n }\n : null;\n if (!period) continue;\n results.push(\n await closeGroup(\n runtime,\n {\n payerTenantId: close.payerTenantId,\n currency: close.currency,\n closeId: close.id,\n period,\n candidates: [],\n },\n now,\n ),\n );\n }\n const result: PeriodCloseResult = {\n sellerTenantId: runtime.sellerTenantId,\n ...(mode.kind === 'explicit' ? mode.period : previousCalendarMonth(now)),\n groups: results,\n };\n if (results.some((group) => group.outcome === 'failed')) {\n throw new BillingPeriodCloseError(result);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Collection\n// ---------------------------------------------------------------------------\n\nasync function collectGroups(\n runtime: BillingRuntime,\n mode: CloseMode,\n schedules: PayerSchedules,\n): Promise<CandidateGroup[]> {\n // Nothing after this instant is billable to any payer this run; each\n // collector narrows it to the payer's own period end.\n const bound = mode.kind === 'explicit' ? mode.period.periodEnd : mode.now;\n const candidates: Candidate[] = [];\n if (runtime.kind === 'provider') {\n candidates.push(...(await collectClientCharges(runtime, bound, schedules)));\n candidates.push(...(await collectAdjustments(runtime, bound, schedules)));\n } else {\n candidates.push(...(await collectRetailCharges(runtime, bound, schedules)));\n }\n candidates.push(...(await collectFlatPlans(runtime, bound, schedules)));\n candidates.push(...(await collectCarriedCredits(runtime, schedules)));\n\n const byGroup = new Map<string, CandidateGroup>();\n for (const candidate of candidates) {\n const key = `${candidate.payerTenantId}|${candidate.currency}`;\n let group = byGroup.get(key);\n if (!group) {\n const { period } = await schedules.get(candidate.payerTenantId);\n if (!period) continue;\n group = {\n payerTenantId: candidate.payerTenantId,\n currency: candidate.currency,\n closeId: await periodCloseId(\n runtime,\n candidate.payerTenantId,\n candidate.currency,\n period,\n ),\n period,\n candidates: [],\n };\n byGroup.set(key, group);\n }\n group.candidates.push(candidate);\n }\n\n // Drop sources another close already claimed; keep this close's own claims\n // so a retry rebuilds the same invoice.\n const groups = [...byGroup.values()].sort((a, b) =>\n `${a.payerTenantId}|${a.currency}`.localeCompare(\n `${b.payerTenantId}|${b.currency}`,\n ),\n );\n const flatClaims = await loadFlatClaims(\n runtime,\n candidates.flatMap((candidate) => (candidate.flat ? [candidate.flat] : [])),\n );\n for (const group of groups) {\n const ids = await Promise.all(\n group.candidates.map((candidate) =>\n candidate.flat\n ? ''\n : sourceId(candidate.sourceType, candidate.sourceId),\n ),\n );\n const claimed = new Map<string, string>();\n const lookup = ids.filter(Boolean);\n for (let offset = 0; offset < lookup.length; offset += runtime.pageSize) {\n const rows = await runtime.sources.list({\n where: { id: lookup.slice(offset, offset + runtime.pageSize) },\n });\n for (const row of rows) {\n if (row.id) claimed.set(row.id, row.periodCloseId);\n }\n }\n group.candidates = group.candidates.filter((candidate, index) => {\n if (candidate.flat) {\n const claims = flatClaims.get(candidate.flat.subscriptionId) ?? [];\n return (\n claims.some((claim) => claim.periodCloseId === group.closeId) ||\n uncovered(candidate.flat, claims).length > 0\n );\n }\n const owner = claimed.get(ids[index]);\n return owner === undefined || owner === group.closeId;\n });\n }\n // A carried credit alone is not new activity: it stays claimable at its\n // original close until the payer is next billed.\n return groups.filter((group) =>\n group.candidates.some(\n (candidate) => candidate.sourceType !== 'credit_carry_forward',\n ),\n );\n}\n\nasync function listAll<T>(\n pageSize: number,\n page: (limit: number, offset: number) => Promise<T[]>,\n): Promise<T[]> {\n const all: T[] = [];\n for (let offset = 0; ; offset += pageSize) {\n const rows = await page(pageSize, offset);\n all.push(...rows);\n if (rows.length < pageSize) return all;\n }\n}\n\ninterface UnclaimedRow {\n id: string;\n payer: string;\n at: Date;\n}\n\n/**\n * Rows in `table` matching `where` that no close has claimed yet, with their\n * payer (`tenant_id`) and billing instant. The anti-join keeps each run\n * proportional to unbilled work rather than to the whole charge history.\n */\nasync function unclaimedRows(\n runtime: BillingRuntime,\n table: string,\n sourceType: BillingLineSourceType,\n atColumn: string,\n where: string,\n params: unknown[],\n): Promise<UnclaimedRow[]> {\n const rows: UnclaimedRow[] = [];\n for (let offset = 0; ; offset += runtime.pageSize) {\n const result = await runtime.db.query(\n `SELECT c.id AS id, c.tenant_id AS payer, c.${atColumn} AS at\n FROM ${table} c\n LEFT JOIN _smrt_billing_line_sources s\n ON s.source_type = ? AND s.source_id = CAST(c.id AS TEXT)\n WHERE s.id IS NULL AND ${where}\n ORDER BY c.id\n LIMIT ? OFFSET ?`,\n sourceType,\n ...params,\n runtime.pageSize,\n offset,\n );\n for (const row of result.rows) {\n rows.push({\n id: String(row.id),\n payer: tenantKey(row.payer as string | null),\n at: new Date(row.at as string | Date),\n });\n }\n if (result.rows.length < runtime.pageSize) return rows;\n }\n}\n\n/** Ids of rows billable in their payer's period this run. */\nasync function billableIds(\n rows: UnclaimedRow[],\n schedules: PayerSchedules,\n): Promise<string[]> {\n const ids: string[] = [];\n for (const row of rows) {\n const { period } = await schedules.get(row.payer);\n if (period && row.at.getTime() < period.periodEnd.getTime()) {\n ids.push(row.id);\n }\n }\n return ids;\n}\n\n/** Load rows by id in pages through a collection. */\nasync function loadByIds<T>(\n pageSize: number,\n ids: string[],\n load: (batch: string[]) => Promise<T[]>,\n): Promise<T[]> {\n const rows: T[] = [];\n for (let offset = 0; offset < ids.length; offset += pageSize) {\n rows.push(...(await load(ids.slice(offset, offset + pageSize))));\n }\n return rows;\n}\n\nfunction usageDescription(\n metricKey: string,\n serviceKey: string,\n quantity: number,\n usageTenantId?: string,\n): string {\n const service = serviceKey ? ` (${serviceKey})` : '';\n const tenant = usageTenantId ? ` for tenant ${usageTenantId}` : '';\n const units = Number.isFinite(quantity)\n ? ` — ${Number(quantity.toFixed(6))} units`\n : '';\n return `Usage: ${metricKey}${service}${tenant}${units}`;\n}\n\n/** Wholesale books this seller publishes; charges priced by others are not ours. */\nasync function sellerBookIds(runtime: BillingRuntime): Promise<Set<string>> {\n const books = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.books.list({\n where: { tenantId: runtime.sellerTenantId },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n return new Set(books.map((book) => String(book.id)));\n}\n\nfunction isSellerCharge(charge: ClientCharge, books: Set<string>): boolean {\n return !charge.priceBookId || books.has(String(charge.priceBookId));\n}\n\nasync function collectClientCharges(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const books = await sellerBookIds(runtime);\n const ids = await billableIds(\n await unclaimedRows(\n runtime,\n '_smrt_client_charges',\n 'client_charge',\n 'approved_at',\n \"c.status IN ('approved', 'adjusted') AND c.approved_at < ?\",\n [bound.toISOString()],\n ),\n schedules,\n );\n const charges = await loadByIds(runtime.pageSize, ids, (batch) =>\n runtime.charges.list({ where: { id: batch } }),\n );\n return charges\n .filter((charge) => charge.id && isSellerCharge(charge, books))\n .filter((charge) => tenantKey(charge.tenantId) !== runtime.sellerTenantId)\n .map((charge) => {\n const payer = tenantKey(charge.tenantId);\n const usageTenant = tenantKey(charge.usageTenantId);\n const child = usageTenant && usageTenant !== payer ? usageTenant : '';\n return {\n sourceType: 'client_charge' as const,\n sourceId: String(charge.id),\n payerTenantId: payer,\n currency: normalizeCurrency(charge.currency),\n lineKey: `usage|${charge.serviceKey}|${charge.metricKey}|${child}`,\n lineDescription: usageDescription(\n charge.metricKey,\n charge.serviceKey,\n Number(charge.quantity) || 0,\n child,\n ),\n amount: Number(charge.amount),\n quantity: Number(charge.quantity) || 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n };\n });\n}\n\nasync function collectAdjustments(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const books = await sellerBookIds(runtime);\n const ids = await billableIds(\n await unclaimedRows(\n runtime,\n '_smrt_billing_adjustments',\n 'billing_adjustment',\n 'created_at',\n 'c.created_at < ?',\n [bound.toISOString()],\n ),\n schedules,\n );\n const adjustments = await loadByIds(runtime.pageSize, ids, (batch) =>\n runtime.adjustments.list({ where: { id: batch } }),\n );\n const chargeIds = [\n ...new Set(adjustments.map((row) => String(row.clientChargeId))),\n ].filter(Boolean);\n const charges = new Map<string, ClientCharge>();\n for (let offset = 0; offset < chargeIds.length; offset += runtime.pageSize) {\n const rows = await runtime.charges.list({\n where: { id: chargeIds.slice(offset, offset + runtime.pageSize) },\n });\n for (const row of rows) charges.set(String(row.id), row);\n }\n const candidates: Candidate[] = [];\n for (const adjustment of adjustments) {\n const charge = charges.get(String(adjustment.clientChargeId));\n if (\n !adjustment.id ||\n !charge ||\n !isSellerCharge(charge, books) ||\n (charge.status !== 'approved' && charge.status !== 'adjusted')\n ) {\n continue;\n }\n const payer = tenantKey(adjustment.tenantId);\n if (!payer || payer === runtime.sellerTenantId) continue;\n candidates.push({\n sourceType: 'billing_adjustment',\n sourceId: String(adjustment.id),\n payerTenantId: payer,\n currency: normalizeCurrency(adjustment.currency),\n lineKey: 'adjustment',\n lineDescription: 'Billing adjustments',\n amount: Number(adjustment.amount),\n quantity: 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n });\n }\n return candidates;\n}\n\nasync function collectRetailCharges(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const ids = await billableIds(\n await unclaimedRows(\n runtime,\n '_smrt_retail_charges',\n 'retail_charge',\n 'approved_at',\n \"c.reseller_tenant_id = ? AND c.status = 'approved' AND c.approved_at < ?\",\n [runtime.sellerTenantId, bound.toISOString()],\n ),\n schedules,\n );\n const charges = await loadByIds(runtime.pageSize, ids, (batch) =>\n runtime.retailCharges.list({ where: { id: batch } }),\n );\n return charges\n .filter((charge: RetailCharge) => charge.id)\n .map((charge: RetailCharge) => ({\n sourceType: 'retail_charge' as const,\n sourceId: String(charge.id),\n payerTenantId: tenantKey(charge.tenantId),\n currency: normalizeCurrency(charge.currency),\n lineKey: `retail|${charge.serviceKey}|${charge.metricKey}`,\n lineDescription: usageDescription(\n charge.metricKey,\n charge.serviceKey,\n Number(charge.quantity) || 0,\n ),\n amount: Number(charge.amount),\n quantity: Number(charge.quantity) || 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n }));\n}\n\n/** Net credits carried forward by this seller's earlier closes. */\nasync function collectCarriedCredits(\n runtime: BillingRuntime,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n // Only unconsumed credits: a close is marked completed once a later close\n // claims its credit, so this scan stays proportional to open credits.\n const carried = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.closes.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n kind: runtime.kind,\n status: 'carried_forward',\n },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n const candidates: Candidate[] = [];\n for (const close of carried) {\n if (!close.id || !(Number(close.subtotal) < 0)) continue;\n const { period } = await schedules.get(close.payerTenantId);\n if (!period || close.periodEnd.getTime() > period.periodStart.getTime()) {\n continue;\n }\n candidates.push({\n sourceType: 'credit_carry_forward' as const,\n sourceId: String(close.id),\n payerTenantId: tenantKey(close.payerTenantId),\n currency: normalizeCurrency(close.currency),\n lineKey: `carry-forward|${close.id}`,\n lineDescription: `Credit carried forward from ${isoDate(\n close.periodStart,\n )} to ${isoDate(new Date(close.periodEnd.getTime() - 1))}`,\n amount: Number(close.subtotal),\n quantity: 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n });\n }\n return candidates;\n}\n\nfunction isoDate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\n/**\n * Flat monthly plan fees, billed in arrears for each payer's period,\n * including the period a subscription is canceled in. Provider-managed\n * subscriptions (a provider subscription id) are billed by the provider\n * itself and skipped; incomplete subscriptions are not billed, and trials\n * are not billed (with proration, only the trial's time is not billed).\n */\nasync function collectFlatPlans(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const subscriptions = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.subscriptions.list({\n where: {\n // A subscription canceled during the period still owes that period.\n status: ['active', 'past_due', 'unpaid', 'canceled'],\n subscriberKind: 'tenant',\n 'startedAt <': bound.toISOString(),\n },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n const planIds = [\n ...new Set(subscriptions.map((row) => String(row.planId))),\n ].filter(Boolean);\n const plans = new Map<string, SubscriptionPlan>();\n for (let offset = 0; offset < planIds.length; offset += runtime.pageSize) {\n const rows = await runtime.plans.list({\n where: { id: planIds.slice(offset, offset + runtime.pageSize) },\n });\n for (const row of rows) plans.set(String(row.id), row);\n }\n\n const candidates: Candidate[] = [];\n for (const subscription of subscriptions) {\n const plan = plans.get(String(subscription.planId));\n if (!subscription.id || !plan || !isFlatPlan(subscription, plan)) continue;\n const planSeller = tenantKey(plan.tenantId);\n const subscriber = tenantKey(subscription.tenantId);\n let payer: string;\n if (runtime.kind === 'provider') {\n if (planSeller && planSeller !== runtime.sellerTenantId) continue;\n const relationship =\n await runtime.billingRelationships.getRelationship(subscriber);\n payer = relationship ? relationship.billingOwnerTenantId : subscriber;\n } else {\n if (planSeller !== runtime.sellerTenantId) continue;\n const relationship =\n await runtime.billingRelationships.getRelationship(subscriber);\n if (relationship?.resellerTenantId !== runtime.sellerTenantId) continue;\n payer = subscriber;\n }\n payer = tenantKey(payer);\n if (!payer || payer === runtime.sellerTenantId) continue;\n const schedule = await schedules.get(payer);\n if (!schedule.period || !schedule.billsFlatPlans) continue;\n const window = flatWindow(subscription, schedule.period, schedule.prorate);\n if (!window) continue;\n const forTenant = payer !== subscriber ? ` for tenant ${subscriber}` : '';\n candidates.push({\n sourceType: 'subscription_period',\n // Assigned when claimed: the claim's number for its subscription.\n sourceId: '',\n payerTenantId: payer,\n currency: normalizeCurrency(plan.currency),\n lineKey: flatLineKey(String(subscription.id)),\n lineDescription: '',\n amount: 0,\n quantity: 1,\n discountable: true,\n periodStart: window.start,\n periodEnd: window.end,\n flat: {\n subscriptionId: String(subscription.id),\n planAmount: Number(plan.priceAmount),\n label: `${plan.name || plan.planKey}${forTenant}`,\n windowStart: window.start,\n windowEnd: window.end,\n cycleMs:\n schedule.period.cycleEnd.getTime() -\n schedule.period.cycleStart.getTime(),\n },\n });\n }\n return candidates;\n}\n\n/** Period-independent eligibility of a flat plan subscription. */\nfunction isFlatPlan(\n subscription: TenantSubscription,\n plan: SubscriptionPlan,\n): boolean {\n if (subscription.stripeSubscriptionId) return false;\n if (plan.billingInterval !== 'month') return false;\n if (!Number.isSafeInteger(plan.priceAmount) || plan.priceAmount <= 0) {\n return false;\n }\n if (subscription.status === 'canceled' && !subscription.canceledAt) {\n return false;\n }\n return true;\n}\n\n/**\n * The part of `period` a subscription is billed for, or null. Without\n * proration that is the whole period if the subscription was active in it\n * and out of trial at its start; with proration it is exactly the time the\n * subscription was active and out of trial.\n */\nfunction flatWindow(\n subscription: TenantSubscription,\n period: BillingPeriod,\n prorate: boolean,\n): { start: Date; end: Date } | null {\n const start = period.periodStart.getTime();\n const end = period.periodEnd.getTime();\n const startedAt = subscription.startedAt.getTime();\n const canceledAt = subscription.canceledAt?.getTime();\n const trialEndsAt = subscription.trialEndsAt?.getTime();\n if (!(startedAt < end)) return null;\n if (canceledAt !== undefined && canceledAt <= start) return null;\n if (!prorate) {\n if (trialEndsAt !== undefined && trialEndsAt > start) return null;\n return { start: period.periodStart, end: period.periodEnd };\n }\n const from = Math.max(start, startedAt, trialEndsAt ?? start);\n const to = Math.min(end, canceledAt ?? end);\n return to > from ? { start: new Date(from), end: new Date(to) } : null;\n}\n\n// ---------------------------------------------------------------------------\n// Flat-plan coverage\n// ---------------------------------------------------------------------------\n\nfunction flatLineKey(subscriptionId: string): string {\n return `subscription|${subscriptionId}`;\n}\n\n/**\n * Flat-plan claims of each subscription that overlap the windows being\n * billed (ending after the earliest start and starting before the latest\n * end), by subscription id. The read is bounded by those windows, not by\n * billing history.\n */\nasync function loadFlatClaims(\n runtime: BillingRuntime,\n flats: FlatWindow[],\n): Promise<Map<string, BillingLineSource[]>> {\n const bySubscription = new Map<string, [number, number]>();\n for (const flat of flats) {\n const start = flat.windowStart.getTime();\n const end = flat.windowEnd.getTime();\n const known = bySubscription.get(flat.subscriptionId);\n bySubscription.set(\n flat.subscriptionId,\n known\n ? [Math.min(known[0], start), Math.max(known[1], end)]\n : [start, end],\n );\n }\n const entries = [...bySubscription.entries()];\n const claims = new Map<string, BillingLineSource[]>();\n for (let offset = 0; offset < entries.length; offset += runtime.pageSize) {\n const batch = entries.slice(offset, offset + runtime.pageSize);\n const after = Math.min(...batch.map(([, [start]]) => start));\n const before = Math.max(...batch.map(([, [, end]]) => end));\n const rows = await runtime.sources.list({\n where: {\n sourceType: 'subscription_period',\n lineKey: batch.map(([subscriptionId]) => flatLineKey(subscriptionId)),\n 'periodEnd >': new Date(after).toISOString(),\n 'periodStart <': new Date(before).toISOString(),\n },\n });\n for (const row of rows) {\n const subscriptionId = row.lineKey.slice('subscription|'.length);\n const list = claims.get(subscriptionId) ?? [];\n list.push(row);\n claims.set(subscriptionId, list);\n }\n }\n return claims;\n}\n\n/** The parts of a flat window no existing claim covers, in order. */\nfunction uncovered(\n flat: FlatWindow,\n claims: BillingLineSource[],\n): Array<[number, number]> {\n const start = flat.windowStart.getTime();\n const end = flat.windowEnd.getTime();\n const covered = claims\n .filter((claim) => claim.periodStart && claim.periodEnd)\n .map(\n (claim) =>\n [\n (claim.periodStart as Date).getTime(),\n (claim.periodEnd as Date).getTime(),\n ] as [number, number],\n )\n .filter(([from, to]) => from < end && to > start)\n .sort((a, b) => a[0] - b[0]);\n const gaps: Array<[number, number]> = [];\n let cursor = start;\n for (const [from, to] of covered) {\n if (from > cursor) gaps.push([cursor, Math.min(from, end)]);\n cursor = Math.max(cursor, to);\n if (cursor >= end) break;\n }\n if (cursor < end) gaps.push([cursor, end]);\n return gaps.filter(([from, to]) => to > from);\n}\n\n/** The subscription's highest claim sequence number (0 before its first). */\nasync function lastFlatSequence(\n runtime: BillingRuntime,\n subscriptionId: string,\n): Promise<number> {\n const [last] = await runtime.sources.list({\n where: {\n sourceType: 'subscription_period',\n lineKey: flatLineKey(subscriptionId),\n },\n orderBy: 'chainSequence DESC',\n limit: 1,\n });\n return Number(last?.chainSequence) || 0;\n}\n\nfunction flatSourceId(subscriptionId: string, sequence: number): string {\n return `${subscriptionId}:seq:${sequence}`;\n}\n\nconst MAX_FLAT_CLAIM_CONFLICTS = 8;\n\n/**\n * Claim every uncovered part of a flat window for `closeId`, one claim per\n * gap, each priced as the plan amount × gap length / cycle length (rounded\n * half up) and recording exactly the time it bills.\n *\n * A subscription's claims are numbered 1, 2, 3, … and a claim's id is derived\n * from its number, so only one claimer can ever take number n + 1. A claimer\n * reads the last number n first and the coverage second; every claim up to n\n * was committed before claim n was, so the coverage read sees them all, and a\n * claim written after the first read takes n + 1 and makes this insert\n * conflict. A conflicting claimer re-reads and retries.\n */\nasync function claimFlatWindow(\n runtime: BillingRuntime,\n closeId: string,\n candidate: Candidate,\n flat: FlatWindow,\n account: BillingAccount,\n): Promise<void> {\n let conflicts = 0;\n for (;;) {\n const sequence = (await lastFlatSequence(runtime, flat.subscriptionId)) + 1;\n const claims =\n (await loadFlatClaims(runtime, [flat])).get(flat.subscriptionId) ?? [];\n const gap = uncovered(flat, claims)\n .map(([from, to]) => ({\n from,\n to,\n amount: prorateMinorUnits(flat.planAmount, to - from, flat.cycleMs),\n }))\n .find((part) => part.amount > 0);\n if (!gap) return;\n const billedStart = new Date(gap.from);\n const billedEnd = new Date(gap.to);\n const claimSourceId = flatSourceId(flat.subscriptionId, sequence);\n const id = await sourceId('subscription_period', claimSourceId);\n const prorated = gap.to - gap.from < flat.cycleMs ? ' (prorated)' : '';\n try {\n await runtime.sources.create({\n id,\n sellerTenantId: runtime.sellerTenantId,\n periodCloseId: closeId,\n sourceType: 'subscription_period',\n sourceId: claimSourceId,\n chainSequence: sequence,\n lineKey: candidate.lineKey,\n lineDescription: `${flat.label} — ${isoDate(billedStart)} to ${isoDate(\n new Date(billedEnd.getTime() - 1),\n )}${prorated}`,\n amount: gap.amount,\n discount: flatDiscount(gap.amount, account),\n quantity: 1,\n currency: candidate.currency,\n periodStart: billedStart,\n periodEnd: billedEnd,\n _insertOnly: true,\n });\n } catch (error) {\n // Another claim took this number: re-read and retry.\n if (!(await runtime.sources.get(id))) throw error;\n conflicts += 1;\n if (conflicts >= MAX_FLAT_CLAIM_CONFLICTS) {\n throw new Error(\n `Could not claim subscription ${flat.subscriptionId}: its billed coverage kept changing.`,\n );\n }\n }\n }\n}\n\nfunction flatDiscount(amount: number, account: BillingAccount): number {\n return Math.round(\n (amount * Number(account.flatDiscountBasisPoints)) / 10_000,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Per-payer close\n// ---------------------------------------------------------------------------\n\nasync function closeGroup(\n runtime: BillingRuntime,\n group: CandidateGroup,\n now: Date,\n): Promise<PeriodCloseGroupResult> {\n const { period } = group;\n const base = {\n payerTenantId: group.payerTenantId,\n currency: group.currency,\n periodStart: period.periodStart,\n periodEnd: period.periodEnd,\n closeId: group.closeId,\n };\n let leaseToken: string | null = null;\n try {\n const existing = await runtime.closes.get(group.closeId);\n const account = await runtime.getAccount(group.payerTenantId);\n if (!account?.id) {\n throw new Error(\n `No billing account for payer ${group.payerTenantId}; create one with upsertAccount().`,\n );\n }\n const close =\n existing ?? (await createClose(runtime, period, group, account, now));\n if (close.status === 'completed' || close.status === 'carried_forward') {\n return resultOf(base, close, close.status);\n }\n leaseToken = await runtime.acquireCloseLease(String(close.id), now);\n if (!leaseToken) return { ...base, outcome: 'busy' };\n const fresh = (await runtime.closes.get(String(close.id))) ?? close;\n const outcome = await advance(runtime, fresh, group.candidates, account);\n return resultOf(base, fresh, outcome);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n await recordFailure(runtime, group.closeId, message);\n return { ...base, outcome: 'failed', error: message };\n } finally {\n if (leaseToken) {\n await runtime.releaseCloseLease(group.closeId, leaseToken);\n }\n }\n}\n\nfunction resultOf(\n base: Omit<PeriodCloseGroupResult, 'outcome'>,\n close: BillingPeriodClose,\n outcome: PeriodCloseOutcome,\n): PeriodCloseGroupResult {\n return {\n ...base,\n outcome,\n invoiceId: close.invoiceId || undefined,\n providerInvoiceId: close.providerInvoiceId || undefined,\n };\n}\n\nasync function recordFailure(\n runtime: BillingRuntime,\n closeId: string,\n message: string,\n): Promise<void> {\n try {\n const close = await runtime.closes.get(closeId);\n if (!close) return;\n close.attempts = (Number(close.attempts) || 0) + 1;\n close.lastError = message.slice(0, 2000);\n await close.save();\n } catch {\n // The failure is reported in the result either way.\n }\n}\n\nasync function createClose(\n runtime: BillingRuntime,\n period: BillingPeriod,\n group: CandidateGroup,\n account: BillingAccount,\n now: Date,\n): Promise<BillingPeriodClose> {\n try {\n return await runtime.closes.create({\n id: group.closeId,\n sellerTenantId: runtime.sellerTenantId,\n kind: runtime.kind,\n payerTenantId: group.payerTenantId,\n currency: group.currency,\n periodStart: period.periodStart,\n periodEnd: period.periodEnd,\n billingAccountId: String(account.id),\n status: 'collecting',\n issuedAt: now,\n _insertOnly: true,\n });\n } catch (error) {\n const concurrent = await runtime.closes.get(group.closeId);\n if (concurrent) return concurrent;\n throw error;\n }\n}\n\nasync function advance(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n candidates: Candidate[],\n account: BillingAccount,\n): Promise<PeriodCloseOutcome> {\n if (close.status === 'collecting') {\n const collected = await collectAndInvoice(\n runtime,\n close,\n candidates,\n account,\n );\n if (collected === 'empty') {\n close.status = 'completed';\n await close.save();\n return 'empty';\n }\n if (collected === 'carried_forward') return 'carried_forward';\n }\n const invoice = await runtime.getInvoice(close.invoiceId);\n if (close.status === 'invoiced') {\n await pushToProvider(runtime, close, invoice, account);\n }\n if (close.status === 'pushed') {\n await sendAndReconcile(runtime, close, invoice, account);\n }\n if (close.status === 'sent') {\n await postRevenue(runtime, invoice);\n close.status = 'completed';\n close.lastError = '';\n await close.save();\n }\n return 'completed';\n}\n\n/** Claim sources, then build the invoice from everything this close owns. */\nasync function collectAndInvoice(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n candidates: Candidate[],\n account: BillingAccount,\n): Promise<'empty' | 'carried_forward' | 'invoiced'> {\n const closeId = String(close.id);\n for (const candidate of candidates) {\n if (candidate.flat) {\n await claimFlatWindow(\n runtime,\n closeId,\n candidate,\n candidate.flat,\n account,\n );\n continue;\n }\n const id = await sourceId(candidate.sourceType, candidate.sourceId);\n const discount = candidate.discountable\n ? flatDiscount(candidate.amount, account)\n : 0;\n try {\n await runtime.sources.create({\n id,\n sellerTenantId: runtime.sellerTenantId,\n periodCloseId: closeId,\n sourceType: candidate.sourceType,\n sourceId: candidate.sourceId,\n lineKey: candidate.lineKey,\n lineDescription: candidate.lineDescription,\n amount: candidate.amount,\n discount,\n quantity: candidate.quantity,\n currency: candidate.currency,\n periodStart: candidate.periodStart,\n periodEnd: candidate.periodEnd,\n _insertOnly: true,\n });\n } catch (error) {\n // Already claimed — by this close on an earlier attempt, or by another\n // close that won the race. Only a missing row is a real failure.\n if (!(await runtime.sources.get(id))) throw error;\n }\n }\n\n const sources = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.sources.list({\n where: { periodCloseId: closeId },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n if (sources.length === 0) return 'empty';\n // Credits this close absorbed are consumed at their original close.\n for (const source of sources) {\n if (source.sourceType !== 'credit_carry_forward') continue;\n const carried = await runtime.closes.get(source.sourceId);\n if (carried?.status === 'carried_forward') {\n carried.status = 'completed';\n await carried.save();\n }\n }\n const lines = groupLines(sources);\n const subtotal = lines.reduce(\n (sum, line) => sum + line.amount - line.discount,\n 0,\n );\n if (subtotal < 0) {\n // A net credit is not invoiced. The claims stay with this close, so a\n // source's later eligibility cannot change the balance, and the credit\n // itself becomes a source the payer's next close bills.\n close.subtotal = subtotal;\n close.lineCount = lines.length;\n close.status = 'carried_forward';\n await close.save();\n return 'carried_forward';\n }\n\n const invoiceId = await deterministicId(['billing-invoice', closeId]);\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n let invoice = await runtime.invoices.get(invoiceId);\n if (!invoice) {\n const issueDate = close.issuedAt;\n const dueDate = new Date(\n issueDate.getTime() + Number(account.paymentTermsDays) * 86_400_000,\n );\n try {\n invoice = await runtime.invoices.create({\n id: invoiceId,\n tenantId: runtime.sellerTenantId,\n customerId: account.customerId,\n invoiceNumber: invoiceNumber(runtime, close, closeId),\n reference: `billing-period:${close.periodStart.toISOString()}/${close.periodEnd.toISOString()}`,\n issueDate,\n dueDate,\n currency: close.currency,\n subtotal: 0,\n taxAmount: 0,\n totalAmount: 0,\n _insertOnly: true,\n });\n } catch (error) {\n invoice = await runtime.invoices.get(invoiceId);\n if (!invoice) throw error;\n }\n }\n if (invoice.status !== InvoiceStatus.DRAFT) {\n throw new Error(`Invoice ${invoice.invoiceNumber} is no longer a draft.`);\n }\n for (const [index, line] of lines.entries()) {\n await runtime.lineItems.create({\n id: await deterministicId(['billing-invoice-line', closeId, line.key]),\n tenantId: runtime.sellerTenantId,\n invoiceId,\n description: line.description,\n quantity: 1,\n unitPrice: line.amount,\n discount: line.discount,\n taxRate: 0.0,\n amount: line.amount - line.discount,\n sourceType: 'billing_period_close',\n sourceId: closeId,\n periodStart: line.periodStart ?? close.periodStart,\n periodEnd: line.periodEnd ?? close.periodEnd,\n sortOrder: index,\n });\n }\n await invoice.save();\n });\n\n close.invoiceId = invoiceId;\n close.subtotal = subtotal;\n close.lineCount = lines.length;\n close.status = 'invoiced';\n await close.save();\n return 'invoiced';\n}\n\ninterface InvoiceLineGroup {\n key: string;\n description: string;\n amount: number;\n discount: number;\n periodStart: Date | null;\n periodEnd: Date | null;\n}\n\nfunction groupLines(sources: BillingLineSource[]): InvoiceLineGroup[] {\n const lines = new Map<string, InvoiceLineGroup & { quantity: number }>();\n for (const source of sources) {\n // Each numbered flat claim (#3116) is its own line with its own service\n // period, keyed by the claim alone so a resumed close rebuilds the same\n // line ids whatever other claims it gained; pre-#3116 claims (number 0)\n // keep their original key.\n const key =\n source.sourceType === 'subscription_period' &&\n Number(source.chainSequence) > 0\n ? `${source.lineKey}|${source.periodStart?.toISOString() ?? ''}`\n : source.lineKey;\n let line = lines.get(key);\n if (!line) {\n line = {\n key,\n description: source.lineDescription,\n amount: 0,\n discount: 0,\n quantity: 0,\n periodStart: source.periodStart,\n periodEnd: source.periodEnd,\n };\n lines.set(key, line);\n }\n line.amount += Number(source.amount);\n line.discount += Number(source.discount);\n line.quantity += Number(source.quantity) || 0;\n }\n return [...lines.values()]\n .sort((a, b) => a.key.localeCompare(b.key))\n .map((line) => ({\n key: line.key,\n // Usage lines describe the summed quantity, not the first event's.\n description:\n line.key.startsWith('usage|') || line.key.startsWith('retail|')\n ? line.description.replace(\n / — [\\d.e+-]+ units$/,\n ` — ${Number(line.quantity.toFixed(6))} units`,\n )\n : line.description,\n amount: line.amount,\n discount: line.discount,\n periodStart: line.periodStart,\n periodEnd: line.periodEnd,\n }));\n}\n\nfunction invoiceNumber(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n closeId: string,\n): string {\n const month = close.periodStart.toISOString().slice(0, 7).replace('-', '');\n return `${runtime.invoiceNumberPrefix}-${month}-${closeId.slice(0, 8).toUpperCase()}`;\n}\n\nasync function pushToProvider(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n invoice: Invoice,\n account: BillingAccount,\n): Promise<void> {\n const synced = await runtime.ensureProviderCustomer(account);\n const lineItems = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n runtime.lineItems.findByInvoice(String(invoice.id)),\n );\n const lines: BillingProviderInvoiceLine[] = [];\n const taxCode =\n synced.automaticTax && runtime.taxCodes.invoice\n ? { taxCode: runtime.taxCodes.invoice }\n : {};\n for (const item of [...lineItems].sort((a, b) => a.sortOrder - b.sortOrder)) {\n const gross = item.quantity * item.unitPrice;\n lines.push({\n description: item.description,\n amount: gross,\n periodStart: item.periodStart ?? undefined,\n periodEnd: item.periodEnd ?? undefined,\n ...taxCode,\n });\n if (item.discount > 0) {\n lines.push({\n description: `Discount: ${item.description}`,\n amount: -item.discount,\n periodStart: item.periodStart ?? undefined,\n periodEnd: item.periodEnd ?? undefined,\n ...taxCode,\n });\n }\n }\n // A payer with its default card on file is charged automatically (#3139),\n // decided once and recorded before the first push (#3190): a retry after a\n // push whose response was lost must ask for, and report, the same method\n // even if a card was saved since.\n let collectionMethod = invoice.collectionMethod;\n if (!collectionMethod) {\n collectionMethod =\n runtime.autoChargeInvoices &&\n (await defaultCardFor(runtime, account, synced.providerCustomerId))\n ? 'charge_automatically'\n : 'send_invoice';\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n invoice.collectionMethod = collectionMethod;\n await invoice.save();\n });\n }\n const { providerInvoiceId } = await runtime.provider.pushInvoice({\n invoiceId: String(invoice.id),\n invoiceNumber: invoice.invoiceNumber,\n providerCustomerId: synced.providerCustomerId,\n currency: close.currency,\n issueDate: invoice.issueDate,\n dueDate: invoice.dueDate,\n lines,\n subtotal: invoice.subtotal,\n idempotencyKey: `smrt-billing-close:${close.id}`,\n automaticTax: synced.automaticTax,\n collectionMethod,\n memo: invoice.customerNotes || undefined,\n });\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n invoice.externalId = providerInvoiceId;\n invoice.customerExternalId = synced.providerCustomerId;\n invoice.externalProvider = runtime.provider.name;\n invoice.syncedAt = new Date();\n await invoice.save();\n });\n close.providerInvoiceId = providerInvoiceId;\n close.status = 'pushed';\n await close.save();\n}\n\nasync function sendAndReconcile(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n invoice: Invoice,\n account: BillingAccount,\n): Promise<void> {\n const providerInvoiceId = close.providerInvoiceId;\n let state = await runtime.provider.getInvoice(providerInvoiceId);\n if (state.status === 'draft') {\n // Re-sync the tax location: a send refused for a missing or invalid\n // address succeeds once the account is corrected.\n await runtime.ensureProviderCustomer(account);\n await runtime.provider.sendInvoice(providerInvoiceId);\n state = await runtime.provider.getInvoice(providerInvoiceId);\n }\n if (state.status === 'draft' || state.status === 'void') {\n throw new Error(\n `Provider invoice ${providerInvoiceId} is ${state.status}; expected it to be sent.`,\n );\n }\n if (\n state.currency !== close.currency ||\n state.subtotal !== invoice.subtotal ||\n state.total !== state.subtotal + state.taxAmount\n ) {\n throw new Error(\n `Provider invoice ${providerInvoiceId} does not match invoice ${invoice.invoiceNumber}: ` +\n `provider ${state.subtotal}+${state.taxAmount}=${state.total} ${state.currency}, ` +\n `local subtotal ${invoice.subtotal} ${close.currency}.`,\n );\n }\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n invoice.providerTaxAmount = state.taxAmount;\n if (invoice.status === InvoiceStatus.DRAFT) invoice.markSent();\n await invoice.save();\n });\n if (invoice.totalAmount !== state.total) {\n throw new Error(\n `Invoice ${invoice.invoiceNumber} total ${invoice.totalAmount} does not match provider total ${state.total}.`,\n );\n }\n close.status = 'sent';\n await close.save();\n}\n\n/**\n * Recognize revenue once. A journal posted by an attempt that died before\n * linking it is adopted instead of posting a second one.\n */\nasync function postRevenue(\n runtime: BillingRuntime,\n invoice: Invoice,\n): Promise<void> {\n if (invoice.totalAmount <= 0 || invoice.arJournalId) return;\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const posted = await runtime.journals.list({\n where: {\n sourceModule: 'smrt-commerce',\n sourceRef: String(invoice.id),\n status: 'posted',\n },\n limit: 1,\n });\n if (posted[0]?.id) {\n invoice.arJournalId = String(posted[0].id);\n await invoice.save();\n return;\n }\n await invoice.recognizeRevenue({\n arAccountId: runtime.ledger.arAccountId,\n revenueAccountId: runtime.ledger.revenueAccountId,\n taxAccountId: runtime.ledger.taxAccountId,\n });\n });\n}\n","/**\n * InvoiceLineItem model - individual items on an invoice\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { minorToMajorUnits } from '../billing/units.js';\nimport type {\n AccountingLineItemInput,\n InvoiceLineItemOptions,\n} from '../types/index.js';\n\n/**\n * InvoiceLineItem represents a single line item on an invoice.\n *\n * Line items contain details of what is being billed, including\n * quantity, pricing, and optional source tracking (e.g., from ad campaigns).\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * invoiceId: invoice.id,\n * description: 'Display Advertising - Summer Campaign',\n * quantity: 50000, // impressions\n * unitPrice: 1, // 1 cent per impression (minor units)\n * taxRate: 0.05,\n * sourceType: 'campaign',\n * sourceId: 'campaign-uuid',\n * periodStart: new Date('2025-06-01'),\n * periodEnd: new Date('2025-06-30')\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class InvoiceLineItem extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global invoice line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent invoice\n */\n @foreignKey('Invoice')\n invoiceId: string = '';\n\n /**\n * Item description\n */\n description: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /**\n * Quantity (e.g., impressions, hours, units).\n *\n * Left INTEGER: this package's own example is `quantity: 50000` impressions,\n * and nothing in it writes a fractional quantity, so whether fractional\n * quantities are intended here is unresolved. `ContractLineItem.quantity` is\n * decimal, which is the inconsistency to settle deliberately rather than by\n * changing a column type in passing (#2361).\n */\n quantity: number = 1;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis).\n *\n * Money is exact, so it is stored as minor units and never as a float —\n * `$19.99` is `1999`. An integer literal is what maps this to an INTEGER\n * column (#2361).\n */\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2361).\n */\n discount: number = 0;\n\n /**\n * Tax rate as a decimal fraction (e.g., 0.05 for 5%).\n *\n * A genuine rate, not money: it is inherently fractional, so the `0.0`\n * initializer is load-bearing and maps it to a DECIMAL column. INTEGER would\n * truncate every rate to 0 (#2361).\n */\n taxRate: number = 0.0;\n\n /**\n * Calculated line amount, in integer minor units (#2361).\n */\n amount: number = 0;\n\n // ============================================================================\n // Source Tracking\n // ============================================================================\n\n /**\n * Type of source ('campaign' | 'contract' | 'manual' | etc.)\n */\n sourceType: string = '';\n\n /**\n * ID of the source (e.g., campaign ID, contract ID)\n */\n sourceId: string = '';\n\n /**\n * Service period start (for time-based billing)\n */\n periodStart: Date | null = null;\n\n /**\n * Service period end (for time-based billing)\n */\n periodEnd: Date | null = null;\n\n // ============================================================================\n // Accounting\n // ============================================================================\n\n /**\n * Revenue account ID (cross-package ref to smrt-ledgers)\n * Used for revenue recognition to specific accounts\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Account')\n revenueAccountId: string = '';\n\n /**\n * Sort order within the invoice\n */\n sortOrder: number = 0;\n\n constructor(options: InvoiceLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.invoiceId !== undefined) this.invoiceId = options.invoiceId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.sourceType !== undefined) this.sourceType = options.sourceType;\n if (options.sourceId !== undefined) this.sourceId = options.sourceId;\n if (options.periodStart !== undefined)\n this.periodStart = options.periodStart;\n if (options.periodEnd !== undefined) this.periodEnd = options.periodEnd;\n if (options.revenueAccountId !== undefined)\n this.revenueAccountId = options.revenueAccountId;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n /**\n * Calculate the line amount, in integer minor units.\n *\n * Formula: `getSubtotal() + getTaxAmount()`, i.e.\n * `(quantity * unitPrice - discount) * (1 + taxRate)` with the tax rounded.\n *\n * Tax is calculated on the discounted subtotal. This follows the common\n * \"discount before tax\" approach used in most North American jurisdictions.\n * For jurisdictions requiring different tax calculation methods, override\n * this method or calculate amounts externally.\n */\n calculateAmount(): number {\n return this.getSubtotal() + this.getTaxAmount();\n }\n\n /**\n * Get subtotal (before tax), in integer minor units.\n *\n * `quantity` is an integer and `unitPrice` / `discount` are integer minor\n * units, so this is exact with no rounding.\n */\n getSubtotal(): number {\n return this.quantity * this.unitPrice - this.discount;\n }\n\n /**\n * Get tax amount, in integer minor units.\n *\n * `taxRate` is a genuine fraction, so this product is where a rate meets\n * money and the only place rounding is needed. Rounding here — rather than\n * letting a fractional tax leak into `Invoice.taxAmount` — is what lets the\n * invoice's guards compare integers exactly instead of tolerating an epsilon\n * (#2401).\n */\n getTaxAmount(): number {\n return Math.round(this.getSubtotal() * this.taxRate);\n }\n\n /**\n * Check if line item has source tracking\n */\n hasSource(): boolean {\n return !!this.sourceType && !!this.sourceId;\n }\n\n /**\n * Check if line item has a service period\n */\n hasPeriod(): boolean {\n return !!this.periodStart && !!this.periodEnd;\n }\n\n /**\n * Convert to line item format for SDK accounting provider.\n *\n * `@happyvertical/accounting` takes currency **major** units. Pass the\n * invoice currency to convert (as {@link Invoice.toAccountingInput} does);\n * without it, money stays in this package's integer minor units.\n */\n toAccountingLineItem(currency?: string): AccountingLineItemInput {\n const money = (value: number) =>\n currency ? minorToMajorUnits(value, currency) : value;\n return {\n description: this.description,\n sku: this.sku || undefined,\n quantity: this.quantity,\n unitPrice: money(this.unitPrice),\n discount: this.discount ? money(this.discount) : undefined,\n taxRate: this.taxRate || undefined,\n amount: money(this.amount),\n periodStart: this.periodStart || undefined,\n periodEnd: this.periodEnd || undefined,\n };\n }\n}\n\nexport default InvoiceLineItem;\n","/**\n * InvoiceLineItemCollection - Collection manager for InvoiceLineItem objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { InvoiceLineItem } from '../models/InvoiceLineItem.js';\nimport type { AccountingLineItemInput } from '../types/index.js';\n\nexport class InvoiceLineItemCollection extends SmrtCollection<InvoiceLineItem> {\n static readonly _itemClass = InvoiceLineItem;\n\n /**\n * Find line items by invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Array of line items, sorted by sortOrder\n */\n async findByInvoice(invoiceId: string): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { invoiceId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n /**\n * Find line items by source\n *\n * @param sourceType - Source type (e.g., 'campaign', 'contract')\n * @param sourceId - Source ID\n * @returns Array of line items\n */\n async findBySource(\n sourceType: string,\n sourceId: string,\n ): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { sourceType, sourceId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find line items by source type only\n *\n * @param sourceType - Source type (e.g., 'campaign')\n * @returns Array of line items\n */\n async findBySourceType(sourceType: string): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { sourceType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Calculate total amount for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Total amount\n */\n async getTotalForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.amount, 0);\n }\n\n /**\n * Calculate subtotal (before tax) for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Subtotal\n */\n async getSubtotalForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.getSubtotal(), 0);\n }\n\n /**\n * Calculate tax amount for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Tax amount\n */\n async getTaxForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.getTaxAmount(), 0);\n }\n\n /**\n * Get the next sort order for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Next sort order number\n */\n async getNextSortOrder(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n if (lineItems.length === 0) return 0;\n\n const maxSortOrder = Math.max(...lineItems.map((item) => item.sortOrder));\n return maxSortOrder + 1;\n }\n\n /**\n * Recalculate all line item amounts for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Updated line items\n */\n async recalculateAmounts(invoiceId: string): Promise<InvoiceLineItem[]> {\n const lineItems = await this.findByInvoice(invoiceId);\n\n for (const item of lineItems) {\n item.amount = item.calculateAmount();\n }\n\n await Promise.all(lineItems.map((item) => item.save()));\n\n return lineItems;\n }\n\n /**\n * Convert line items to accounting format for SDK sync\n *\n * @param invoiceId - Invoice ID\n * @returns Array of line items in accounting format, money in integer\n * minor units (`Invoice.toAccountingInput()` converts to major units)\n */\n async toAccountingLineItems(\n invoiceId: string,\n ): Promise<AccountingLineItemInput[]> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.map((item) => item.toAccountingLineItem());\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all invoice line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of invoice line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<InvoiceLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global invoice line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global invoice line items\n */\n async findGlobal(): Promise<InvoiceLineItem[]> {\n return queryGlobal<InvoiceLineItem>(this);\n }\n\n /**\n * Find invoice line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global invoice line items\n */\n async findWithGlobals(tenantId: string): Promise<InvoiceLineItem[]> {\n return queryWithGlobals<InvoiceLineItem>(\n this,\n tenantId,\n 'InvoiceLineItem.findWithGlobals',\n );\n }\n}\n","/**\n * Provider event processing (#3060): verified webhook deliveries queued in\n * smrt-jobs' durable delivery inbox are applied here.\n *\n * `observe()` re-reads the invoice or subscription from the provider, so an\n * event is applied against current provider state rather than the snapshot it\n * carried; out-of-order and duplicate deliveries therefore converge. The\n * application writes, the inbox completion, and the host standing hook share\n * one transaction.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport {\n type ForgeDelivery,\n type ForgeObservation,\n type ForgeProjectionContext,\n ForgeProjectionRuntime,\n type ForgeProjector,\n} from '@happyvertical/smrt-jobs';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport {\n SpendingPolicyEvaluator,\n type SubscriptionStatus,\n TenantSubscriptionCollection,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { InvoiceCollection } from '../collections/InvoiceCollection.js';\nimport { PaymentAllocationCollection } from '../collections/PaymentAllocationCollection.js';\nimport { PaymentCollection } from '../collections/PaymentCollection.js';\nimport {\n type BillingAccount,\n BillingAccountCollection,\n BillingLineSourceCollection,\n BillingPaymentAttemptCollection,\n BillingPeriodCloseCollection,\n type BillingStanding,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus, PaymentMethod, PaymentStatus } from '../types/index.js';\nimport {\n CARD_SETUP_PURPOSE,\n COLLECT_ADDRESS_METADATA,\n recordSavedCard,\n SAVE_CARD_METADATA,\n type SavedCard,\n savedCardFromCheckout,\n} from './cards.js';\nimport { CREDIT_PURCHASE_PURPOSE, creditMetadata } from './credits.js';\nimport {\n type AttemptTarget,\n applyPaymentAttempt,\n attemptTarget,\n decideAttempt,\n} from './payment-attempts.js';\nimport type {\n BillingPaymentAttemptState,\n BillingProvider,\n BillingProviderCheckoutState,\n BillingProviderEvent,\n BillingProviderInvoiceState,\n BillingProviderSubscriptionState,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport { applyAutoTopUpOutcome, autoTopUpPaymentId } from './top-up.js';\nimport { deterministicId, normalizeCurrency, tenantKey } from './units.js';\n\ntype InvoiceEvent = Extract<BillingProviderEvent, { kind: 'invoice' }>;\ntype SubscriptionEvent = Extract<\n BillingProviderEvent,\n { kind: 'subscription' }\n>;\ntype CheckoutEvent = Extract<\n BillingProviderEvent,\n { kind: 'checkout_completed' }\n>;\ntype PaymentEvent = Extract<BillingProviderEvent, { kind: 'payment' }>;\ntype AttemptEvent = Extract<BillingProviderEvent, { kind: 'payment_attempt' }>;\n\ntype ObservedEvent =\n | { event: InvoiceEvent; invoice: BillingProviderInvoiceState }\n | {\n event: SubscriptionEvent;\n subscription: BillingProviderSubscriptionState;\n }\n | {\n event: CheckoutEvent;\n /** The session re-read from the provider, when it can be. */\n checkout: BillingProviderCheckoutState | null;\n /** A card the session saved, already the provider default. */\n card: SavedCard | null;\n }\n | { event: PaymentEvent; paymentId: string }\n | {\n event: AttemptEvent;\n providerName: string;\n attempt: BillingPaymentAttemptState;\n target: AttemptTarget;\n };\n\nconst PROJECTION = 'billing-provider-events';\n\n/** Apply up to `limit` queued events for a runtime; returns how many ran. */\nexport async function processBillingEvents(\n runtime: BillingRuntime,\n limit: number,\n): Promise<number> {\n if (!Number.isSafeInteger(limit) || limit <= 0) {\n throw new Error('limit must be a positive integer.');\n }\n const processor = new ForgeProjectionRuntime({\n db: runtime.db,\n workerId: `billing-${crypto.randomUUID()}`,\n providers: runtime.eventProviders,\n leaseMs: 60_000,\n });\n const projector = createBillingEventProjector(runtime);\n let processed = 0;\n while (processed < limit) {\n const delivery = await processor.processNext(projector);\n if (!delivery) break;\n processed += 1;\n }\n return processed;\n}\n\nexport function createBillingEventProjector(\n runtime: BillingRuntime,\n): ForgeProjector<ObservedEvent> {\n return {\n async observe(\n delivery: ForgeDelivery,\n ): Promise<ForgeObservation<ObservedEvent> | null> {\n // The claim is filtered to this seller's namespace; anything else is\n // released for retry rather than acknowledged as if it were applied.\n const provider = runtime.providerForNamespace(delivery.provider);\n if (\n !provider ||\n tenantKey(delivery.tenantId) !== runtime.sellerTenantId\n ) {\n throw new Error(\n `Billing delivery ${delivery.deliveryId} belongs to another seller.`,\n );\n }\n const event = readEvent(delivery);\n let value: ObservedEvent;\n if (event.kind === 'invoice') {\n value = {\n event,\n invoice: await provider.getInvoice(event.providerInvoiceId),\n };\n } else if (event.kind === 'subscription') {\n value = {\n event,\n subscription: await provider.getSubscription(\n event.providerSubscriptionId,\n ),\n };\n } else if (event.kind === 'checkout_completed') {\n if (provider !== runtime.provider) {\n // Checkouts and saved-card charges belong to the issuing provider;\n // a payment rail reports its checkouts as payment attempts.\n throw new Error(\n `Billing delivery ${delivery.deliveryId}: ${provider.name} does not report checkouts.`,\n );\n }\n value = await observeCheckout(runtime, event);\n } else if (event.kind === 'payment') {\n if (provider !== runtime.provider) {\n // Saved-card charges belong to the issuing provider.\n throw new Error(\n `Billing delivery ${delivery.deliveryId}: ${provider.name} does not charge saved cards.`,\n );\n }\n // Only this seller's auto top-up attempts; another runtime's charge\n // on a shared provider account is not ours to apply.\n const paymentId = autoTopUpPaymentId(event.chargeKey);\n if (!paymentId || !(await isOwnTopUp(runtime, paymentId))) {\n return null;\n }\n value = { event, paymentId };\n } else if (event.kind === 'payment_attempt') {\n const observed = await observeAttempt(runtime, provider, event);\n if (!observed) return null;\n value = observed;\n } else {\n return null;\n }\n // Each event is its own subject: provider state is re-read above, so\n // ordering is resolved by the state, not by a version counter.\n return {\n projection: PROJECTION,\n subjectKey: event.eventId,\n version: 1,\n value,\n };\n },\n\n async project(\n observation: ForgeObservation<ObservedEvent>,\n context: ForgeProjectionContext,\n ): Promise<void> {\n const value = observation.value;\n if ('invoice' in value) {\n await applyInvoiceEvent(\n runtime,\n context.db,\n value.event,\n value.invoice,\n );\n } else if ('subscription' in value) {\n await applySubscriptionState(runtime, context.db, value.subscription);\n } else if ('attempt' in value) {\n await applyAttempt(runtime, context.db, value);\n } else if ('paymentId' in value) {\n await applyAutoTopUpOutcome(runtime, context.db, value.paymentId, {\n status: value.event.status,\n providerPaymentId: value.event.providerPaymentId,\n providerCustomerId: value.event.providerCustomerId,\n amount: value.event.amount,\n currency: value.event.currency,\n subtotal: value.event.subtotal,\n tax: value.event.tax,\n failureCode: value.event.failureCode,\n });\n } else {\n if (value.card) await recordSavedCard(runtime, context.db, value.card);\n await applyCheckout(runtime, context.db, value.event, value.checkout);\n }\n },\n };\n}\n\n/**\n * Re-read a completed checkout (Stripe's event amounts are not ISO minor\n * units) and, when it saved a card for one of this seller's accounts, make it\n * the provider customer's default. That provider write is idempotent, so a\n * retried event repeats it harmlessly.\n */\nasync function observeCheckout(\n runtime: BillingRuntime,\n event: CheckoutEvent,\n): Promise<Extract<ObservedEvent, { checkout: unknown }>> {\n const getCheckout = runtime.provider.getCheckout;\n const checkout = getCheckout\n ? await getCheckout.call(runtime.provider, event.sessionId)\n : null;\n if (checkout && checkout.sessionId !== event.sessionId) {\n throw new Error(\n `Checkout ${event.sessionId} re-read as ${checkout.sessionId}.`,\n );\n }\n const savesCard =\n (event.mode === 'setup' &&\n event.metadata.smrt_purpose === CARD_SETUP_PURPOSE) ||\n event.metadata[SAVE_CARD_METADATA] === '1';\n const collectsAddress = event.metadata[COLLECT_ADDRESS_METADATA] === '1';\n let card: SavedCard | null = null;\n if ((savesCard || collectsAddress) && !checkout) {\n // Never acknowledge a saved card or address the provider cannot report.\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot re-read checkout ${event.sessionId} to apply what it saved.`,\n );\n }\n if (checkout && (savesCard || collectsAddress)) {\n card = await savedCardFromCheckout(\n runtime,\n event.metadata,\n checkout,\n savesCard,\n );\n if (card?.paymentMethodId) {\n const setDefault = runtime.provider.setDefaultPaymentMethod;\n if (!setDefault) {\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot set a default payment method.`,\n );\n }\n await setDefault.call(\n runtime.provider,\n card.providerCustomerId,\n card.paymentMethodId,\n );\n }\n }\n return { event, checkout, card };\n}\n\nasync function isOwnTopUp(\n runtime: BillingRuntime,\n paymentId: string,\n): Promise<boolean> {\n const payments = await PaymentCollection.create({ db: runtime.db });\n const payment = await withSystemContext(() => payments.get(paymentId));\n return Boolean(\n payment &&\n tenantKey(payment.tenantId) === runtime.sellerTenantId &&\n payment.externalProvider === runtime.provider.name,\n );\n}\n\nfunction readEvent(delivery: ForgeDelivery): BillingProviderEvent {\n const event = (delivery.payload as { event?: BillingProviderEvent }).event;\n if (!event || typeof event !== 'object' || !event.kind || !event.eventId) {\n throw new Error(`Billing delivery ${delivery.deliveryId} has no event.`);\n }\n return event;\n}\n\n// ---------------------------------------------------------------------------\n// Invoices\n// ---------------------------------------------------------------------------\n\nasync function applyInvoiceEvent(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n event: InvoiceEvent,\n state: BillingProviderInvoiceState,\n): Promise<void> {\n const invoices = await InvoiceCollection.create({ db });\n const invoice = await invoices.findByExternalId(state.providerInvoiceId);\n if (!invoice?.id || invoice.externalProvider !== runtime.provider.name) {\n return; // Not an invoice this runtime issued.\n }\n const closes = await BillingPeriodCloseCollection.create({ db });\n const [close] = await closes.list({\n where: { invoiceId: invoice.id, sellerTenantId: runtime.sellerTenantId },\n limit: 1,\n });\n if (!close?.id) return;\n const accounts = await BillingAccountCollection.create({ db });\n const account = await accounts.get(close.billingAccountId);\n if (!account) {\n throw new Error(`Billing account ${close.billingAccountId} is missing.`);\n }\n\n let standing: BillingStanding | null = null;\n if (state.status === 'paid') {\n // A skipped event (the rail records this payment itself) leaves the\n // standing to the rail's settlement.\n if (await settlePaidInvoice(runtime, db, invoice, state)) {\n standing = 'current';\n }\n } else if (state.status === 'void') {\n if (\n invoice.status !== InvoiceStatus.PAID &&\n invoice.status !== InvoiceStatus.CANCELLED &&\n invoice.status !== InvoiceStatus.WRITTEN_OFF\n ) {\n invoice.cancel();\n await invoice.save();\n }\n } else if (state.status === 'uncollectible') {\n // Written off at the provider (#3139): written off here too, so it no\n // longer counts as overdue against the payer. It can still be paid (the\n // `paid` branch records the payment and reinstates the payer). Only the\n // first observation changes standing: a late event about an invoice\n // already written off must not undo a later reinstatement. A DRAFT is an\n // invoice whose close parked after the send reached the provider; the\n // close still completes from WRITTEN_OFF once its error is resolved.\n if (\n invoice.status === InvoiceStatus.DRAFT ||\n invoice.status === InvoiceStatus.SENT ||\n invoice.status === InvoiceStatus.VIEWED ||\n invoice.status === InvoiceStatus.PARTIAL ||\n invoice.status === InvoiceStatus.OVERDUE\n ) {\n invoice.status = InvoiceStatus.WRITTEN_OFF;\n await invoice.save();\n standing = 'uncollectible';\n }\n } else if (state.status === 'open') {\n if (event.type === 'payment_failed' || event.type === 'overdue') {\n standing = 'past_due';\n if (\n event.type === 'overdue' &&\n (invoice.status === InvoiceStatus.SENT ||\n invoice.status === InvoiceStatus.VIEWED)\n ) {\n invoice.status = InvoiceStatus.OVERDUE;\n await invoice.save();\n }\n } else if (event.type === 'uncollectible') {\n // A provider that reports written-off invoices as open.\n standing = 'uncollectible';\n }\n }\n if (\n standing &&\n standing !== 'current' &&\n runtime.hasPaymentRails &&\n (await pauseForConfirmingAttempts(\n runtime,\n db,\n String(invoice.id),\n standing,\n ))\n ) {\n // A rail payment for this invoice is confirming (#3138): dunning pauses.\n // If the payment ends without settling, it re-applies this standing.\n standing = null;\n }\n if (standing) {\n await applyStanding(\n runtime,\n db,\n account,\n invoice,\n String(close.id),\n standing,\n );\n }\n}\n\nasync function settlePaidInvoice(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoice: Invoice,\n state: BillingProviderInvoiceState,\n): Promise<boolean> {\n if (invoice.status === InvoiceStatus.PAID) return true;\n if (\n state.paidOutOfBand &&\n runtime.hasPaymentRails &&\n (await railClosedOutOfBand(runtime, db, String(invoice.id)))\n ) {\n // This rail closed the invoice at the issuer (#3138) and records the\n // payment itself in its own event; the issuer collected nothing.\n return false;\n }\n if (invoice.status === InvoiceStatus.DRAFT) {\n // Period close has not recorded the send yet; retry after it has.\n throw new Error(\n `Invoice ${invoice.invoiceNumber} is still a draft; the paid event will be retried.`,\n );\n }\n if (\n state.currency !== normalizeCurrency(invoice.currency) ||\n state.total !== invoice.totalAmount ||\n state.amountPaid !== state.total\n ) {\n throw new Error(\n `Provider invoice ${state.providerInvoiceId} paid ${state.amountPaid} of ${state.total} ${state.currency}; ` +\n `local invoice ${invoice.invoiceNumber} totals ${invoice.totalAmount} ${invoice.currency}.`,\n );\n }\n const allocations = await PaymentAllocationCollection.create({ db });\n if (state.amountPaid > 0) {\n const payments = await PaymentCollection.create({ db });\n const paymentId = await deterministicId([\n 'billing-invoice-payment',\n runtime.provider.name,\n state.providerInvoiceId,\n ]);\n const payment =\n (await payments.get(paymentId)) ??\n (await payments.create({\n id: paymentId,\n tenantId: runtime.sellerTenantId,\n customerId: invoice.customerId,\n amount: state.amountPaid,\n currency: invoice.currency,\n // Closed out of band by someone else (wire, cheque): not a card.\n method: state.paidOutOfBand\n ? PaymentMethod.OTHER\n : PaymentMethod.CREDIT_CARD,\n reference: invoice.invoiceNumber,\n externalId: state.providerInvoiceId,\n externalProvider: runtime.provider.name,\n _insertOnly: true,\n }));\n if (payment.status !== PaymentStatus.COMPLETED) {\n await payment.recordPayment({\n ledgerId: '',\n cashAccountId: runtime.ledger.cashAccountId,\n receivablesAccountId: runtime.ledger.arAccountId,\n });\n }\n const allocationId = await deterministicId([\n 'billing-invoice-allocation',\n paymentId,\n String(invoice.id),\n ]);\n if (!(await allocations.get(allocationId))) {\n await allocations.create({\n id: allocationId,\n tenantId: runtime.sellerTenantId,\n paymentId,\n invoiceId: String(invoice.id),\n amount: state.amountPaid,\n allocatedBy: 'billing-period-close',\n _insertOnly: true,\n });\n }\n }\n // A written-off invoice paid later keeps its terminal status; the payment\n // and allocation above record the recovery.\n if (invoice.status === InvoiceStatus.WRITTEN_OFF) return true;\n invoice.updatePaymentStatus(\n await allocations.getTotalAllocatedToInvoice(String(invoice.id)),\n );\n await invoice.save();\n return true;\n}\n\nconst DELINQUENT_TARGET: Record<\n BillingStanding,\n (status: SubscriptionStatus) => SubscriptionStatus | null\n> = {\n current: (status) =>\n status === 'past_due' || status === 'unpaid' ? 'active' : null,\n past_due: (status) => (status === 'active' ? 'past_due' : null),\n uncollectible: (status) =>\n status === 'active' || status === 'past_due' ? 'unpaid' : null,\n};\n\nasync function applyStanding(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n account: BillingAccount,\n invoice: Invoice,\n closeId: string,\n standing: BillingStanding,\n): Promise<void> {\n if (standing === 'current') {\n // Another unpaid, overdue invoice keeps the payer past due.\n const invoices = await InvoiceCollection.create({ db });\n const overdue = await invoices.list({\n where: { customerId: invoice.customerId, status: InvoiceStatus.OVERDUE },\n });\n if (overdue.some((row) => row.id !== invoice.id)) standing = 'past_due';\n }\n\n // Flat-plan subscriptions billed on this invoice follow the payer's standing.\n const sources = await BillingLineSourceCollection.create({ db });\n const rows = await sources.list({\n where: { periodCloseId: closeId, sourceType: 'subscription_period' },\n });\n const subscriptionIds = [\n ...new Set(rows.map((row) => row.sourceId.split(':')[0])),\n ].filter(Boolean);\n if (subscriptionIds.length > 0) {\n await withSystemContext(async () => {\n const subscriptions = await TenantSubscriptionCollection.create({ db });\n for (const subscription of await subscriptions.list({\n where: { id: subscriptionIds },\n })) {\n const target = DELINQUENT_TARGET[standing](subscription.status);\n if (target) {\n subscription.status = target;\n await subscription.save();\n }\n }\n });\n }\n\n const previous = account.standing;\n if (previous === standing) return;\n account.standing = standing;\n await account.save();\n await runtime.onPayerStanding?.({\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId: account.payerTenantId,\n billingAccountId: String(account.id),\n invoiceId: String(invoice.id),\n previous,\n standing,\n db,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Provider-managed subscriptions\n// ---------------------------------------------------------------------------\n\nasync function applySubscriptionState(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n state: BillingProviderSubscriptionState,\n): Promise<void> {\n await withSystemContext(async () => {\n const subscriptions = await TenantSubscriptionCollection.create({ db });\n const rows = await subscriptions.list({\n where: {\n stripeSubscriptionId: state.providerSubscriptionId,\n externalProvider: runtime.provider.name,\n },\n });\n for (const subscription of rows) {\n subscription.status = state.status;\n subscription.cancelAtPeriodEnd = state.cancelAtPeriodEnd;\n if (state.currentPeriodStart)\n subscription.currentPeriodStart = state.currentPeriodStart;\n if (state.currentPeriodEnd)\n subscription.currentPeriodEnd = state.currentPeriodEnd;\n subscription.canceledAt = state.canceledAt ?? null;\n subscription.trialEndsAt = state.trialEndsAt ?? null;\n await subscription.save();\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Prepaid credit purchases\n// ---------------------------------------------------------------------------\n\nasync function applyCheckout(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n event: CheckoutEvent,\n checkout: BillingProviderCheckoutState | null,\n): Promise<void> {\n const metadata = creditMetadata(event.metadata);\n if (!metadata || metadata.purpose !== CREDIT_PURCHASE_PURPOSE) return;\n if (metadata.sellerTenantId !== runtime.sellerTenantId) return;\n // The re-read session is authoritative; the event's own amounts are used\n // only for a provider that cannot re-read (and reports minor units).\n const paid = checkout ? checkout.complete && checkout.paid : event.paid;\n if (!paid) return; // An async payment settles on a later event.\n const currency = normalizeCurrency(\n (checkout ? checkout.currency : event.currency) ?? '',\n );\n const subtotal = checkout ? checkout.amountSubtotal : event.amountSubtotal;\n const tax = (checkout ? checkout.amountTax : event.amountTax) ?? 0;\n const total = checkout ? checkout.amountTotal : event.amountTotal;\n if (\n currency !== metadata.currency ||\n subtotal !== metadata.amount ||\n !Number.isSafeInteger(tax) ||\n tax < 0 ||\n // Credit is granted only for money collected in full: the total is the\n // credit plus provider tax, with no discount.\n total !== metadata.amount + tax\n ) {\n throw new Error(\n `Checkout ${event.sessionId} collected ${total} (${subtotal} + ${tax} tax) ${currency}; ` +\n `expected ${metadata.amount} ${metadata.currency} plus tax.`,\n );\n }\n\n // The credit lands on the payer-owned (or child-owned) balance policy.\n await withSystemContext(async () => {\n const evaluator = await SpendingPolicyEvaluator.create({ db });\n await evaluator.grantCredit({\n spendingPolicyId: metadata.spendingPolicyId,\n amount: metadata.amount,\n reason: 'Prepaid credit purchase',\n source: `${runtime.provider.name}-checkout`,\n sourceId: event.sessionId,\n ...(metadata.grantedByTenantId\n ? { grantedByTenantId: metadata.grantedByTenantId }\n : {}),\n });\n });\n\n // The cash is recorded in the seller's books against prepaid credit, and\n // any tax collected is moved to tax payable.\n const accounts = await BillingAccountCollection.create({ db });\n const account = await accounts.get(metadata.billingAccountId);\n if (\n !account ||\n tenantKey(account.sellerTenantId) !== runtime.sellerTenantId ||\n tenantKey(account.payerTenantId) !== metadata.payerTenantId\n ) {\n throw new Error(`Checkout ${event.sessionId} has no matching account.`);\n }\n const payments = await PaymentCollection.create({ db });\n const paymentId = await deterministicId([\n 'billing-credit-payment',\n runtime.provider.name,\n event.sessionId,\n ]);\n const payment =\n (await payments.get(paymentId)) ??\n (await payments.create({\n id: paymentId,\n tenantId: runtime.sellerTenantId,\n customerId: account.customerId,\n amount: total,\n currency: metadata.currency,\n method: PaymentMethod.CREDIT_CARD,\n reference: `credit:${metadata.spendingPolicyId}`,\n externalId: event.sessionId,\n externalProvider: runtime.provider.name,\n _insertOnly: true,\n }));\n if (payment.status !== PaymentStatus.COMPLETED) {\n // The payment row, its journal, and the tax reclass commit together, so\n // a replay that finds the payment completed has nothing left to post.\n await payment.recordPayment({\n ledgerId: '',\n cashAccountId: runtime.ledger.cashAccountId,\n receivablesAccountId: runtime.ledger.prepaidCreditAccountId,\n });\n if (tax > 0) {\n const journals = await JournalCollection.create({ db });\n const journal = await journals.create({\n date: new Date(),\n description: `Tax collected on prepaid credit ${event.sessionId}`,\n sourceModule: 'smrt-commerce',\n sourceRef: `${paymentId}:tax`,\n });\n await journal.save();\n await journal.addEntry({\n accountId: runtime.ledger.prepaidCreditAccountId,\n debit: tax,\n memo: `Tax on credit ${metadata.spendingPolicyId}`,\n });\n await journal.addEntry({\n accountId: runtime.ledger.taxAccountId,\n credit: tax,\n memo: `Tax on credit ${metadata.spendingPolicyId}`,\n });\n await journal.post();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Payment-rail attempts (#3138)\n// ---------------------------------------------------------------------------\n\nasync function observeAttempt(\n runtime: BillingRuntime,\n provider: BillingProvider,\n event: AttemptEvent,\n): Promise<ObservedEvent | null> {\n if (!provider.getPaymentAttempt) {\n throw new Error(`Provider ${provider.name} has no payment attempts.`);\n }\n const attempt = await provider.getPaymentAttempt(event.checkoutId);\n if (!attempt) return null; // Not a checkout this rail created.\n const target = attemptTarget(runtime, attempt.metadata);\n if (!target) return null; // Unsigned, foreign, or another seller's.\n const decision = decideAttempt(attempt, runtime.paymentPolicy, target);\n if (decision.action === 'settle' && target.purpose === 'invoice_payment') {\n // Close the issuer's invoice before recording the payment, so it stops\n // dunning. Idempotent, and retried with the event on failure.\n const invoice = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () =>\n (await InvoiceCollection.create({ db: runtime.db })).get(\n target.invoiceId,\n ),\n );\n if (\n invoice?.id &&\n invoice.status !== InvoiceStatus.PAID &&\n invoice.externalProvider === runtime.provider.name &&\n invoice.externalId\n ) {\n if (!runtime.provider.markInvoicePaidOutOfBand) {\n throw new Error(\n `${runtime.provider.name} cannot close invoice ${invoice.invoiceNumber} paid on ${provider.name}.`,\n );\n }\n // The issuer may have collected the invoice itself (a card charge\n // whose event is not applied yet): then it must not be closed out of\n // band, and the rail money becomes customer credit.\n const issuer = await runtime.provider.getInvoice(invoice.externalId);\n if (issuer.status === 'paid' && !issuer.paidOutOfBand) {\n return {\n event,\n providerName: provider.name,\n attempt,\n target: { ...target, issuerCollected: true },\n };\n }\n // Committed before the issuer is told, so the issuer's own `paid`\n // event waits for this settlement instead of recording a payment.\n const row = await runtime.recordPaymentAttemptStart({\n orderId: attempt.orderId,\n provider: provider.name,\n checkoutId: attempt.checkoutId,\n checkoutUrl: attempt.checkoutUrl ?? '',\n purpose: target.purpose,\n payerTenantId: target.payerTenantId,\n billingAccountId: target.billingAccountId,\n invoiceId: target.invoiceId,\n amount: target.amount,\n currency: target.currency,\n });\n if (row.flag === 'out_of_band_without_settlement' || row.resolvedAt) {\n // Closing again after an operator reopened the invoice: this rail\n // owns the close once more, so the old resolution no longer applies.\n await runtime.db.query(\n `UPDATE ${runtime.attempts.tableName}\n SET flag = '', resolved_at = NULL\n WHERE id = ? AND flag = 'out_of_band_without_settlement'`,\n String(row.id),\n );\n }\n if (!row.outOfBandRequestedAt) {\n // Column-scoped, so it cannot overwrite a concurrent writer's fields.\n await runtime.db.query(\n `UPDATE ${runtime.attempts.tableName}\n SET out_of_band_requested_at = ?\n WHERE id = ? AND out_of_band_requested_at IS NULL`,\n new Date().toISOString(),\n String(row.id),\n );\n }\n await runtime.provider.markInvoicePaidOutOfBand(invoice.externalId);\n }\n }\n return { event, providerName: provider.name, attempt, target };\n}\n\nasync function applyAttempt(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n value: Extract<ObservedEvent, { attempt: BillingPaymentAttemptState }>,\n): Promise<void> {\n const outcome = await applyPaymentAttempt(\n runtime,\n db,\n value.providerName,\n value.attempt,\n value.target,\n value.event.eventId.startsWith('poll:') ? 'poll' : 'webhook',\n );\n if (value.target.purpose !== 'invoice_payment') return;\n if (!outcome.settled && !outcome.endedUnsettled) return;\n const invoices = await InvoiceCollection.create({ db });\n const invoice = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n invoices.get(value.target.invoiceId),\n );\n if (!invoice?.id) return;\n const closes = await BillingPeriodCloseCollection.create({ db });\n const [close] = await closes.list({\n where: { invoiceId: invoice.id, sellerTenantId: runtime.sellerTenantId },\n limit: 1,\n });\n if (!close?.id) return;\n const accounts = await BillingAccountCollection.create({ db });\n const account = await accounts.get(close.billingAccountId);\n if (!account) return;\n if (outcome.settled && (await invoiceSettled(runtime, db, invoice))) {\n await applyStanding(\n runtime,\n db,\n account,\n invoice,\n String(close.id),\n 'current',\n );\n } else if (outcome.endedUnsettled && invoice.status !== InvoiceStatus.PAID) {\n // The dunning paused while this payment confirmed; resume it with the\n // standing the issuer asked for meanwhile, or from the invoice's state.\n const paused = outcome.attempt.pausedStanding;\n const resumed: BillingStanding | null =\n paused === 'past_due' || paused === 'uncollectible'\n ? paused\n : invoice.status === InvoiceStatus.OVERDUE\n ? 'past_due'\n : null;\n if (resumed) {\n await applyStanding(\n runtime,\n db,\n account,\n invoice,\n String(close.id),\n resumed,\n );\n }\n }\n}\n\n/**\n * Paid in full: `PAID`, or a written-off invoice (which keeps its status)\n * whose payments now cover it (#3188).\n */\nasync function invoiceSettled(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoice: Invoice,\n): Promise<boolean> {\n if (invoice.status === InvoiceStatus.PAID) return true;\n if (invoice.status !== InvoiceStatus.WRITTEN_OFF) return false;\n const allocated = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () =>\n (\n await PaymentAllocationCollection.create({ db })\n ).getTotalAllocatedToInvoice(String(invoice.id)),\n );\n return allocated >= invoice.totalAmount;\n}\n\nconst STANDING_SEVERITY: Record<string, number> = {\n '': 0,\n past_due: 1,\n uncollectible: 2,\n};\n\n/**\n * Record a paused standing on the invoice's confirming rail attempts.\n * Returns whether any attempt is confirming (and so dunning pauses).\n */\nasync function pauseForConfirmingAttempts(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoiceId: string,\n standing: BillingStanding,\n): Promise<boolean> {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n // Lock before reading, so a concurrent writer's fields are not lost.\n await db.query(\n `UPDATE ${attempts.tableName} SET updated_at = updated_at\n WHERE seller_tenant_id = ? AND invoice_id = ? AND status = 'confirming'`,\n runtime.sellerTenantId,\n invoiceId,\n );\n const rows = await attempts.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n invoiceId,\n status: 'confirming',\n },\n });\n for (const row of rows) {\n if (\n (STANDING_SEVERITY[standing] ?? 0) >\n (STANDING_SEVERITY[row.pausedStanding] ?? 0)\n ) {\n row.pausedStanding = standing;\n await row.save();\n }\n }\n return rows.length > 0;\n}\n\n/** A rail of this runtime asked the issuer to close this invoice. */\nasync function railClosedOutOfBand(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoiceId: string,\n): Promise<boolean> {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n const rows = await attempts.list({\n where: { sellerTenantId: runtime.sellerTenantId, invoiceId },\n });\n // An operator who reopened the invoice at the issuer resolves the flagged\n // attempt; a later out-of-band close is then someone else's and recorded.\n return rows.some(\n (row) =>\n Boolean(row.outOfBandRequestedAt) &&\n !(row.flag === 'out_of_band_without_settlement' && row.resolvedAt),\n );\n}\n","/**\n * One seller's billing wiring (#3060): its payment provider, ledger accounts,\n * billing-owner reader, and the collections period close and provider events\n * write through.\n */\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { ForgeDeliveryCollection } from '@happyvertical/smrt-jobs';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport {\n type AutoTopUpHook,\n BillingAdjustmentCollection,\n type BillingRelationshipReader,\n ClientChargeCollection,\n PriceBookCollection,\n RetailChargeCollection,\n SpendingPolicyCollection,\n SubscriptionPlanCollection,\n TenantSubscriptionCollection,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { CustomerCollection } from '../collections/CustomerCollection.js';\nimport { InvoiceCollection } from '../collections/InvoiceCollection.js';\nimport { InvoiceLineItemCollection } from '../collections/InvoiceLineItemCollection.js';\nimport { PaymentInstrumentCollection } from '../collections/PaymentInstrumentCollection.js';\nimport {\n type BillingAccount,\n BillingAccountCollection,\n type BillingCloseKind,\n BillingLineSourceCollection,\n type BillingPaymentAttempt,\n BillingPaymentAttemptCollection,\n type BillingPaymentAttemptPurpose,\n BillingPeriodCloseCollection,\n type BillingStanding,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { type Address, InvoiceStatus } from '../types/index.js';\nimport {\n type BillingCardOnFile,\n type CreateCardSetupCheckoutInput,\n cardOnFile,\n createCardSetupCheckout,\n} from './cards.js';\nimport {\n type CreateCreditCheckoutInput,\n createCreditCheckout,\n} from './credits.js';\nimport {\n billingPeriodContaining,\n type ScheduledBillingPeriod,\n} from './cycles.js';\nimport { processBillingEvents } from './events.js';\nimport {\n type CreateInvoicePaymentInput,\n createInvoicePayment,\n} from './invoice-payments.js';\nimport {\n type BillingPaymentPolicy,\n type CryptoConversionInput,\n type ManualRefundInput,\n type ManualRefundResult,\n normalizePaymentPolicy,\n recordCryptoConversion,\n recordManualRefund,\n} from './payment-attempts.js';\nimport {\n type ClosePeriodInput,\n closeBillingPeriod,\n type PeriodCloseResult,\n} from './period-close.js';\nimport {\n type BillingProvider,\n type BillingProviderCheckoutSession,\n providerCapabilities,\n} from './provider.js';\nimport {\n type AutoTopUpFailureHook,\n type AutoTopUpHookOptions,\n createAutoTopUpHook,\n} from './top-up.js';\nimport { canonicalTenantId, deterministicId, tenantKey } from './units.js';\n\n/** Ledger accounts billing posts to, in the seller's books. */\nexport interface BillingLedgerAccounts {\n /** Accounts receivable (debited when an invoice is issued). */\n arAccountId: string;\n /** Revenue (credited with the invoice subtotal). */\n revenueAccountId: string;\n /** Tax payable (credited with provider-calculated tax). */\n taxAccountId: string;\n /** Cash or clearing account payments settle into. */\n cashAccountId: string;\n /** Liability credited when prepaid credit is purchased. */\n prepaidCreditAccountId: string;\n /**\n * Asset debited when a crypto rail payment settles and the seller holds\n * the crypto (#3138). Defaults to `cashAccountId`.\n */\n cryptoHoldingsAccountId?: string;\n /** Realized gain or loss when held crypto is converted (#3138). */\n fxGainLossAccountId?: string;\n /** Conversion and network fees (#3138). */\n feesAccountId?: string;\n}\n\n/** A payer's standing changed after a provider invoice event. */\nexport interface PayerStandingChange {\n sellerTenantId: string;\n payerTenantId: string;\n billingAccountId: string;\n invoiceId: string;\n previous: BillingStanding;\n standing: BillingStanding;\n /**\n * The event transaction's database handle. Host writes made through it\n * (for example suspending the tenant) commit or roll back with the event.\n */\n db: DatabaseInterface;\n}\n\n/**\n * Host hook for suspension or reinstatement. It runs inside the event's\n * transaction and may run again if the event is retried, so it must be\n * idempotent.\n */\nexport type PayerStandingHook = (\n change: PayerStandingChange,\n) => void | Promise<void>;\n\nexport interface BillingRuntimeOptions extends SmrtClassOptions {\n /** The tenant that issues invoices. */\n sellerTenantId: string;\n kind: BillingCloseKind;\n /** The issuing provider: it issues, taxes, and sends invoices. */\n provider: BillingProvider;\n /**\n * Additional payment rails (#3138), for example a crypto checkout\n * provider. A payer picks a rail per payment; each rail has its own inbox\n * namespace and webhook route. Names must be unique across all providers.\n */\n paymentProviders?: BillingProvider[];\n /** Per-seller rules for rail payments (#3138). */\n paymentPolicy?: Partial<BillingPaymentPolicy>;\n /** smrt-tenancy's `BillingRelationshipService` (read access required). */\n billingRelationships: BillingRelationshipReader;\n ledger: BillingLedgerAccounts;\n /** Invoice number prefix (default `INV`). */\n invoiceNumberPrefix?: string;\n onPayerStanding?: PayerStandingHook;\n /** How long one worker may hold a period close (default 10 minutes). */\n leaseMs?: number;\n /** Page size for charge scans (default 500). */\n pageSize?: number;\n /**\n * Bill payers whose default card with this provider is on file (saved by\n * {@link BillingRuntime.createCardSetupCheckout} or a credit purchase with\n * `savePaymentMethod`) with automatically charged invoices (#3139): the\n * provider charges the card after the invoice is sent, and the `paid` or\n * `payment_failed` event settles it. Default false (every invoice is sent\n * for payment). Activate service on the `paid` event (a `current`\n * standing), not on send: collection happens on the provider's schedule.\n */\n autoChargeInvoices?: boolean;\n /**\n * Product tax codes for provider-calculated tax (Stripe Tax `txcd_...`,\n * #3194): `credit` classifies prepaid credit (checkout purchases and\n * automatic top-ups), `invoice` the lines of billing-period invoices.\n * Omitted: the provider account's default product tax code.\n */\n taxCodes?: BillingTaxCodes;\n /**\n * Called when an automatic top-up charge ends without collecting (a\n * decline, or the issuer requiring the payer to authenticate), from the\n * hook or the provider's `payment` event (#3139).\n */\n onAutoTopUpFailed?: AutoTopUpFailureHook;\n}\n\n/** Product tax codes (#3194). */\nexport interface BillingTaxCodes {\n credit?: string;\n invoice?: string;\n}\n\nexport interface UpsertBillingAccountInput {\n payerTenantId: string;\n name: string;\n email?: string;\n /** The payer's tax location; required when `automaticTax` is on. */\n billingAddress?: Address;\n taxExempt?: boolean;\n taxId?: string;\n automaticTax?: boolean;\n flatDiscountBasisPoints?: number;\n paymentTermsDays?: number;\n /** Link an existing provider customer (for example from checkout). */\n providerCustomerId?: string;\n /**\n * Billing-cycle anchor (#3116): periods run monthly from this instant\n * (anchor at signup/activation for no partial first period). `null` returns\n * the payer to UTC calendar months; omitted leaves it unchanged. Changing it\n * never bills time twice.\n */\n billingAnchorAt?: Date | null;\n /** Prorate flat plans to the time they were active in a period (#3116). */\n prorateFlatPlans?: boolean;\n}\n\n/** A synced account: its provider customer id is always present. */\nexport interface SyncedBillingAccount {\n account: BillingAccount;\n providerCustomerId: string;\n automaticTax: boolean;\n /** The customer has a billing address country (its tax location). */\n hasTaxLocation: boolean;\n}\n\nexport interface EnsureProviderCustomerOptions {\n /**\n * Refuse a taxed account without a tax location (default true). Card setup\n * turns it off because the address is collected at checkout.\n */\n requireTaxLocation?: boolean;\n}\n\nconst DEFAULT_LEASE_MS = 10 * 60 * 1000;\n\n/**\n * Billing for one seller. Construct once per process with\n * {@link BillingRuntime.create}; register it with `registerBillingRuntime()`\n * to drive it from smrt-jobs.\n *\n * Period close and event processing are privileged system operations: they\n * read every payer's charges and write in a system context. Call them only\n * from trusted host code (a job, an operator action), never from a\n * tenant-facing route.\n */\nexport class BillingRuntime {\n readonly sellerTenantId: string;\n readonly kind: BillingCloseKind;\n readonly provider: BillingProvider;\n readonly billingRelationships: BillingRelationshipReader;\n readonly ledger: BillingLedgerAccounts;\n readonly invoiceNumberPrefix: string;\n readonly onPayerStanding?: PayerStandingHook;\n readonly leaseMs: number;\n readonly pageSize: number;\n readonly autoChargeInvoices: boolean;\n readonly taxCodes: Readonly<BillingTaxCodes>;\n readonly onAutoTopUpFailed?: AutoTopUpFailureHook;\n /** The inbox provider namespace for the issuing provider's events. */\n readonly eventProvider: string;\n /** Per-seller rules for rail payments (#3138). */\n readonly paymentPolicy: BillingPaymentPolicy;\n private readonly providersByName = new Map<string, BillingProvider>();\n readonly attempts: BillingPaymentAttemptCollection;\n\n private constructor(\n options: BillingRuntimeOptions,\n readonly db: DatabaseInterface,\n readonly accounts: BillingAccountCollection,\n readonly closes: BillingPeriodCloseCollection,\n readonly sources: BillingLineSourceCollection,\n readonly customers: CustomerCollection,\n readonly invoices: InvoiceCollection,\n readonly lineItems: InvoiceLineItemCollection,\n readonly journals: JournalCollection,\n readonly charges: ClientChargeCollection,\n readonly adjustments: BillingAdjustmentCollection,\n readonly retailCharges: RetailChargeCollection,\n readonly books: PriceBookCollection,\n readonly subscriptions: TenantSubscriptionCollection,\n readonly plans: SubscriptionPlanCollection,\n readonly policies: SpendingPolicyCollection,\n readonly instruments: PaymentInstrumentCollection,\n attempts: BillingPaymentAttemptCollection,\n ) {\n this.attempts = attempts;\n this.sellerTenantId = canonicalTenantId(\n options.sellerTenantId,\n 'sellerTenantId',\n );\n if (options.kind !== 'provider' && options.kind !== 'reseller') {\n throw new Error('Billing runtime kind must be provider or reseller.');\n }\n this.kind = options.kind;\n this.provider = options.provider;\n this.billingRelationships = options.billingRelationships;\n this.ledger = options.ledger;\n this.invoiceNumberPrefix = options.invoiceNumberPrefix || 'INV';\n this.onPayerStanding = options.onPayerStanding;\n this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;\n this.pageSize = options.pageSize ?? 500;\n this.autoChargeInvoices = options.autoChargeInvoices ?? false;\n this.taxCodes = Object.freeze({ ...options.taxCodes });\n for (const [key, code] of Object.entries(this.taxCodes)) {\n if (code !== undefined && (typeof code !== 'string' || !code.trim())) {\n throw new Error(`taxCodes.${key} must be a non-empty string.`);\n }\n }\n this.onAutoTopUpFailed = options.onAutoTopUpFailed;\n // One inbox namespace per seller: the delivery claim is cross-tenant, so\n // a runtime must never be able to claim another seller's events.\n this.eventProvider = `${options.provider.name}-billing:${this.sellerTenantId}`;\n if (!providerCapabilities(options.provider).issuesInvoices) {\n throw new Error(\n `Provider ${options.provider.name} does not issue invoices; pass it in paymentProviders.`,\n );\n }\n for (const candidate of [\n options.provider,\n ...(options.paymentProviders ?? []),\n ]) {\n if (!candidate?.name || this.providersByName.has(candidate.name)) {\n throw new Error(\n `Billing provider names must be unique and non-empty (${candidate?.name ?? 'missing'}).`,\n );\n }\n this.providersByName.set(candidate.name, candidate);\n }\n this.paymentPolicy = normalizePaymentPolicy(options.paymentPolicy);\n if (!Number.isFinite(this.leaseMs) || this.leaseMs <= 0) {\n throw new Error('leaseMs must be a positive number.');\n }\n if (!Number.isSafeInteger(this.pageSize) || this.pageSize <= 0) {\n throw new Error('pageSize must be a positive integer.');\n }\n const optionalLedger = new Set([\n 'cryptoHoldingsAccountId',\n 'fxGainLossAccountId',\n 'feesAccountId',\n ]);\n for (const [key, value] of Object.entries(options.ledger ?? {})) {\n if (!value && !(optionalLedger.has(key) && value === undefined)) {\n throw new Error(`Ledger account ${key} is required.`);\n }\n }\n }\n\n static async create(options: BillingRuntimeOptions): Promise<BillingRuntime> {\n const {\n sellerTenantId: _seller,\n kind: _kind,\n provider: _provider,\n billingRelationships: _relationships,\n ledger: _ledger,\n invoiceNumberPrefix: _prefix,\n onPayerStanding: _hook,\n paymentProviders: _rails,\n paymentPolicy: _paymentPolicy,\n leaseMs: _leaseMs,\n pageSize: _pageSize,\n autoChargeInvoices: _autoCharge,\n taxCodes: _taxCodes,\n onAutoTopUpFailed: _topUpFailed,\n ...classOptions\n } = options;\n const accounts = await BillingAccountCollection.create(classOptions);\n const shared = { ...classOptions, db: accounts.db };\n return new BillingRuntime(\n options,\n accounts.db,\n accounts,\n await BillingPeriodCloseCollection.create(shared),\n await BillingLineSourceCollection.create(shared),\n await CustomerCollection.create(shared),\n await InvoiceCollection.create(shared),\n await InvoiceLineItemCollection.create(shared),\n await JournalCollection.create(shared),\n await ClientChargeCollection.create(shared),\n await BillingAdjustmentCollection.create(shared),\n await RetailChargeCollection.create(shared),\n await PriceBookCollection.create(shared),\n await TenantSubscriptionCollection.create(shared),\n await SubscriptionPlanCollection.create(shared),\n await SpendingPolicyCollection.create(shared),\n await PaymentInstrumentCollection.create(shared),\n await BillingPaymentAttemptCollection.create(shared),\n );\n }\n\n // -------------------------------------------------------------------------\n // Providers and rails (#3138)\n // -------------------------------------------------------------------------\n\n /** A provider by name; the issuing provider when `name` is omitted. */\n providerFor(name?: string): BillingProvider {\n if (!name) return this.provider;\n const found = this.providersByName.get(name);\n if (!found) {\n throw new Error(`No billing provider named ${name} on this runtime.`);\n }\n return found;\n }\n\n /**\n * Whether any payment rail is configured (#3138). A runtime without one\n * never touches payment attempts, so it needs no attempt table.\n */\n get hasPaymentRails(): boolean {\n return this.providers.some(\n (provider) => providerCapabilities(provider).paymentAttempts,\n );\n }\n\n /** Every provider of this runtime: the issuing one first. */\n get providers(): BillingProvider[] {\n return [...this.providersByName.values()];\n }\n\n /** A provider's inbox namespace (per provider, per seller). */\n eventProviderFor(name?: string): string {\n return `${this.providerFor(name).name}-billing:${this.sellerTenantId}`;\n }\n\n /** Every inbox namespace this runtime claims events from. */\n get eventProviders(): string[] {\n return this.providers.map((provider) =>\n this.eventProviderFor(provider.name),\n );\n }\n\n /** The provider whose inbox namespace this is, or null. */\n providerForNamespace(namespace: string): BillingProvider | null {\n return (\n this.providers.find(\n (provider) => this.eventProviderFor(provider.name) === namespace,\n ) ?? null\n );\n }\n\n // -------------------------------------------------------------------------\n // Accounts\n // -------------------------------------------------------------------------\n\n async getAccount(payerTenantId: string): Promise<BillingAccount | null> {\n return this.accounts.get(await this.accountId(payerTenantId));\n }\n\n private accountId(payerTenantId: string): Promise<string> {\n return deterministicId([\n 'billing-account',\n this.sellerTenantId,\n canonicalTenantId(payerTenantId, 'payerTenantId'),\n ]);\n }\n\n /**\n * Create or update a payer's account and its commerce `Customer` (in the\n * seller's tenant). The customer's billing address is the payer's tax\n * location for provider-calculated tax.\n */\n async upsertAccount(\n input: UpsertBillingAccountInput,\n ): Promise<BillingAccount> {\n const payerTenantId = canonicalTenantId(\n input.payerTenantId,\n 'payerTenantId',\n );\n if (\n input.billingAnchorAt !== undefined &&\n input.billingAnchorAt !== null &&\n !(\n input.billingAnchorAt instanceof Date &&\n Number.isFinite(input.billingAnchorAt.getTime())\n )\n ) {\n throw new Error('billingAnchorAt must be a valid date or null.');\n }\n const id = await this.accountId(payerTenantId);\n const existing = await this.accounts.get(id);\n const customerId = await withTenant(\n { tenantId: this.sellerTenantId },\n async () => {\n const customer = existing?.customerId\n ? await this.customers.get(existing.customerId)\n : null;\n const values = {\n tenantId: this.sellerTenantId,\n ...(input.billingAddress !== undefined\n ? { defaultBillingAddress: input.billingAddress }\n : {}),\n ...(input.taxExempt !== undefined\n ? { taxExempt: input.taxExempt }\n : {}),\n ...(input.taxId !== undefined ? { taxId: input.taxId } : {}),\n };\n if (customer) {\n Object.assign(customer, values);\n await customer.save();\n return String(customer.id);\n }\n const created = await this.customers.create({\n id: await deterministicId(['billing-customer', id]),\n ...values,\n });\n return String(created.id);\n },\n );\n const account =\n existing ??\n (await this.accounts.create({\n id,\n sellerTenantId: this.sellerTenantId,\n payerTenantId,\n customerId,\n name: input.name,\n }));\n account.customerId = customerId;\n account.name = input.name;\n if (input.email !== undefined) account.email = input.email;\n if (input.automaticTax !== undefined)\n account.automaticTax = input.automaticTax;\n if (input.flatDiscountBasisPoints !== undefined)\n account.flatDiscountBasisPoints = input.flatDiscountBasisPoints;\n if (input.paymentTermsDays !== undefined)\n account.paymentTermsDays = input.paymentTermsDays;\n if (input.billingAnchorAt !== undefined)\n account.billingAnchorAt = input.billingAnchorAt;\n if (input.prorateFlatPlans !== undefined)\n account.prorateFlatPlans = input.prorateFlatPlans;\n if (input.providerCustomerId) {\n account.provider = this.provider.name;\n account.providerCustomerId = input.providerCustomerId;\n }\n await account.save();\n return account;\n }\n\n /**\n * Create or update the provider customer, including its tax location.\n * Creation is idempotent per account at the provider (sdk#1268): an\n * attempt that dies between creation and this save finds the same\n * customer on retry.\n */\n async ensureProviderCustomer(\n account: BillingAccount,\n options: EnsureProviderCustomerOptions = {},\n ): Promise<SyncedBillingAccount> {\n const customer = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.customers.get(account.customerId),\n );\n if (!customer) {\n throw new Error(`Billing account ${account.id} has no customer.`);\n }\n const address = customer.defaultBillingAddress ?? {};\n if (\n options.requireTaxLocation !== false &&\n account.automaticTax &&\n !customer.taxExempt &&\n !address.country\n ) {\n throw new Error(\n `Billing account ${account.id} has no tax location; set a billing address country.`,\n );\n }\n const providerCustomerId =\n account.provider === this.provider.name ? account.providerCustomerId : '';\n const synced = await this.provider.syncCustomer({\n accountId: String(account.id),\n providerCustomerId: providerCustomerId || undefined,\n name: account.name,\n email: account.email || undefined,\n billingAddress: address,\n taxExempt: customer.taxExempt,\n });\n if (\n account.provider !== this.provider.name ||\n account.providerCustomerId !== synced.providerCustomerId\n ) {\n account.provider = this.provider.name;\n account.providerCustomerId = synced.providerCustomerId;\n await account.save();\n }\n return {\n account,\n providerCustomerId: synced.providerCustomerId,\n automaticTax: account.automaticTax && !customer.taxExempt,\n hasTaxLocation: Boolean(address.country),\n };\n }\n\n /** Whether the account's customer has a tax location (address country). */\n async hasTaxLocation(account: BillingAccount): Promise<boolean> {\n const customer = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.customers.get(account.customerId),\n );\n return Boolean(customer?.defaultBillingAddress?.country);\n }\n\n /** Whether provider-calculated tax applies to this account's charges. */\n async accountIsTaxed(account: BillingAccount): Promise<boolean> {\n if (!account.automaticTax) return false;\n const customer = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.customers.get(account.customerId),\n );\n return !customer?.taxExempt;\n }\n\n // -------------------------------------------------------------------------\n // Period close\n // -------------------------------------------------------------------------\n\n /**\n * Close billing periods. Without a period, each payer's last ended period\n * on its own schedule (the previous UTC calendar month unless its account\n * has a `billingAnchorAt`), so running it daily is safe. See\n * {@link ClosePeriodInput}.\n */\n closePeriod(input: ClosePeriodInput = {}): Promise<PeriodCloseResult> {\n return closeBillingPeriod(this, input);\n }\n\n /**\n * The payer's billing period containing `at` (default now) on its account's\n * schedule — for example to show the next invoice date. A payer without an\n * account is on calendar months.\n */\n async billingPeriodFor(\n payerTenantId: string,\n at: Date = new Date(),\n ): Promise<ScheduledBillingPeriod> {\n const account = await this.getAccount(payerTenantId);\n return billingPeriodContaining(account?.billingAnchorAt ?? null, at);\n }\n\n async getInvoice(invoiceId: string): Promise<Invoice> {\n const invoice = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.invoices.get(invoiceId),\n );\n if (!invoice) throw new Error(`Invoice ${invoiceId} was not found.`);\n return invoice;\n }\n\n /** Take the close's lease; returns the token, or null if another holds it. */\n async acquireCloseLease(\n closeId: string,\n now = new Date(),\n ): Promise<string | null> {\n const token = crypto.randomUUID();\n const nowIso = now.toISOString();\n const expires = new Date(now.getTime() + this.leaseMs).toISOString();\n const result = await this.db.query(\n `UPDATE _smrt_billing_period_closes\n SET lease_token = ?, lease_expires_at = ?, updated_at = ?\n WHERE id = ?\n AND (lease_token IS NULL OR lease_expires_at IS NULL\n OR lease_expires_at < ?)\n RETURNING id`,\n token,\n expires,\n nowIso,\n closeId,\n nowIso,\n );\n return result.rows.length === 1 ? token : null;\n }\n\n async releaseCloseLease(closeId: string, token: string): Promise<void> {\n await this.db.query(\n `UPDATE _smrt_billing_period_closes\n SET lease_token = NULL, lease_expires_at = NULL\n WHERE id = ? AND lease_token = ?`,\n closeId,\n token,\n );\n }\n\n // -------------------------------------------------------------------------\n // Provider events\n // -------------------------------------------------------------------------\n\n /**\n * Verify and durably enqueue a provider webhook. Returns `accepted: false`\n * for a duplicate delivery or an event this package does not act on.\n * Respond 2xx whenever this resolves; a verification failure throws\n * `BillingWebhookVerificationError`. An ignored event's `type` names what\n * was ignored: alert on one ending in `:unverified_credit_purchase` (a\n * paid checkout that claims to be a credit purchase but failed\n * verification, for example after a webhook-secret rotation).\n */\n async acceptWebhook(\n payload: string,\n signature: string,\n options: {\n /** The rail whose webhook this is (default the issuing provider). */\n provider?: string;\n /** Request headers, for rails that sign with their own header. */\n headers?: Headers | Record<string, string | undefined>;\n } = {},\n ): Promise<{\n accepted: boolean;\n eventId: string;\n kind: string;\n type?: string;\n }> {\n const provider = this.providerFor(options.provider);\n const event = await provider.verifyWebhook(\n payload,\n signature,\n options.headers,\n );\n if (event.kind === 'ignored') {\n return {\n accepted: false,\n eventId: event.eventId,\n kind: event.kind,\n type: event.type,\n };\n }\n return withTenant({ tenantId: this.sellerTenantId }, async () => {\n const inbox = await ForgeDeliveryCollection.create({ db: this.db });\n const { accepted } = await inbox.accept({\n provider: this.eventProviderFor(provider.name),\n deliveryId: event.eventId,\n eventName: event.kind,\n payload: { event },\n });\n return { accepted, eventId: event.eventId, kind: event.kind };\n });\n }\n\n /** Apply up to `limit` queued provider events; returns how many ran. */\n processEvents(limit = 25): Promise<number> {\n return processBillingEvents(this, limit);\n }\n\n // -------------------------------------------------------------------------\n // Prepaid credit\n // -------------------------------------------------------------------------\n\n /**\n * Start a provider checkout that credits a prepaid balance policy when\n * paid. The caller pays: the policy's tenant for its own balance, or the\n * parent that set a delegated balance.\n */\n createCreditCheckout(\n input: CreateCreditCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n return createCreditCheckout(this, input);\n }\n\n // -------------------------------------------------------------------------\n // Card on file and automatic top-ups (#3139)\n // -------------------------------------------------------------------------\n\n /**\n * Start a provider checkout that saves the payer's card without charging\n * it. When the checkout completes, event processing makes the card the\n * provider customer's default, records it as the payer's default\n * `PaymentInstrument`, and adopts the billing address collected at\n * checkout as its tax location. Callable by the payer or in a system\n * context. Throws when the provider cannot save payment methods.\n */\n createCardSetupCheckout(\n input: CreateCardSetupCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n return createCardSetupCheckout(this, input);\n }\n\n /**\n * Whether the payer has a card on file (#3187): the default card that\n * `autoChargeInvoices` bills and automatic top-ups charge, decided by the\n * same check they use. Read-only; callable by the payer or in a system\n * context.\n */\n cardOnFile(payerTenantId: string): Promise<BillingCardOnFile> {\n return cardOnFile(this, payerTenantId);\n }\n\n /**\n * The `autoTopUp` hook for smrt-subscriptions' `SpendingPolicyEvaluator`:\n * charges the payer's saved card off-session and credits the balance once\n * the charge succeeds. Without a provider that can charge a saved card (or\n * without a card) it never tops up. Enable `payment_intent.*` events on\n * the provider webhook: charges that settle later are credited from them.\n */\n autoTopUpHook(options?: AutoTopUpHookOptions): AutoTopUpHook {\n return createAutoTopUpHook(this, options);\n }\n\n /**\n * Write off a sent, unpaid invoice at the provider (#3139). The provider's\n * `uncollectible` event then moves the payer to `uncollectible` standing.\n * Call only from trusted operator code. Throws when the provider cannot.\n */\n async markInvoiceUncollectible(invoiceId: string): Promise<void> {\n const markUncollectible = this.provider.markInvoiceUncollectible;\n if (!markUncollectible) {\n throw new Error(\n `Billing provider ${this.provider.name} cannot write off invoices.`,\n );\n }\n const invoice = await this.getInvoice(invoiceId);\n if (\n invoice.externalProvider !== this.provider.name ||\n !invoice.externalId ||\n (invoice.status !== InvoiceStatus.SENT &&\n invoice.status !== InvoiceStatus.VIEWED &&\n invoice.status !== InvoiceStatus.PARTIAL &&\n invoice.status !== InvoiceStatus.OVERDUE)\n ) {\n throw new Error(\n `Invoice ${invoice.invoiceNumber} is not a sent, unpaid ${this.provider.name} invoice.`,\n );\n }\n await markUncollectible.call(this.provider, invoice.externalId);\n }\n\n // -------------------------------------------------------------------------\n // Rail payments (#3138)\n // -------------------------------------------------------------------------\n\n /**\n * Pay an issued invoice on a payment rail: opens a rail checkout for the\n * amount due. Settlement records the payment and closes the issuing\n * provider's invoice as paid out of band. Callable by the payer or from a\n * system context.\n */\n createInvoicePayment(\n input: CreateInvoicePaymentInput,\n ): Promise<BillingProviderCheckoutSession> {\n return createInvoicePayment(this, input);\n }\n\n /** Record a rail checkout just opened (idempotent). */\n async recordPaymentAttemptStart(input: {\n /** The rail order id (the checkout idempotency key), when known. */\n orderId?: string;\n provider: string;\n checkoutId: string;\n checkoutUrl: string;\n purpose: BillingPaymentAttemptPurpose;\n payerTenantId: string;\n billingAccountId: string;\n invoiceId?: string;\n spendingPolicyId?: string;\n amount: number;\n currency: string;\n }): Promise<BillingPaymentAttempt> {\n const id = await this.paymentAttemptId(input.provider, input.checkoutId);\n const existing = await this.attempts.get(id);\n if (existing) return existing;\n try {\n return await this.attempts.create({\n id,\n sellerTenantId: this.sellerTenantId,\n payerTenantId: input.payerTenantId,\n billingAccountId: input.billingAccountId,\n purpose: input.purpose,\n invoiceId: input.invoiceId ?? '',\n spendingPolicyId: input.spendingPolicyId ?? '',\n provider: input.provider,\n orderId: input.orderId ?? '',\n checkoutId: input.checkoutId,\n checkoutUrl: input.checkoutUrl,\n amount: input.amount,\n currency: input.currency,\n status: 'open',\n exception: 'none',\n timeline: [\n {\n at: new Date().toISOString(),\n status: 'open',\n exception: 'none',\n source: 'created',\n },\n ],\n _insertOnly: true,\n });\n } catch (error) {\n const concurrent = await this.attempts.get(id);\n if (concurrent) return concurrent;\n throw error;\n }\n }\n\n /** The deterministic id of a rail checkout's attempt row. */\n paymentAttemptId(provider: string, checkoutId: string): Promise<string> {\n return deterministicId(['billing-payment-attempt', provider, checkoutId]);\n }\n\n /**\n * This seller's rail payment attempts, newest first — for \"payment\n * confirming\" displays and operator queues. Every filter is applied in the\n * query, before `limit` (default 100) and `offset`, so paging through\n * `{ unresolved: true }` lists every open exception however old (#3186).\n */\n async listPaymentAttempts(\n filter: {\n payerTenantId?: string;\n invoiceId?: string;\n status?: string;\n /** Only attempts with (`true`) or without (`false`) a flag. */\n flagged?: boolean;\n /**\n * Only open exceptions: flagged attempts no operator has resolved yet.\n * Combine with `flagged: true` or leave `flagged` unset.\n */\n unresolved?: true;\n limit?: number;\n /** Rows to skip, for paging (default 0). */\n offset?: number;\n } = {},\n ): Promise<BillingPaymentAttempt[]> {\n const where: Record<string, unknown> = {\n sellerTenantId: this.sellerTenantId,\n };\n if (filter.payerTenantId) {\n where.payerTenantId = canonicalTenantId(\n filter.payerTenantId,\n 'payerTenantId',\n );\n }\n if (filter.invoiceId) where.invoiceId = filter.invoiceId;\n if (filter.status) where.status = filter.status;\n if (filter.unresolved && filter.flagged === false) {\n throw new Error('An unresolved attempt is always flagged.');\n }\n if (filter.flagged === true || filter.unresolved) where['flag !='] = '';\n else if (filter.flagged === false) where.flag = '';\n if (filter.unresolved) where.resolvedAt = null;\n return this.attempts.list({\n where,\n // `id` breaks created_at ties, so pages neither repeat nor skip rows.\n orderBy: ['created_at DESC', 'id DESC'],\n limit: filter.limit ?? 100,\n offset: filter.offset ?? 0,\n });\n }\n\n /**\n * Queue a re-read of open and confirming attempts (the polling fallback\n * for missed webhooks). Returns how many were queued; apply them with\n * `processEvents()`.\n *\n * Confirming attempts (money seen) go first, then open ones, each oldest\n * first, so a burst of newer or abandoned checkouts cannot starve a paid\n * attempt whose settlement webhook was missed (#3183). Rows leave the set\n * once the gateway reports a terminal state.\n */\n async refreshPaymentAttempts(limit = 100): Promise<number> {\n const rows: BillingPaymentAttempt[] = [];\n for (const status of ['confirming', 'open']) {\n const remaining = limit - rows.length;\n if (remaining <= 0) break;\n rows.push(\n ...(await this.attempts.list({\n where: { sellerTenantId: this.sellerTenantId, status },\n orderBy: 'created_at ASC',\n limit: remaining,\n })),\n );\n }\n let queued = 0;\n for (const row of rows) {\n if (await this.refreshPaymentAttempt(row.provider, row.checkoutId)) {\n queued += 1;\n }\n }\n return queued;\n }\n\n /** Queue a re-read of one rail checkout; false if one is already queued. */\n async refreshPaymentAttempt(\n provider: string,\n checkoutId: string,\n ): Promise<boolean> {\n const rail = this.providerFor(provider);\n if (!providerCapabilities(rail).paymentAttempts) {\n throw new Error(`Provider ${rail.name} has no payment attempts.`);\n }\n // One poll per checkout per minute: a burst of refreshes queues once.\n const bucket = Math.floor(Date.now() / 60_000);\n return withTenant({ tenantId: this.sellerTenantId }, async () => {\n const inbox = await ForgeDeliveryCollection.create({ db: this.db });\n const { accepted } = await inbox.accept({\n provider: this.eventProviderFor(rail.name),\n deliveryId: `poll:${checkoutId}:${bucket}`,\n eventName: 'payment_attempt',\n payload: {\n event: {\n kind: 'payment_attempt',\n eventId: `poll:${checkoutId}:${bucket}`,\n checkoutId,\n },\n },\n });\n return accepted;\n });\n }\n\n /** Close an attempt's flag with an operator note (the flag is kept). */\n async resolvePaymentAttempt(\n attemptId: string,\n resolution: string,\n ): Promise<BillingPaymentAttempt> {\n const attempt = await this.attempts.get(attemptId);\n if (!attempt || tenantKey(attempt.sellerTenantId) !== this.sellerTenantId) {\n throw new Error(`Payment attempt ${attemptId} was not found.`);\n }\n if (!resolution.trim()) throw new Error('A resolution note is required.');\n // Column-scoped: never overwrites what an event or refund wrote.\n await this.db.query(\n `UPDATE ${this.attempts.tableName}\n SET resolution = ?, resolved_at = ?\n WHERE id = ?`,\n resolution.trim(),\n new Date().toISOString(),\n String(attempt.id),\n );\n return (await this.attempts.get(String(attempt.id))) ?? attempt;\n }\n\n /**\n * Record a refund an operator made outside billing (no provider call):\n * posts a reversing journal and, for a credit purchase, removes the\n * credit. Idempotent by `reference`.\n */\n recordManualRefund(input: ManualRefundInput): Promise<ManualRefundResult> {\n return recordManualRefund(this, input);\n }\n\n /**\n * Record converting held crypto to fiat: cash and fees against the\n * holdings' carrying value, with the difference to FX gain/loss.\n * Idempotent by `reference`.\n */\n recordCryptoConversion(\n input: CryptoConversionInput,\n ): Promise<{ journalId: string }> {\n return recordCryptoConversion(this, input);\n }\n\n /** Run in a system context (cross-tenant reads and payer-owned writes). */\n system<T>(operation: () => Promise<T>): Promise<T> {\n return withSystemContext(operation);\n }\n}\n","/**\n * Vendor model - links to Profile with vendor-specific data\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { type VendorOptions, VendorStatus } from '../types/index.js';\n\n/**\n * Vendor represents the vendor/supplier role for a Profile.\n *\n * One Profile can have both Customer and Vendor records - e.g., a company\n * you both buy from and sell to.\n *\n * @example\n * ```typescript\n * const vendor = await vendors.create({\n * profileId: 'profile-uuid',\n * leadTimeDays: 14,\n * minimumOrderAmount: 500,\n * paymentTerms: 'Net 60'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Vendor extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global vendors\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Reference to smrt-profiles Profile\n * Plain string for cross-package reference\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n profileId: string = '';\n\n /**\n * Typical lead time in days for orders\n */\n leadTimeDays: number = 0;\n\n /**\n * Minimum order amount required, in **integer minor units** (cents,\n * satoshis). Compared against `Contract.totalAmount`, which is also minor\n * units (#2401).\n */\n minimumOrderAmount: number = 0;\n\n /**\n * Payment terms for this vendor (e.g., \"Net 60\")\n */\n paymentTerms: string = '';\n\n /**\n * Default currency for transactions with this vendor\n */\n currency: string = 'USD';\n\n /**\n * Default contact email for orders\n */\n defaultContactEmail: string = '';\n\n /**\n * Default contact phone for orders\n */\n defaultContactPhone: string = '';\n\n /**\n * Vendor status\n */\n status: VendorStatus = VendorStatus.ACTIVE;\n\n /**\n * Internal notes about this vendor\n */\n notes: string = '';\n\n /**\n * Per-currency payout destinations for this vendor.\n *\n * Stored as a JSON column. Keys are payout-rail-qualified currency codes —\n * the code must encode both the asset and the rail (e.g. `USDC-base`,\n * `USDC-solana`, `BTC`, `USD-stripe`, `USD-wire`). Values are whatever the\n * receiving rail needs as a destination (EVM address, BTC address, Stripe\n * Connect account id, IBAN, etc.).\n *\n * MVP shape: a flat map on the vendor row. A vendor with no entry for a\n * given currency cannot be paid in that currency — filtering is the\n * consumer's responsibility (the upstream `PaymentIntent` builder filters\n * `paymentOptions` at quote time against this map).\n *\n * Use {@link getPayoutAddress} for a `Map.get`-style lookup that returns\n * `undefined` for missing entries; direct property access is also fine.\n *\n * Sensitive (#1540): payout destinations are excluded from generated API/MCP\n * responses and rejected as a `where` filter key.\n */\n @field({ sensitive: true })\n payoutAddresses: Record<string, string> = {};\n\n constructor(options: VendorOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.leadTimeDays !== undefined)\n this.leadTimeDays = options.leadTimeDays;\n if (options.minimumOrderAmount !== undefined)\n this.minimumOrderAmount = options.minimumOrderAmount;\n if (options.paymentTerms !== undefined)\n this.paymentTerms = options.paymentTerms;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.defaultContactEmail !== undefined)\n this.defaultContactEmail = options.defaultContactEmail;\n if (options.defaultContactPhone !== undefined)\n this.defaultContactPhone = options.defaultContactPhone;\n if (options.status !== undefined) this.status = options.status;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.payoutAddresses !== undefined) {\n this.payoutAddresses = Vendor.normalizePayoutAddresses(\n options.payoutAddresses,\n );\n }\n }\n\n /**\n * The framework's {@link SmrtObject.initialize} re-applies `options` keys on\n * top of constructor-set values so option data always wins over field\n * initializers. That means our constructor's normalization runs *before*\n * `initializePropertiesFromOptions` overwrites `payoutAddresses` with the\n * raw cloned value. Re-normalize once super has finished so consumers\n * who pass a string, a partially-typed map, or `null` end up with a\n * clean `Record<string, string>` before any save / read happens.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.payoutAddresses = Vendor.normalizePayoutAddresses(\n this.payoutAddresses as unknown,\n );\n return this;\n }\n\n /**\n * Save with a final normalization pass so direct field assignments\n * (`vendor.payoutAddresses = somethingFunny`) can't smuggle non-string\n * values into the persisted row.\n */\n override async save(): Promise<this> {\n this.payoutAddresses = Vendor.normalizePayoutAddresses(\n this.payoutAddresses as unknown,\n );\n return super.save() as Promise<this>;\n }\n\n /**\n * Check if vendor is active\n */\n isActive(): boolean {\n return this.status === VendorStatus.ACTIVE;\n }\n\n /**\n * Check if order meets minimum requirements\n */\n meetsMinimumOrder(amount: number): boolean {\n return amount >= this.minimumOrderAmount;\n }\n\n /**\n * Get the profile ID for external lookup.\n *\n * To get the actual Profile object, use the ProfileCollection from smrt-profiles:\n * ```typescript\n * const profiles = await ProfileCollection.create(options);\n * const profile = await profiles.get({ id: vendor.profileId });\n * ```\n *\n * @returns The profile ID or empty string if not set\n */\n getProfileId(): string {\n return this.profileId;\n }\n\n /**\n * Look up the payout destination for a single currency. Returns\n * `undefined` if the vendor has no entry for that currency — caller's\n * job to decide whether that means \"skip the option\" or \"raise a\n * configuration error\". The lookup is case-sensitive; the contract is\n * that the currency code passed in matches exactly what was stored\n * (`USDC-base`, not `usdc-base`).\n */\n getPayoutAddress(currency: string): string | undefined {\n const map = this.payoutAddresses;\n if (!map || typeof map !== 'object') return undefined;\n const value = (map as Record<string, unknown>)[currency];\n return typeof value === 'string' ? value : undefined;\n }\n\n /**\n * Set the payout destination for a single currency. Mutates the live\n * map and returns `this` for chaining. Use {@link clearPayoutAddress}\n * to remove a single entry.\n */\n setPayoutAddress(currency: string, destination: string): this {\n this.payoutAddresses = {\n ...(this.payoutAddresses ?? {}),\n [currency]: destination,\n };\n return this;\n }\n\n /**\n * Remove a single currency entry from the payout map. No-op if the\n * currency was already absent.\n */\n clearPayoutAddress(currency: string): this {\n if (!this.payoutAddresses || !(currency in this.payoutAddresses)) {\n return this;\n }\n const next = { ...this.payoutAddresses };\n delete next[currency];\n this.payoutAddresses = next;\n return this;\n }\n\n /**\n * Normalize an input value (object, pre-serialized JSON string, or\n * `null`/`undefined`) into a plain `Record<string, string>`. Used by\n * the constructor so consumers can pass either shape into\n * `VendorCollection.create({ payoutAddresses: ... })`.\n *\n * Non-string values inside an input object are silently dropped — a\n * `Record<string, string>` invariant is what consumers downstream\n * (`PaymentIntent` builders, payout SDK adapters) rely on.\n */\n private static normalizePayoutAddresses(\n value: unknown,\n ): Record<string, string> {\n if (value == null) return {};\n let parsed: unknown = value;\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value);\n } catch {\n return {};\n }\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return {};\n }\n const out: Record<string, string> = {};\n for (const [key, v] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof v === 'string') out[key] = v;\n }\n return out;\n }\n}\n\nexport default Vendor;\n","/**\n * Contract model with STI for different contract types\n * @packageDocumentation\n */\n\nimport {\n foreignKey,\n type Meta,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport {\n type ContractOptions,\n ContractStatus,\n ContractType,\n type LicenseSaleOptions,\n} from '../types/index.js';\nimport { Customer } from './Customer.js';\nimport { Vendor } from './Vendor.js';\n\n/**\n * Legal status transitions for a Contract (and all its STI subtypes), keyed by\n * the prior persisted status. A status re-saved unchanged (no-op) and a\n * brand-new row are always permitted; this map governs *changes* only.\n *\n * Flow: DRAFT → SENT → (ACCEPTED | DECLINED); ACCEPTED → (COMPLETED |\n * CANCELLED). An estimate/order may also be cancelled directly from DRAFT/SENT.\n * DECLINED / COMPLETED / CANCELLED are terminal. This guards against raw\n * mass-assignment forcing, e.g., a DECLINED contract back to ACCEPTED or a\n * COMPLETED one back to DRAFT. See S5 audit #1390.\n */\nconst CONTRACT_STATUS_TRANSITIONS: Record<ContractStatus, ContractStatus[]> = {\n [ContractStatus.DRAFT]: [\n ContractStatus.SENT,\n ContractStatus.ACCEPTED,\n ContractStatus.DECLINED,\n ContractStatus.CANCELLED,\n ],\n [ContractStatus.SENT]: [\n ContractStatus.ACCEPTED,\n ContractStatus.DECLINED,\n ContractStatus.CANCELLED,\n ],\n [ContractStatus.ACCEPTED]: [\n ContractStatus.COMPLETED,\n ContractStatus.CANCELLED,\n ],\n [ContractStatus.DECLINED]: [],\n [ContractStatus.COMPLETED]: [],\n [ContractStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each Contract instance was loaded with,\n * for the save-time transition guard. WeakMap keeps it out of the schema and\n * GCs with the instance — same pattern as the other commerce models.\n */\nconst loadedContractStatus = new WeakMap<Contract, ContractStatus>();\n\n/**\n * Contract is the base class for all commercial agreements.\n *\n * Uses Single Table Inheritance (STI) to support different contract types:\n * - Estimate: Quote/proposal for customer\n * - Order: Customer purchase order\n * - Lease: Rental/lease agreement\n * - Agreement: Service/maintenance agreement\n * - PurchaseOrder: Order to vendor/supplier\n *\n * @example\n * ```typescript\n * // Create a customer order\n * const order = await contracts.create({\n * _meta_type: 'Order',\n * customerId: customer.id,\n * totalAmount: 150000, // $1,500.00 in cents\n * status: ContractStatus.DRAFT\n * });\n *\n * // Create a purchase order to vendor\n * const po = await contracts.create({\n * _meta_type: 'PurchaseOrder',\n * vendorId: vendor.id,\n * totalAmount: 500000, // $5,000.00 in cents\n * reference: 'PO-2024-001'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Contract extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global contracts\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Contract type discriminator (STI)\n */\n contractType: ContractType = ContractType.ORDER;\n\n /**\n * Current status of the contract\n */\n status: ContractStatus = ContractStatus.DRAFT;\n\n /**\n * Customer ID (for sales contracts: Estimate, Order, Agreement)\n */\n @foreignKey(Customer)\n customerId: string | null = null;\n\n /**\n * Vendor ID (for purchase contracts: PurchaseOrder)\n */\n @foreignKey(Vendor)\n vendorId: string | null = null;\n\n /**\n * Subtotal before tax, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so it is stored\n * as minor units and never as a float — `$19.99` is `1999`, not `19.99`\n * (#2401).\n */\n subtotal: number = 0;\n\n /**\n * Tax amount, in integer minor units (#2401).\n */\n taxAmount: number = 0;\n\n /**\n * Total amount including tax, in integer minor units (#2401).\n */\n totalAmount: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n currency: string = 'USD';\n\n /**\n * Date contract was issued\n */\n issueDate: Date = new Date();\n\n /**\n * Payment due date\n */\n dueDate: Date | null = null;\n\n /**\n * Expiry date (for estimates/quotes)\n */\n expiryDate: Date | null = null;\n\n /**\n * External reference number (PO number, quote number, etc.)\n */\n reference: string = '';\n\n /**\n * Internal notes\n */\n notes: string = '';\n\n /**\n * Terms and conditions\n */\n terms: string = '';\n\n /**\n * Sales channel that owns this contract.\n *\n * Plain string so consumers can model their own channels without an enum\n * change (e.g. `dtc-web`, `wholesale-b2b`, `pos-store-1`, `marketplace-faire`).\n * Empty string means \"unattributed / legacy\".\n */\n channelId: string = '';\n\n /**\n * STI discriminator field\n */\n static readonly _stiField = 'contractType';\n\n constructor(options: ContractOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractType !== undefined)\n this.contractType = options.contractType;\n if (options.status !== undefined) this.status = options.status;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.vendorId !== undefined) this.vendorId = options.vendorId;\n if (options.subtotal !== undefined) this.subtotal = options.subtotal;\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.totalAmount !== undefined)\n this.totalAmount = options.totalAmount;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.issueDate !== undefined) this.issueDate = options.issueDate;\n if (options.dueDate !== undefined) this.dueDate = options.dueDate;\n if (options.expiryDate !== undefined) this.expiryDate = options.expiryDate;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.terms !== undefined) this.terms = options.terms;\n if (options.channelId !== undefined) this.channelId = options.channelId;\n }\n\n /**\n * Check if contract is in draft state\n */\n isDraft(): boolean {\n return this.status === ContractStatus.DRAFT;\n }\n\n /**\n * Check if contract is accepted\n */\n isAccepted(): boolean {\n return this.status === ContractStatus.ACCEPTED;\n }\n\n /**\n * Check if contract is completed\n */\n isCompleted(): boolean {\n return this.status === ContractStatus.COMPLETED;\n }\n\n /**\n * Check if contract is expired (for estimates)\n */\n isExpired(): boolean {\n if (!this.expiryDate) return false;\n return new Date() > this.expiryDate;\n }\n\n /**\n * Check if contract is overdue\n */\n isOverdue(): boolean {\n if (!this.dueDate) return false;\n if (this.status === ContractStatus.COMPLETED) return false;\n return new Date() > this.dueDate;\n }\n\n /**\n * Calculate and update totals from line items\n */\n async recalculateTotals(): Promise<void> {\n // This would typically query ContractLineItems and sum them\n // For now, just ensure totalAmount = subtotal + taxAmount\n this.totalAmount = this.subtotal + this.taxAmount;\n }\n\n /**\n * Capture the status the row was loaded with so the save-time transition\n * guard can reject illegal status flips made via raw field assignment\n * (mass-assignment on the generated update route, a stale caller, etc.).\n * Only persisted rows carry a prior status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedContractStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Validate the amounts and the status transition before persisting, then\n * save. Inherited by every STI subtype (Estimate/Order/Lease/.../LicenseSale),\n * so a forged `status` on any of them is caught here. LicenseSale layers its\n * own rights-immutability guard on top via `super.save()`. See S5 audit\n * #1390.\n *\n * `subtotal` / `taxAmount` / `totalAmount` are mass-assignable on the\n * generated create/update routes, so the minor-units check runs on every\n * write surface (#2401).\n */\n override async save(): Promise<this> {\n assertIntegerMinorUnits('Contract', this.reference || this.id || '<new>', [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['totalAmount', this.totalAmount],\n ]);\n const prior = await this.resolvePriorStatus();\n this.assertContractStatusTransition(prior);\n const result = (await super.save()) as this;\n loadedContractStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Reject an illegal status flip done via raw assignment. No-op transitions\n * and brand-new rows are always allowed.\n */\n protected assertContractStatusTransition(\n prior: ContractStatus | undefined,\n ): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = CONTRACT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Contract ${this.reference || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'.`,\n );\n }\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status (S5 audit #1390 round 5, codex\n * MEDIUM#2). The WeakMap is only populated when {@link initialize} loaded the\n * row from the DB; it is empty for an instance built via\n * `collection.create({ id: <existing>, _skipLoad: true })` — the upsert path\n * that lets a caller write onto an existing row without hydrating it. Trusting\n * an empty WeakMap there would treat the write as a brand-new row and skip the\n * transition guard entirely (a poisonable prior-state). Mirrors the\n * authoritative-prior-load applied to Payment/PaymentIntent/Invoice/Payout.\n *\n * So when this instance carries an `id`, read the persisted row straight from\n * the database and treat its `status` as the prior — a create-onto-existing is\n * an update. Only when no row exists in the DB (truly new) do we fall back to\n * the WeakMap (which is also empty then), i.e. `undefined` = genuinely new.\n * The raw row's `status` column is single-word (no snake_case transform).\n */\n protected async resolvePriorStatus(): Promise<ContractStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as ContractStatus;\n }\n } catch {\n // DB not ready / table absent — fall through to the in-memory record.\n }\n }\n return loadedContractStatus.get(this);\n }\n}\n\n// Every STI child below MUST repeat the `@TenantScoped` decoration. The\n// tenancy registry keys off the concrete className passed by the\n// collection, not the inheritance chain — `OrderCollection.list()` sends\n// `'Order'` to the interceptor, which then misses the `'Contract'`\n// registration and skips tenant auto-filter/auto-populate. Without an\n// explicit `@TenantScoped` on each subclass, saves end up with\n// `tenantId: null` (the row becomes global / invisible to tenant-scoped\n// queries) and lists return rows across tenants.\n\n/**\n * Estimate - Quote or proposal for a customer\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Estimate extends Contract {\n override contractType = ContractType.ESTIMATE;\n}\n\n/**\n * Order - Customer purchase order\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Order extends Contract {\n override contractType = ContractType.ORDER;\n}\n\n/**\n * Lease - Rental or lease agreement\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Lease extends Contract {\n override contractType = ContractType.LEASE;\n}\n\n/**\n * Agreement - Service or maintenance agreement\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Agreement extends Contract {\n override contractType = ContractType.AGREEMENT;\n}\n\n/**\n * PurchaseOrder - Order sent to vendor/supplier\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class PurchaseOrder extends Contract {\n override contractType = ContractType.PURCHASE_ORDER;\n}\n\n/**\n * WholesaleOrder - B2B order placed by a wholesale customer.\n *\n * Behaves like an Order but is conventionally created against a customer with\n * `customerType: 'wholesale'`, uses NET-30/60 payment terms, and is delivered\n * via the wholesale-portal channel rather than retail checkout.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class WholesaleOrder extends Contract {\n override contractType = ContractType.WHOLESALE_ORDER;\n}\n\n/**\n * ProductionOrder - instruction to commission goods from an internal or\n * external factory.\n *\n * The manufacturing equivalent of a {@link PurchaseOrder} — but instead of\n * buying finished inventory, you're paying to *make* it. A ProductionOrder\n * consumes raw materials per a Bill of Materials (see\n * `@happyvertical/smrt-manufacturing`) and produces finished SKU stock when\n * posted (see `@happyvertical/smrt-inventory`).\n *\n * Carries two STI-meta fields that `ProductionService.consumeMaterials` /\n * `runProduction` read from the loaded row to resolve which BOM to walk:\n *\n * - `productId` — plain string ref to the upstream `Product` (or any\n * STI subtype, e.g. apparel `Style`). Used to look up the *active*\n * BOM via `BomService.findActiveForProduct` when no explicit\n * revision was pinned at the time the order was placed.\n * - `bomId` — optional pin to a specific BOM revision. When set, the\n * manufacturing service consumes against that exact revision even\n * if a newer BOM has become active in the meantime (keeps a\n * posted run from silently switching recipes mid-flight).\n *\n * Both fields are `Meta<string>` so the scanner routes them into the\n * shared `_meta_data` JSON column on the Contract STI table instead of\n * widening the base schema with manufacturing-specific columns.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class ProductionOrder extends Contract {\n override contractType = ContractType.PRODUCTION_ORDER;\n\n /**\n * Plain string reference to the upstream product the order is asked\n * to make. Cross-package id; never `@foreignKey()`. Required by\n * `ProductionService.consumeMaterials` unless `bomId` is supplied.\n */\n productId: Meta<string> = '';\n\n /**\n * Optional explicit BOM revision the order locked to at posting\n * time. When set, manufacturing uses this exact row even if the\n * product's active BOM has since changed. When empty, falls back\n * to the active BOM for `productId`.\n */\n bomId: Meta<string> = '';\n}\n\n/**\n * Cart - transient order-in-progress for a shopper.\n *\n * Persisted as a Contract so it can hold the same line items, totals, and\n * customer reference as a real Order, then convert in place (the application\n * promotes the row from `_meta_type: Cart` to `_meta_type: Order` at checkout\n * rather than copying data between tables).\n *\n * A Cart starts in {@link ContractStatus.DRAFT} via the base `Contract.status`\n * default — the subclass doesn't need its own override. Application code\n * is responsible for promoting the row to `ContractStatus.PENDING` /\n * `ACCEPTED` at checkout time; the framework doesn't enforce a state\n * machine on the cart → order transition.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Cart extends Contract {\n override contractType = ContractType.CART;\n}\n\n/**\n * Snapshot of the rights granted by a {@link LicenseSale}. The fields are\n * intentionally typed (not a generic JSON blob) so they survive schema\n * migrations and downstream consumers (PDF templating, hash-registry\n * publishers) can rely on the shape without re-parsing.\n *\n * Each field is opaque text — the values that make sense vary by\n * industry. A stock-media marketplace might use:\n *\n * - `medium`: `web,print,social`\n * - `distributionScope`: `worldwide` / `territorial`\n * - `exclusivity`: `exclusive` / `non-exclusive`\n * - `duration`: `perpetual` / `12-months` / `event-only`\n * - `territory`: ISO-3166 list or `worldwide`\n *\n * Booleans (`sublicensing`, `derivatives`) are typed as such; everything\n * else is a string so the model doesn't dictate vocabulary.\n */\nexport interface LicenseRightsSnapshot {\n medium: string;\n distributionScope: string;\n exclusivity: string;\n duration: string;\n territory: string;\n sublicensing: boolean;\n derivatives: boolean;\n}\n\n/**\n * Module-scoped WeakMap of \"rights snapshot the row was loaded with\".\n *\n * Used by {@link LicenseSale}'s immutability guard. We can't store the\n * captured snapshot as an instance field because the AST scanner would\n * pick it up as a persisted column; we can't store it as a `Meta<T>`\n * because then it'd round-trip with the row and the comparison would\n * always tautologically succeed. A WeakMap keyed by the instance is\n * the cleanest scope: no schema interaction, garbage-collected with\n * the instance.\n */\nconst issuedRightsSnapshot = new WeakMap<LicenseSale, string>();\n\n/**\n * LicenseSale — industry-neutral licensing primitive.\n *\n * Sits alongside the other Contract STI subtypes (Estimate, Order,\n * Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder,\n * Cart) and represents a single sale of rights for a fee. A\n * stock-media marketplace selling a one-time license, a music-\n * licensing platform issuing a sync license, a code-asset marketplace\n * granting a redistribution right — all use the same primitive.\n *\n * Immutability invariant: once a `LicenseSale` is saved with\n * {@link ContractStatus.ACCEPTED}, its rights snapshot becomes\n * frozen. Re-saving with mutated rights throws. The only legal\n * transition out of ACCEPTED is `revoke()` (which moves status to\n * CANCELLED without changing the rights). If a different snapshot\n * is genuinely needed, the operator's contract is to issue a *new*\n * LicenseSale row and let the old one stand as historical record.\n *\n * All subtype-specific fields are declared as `Meta<T>` so the\n * scanner routes them into the shared `_meta_data` JSON column on\n * the `contracts` STI table — no migration of the base schema needed\n * to land this subtype.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class LicenseSale extends Contract {\n override contractType = ContractType.LICENSE_SALE;\n\n /**\n * Plain string reference to the upstream `Sku` (from\n * `@happyvertical/smrt-products`) that this license was sold against.\n * Cross-package id; never `@foreignKey()`.\n */\n skuId: Meta<string> = '';\n\n /**\n * Plain string reference to the `Payment` that satisfied the\n * purchase. Cross-model reference matches the rest of the\n * package's convention.\n */\n paymentId: Meta<string> = '';\n\n /**\n * The licensee's email address — the primary identity carried\n * forward into PDF generation and any downstream hash-registry\n * publication. Required; without it, downstream rights cannot be\n * attributed.\n */\n licenseeEmail: Meta<string> = '';\n\n /**\n * Optional legal-entity name of the licensee for purchases that\n * warrant it (typically high-tier B2B licenses). Empty string for\n * individual-buyer licenses where the email *is* the legal identity.\n */\n licenseeLegalEntity: Meta<string> = '';\n\n /**\n * Optional ISO-3166 jurisdiction code of the licensee, captured for\n * tax / disclosure / governing-law purposes. Empty when not\n * required.\n */\n licenseeJurisdiction: Meta<string> = '';\n\n // -------- Rights snapshot (immutable once issued) --------\n\n /** Permitted media — `web,print,social`, `broadcast`, etc. */\n rightsMedium: Meta<string> = '';\n\n /** Distribution scope — `worldwide`, `territorial`, `private`, etc. */\n rightsDistributionScope: Meta<string> = '';\n\n /** Exclusivity — `exclusive` / `non-exclusive` / `co-exclusive`. */\n rightsExclusivity: Meta<string> = '';\n\n /** Duration — `perpetual` / `12-months` / `event-only`, etc. */\n rightsDuration: Meta<string> = '';\n\n /** Territory — ISO-3166 list, `worldwide`, region name, etc. */\n rightsTerritory: Meta<string> = '';\n\n /** Whether the licensee may sublicense the rights. */\n rightsSublicensing: Meta<boolean> = false;\n\n /** Whether the licensee may create derivative works. */\n rightsDerivatives: Meta<boolean> = false;\n\n // -------- Artefacts --------\n\n /** URL of the signed PDF (typically a cloud-storage public URL). */\n pdfUrl: Meta<string> = '';\n\n /**\n * SHA-256 of the signed PDF bytes (hex string, no `0x` prefix).\n * Lets downstream consumers verify the PDF the licensee holds is\n * the one this row was issued for, without re-fetching.\n */\n pdfHash: Meta<string> = '';\n\n /**\n * Optional reference to an on-chain hash-registry entry — set when\n * the LicenseSale's `pdfHash` has been anchored on a public chain\n * for tamper-evidence. Format is whatever the hash-registry\n * service emits (e.g. `chain:tx-hash:log-index`). Empty for\n * deployments that don't publish hashes on-chain.\n */\n onChainHashRegistryRef: Meta<string> = '';\n\n constructor(options: LicenseSaleOptions = {}) {\n super(options);\n if (options.skuId !== undefined) this.skuId = options.skuId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.licenseeEmail !== undefined)\n this.licenseeEmail = options.licenseeEmail;\n if (options.licenseeLegalEntity !== undefined)\n this.licenseeLegalEntity = options.licenseeLegalEntity;\n if (options.licenseeJurisdiction !== undefined)\n this.licenseeJurisdiction = options.licenseeJurisdiction;\n if (options.rightsMedium !== undefined)\n this.rightsMedium = options.rightsMedium;\n if (options.rightsDistributionScope !== undefined)\n this.rightsDistributionScope = options.rightsDistributionScope;\n if (options.rightsExclusivity !== undefined)\n this.rightsExclusivity = options.rightsExclusivity;\n if (options.rightsDuration !== undefined)\n this.rightsDuration = options.rightsDuration;\n if (options.rightsTerritory !== undefined)\n this.rightsTerritory = options.rightsTerritory;\n if (options.rightsSublicensing !== undefined)\n this.rightsSublicensing = options.rightsSublicensing;\n if (options.rightsDerivatives !== undefined)\n this.rightsDerivatives = options.rightsDerivatives;\n if (options.pdfUrl !== undefined) this.pdfUrl = options.pdfUrl;\n if (options.pdfHash !== undefined) this.pdfHash = options.pdfHash;\n if (options.onChainHashRegistryRef !== undefined)\n this.onChainHashRegistryRef = options.onChainHashRegistryRef;\n }\n\n /**\n * Once `super.initialize()` has applied the framework's option-\n * override pass, capture the rights snapshot if (and only if) the\n * row arrived already issued. That's how the immutability guard\n * tells \"freshly drafting a license\" (snapshot still mutable) apart\n * from \"loaded an already-issued row\" (snapshot frozen).\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (this.status === ContractStatus.ACCEPTED && (await this.isSaved())) {\n issuedRightsSnapshot.set(this, this.serializeRightsSnapshot());\n }\n return this;\n }\n\n /**\n * Return the current rights snapshot as a typed object. Read-only\n * for callers — mutation should go through assignment to the\n * individual `rights*` fields, gated by the issued-immutability\n * guard.\n */\n getRightsSnapshot(): LicenseRightsSnapshot {\n return {\n medium: this.rightsMedium,\n distributionScope: this.rightsDistributionScope,\n exclusivity: this.rightsExclusivity,\n duration: this.rightsDuration,\n territory: this.rightsTerritory,\n sublicensing: this.rightsSublicensing,\n derivatives: this.rightsDerivatives,\n };\n }\n\n /**\n * Revoke an issued license — moves status to `CANCELLED` without\n * touching the rights snapshot (which stays frozen). Throws if\n * called on a non-issued license; a draft license should be\n * deleted, not revoked.\n */\n revoke(): void {\n if (this.status !== ContractStatus.ACCEPTED) {\n throw new Error(\n `LicenseSale ${this.id ?? '<new>'}: cannot revoke from status '${this.status}' — only ACCEPTED licenses can be revoked`,\n );\n }\n this.status = ContractStatus.CANCELLED;\n }\n\n /**\n * Save with the immutability check: if the row was loaded with\n * `ACCEPTED` status, the rights snapshot at save time must match\n * the snapshot at load time. Anything else means an out-of-band\n * mutation crept in and we surface it instead of letting the row\n * drift silently.\n *\n * Newly-drafted licenses (where the row was constructed without\n * `ACCEPTED` status) capture their snapshot at the moment the\n * caller transitions to `ACCEPTED` and saves — `validateImmutable`\n * picks that up post-save on the next call.\n */\n override async save(): Promise<this> {\n this.validateImmutable();\n const result = (await super.save()) as this;\n // If this save transitioned the row to ACCEPTED, freeze the\n // snapshot for future saves.\n if (\n this.status === ContractStatus.ACCEPTED &&\n !issuedRightsSnapshot.has(this)\n ) {\n issuedRightsSnapshot.set(this, this.serializeRightsSnapshot());\n }\n return result;\n }\n\n /**\n * Compare the current rights against the captured snapshot\n * (populated either at load time by `initialize` or at first\n * issuance by `save`). Throws on mismatch.\n */\n private validateImmutable(): void {\n const captured = issuedRightsSnapshot.get(this);\n if (!captured) return;\n const current = this.serializeRightsSnapshot();\n if (captured !== current) {\n throw new Error(\n `LicenseSale ${this.id ?? '<new>'}: rights snapshot is immutable once issued (status=ACCEPTED). ` +\n 'Use revoke() to cancel the existing license and issue a new one with the updated rights.',\n );\n }\n }\n\n private serializeRightsSnapshot(): string {\n // Stable key ordering so a re-serialization after a no-op\n // assignment doesn't trip the equality check.\n return JSON.stringify({\n medium: this.rightsMedium,\n distributionScope: this.rightsDistributionScope,\n exclusivity: this.rightsExclusivity,\n duration: this.rightsDuration,\n territory: this.rightsTerritory,\n sublicensing: this.rightsSublicensing,\n derivatives: this.rightsDerivatives,\n });\n }\n}\n\nexport default Contract;\n","/**\n * ContractCollection - Collection manager for Contract objects (STI)\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Contract } from '../models/Contract.js';\nimport { ContractStatus, ContractType } from '../types/index.js';\n\nexport class ContractCollection extends SmrtCollection<Contract> {\n static readonly _itemClass = Contract;\n\n /**\n * Find contracts by customer\n *\n * @param customerId - Customer ID\n * @returns Array of contracts\n */\n async findByCustomer(customerId: string): Promise<Contract[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find contracts by vendor\n *\n * @param vendorId - Vendor ID\n * @returns Array of contracts\n */\n async findByVendor(vendorId: string): Promise<Contract[]> {\n return await this.list({\n where: { vendorId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find contracts by type (STI)\n *\n * @param contractType - Contract type\n * @returns Array of contracts\n */\n async findByType(contractType: ContractType): Promise<Contract[]> {\n return await this.list({\n where: { contractType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find contracts by status\n *\n * @param status - Contract status\n * @returns Array of contracts\n */\n async findByStatus(status: ContractStatus): Promise<Contract[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all draft contracts\n *\n * @returns Array of draft contracts\n */\n async findDrafts(): Promise<Contract[]> {\n return await this.findByStatus(ContractStatus.DRAFT);\n }\n\n /**\n * Find all accepted contracts\n *\n * @returns Array of accepted contracts\n */\n async findAccepted(): Promise<Contract[]> {\n return await this.findByStatus(ContractStatus.ACCEPTED);\n }\n\n /**\n * Find overdue contracts\n *\n * @returns Array of overdue contracts\n */\n async findOverdue(): Promise<Contract[]> {\n const now = new Date().toISOString();\n // Get all contracts past due date that aren't completed or cancelled\n const all = await this.list({\n where: {\n 'dueDate <': now,\n },\n orderBy: 'dueDate ASC',\n });\n return all.filter(\n (c) =>\n c.status !== ContractStatus.COMPLETED &&\n c.status !== ContractStatus.CANCELLED,\n );\n }\n\n /**\n * Find expired estimates/quotes\n *\n * @returns Array of expired estimates\n */\n async findExpired(): Promise<Contract[]> {\n const now = new Date().toISOString();\n // Get estimates past expiry date that aren't accepted, declined, or cancelled\n const estimates = await this.list({\n where: {\n contractType: ContractType.ESTIMATE,\n 'expiryDate <': now,\n },\n orderBy: 'expiryDate ASC',\n });\n return estimates.filter(\n (c) =>\n c.status !== ContractStatus.ACCEPTED &&\n c.status !== ContractStatus.DECLINED &&\n c.status !== ContractStatus.CANCELLED,\n );\n }\n\n /**\n * Find contracts in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of contracts\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Contract[]> {\n return await this.list({\n where: {\n 'issueDate >=': startDate.toISOString(),\n 'issueDate <=': endDate.toISOString(),\n },\n orderBy: 'issueDate DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contracts belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contracts for the tenant\n */\n async findByTenant(tenantId: string): Promise<Contract[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contracts (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contracts\n */\n async findGlobal(): Promise<Contract[]> {\n return queryGlobal<Contract>(this);\n }\n\n /**\n * Find contracts for a tenant including global contracts.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contracts\n */\n async findWithGlobals(tenantId: string): Promise<Contract[]> {\n return queryWithGlobals<Contract>(\n this,\n tenantId,\n 'Contract.findWithGlobals',\n );\n }\n}\n","/**\n * ContractLineItem model - individual items on a contract\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ContractLineItemOptions } from '../types/index.js';\n\n/**\n * ContractLineItem represents a single line item on a contract.\n *\n * Line items contain the details of what is being sold/purchased,\n * including quantity, pricing, and optional subscription details.\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * contractId: order.id,\n * description: 'Widget Pro',\n * quantity: 10,\n * unitPrice: 4999, // $49.99 in cents\n * taxRate: 0.08\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ContractLineItem extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract\n */\n @foreignKey('Contract')\n contractId: string = '';\n\n /**\n * Item description\n */\n description: string = '';\n\n /**\n * Quantity ordered. Decimal on purpose — contracts price fractional\n * quantities (hours, weight, bandwidth), unlike `InvoiceLineItem.quantity`.\n */\n quantity: number = 1.0;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so `$49.99` is\n * `4999` (#2401).\n */\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2401).\n */\n discount: number = 0;\n\n /**\n * Tax rate as decimal (e.g., 0.08 for 8%). A rate is inherently fractional,\n * so it stays DECIMAL — INTEGER would truncate every meaningful value.\n */\n taxRate: number = 0.0;\n\n /**\n * Calculated line amount (qty * price - discount + tax), in integer minor\n * units. Derived by {@link ContractLineItem.calculateAmount}, which rounds\n * the fractional tax to a whole minor unit (#2401).\n */\n amount: number = 0;\n\n /**\n * Optional product reference (cross-package, plain string)\n */\n @crossPackageRef('@happyvertical/smrt-products:Product')\n productId: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /**\n * Start date (for lease/subscription items)\n */\n startDate: Date | null = null;\n\n /**\n * End date (for lease/subscription items)\n */\n endDate: Date | null = null;\n\n /**\n * Billing period (for subscriptions: 'monthly', 'yearly', etc.)\n */\n billingPeriod: string = '';\n\n /**\n * Sort order within the contract\n */\n sortOrder: number = 0;\n\n constructor(options: ContractLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.startDate !== undefined) this.startDate = options.startDate;\n if (options.endDate !== undefined) this.endDate = options.endDate;\n if (options.billingPeriod !== undefined)\n this.billingPeriod = options.billingPeriod;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n /**\n * Calculate the line amount, in integer minor units.\n *\n * `quantity` is decimal and `taxRate` is a fraction, so the product is not\n * generally a whole number of minor units. Rounding happens here — at the\n * one boundary where a rate meets money — so that every value that reaches a\n * money column is already exact and every downstream comparison can be a\n * plain `===` instead of an epsilon tolerance (#2401).\n */\n calculateAmount(): number {\n const subtotal = this.quantity * this.unitPrice - this.discount;\n const tax = subtotal * this.taxRate;\n return Math.round(subtotal + tax);\n }\n\n /**\n * Check if this is a subscription/recurring item\n */\n isRecurring(): boolean {\n return !!this.billingPeriod && !!this.startDate;\n }\n\n /**\n * Check if subscription is active\n */\n isSubscriptionActive(): boolean {\n if (!this.isRecurring()) return false;\n if (!this.startDate) return false;\n\n const now = new Date();\n if (now < this.startDate) return false;\n if (this.endDate && now > this.endDate) return false;\n\n return true;\n }\n}\n\nexport default ContractLineItem;\n","/**\n * ContractLineItemCollection - Collection manager for ContractLineItem objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { ContractLineItem } from '../models/ContractLineItem.js';\n\nexport class ContractLineItemCollection extends SmrtCollection<ContractLineItem> {\n static readonly _itemClass = ContractLineItem;\n\n /**\n * Find contract line items by their parent contract.\n *\n * @param contractId - Contract ID\n * @returns Array of contract line items, sorted by sortOrder\n */\n async findByContract(contractId: string): Promise<ContractLineItem[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contract line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contract line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<ContractLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contract line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contract line items\n */\n async findGlobal(): Promise<ContractLineItem[]> {\n return queryGlobal<ContractLineItem>(this);\n }\n\n /**\n * Find contract line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contract line items\n */\n async findWithGlobals(tenantId: string): Promise<ContractLineItem[]> {\n return queryWithGlobals<ContractLineItem>(\n this,\n tenantId,\n 'ContractLineItem.findWithGlobals',\n );\n }\n}\n","/**\n * Fulfillment model - tracks delivery of contract items\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type Address,\n type FulfillmentOptions,\n FulfillmentStatus,\n FulfillmentType,\n} from '../types/index.js';\n\n/**\n * Legal status transitions for a Fulfillment, keyed by the prior persisted\n * status. A status re-saved unchanged (no-op) and a brand-new row are always\n * permitted; this map governs *changes* only.\n *\n * Forward order: PENDING → PROCESSING → SHIPPED → DELIVERED, but progress may\n * SKIP intermediate states — not every order has a PROCESSING step, so\n * PENDING → SHIPPED and PENDING/PROCESSING → DELIVERED are deliberately legal.\n * The map only forbids moving BACKWARD (e.g. SHIPPED → PENDING) and leaving a\n * terminal state. CANCELLED is reachable from any non-terminal state.\n * DELIVERED and CANCELLED are terminal. This guards against raw mass-assignment\n * (the open `api:{create,update}` surface, or a stale caller) forcing, e.g., a\n * DELIVERED shipment back to PENDING or a CANCELLED one back into SHIPPED.\n * Mirrors the Contract/Payment/Payout guards added in S5 audit #1390 —\n * Fulfillment was the one money/state model missed.\n */\nconst FULFILLMENT_STATUS_TRANSITIONS: Record<\n FulfillmentStatus,\n FulfillmentStatus[]\n> = {\n [FulfillmentStatus.PENDING]: [\n FulfillmentStatus.PROCESSING,\n FulfillmentStatus.SHIPPED,\n FulfillmentStatus.DELIVERED,\n FulfillmentStatus.CANCELLED,\n ],\n [FulfillmentStatus.PROCESSING]: [\n FulfillmentStatus.SHIPPED,\n FulfillmentStatus.DELIVERED,\n FulfillmentStatus.CANCELLED,\n ],\n [FulfillmentStatus.SHIPPED]: [\n FulfillmentStatus.DELIVERED,\n FulfillmentStatus.CANCELLED,\n ],\n // Terminal states: no outbound transitions.\n [FulfillmentStatus.DELIVERED]: [],\n [FulfillmentStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each Fulfillment instance was loaded with,\n * for the save-time transition guard. A WeakMap keeps it out of the schema and\n * GCs with the instance — same pattern as the other commerce models.\n */\nconst loadedFulfillmentStatus = new WeakMap<Fulfillment, FulfillmentStatus>();\n\n/**\n * Fulfillment tracks the delivery or completion of contract items.\n *\n * A contract can have multiple fulfillments (e.g., partial shipments).\n * Each fulfillment tracks what was delivered and when.\n *\n * @example\n * ```typescript\n * const fulfillment = await fulfillments.create({\n * contractId: order.id,\n * fulfillmentType: FulfillmentType.SHIPMENT,\n * trackingNumber: 'UPS-123456789',\n * carrier: 'UPS',\n * shippingAddress: {\n * street1: '123 Main St',\n * city: 'Anytown',\n * state: 'CA',\n * postalCode: '90210',\n * country: 'US'\n * }\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Fulfillment extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global fulfillments\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract being fulfilled\n */\n @foreignKey('Contract')\n contractId: string = '';\n\n /**\n * Type of fulfillment\n */\n fulfillmentType: FulfillmentType = FulfillmentType.SHIPMENT;\n\n /**\n * Current fulfillment status\n */\n status: FulfillmentStatus = FulfillmentStatus.PENDING;\n\n /**\n * Tracking number from carrier\n */\n trackingNumber: string = '';\n\n /**\n * Shipping carrier name\n */\n carrier: string = '';\n\n /**\n * Shipping destination address\n */\n shippingAddress: Address = {};\n\n /**\n * When the shipment was sent\n */\n shippedAt: Date | null = null;\n\n /**\n * When the shipment was delivered\n */\n deliveredAt: Date | null = null;\n\n /**\n * Estimated delivery date\n */\n estimatedDelivery: Date | null = null;\n\n /**\n * Notes about this fulfillment\n */\n notes: string = '';\n\n constructor(options: FulfillmentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.fulfillmentType !== undefined)\n this.fulfillmentType = options.fulfillmentType;\n if (options.status !== undefined) this.status = options.status;\n if (options.trackingNumber !== undefined)\n this.trackingNumber = options.trackingNumber;\n if (options.carrier !== undefined) this.carrier = options.carrier;\n if (options.shippingAddress !== undefined)\n this.shippingAddress = options.shippingAddress;\n if (options.shippedAt !== undefined) this.shippedAt = options.shippedAt;\n if (options.deliveredAt !== undefined)\n this.deliveredAt = options.deliveredAt;\n if (options.estimatedDelivery !== undefined)\n this.estimatedDelivery = options.estimatedDelivery;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Capture the status the row was loaded with so the save-time transition\n * guard can reject illegal status flips made via raw field assignment\n * (mass-assignment on the generated update route, a stale caller, etc.).\n * Only persisted rows carry a prior status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedFulfillmentStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Validate the status transition before persisting, then save. Blocks a\n * forged `status` written via raw mass-assignment on the open\n * `api:{create,update}` surface. See S5 audit #1390 follow-up.\n */\n override async save(): Promise<this> {\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n const result = (await super.save()) as this;\n loadedFulfillmentStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Reject an illegal status flip done via raw assignment. No-op transitions\n * and brand-new rows are always allowed.\n */\n private assertStatusTransition(prior: FulfillmentStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = FULFILLMENT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Fulfillment ${this.trackingNumber || this.id}: illegal status ` +\n `transition '${prior}' → '${this.status}'. Use the guarded helpers ` +\n '(markShipped / markDelivered / cancel).',\n );\n }\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status. The WeakMap is only populated when\n * {@link initialize} loaded the row; it is empty for an instance built via\n * `collection.create({ id: <existing>, _skipLoad: true })` (the upsert path\n * that writes onto an existing row without hydrating it). Trusting an empty\n * WeakMap there would treat the write as a brand-new row and skip the guard.\n * So when this instance carries an `id`, read the persisted row straight from\n * the DB and treat its `status` as the prior. `undefined` = genuinely new.\n * Mirrors the authoritative-prior-load on Contract/Payment/Invoice/Payout.\n */\n private async resolvePriorStatus(): Promise<FulfillmentStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as FulfillmentStatus;\n }\n } catch {\n // DB not ready / table absent — fall through to the in-memory record.\n }\n }\n return loadedFulfillmentStatus.get(this);\n }\n\n /**\n * Check if fulfillment is complete\n */\n isDelivered(): boolean {\n return this.status === FulfillmentStatus.DELIVERED;\n }\n\n /**\n * Check if fulfillment is in transit\n */\n isInTransit(): boolean {\n return this.status === FulfillmentStatus.SHIPPED;\n }\n\n /**\n * Check if fulfillment is pending\n */\n isPending(): boolean {\n return this.status === FulfillmentStatus.PENDING;\n }\n\n /**\n * Assert the current in-memory status may legally transition to `next`,\n * surfacing the illegal-transition error at the mutation call site rather\n * than deferring it to save(). A no-op (already in `next`) is allowed.\n */\n private assertCanTransitionTo(next: FulfillmentStatus): void {\n if (this.status === next) return;\n const allowed = FULFILLMENT_STATUS_TRANSITIONS[this.status] ?? [];\n if (!allowed.includes(next)) {\n throw new Error(\n `Fulfillment ${this.trackingNumber || this.id || '<new>'}: cannot move ` +\n `from '${this.status}' to '${next}'.`,\n );\n }\n }\n\n /**\n * Mark as shipped. Rejected from a terminal state (DELIVERED / CANCELLED).\n */\n markShipped(trackingNumber?: string, carrier?: string): void {\n this.assertCanTransitionTo(FulfillmentStatus.SHIPPED);\n this.status = FulfillmentStatus.SHIPPED;\n this.shippedAt = new Date();\n if (trackingNumber) this.trackingNumber = trackingNumber;\n if (carrier) this.carrier = carrier;\n }\n\n /**\n * Mark as delivered. Rejected from CANCELLED (a cancelled shipment can't be\n * delivered).\n */\n markDelivered(): void {\n this.assertCanTransitionTo(FulfillmentStatus.DELIVERED);\n this.status = FulfillmentStatus.DELIVERED;\n this.deliveredAt = new Date();\n }\n\n /**\n * Cancel fulfillment. Rejected once DELIVERED (a delivered shipment can't be\n * cancelled — model a return/refund elsewhere instead).\n */\n cancel(): void {\n this.assertCanTransitionTo(FulfillmentStatus.CANCELLED);\n this.status = FulfillmentStatus.CANCELLED;\n }\n}\n\nexport default Fulfillment;\n","/**\n * FulfillmentCollection - Collection manager for Fulfillment objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Fulfillment } from '../models/Fulfillment.js';\nimport { FulfillmentStatus, type FulfillmentType } from '../types/index.js';\n\nexport class FulfillmentCollection extends SmrtCollection<Fulfillment> {\n static readonly _itemClass = Fulfillment;\n\n /**\n * Find fulfillments by contract\n *\n * @param contractId - Contract ID\n * @returns Array of fulfillments\n */\n async findByContract(contractId: string): Promise<Fulfillment[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find fulfillments by status\n *\n * @param status - Fulfillment status\n * @returns Array of fulfillments\n */\n async findByStatus(status: FulfillmentStatus): Promise<Fulfillment[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find fulfillments by type\n *\n * @param fulfillmentType - Fulfillment type\n * @returns Array of fulfillments\n */\n async findByType(fulfillmentType: FulfillmentType): Promise<Fulfillment[]> {\n return await this.list({\n where: { fulfillmentType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all pending fulfillments\n *\n * @returns Array of pending fulfillments\n */\n async findPending(): Promise<Fulfillment[]> {\n return await this.findByStatus(FulfillmentStatus.PENDING);\n }\n\n /**\n * Find all in-transit shipments\n *\n * @returns Array of shipped fulfillments\n */\n async findInTransit(): Promise<Fulfillment[]> {\n return await this.findByStatus(FulfillmentStatus.SHIPPED);\n }\n\n /**\n * Find by tracking number\n *\n * @param trackingNumber - Tracking number\n * @returns Fulfillment or null\n */\n async findByTracking(trackingNumber: string): Promise<Fulfillment | null> {\n const results = await this.list({\n where: { trackingNumber },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find fulfillments shipped in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of fulfillments\n */\n async findShippedInRange(\n startDate: Date,\n endDate: Date,\n ): Promise<Fulfillment[]> {\n return await this.list({\n where: {\n 'shippedAt >=': startDate.toISOString(),\n 'shippedAt <=': endDate.toISOString(),\n },\n orderBy: 'shippedAt DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all fulfillments belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of fulfillments for the tenant\n */\n async findByTenant(tenantId: string): Promise<Fulfillment[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global fulfillments (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global fulfillments\n */\n async findGlobal(): Promise<Fulfillment[]> {\n return queryGlobal<Fulfillment>(this);\n }\n\n /**\n * Find fulfillments for a tenant including global fulfillments.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global fulfillments\n */\n async findWithGlobals(tenantId: string): Promise<Fulfillment[]> {\n return queryWithGlobals<Fulfillment>(\n this,\n tenantId,\n 'Fulfillment.findWithGlobals',\n );\n }\n}\n","/**\n * FulfillmentLineItem model - tracks which contract items were fulfilled\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { FulfillmentLineItemOptions } from '../types/index.js';\n\n/**\n * Sub-unit rounding tolerance for the over-fulfillment cap, matching the rest\n * of the package's EPSILON convention. Quantities are decimal, so a tiny\n * float-summation overshoot must not trip the guard.\n */\nconst FULFILLMENT_QUANTITY_EPSILON = 0.01;\n\n/**\n * FulfillmentLineItem tracks which contract line items are included\n * in a specific fulfillment and how much was fulfilled.\n *\n * This allows partial fulfillment - e.g., shipping 5 of 10 items ordered.\n *\n * @example\n * ```typescript\n * const fulfillmentItem = await fulfillmentItems.create({\n * fulfillmentId: fulfillment.id,\n * contractLineItemId: lineItem.id,\n * quantityFulfilled: 5 // of 10 ordered\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class FulfillmentLineItem extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global fulfillment line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent fulfillment\n */\n @foreignKey('Fulfillment')\n fulfillmentId: string = '';\n\n /**\n * Contract line item being fulfilled\n */\n @foreignKey('ContractLineItem')\n contractLineItemId: string = '';\n\n /**\n * Quantity fulfilled in this fulfillment\n */\n quantityFulfilled: number = 1.0;\n\n /**\n * Notes about this line item\n */\n notes: string = '';\n\n constructor(options: FulfillmentLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.fulfillmentId !== undefined)\n this.fulfillmentId = options.fulfillmentId;\n if (options.contractLineItemId !== undefined)\n this.contractLineItemId = options.contractLineItemId;\n if (options.quantityFulfilled !== undefined)\n this.quantityFulfilled = options.quantityFulfilled;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Save-time over-fulfillment guard (S5 audit #1390 follow-up, round 2):\n * - `quantityFulfilled` must be a finite, positive number,\n * - the referenced `ContractLineItem` must resolve **within the caller's\n * tenant scope** AND belong to the same contract as the parent\n * Fulfillment, and\n * - the sum of all FulfillmentLineItems against that ContractLineItem\n * (this row included) must not exceed the ordered `ContractLineItem.quantity`.\n *\n * Without this, a caller could ship 50 of 10 ordered — the direct parallel\n * to the PaymentAllocation over-allocation hole #1390 closed.\n *\n * Tenant isolation (round 2): the ContractLineItem is loaded through\n * `ContractLineItemCollection`, so it goes through the tenancy\n * auto-filtering interceptors. A cross-tenant `contractLineItemId` therefore\n * fails closed — it resolves to `null` and trips the existing \"does not\n * exist\" error rather than leaking a foreign-tenant ordered quantity. The\n * raw `this.db.get('contract_line_items', …)` it replaced bypassed those\n * filters. We additionally load the parent Fulfillment (also tenant-scoped)\n * and reject when the line item belongs to a *different* contract than the\n * Fulfillment is fulfilling, so a valid-but-unrelated line id can't be\n * used to fulfill against the wrong order.\n */\n override async save(): Promise<this> {\n if (\n !Number.isFinite(this.quantityFulfilled) ||\n this.quantityFulfilled <= 0\n ) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: quantityFulfilled must be a ` +\n `positive number (got ${this.quantityFulfilled}).`,\n );\n }\n\n if (this.contractLineItemId) {\n const { ContractLineItemCollection } = await import(\n '../collections/ContractLineItemCollection.js'\n );\n const lineItems = await ContractLineItemCollection.create(this.options);\n const orderedLineItem = await lineItems.get(this.contractLineItemId);\n if (!orderedLineItem) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: referenced ContractLineItem ` +\n `'${this.contractLineItemId}' does not exist — refusing to fulfill ` +\n 'against a missing line item (the over-fulfillment cap cannot be ' +\n 'enforced otherwise).',\n );\n }\n\n // The line item must belong to the same contract the parent Fulfillment\n // is fulfilling. Otherwise a caller could point a Fulfillment for\n // contract A at a line item from contract B (over-fulfilling B's order\n // via A's shipment, and falsifying both contracts' fulfilled totals).\n if (this.fulfillmentId) {\n const { FulfillmentCollection } = await import(\n '../collections/FulfillmentCollection.js'\n );\n const fulfillments = await FulfillmentCollection.create(this.options);\n const fulfillment = await fulfillments.get(this.fulfillmentId);\n if (\n fulfillment &&\n orderedLineItem.contractId !== fulfillment.contractId\n ) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: ContractLineItem ` +\n `'${this.contractLineItemId}' belongs to contract ` +\n `'${orderedLineItem.contractId}', but its Fulfillment ` +\n `'${this.fulfillmentId}' fulfills contract ` +\n `'${fulfillment.contractId}' — refusing to fulfill a line item ` +\n 'from a different contract.',\n );\n }\n }\n\n const ordered = Number(orderedLineItem.quantity);\n\n const { FulfillmentLineItemCollection } = await import(\n '../collections/FulfillmentLineItemCollection.js'\n );\n const siblings = await FulfillmentLineItemCollection.create(this.options);\n const existing = await siblings.findByContractLineItem(\n this.contractLineItemId,\n );\n const otherFulfilled = existing.reduce(\n (sum: number, item: FulfillmentLineItem) =>\n item.id === this.id ? sum : sum + item.quantityFulfilled,\n 0,\n );\n\n if (\n Number.isFinite(ordered) &&\n otherFulfilled + this.quantityFulfilled - ordered >\n FULFILLMENT_QUANTITY_EPSILON\n ) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: fulfilling ` +\n `${this.quantityFulfilled} would over-fulfill contract line ` +\n `'${this.contractLineItemId}' — already fulfilled ${otherFulfilled} ` +\n `of ${ordered} ordered.`,\n );\n }\n }\n\n return super.save() as Promise<this>;\n }\n}\n\nexport default FulfillmentLineItem;\n","/**\n * FulfillmentLineItemCollection - Collection manager for FulfillmentLineItem objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { FulfillmentLineItem } from '../models/FulfillmentLineItem.js';\n\nexport class FulfillmentLineItemCollection extends SmrtCollection<FulfillmentLineItem> {\n static readonly _itemClass = FulfillmentLineItem;\n\n /**\n * Find fulfillment line items by their parent fulfillment.\n *\n * @param fulfillmentId - Fulfillment ID\n * @returns Array of fulfillment line items\n */\n async findByFulfillment(\n fulfillmentId: string,\n ): Promise<FulfillmentLineItem[]> {\n return await this.list({\n where: { fulfillmentId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find every fulfillment line item that fulfills a given contract line item,\n * across all fulfillments. Used by the over-fulfillment cap to sum how much\n * of an ordered line has already been fulfilled.\n *\n * @param contractLineItemId - Contract line item ID\n * @returns Array of fulfillment line items targeting that contract line\n */\n async findByContractLineItem(\n contractLineItemId: string,\n ): Promise<FulfillmentLineItem[]> {\n return await this.list({\n where: { contractLineItemId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Sum the quantity already fulfilled for a contract line item across all\n * fulfillments.\n *\n * @param contractLineItemId - Contract line item ID\n * @returns Total quantity fulfilled\n */\n async getTotalFulfilledForContractLine(\n contractLineItemId: string,\n ): Promise<number> {\n const items = await this.findByContractLineItem(contractLineItemId);\n return items.reduce((sum, item) => sum + item.quantityFulfilled, 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all fulfillment line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of fulfillment line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<FulfillmentLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global fulfillment line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global fulfillment line items\n */\n async findGlobal(): Promise<FulfillmentLineItem[]> {\n return queryGlobal<FulfillmentLineItem>(this);\n }\n\n /**\n * Find fulfillment line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global fulfillment line items\n */\n async findWithGlobals(tenantId: string): Promise<FulfillmentLineItem[]> {\n return queryWithGlobals<FulfillmentLineItem>(\n this,\n tenantId,\n 'FulfillmentLineItem.findWithGlobals',\n );\n }\n}\n","/**\n * PaymentIntent — short-lived pre-payment commitment with multi-option\n * semantics.\n *\n * A PaymentIntent is a one-shot quote that locks a USD price for a fixed\n * window and lists one or more `PaymentOption`s describing different\n * payment rails (USDC-on-Base, BTC, Stripe, etc.) that can each satisfy\n * the intent. The first option that receives an incoming payment wins —\n * the intent transitions to {@link PaymentIntentStatus.PAID} and the\n * other options are implicitly retired. Later inbound funds to a\n * retired option must be flagged for refund by the consumer's\n * payment-routing layer.\n *\n * Distinct from `Estimate` (a long-form contract proposal STI subtype\n * of `Contract`): a `PaymentIntent` is short-lived (minutes, not days),\n * carries no line items, and exists to coordinate the moment-of-payment.\n *\n * Industry-neutral: any application that offers more than one route to\n * pay for the same thing — marketplaces, billing systems with multi-\n * provider checkout, agent-driven (x402) flows — benefits.\n *\n * @packageDocumentation\n */\n\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type PaymentIntentOptions,\n PaymentIntentStatus,\n type PaymentOption,\n PaymentStatus,\n} from '../types/index.js';\n\n/*\n * There is deliberately no `PAYMENT_INTENT_EPSILON` here any more (#2401).\n *\n * `PaymentOption.nativeAmount` and `Payment.nativeAmount` / `Payment.amount`\n * are all integer minor units, so reconciling the winning option against the\n * payment that arrived is an exact equality. A tolerance would let a payment\n * that is a cent short satisfy an option it does not actually cover.\n */\n\n/**\n * Legal status transitions for a PaymentIntent, keyed by the prior persisted\n * status. Mirrors the state machine documented on {@link PaymentIntentStatus}\n * and enforced by the dedicated helpers, so a raw `status` mass-assignment on\n * the generated update route can't skip steps (e.g. `awaiting_payment →\n * issued`) or revive a terminal state.\n *\n * `awaiting_payment → paid → (issued | retired)`, with `expired` / `cancelled`\n * as alternate terminal exits from `awaiting_payment`. `issued` may still be\n * `retired` (a reversal after rights were issued).\n */\nconst PAYMENT_INTENT_STATUS_TRANSITIONS: Record<\n PaymentIntentStatus,\n PaymentIntentStatus[]\n> = {\n [PaymentIntentStatus.AWAITING_PAYMENT]: [\n PaymentIntentStatus.PAID,\n PaymentIntentStatus.EXPIRED,\n PaymentIntentStatus.CANCELLED,\n ],\n [PaymentIntentStatus.PAID]: [\n PaymentIntentStatus.ISSUED,\n PaymentIntentStatus.RETIRED,\n ],\n [PaymentIntentStatus.ISSUED]: [PaymentIntentStatus.RETIRED],\n // Terminal states.\n [PaymentIntentStatus.RETIRED]: [],\n [PaymentIntentStatus.EXPIRED]: [],\n [PaymentIntentStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each PaymentIntent was loaded with, so\n * the save-time guard can detect a status being forced to PAID via raw\n * mass-assignment (bypassing the verified transition path). Same WeakMap\n * pattern as the rest of the package.\n */\nconst loadedIntentStatus = new WeakMap<PaymentIntent, PaymentIntentStatus>();\n\n/**\n * Default price-lock window — 15 minutes is a reasonable middle ground:\n * long enough for a buyer to swap wallets / approve a Stripe payment,\n * short enough that volatile-currency drift stays bounded.\n */\nconst DEFAULT_PRICE_LOCK_WINDOW_MS = 15 * 60 * 1000;\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Natural-key idempotency: a consumer that retries `create` with the\n // same (offeringRef, licenseeEmail, idempotencyKey) within the\n // retention window will hit the existing row via upsert rather than\n // creating a duplicate. Tenant id is part of the key so the same\n // idempotency key never collides across tenants.\n conflictColumns: [\n 'tenant_id',\n 'offering_ref',\n 'licensee_email',\n 'idempotency_key',\n ],\n // ROOT FIX (S5 audit #1390 round 4): the generated create/update surface may\n // only set the quote-definition fields. The PAID-state backing fields\n // (`status`, `paymentId`, `paidOptionBackendId`) are EXCLUDED — they are set\n // exclusively by the verified `verifyAndMarkPaid()` / `markIssued()` path, so\n // a caller can never forge a PAID intent (or repoint one) through a generated\n // route. `paymentOptions` IS writable (a quote may be re-priced while\n // AWAITING_PAYMENT) but is frozen by `save()` once the intent is PAID/ISSUED\n // (codex HIGH#2). Timestamps + priceLockExpiresAt are derived and excluded.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'skuId',\n 'offeringRef',\n 'licenseeEmail',\n 'customerId',\n 'paymentOptions',\n 'usdPriceLocked',\n 'priceLockWindowMs',\n 'idempotencyKey',\n 'notes',\n ],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class PaymentIntent extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable so the same model\n * can serve both per-tenant SaaS deployments and single-tenant /\n * global setups.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Plain string reference to the upstream `Sku` (from\n * `@happyvertical/smrt-products`) being purchased. Cross-package\n * reference — plain string, not `@foreignKey()`, to avoid the\n * circular dependency the framework warns against.\n *\n * Required for the marketplace flow; other consumers can leave it\n * empty if they don't model a catalog.\n */\n skuId: string = '';\n\n /**\n * Caller-supplied scope for the idempotency-key natural key. The\n * marketplace sets this to a `Sku` id, but the field is intentionally\n * abstract so other consumers (subscription tiers, donation tiers,\n * tipping flows) can scope idempotency by whatever granularity makes\n * sense.\n *\n * Empty string is permitted but disables the natural-key dedup —\n * every retry then creates a fresh row.\n */\n offeringRef: string = '';\n\n /**\n * The buyer's email address. The licensee on any downstream\n * `LicenseSale` (or similar rights-issuance row) is identified by\n * this email — high-tier purchases also create a `Customer` record\n * and set {@link customerId}, but most purchases get away with email\n * alone.\n */\n licenseeEmail: string = '';\n\n /**\n * Optional link to a `Customer` row for purchases that warrant a\n * full customer record (high-tier licensing, recurring buyers,\n * etc.). Cross-model reference is kept as a plain string to match\n * the rest of the package's cross-package convention.\n */\n customerId: string = '';\n\n /**\n * The payment rails the buyer can choose between to satisfy this\n * intent. Stored as a JSON column. See {@link PaymentOption} for\n * the per-option shape. Empty array means the intent is invalid /\n * mis-built; callers should reject save in that case.\n */\n paymentOptions: PaymentOption[] = [];\n\n /**\n * USD price locked at quote time, in **integer minor units** (US cents).\n * Independent of any specific option's native-currency amount;\n * `usdPriceLocked` is the canonical \"what this costs in USD\"\n * number used downstream for reporting, tax, and drift accounting (#2401).\n */\n usdPriceLocked: number = 0;\n\n /**\n * Length of the price-lock window in milliseconds. Defaults to 15\n * minutes; consumers can override per-intent. Stored so a later\n * audit can see the original window even after {@link priceLockExpiresAt}\n * has passed.\n */\n priceLockWindowMs: number = DEFAULT_PRICE_LOCK_WINDOW_MS;\n\n /**\n * Wall-clock time at which the price lock expires. Set by the\n * constructor (or by `expire()` if you want to short-circuit a\n * specific intent). Once `Date.now()` passes this value and the\n * status is still `AWAITING_PAYMENT`, callers should treat the\n * intent as expired and refuse to record a payment against it.\n */\n priceLockExpiresAt: Date | null = null;\n\n /**\n * Current status — see {@link PaymentIntentStatus} for the state\n * machine. Mutate via the dedicated `markPaid` / `markIssued` /\n * `expire` / `cancel` / `retire` helpers rather than assigning\n * directly, so the helpers' invariant checks run.\n */\n status: PaymentIntentStatus = PaymentIntentStatus.AWAITING_PAYMENT;\n\n /**\n * Caller-supplied idempotency key. Combined with `tenantId`,\n * `offeringRef`, and `licenseeEmail`, this forms the natural key\n * registered in `conflictColumns` — a retried `create` with the\n * same tuple upserts the existing row instead of creating a\n * duplicate.\n *\n * Empty string disables natural-key dedup (every retry creates a\n * fresh row).\n */\n idempotencyKey: string = '';\n\n /**\n * `backendId` of the option that satisfied the intent. Set by\n * {@link markPaid}; remains empty for non-paid intents. Used by\n * {@link isOptionRetired} to flag inbound funds on the other\n * options as \"needs refund\".\n */\n paidOptionBackendId: string = '';\n\n /**\n * Plain string reference to the `Payment` row that satisfied the\n * intent. Cross-model reference matches the rest of the\n * package's convention.\n */\n paymentId: string = '';\n\n /**\n * When the intent transitioned to `PAID`.\n */\n paidAt: Date | null = null;\n\n /**\n * When the intent transitioned to `ISSUED`.\n */\n issuedAt: Date | null = null;\n\n /**\n * When the intent transitioned to `EXPIRED`.\n */\n expiredAt: Date | null = null;\n\n /**\n * When the intent transitioned to `CANCELLED`.\n */\n cancelledAt: Date | null = null;\n\n /**\n * When the intent transitioned to `RETIRED`.\n */\n retiredAt: Date | null = null;\n\n /**\n * Optional human-readable notes (e.g., support tickets, refund\n * memos, cancellation reasons). Append-only by convention.\n */\n notes: string = '';\n\n constructor(options: PaymentIntentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.skuId !== undefined) this.skuId = options.skuId;\n if (options.offeringRef !== undefined)\n this.offeringRef = options.offeringRef;\n if (options.licenseeEmail !== undefined)\n this.licenseeEmail = options.licenseeEmail;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.paymentOptions !== undefined)\n this.paymentOptions = PaymentIntent.normalizePaymentOptions(\n options.paymentOptions,\n );\n if (options.usdPriceLocked !== undefined)\n this.usdPriceLocked = options.usdPriceLocked;\n if (options.priceLockWindowMs !== undefined)\n this.priceLockWindowMs = options.priceLockWindowMs;\n if (options.priceLockExpiresAt !== undefined) {\n this.priceLockExpiresAt = PaymentIntent.coerceDate(\n options.priceLockExpiresAt,\n );\n }\n if (options.status !== undefined) this.status = options.status;\n if (options.idempotencyKey !== undefined)\n this.idempotencyKey = options.idempotencyKey;\n if (options.paidOptionBackendId !== undefined)\n this.paidOptionBackendId = options.paidOptionBackendId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.paidAt !== undefined)\n this.paidAt = PaymentIntent.coerceDate(options.paidAt);\n if (options.issuedAt !== undefined)\n this.issuedAt = PaymentIntent.coerceDate(options.issuedAt);\n if (options.expiredAt !== undefined)\n this.expiredAt = PaymentIntent.coerceDate(options.expiredAt);\n if (options.cancelledAt !== undefined)\n this.cancelledAt = PaymentIntent.coerceDate(options.cancelledAt);\n if (options.retiredAt !== undefined)\n this.retiredAt = PaymentIntent.coerceDate(options.retiredAt);\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Re-normalize `paymentOptions` after the framework's\n * `initializePropertiesFromOptions` pass has overwritten the\n * constructor-set values with the raw cloned input, and stamp a\n * default `priceLockExpiresAt` so newly-created intents have a\n * concrete expiry without callers needing to compute it.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.paymentOptions = PaymentIntent.normalizePaymentOptions(\n this.paymentOptions as unknown,\n );\n this.priceLockExpiresAt = PaymentIntent.coerceDate(this.priceLockExpiresAt);\n this.paidAt = PaymentIntent.coerceDate(this.paidAt);\n this.issuedAt = PaymentIntent.coerceDate(this.issuedAt);\n this.expiredAt = PaymentIntent.coerceDate(this.expiredAt);\n this.cancelledAt = PaymentIntent.coerceDate(this.cancelledAt);\n this.retiredAt = PaymentIntent.coerceDate(this.retiredAt);\n if (!this.priceLockExpiresAt) {\n // Anchor the price-lock window to \"now\" if no explicit expiry\n // was supplied. Using the configured window so consumers that\n // override `priceLockWindowMs` get the right anchor.\n this.priceLockExpiresAt = new Date(\n Date.now() + (this.priceLockWindowMs || DEFAULT_PRICE_LOCK_WINDOW_MS),\n );\n }\n if (await this.isSaved()) {\n loadedIntentStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Save-time state-machine guard (S5 audit #1390 round 2).\n *\n * `status`, `paymentId`, and `paidOptionBackendId` are all mass-assignable on\n * the generated update route, and the sync `markPaid` helper trusts its\n * arguments. Two distinct attacks follow:\n *\n * 1. **Forge a PAID intent** — set `status: 'paid'` (or call bare `markPaid`)\n * against a bogus / pending / non-matching Payment. This falsifies\n * downstream \"this was paid for\" rights issuance.\n * 2. **Repoint an already-PAID intent** — leave `status: 'paid'` but swap\n * `paymentId` / `paidOptionBackendId` to a bogus Payment after the fact.\n * Round 1 only verified the *transition into* PAID, so an already-PAID\n * row could be silently repointed with no re-verify. Likewise an intent\n * forced straight to `ISSUED` (the terminal happy-path that gates rights\n * issuance) was never required to have been backed by a real Payment.\n *\n * This guard therefore:\n * - validates the status transition is legal (no `awaiting_payment → issued`\n * skips, no reviving terminal states);\n * - re-verifies the backing Payment on **every** save where the persisted\n * status is PAID or ISSUED — not just the transition edge — so a repoint\n * of the paid backing fields is caught.\n *\n * Re-verifying an unchanged PAID/ISSUED row is cheap (one Payment load) and\n * closes the repoint hole; freezing the backing fields would instead block\n * legitimate corrections, so we re-verify.\n */\n override async save(): Promise<this> {\n const priorRow = await this.loadPersistedRow();\n const prior = priorRow\n ? (priorRow.status as PaymentIntentStatus)\n : loadedIntentStatus.get(this);\n this.assertStatusTransition(prior);\n\n // Freeze the backing fields once the intent is already PAID/ISSUED in the\n // database (codex HIGH#2). Round 3 re-verified the backing Payment but did\n // so against the *mutable* in-memory `paymentOptions`, so a caller could\n // swap `paymentId` to a different completed Payment AND edit the matching\n // option's `nativeAmount` so the reconciliation still passed — silently\n // repointing a settled intent at unrelated funds. A PAID/ISSUED intent's\n // economic identity (paymentId + paidOptionBackendId + usdPriceLocked +\n // paymentOptions) is settled and must not change; corrections go through\n // `retire()` + a fresh intent.\n if (\n priorRow &&\n (priorRow.status === PaymentIntentStatus.PAID ||\n priorRow.status === PaymentIntentStatus.ISSUED)\n ) {\n this.assertBackingFieldsUnchanged(priorRow);\n }\n\n const persistingPaidOrIssued =\n this.status === PaymentIntentStatus.PAID ||\n this.status === PaymentIntentStatus.ISSUED;\n\n if (persistingPaidOrIssued) {\n await this.assertBackedByCompletedPayment();\n }\n\n const result = (await super.save()) as this;\n loadedIntentStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Load the authoritative persisted row for this intent (S5 audit #1390 round\n * 4). Returns `undefined` when the intent has no `id` or no row exists yet\n * (truly new). Reading the DB directly — rather than trusting the\n * {@link loadedIntentStatus} WeakMap, which is only populated when\n * {@link initialize} hydrated the row — defeats the poisonable-prior-state\n * vector where `create({ id: <existing>, _skipLoad: true })` produces an\n * un-hydrated instance whose WeakMap entry is missing. A create-onto-existing\n * is thus correctly treated as an update.\n */\n private async loadPersistedRow(): Promise<\n Record<string, unknown> | undefined\n > {\n if (!this.id) return undefined;\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n return row ?? undefined;\n } catch {\n // DB not ready / table absent — treat as new (in-memory fallback in save).\n return undefined;\n }\n }\n\n /**\n * Reject any change to the settled backing fields of an already-PAID/ISSUED\n * intent (codex HIGH#2). `paymentOptions` is compared structurally because\n * the winning option's `nativeAmount` is exactly what the reconciliation in\n * {@link assertBackedByCompletedPayment} binds against — letting it drift\n * would let a repointed `paymentId` match a doctored option.\n */\n private assertBackingFieldsUnchanged(\n priorRow: Record<string, unknown>,\n ): void {\n const frozen: Array<[string, unknown, unknown]> = [\n ['paymentId', priorRow.payment_id ?? '', this.paymentId],\n [\n 'paidOptionBackendId',\n priorRow.paid_option_backend_id ?? '',\n this.paidOptionBackendId,\n ],\n [\n 'usdPriceLocked',\n Number(priorRow.usd_price_locked ?? 0),\n Number(this.usdPriceLocked ?? 0),\n ],\n ];\n for (const [field, prior, next] of frozen) {\n if (prior !== next) {\n throw new Error(\n `PaymentIntent ${this.id}: '${field}' is frozen once the intent is ` +\n `PAID/ISSUED (was '${prior}', got '${next}'). A settled intent's ` +\n 'backing fields cannot be repointed — retire() it and issue a new intent.',\n );\n }\n }\n\n const priorOptions = PaymentIntent.normalizePaymentOptions(\n priorRow.payment_options as unknown,\n );\n const nextOptions = PaymentIntent.normalizePaymentOptions(\n this.paymentOptions as unknown,\n );\n if (JSON.stringify(priorOptions) !== JSON.stringify(nextOptions)) {\n throw new Error(\n `PaymentIntent ${this.id}: 'paymentOptions' is frozen once the intent ` +\n 'is PAID/ISSUED — the winning option backs the verified Payment ' +\n 'reconciliation and cannot change. retire() it and issue a new intent.',\n );\n }\n }\n\n /**\n * Reject an illegal status flip done via raw field assignment. Compares the\n * about-to-be-written status against the status the row was loaded with.\n * No-op re-saves (status unchanged) and brand-new rows are allowed (the\n * backing-Payment verification still runs separately for PAID/ISSUED).\n */\n private assertStatusTransition(prior: PaymentIntentStatus | undefined): void {\n if (prior === undefined) return; // new row\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYMENT_INTENT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `PaymentIntent ${this.id}: illegal status transition '${prior}' → ` +\n `'${this.status}'. Use the guarded transition helpers ` +\n '(verifyAndMarkPaid / markIssued / expire / cancel / retire).',\n );\n }\n }\n\n /**\n * Verify the intent's `paidOptionBackendId` / `paymentId` reference a real,\n * COMPLETED, amount-matching Payment. Throws otherwise. Shared by\n * {@link verifyAndMarkPaid} (pre-transition) and the save-time guard\n * (catch-all for raw mass-assignment).\n */\n private async assertBackedByCompletedPayment(): Promise<void> {\n if (!this.paidOptionBackendId) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot persist PAID status without a paidOptionBackendId`,\n );\n }\n if (!this.paymentId) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot persist PAID status without a paymentId`,\n );\n }\n const option = this.getOption(this.paidOptionBackendId);\n if (!option) {\n throw new Error(\n `PaymentIntent ${this.id}: paidOptionBackendId '${this.paidOptionBackendId}' is not one of the listed options`,\n );\n }\n\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: this.paymentId });\n if (!payment) {\n throw new Error(\n `PaymentIntent ${this.id}: referenced Payment '${this.paymentId}' does not exist`,\n );\n }\n if (payment.status !== PaymentStatus.COMPLETED) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${this.paymentId}' is not COMPLETED ` +\n `(status='${payment.status}'); cannot persist PAID intent against it`,\n );\n }\n this.reconcilePaymentWithOption(payment, option, this.paymentId);\n }\n\n /**\n * Reconcile a COMPLETED Payment against the winning {@link PaymentOption}\n * (S5 audit #1390). Beyond the amount check, the Payment must have arrived on\n * the SAME rail and currency the option quoted — otherwise an unrelated\n * completed payment that merely shares a numeric amount (e.g. a USD 199\n * payment) could satisfy a different option (e.g. a `base-usdc` 199 option),\n * marking the intent paid on the wrong rail with no matching funds. Compares\n * the Payment's `backendId` (rail) and native currency (falling back to its\n * settlement currency) against the option's `backendId` / `currency`, then\n * the native amount.\n *\n * Shared by {@link verifyAndMarkPaid} (pre-transition) and\n * {@link assertBackedByCompletedPayment} (save-time catch-all) so both gates\n * enforce the identical invariant.\n */\n private reconcilePaymentWithOption(\n payment: {\n backendId?: string;\n nativeCurrency?: string;\n currency?: string;\n nativeAmount?: number;\n amount?: number;\n },\n option: PaymentOption,\n paymentIdLabel: string,\n ): void {\n // Only enforce the rail check when the Payment actually recorded a backend\n // (PaymentBackend-routed flows). Manually-recorded payments may carry no\n // backendId; the amount + currency checks still apply.\n if (payment.backendId && payment.backendId !== option.backendId) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${paymentIdLabel}' arrived on rail ` +\n `'${payment.backendId}' but option '${option.backendId}' expects rail ` +\n `'${option.backendId}' — cannot mark paid on a mismatched rail`,\n );\n }\n // The option's `currency` is the native-rail-qualified code (e.g.\n // `USDC-base`), which lines up with the Payment's `nativeCurrency`. Fall\n // back to the settlement `currency` when no native currency was recorded.\n const paymentCurrency = payment.nativeCurrency || payment.currency || '';\n if (paymentCurrency && paymentCurrency !== option.currency) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${paymentIdLabel}' currency ` +\n `'${paymentCurrency}' does not match option '${option.backendId}' ` +\n `currency '${option.currency}'`,\n );\n }\n const paymentNative =\n typeof payment.nativeAmount === 'number' && payment.nativeAmount > 0\n ? payment.nativeAmount\n : payment.amount;\n if (\n typeof paymentNative !== 'number' ||\n paymentNative !== option.nativeAmount\n ) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${paymentIdLabel}' amount ${paymentNative} ` +\n `does not match option '${option.backendId}' nativeAmount ${option.nativeAmount}`,\n );\n }\n }\n\n // -------- Status helpers --------\n\n isAwaitingPayment(): boolean {\n return this.status === PaymentIntentStatus.AWAITING_PAYMENT;\n }\n\n isPaid(): boolean {\n return this.status === PaymentIntentStatus.PAID;\n }\n\n isIssued(): boolean {\n return this.status === PaymentIntentStatus.ISSUED;\n }\n\n isCancelled(): boolean {\n return this.status === PaymentIntentStatus.CANCELLED;\n }\n\n isRetired(): boolean {\n return this.status === PaymentIntentStatus.RETIRED;\n }\n\n /**\n * Terminal-state predicate: any status other than `AWAITING_PAYMENT`\n * is terminal (the intent will not accept further state changes\n * except the explicit `markIssued` after `PAID`).\n */\n isTerminal(): boolean {\n return this.status !== PaymentIntentStatus.AWAITING_PAYMENT;\n }\n\n /**\n * `Date.now()`-based predicate: has the price lock window passed?\n * Independent of `status` so callers can detect a stale-but-not-\n * yet-marked-EXPIRED intent (and call `expire()` to advance it).\n */\n isExpired(): boolean {\n if (this.status === PaymentIntentStatus.EXPIRED) return true;\n if (!this.priceLockExpiresAt) return false;\n return Date.now() > this.priceLockExpiresAt.getTime();\n }\n\n // -------- State transitions --------\n\n /**\n * Transition the intent to `PAID`, naming which option satisfied it\n * and which `Payment` row carries the funds. Implicitly retires the\n * other options — callers can check {@link isOptionRetired} to flag\n * later inbound funds for refund.\n *\n * Throws when called on a non-`AWAITING_PAYMENT` intent so a stale\n * caller can't accidentally overwrite a winning option with a\n * losing one.\n */\n markPaid(args: { backendId: string; paymentId: string }): void {\n if (this.status !== PaymentIntentStatus.AWAITING_PAYMENT) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot transition to PAID from status '${this.status}'`,\n );\n }\n // Refuse stale quotes. A backend can report a payment after the price-lock\n // window has passed but before a cleanup job has run `expire()` — the\n // intent is still AWAITING_PAYMENT, yet honoring it would accept funds at\n // an expired USD price. `isExpired()` is status-independent (it checks\n // `priceLockExpiresAt`), so guard on it here; callers should `expire()`\n // and re-quote rather than record against a lapsed lock.\n if (this.isExpired()) {\n throw new Error(\n `PaymentIntent ${this.id}: price lock expired at ` +\n `${this.priceLockExpiresAt?.toISOString() ?? 'unknown'}; cannot record a ` +\n `payment against a stale quote (call expire() and re-quote)`,\n );\n }\n if (!args.backendId) {\n throw new Error(\n `PaymentIntent ${this.id}: markPaid requires a backendId`,\n );\n }\n const option = this.paymentOptions.find(\n (o) => o.backendId === args.backendId,\n );\n if (!option) {\n throw new Error(\n `PaymentIntent ${this.id}: backendId '${args.backendId}' is not one of the listed options`,\n );\n }\n this.status = PaymentIntentStatus.PAID;\n this.paidOptionBackendId = args.backendId;\n this.paymentId = args.paymentId ?? '';\n this.paidAt = new Date();\n }\n\n /**\n * Verify-then-transition variant of {@link markPaid} (S5 audit #1390).\n *\n * `markPaid` trusts the caller-supplied `paymentId` / `backendId` without\n * checking the referenced `Payment` actually exists, is `COMPLETED`, or\n * carries the amount the winning option quoted. That lets a caller mark an\n * intent PAID against a non-existent or still-pending payment. This method\n * loads the `Payment` row and enforces:\n *\n * - the Payment exists,\n * - it is `COMPLETED` (not pending / failed / cancelled / refunded),\n * - it arrived on the same rail (`backendId`) and currency the option\n * quoted (so an unrelated completed payment that merely shares a numeric\n * amount can't satisfy a different option),\n * - and its amount reconciles with the winning option's `nativeAmount`\n * (within sub-cent tolerance), falling back to the option vs. the\n * Payment's settlement `amount` when no native rail is recorded.\n *\n * Only after those pass does it delegate to the sync `markPaid` for the\n * status-machine invariants.\n *\n * @param args.backendId backendId of the option that was satisfied\n * @param args.paymentId id of the Payment row carrying the funds (required)\n */\n async verifyAndMarkPaid(args: {\n backendId: string;\n paymentId: string;\n }): Promise<void> {\n if (!args.paymentId) {\n throw new Error(\n `PaymentIntent ${this.id}: verifyAndMarkPaid requires a paymentId`,\n );\n }\n const option = this.paymentOptions.find(\n (o) => o.backendId === args.backendId,\n );\n if (!option) {\n throw new Error(\n `PaymentIntent ${this.id}: backendId '${args.backendId}' is not one of the listed options`,\n );\n }\n\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: args.paymentId });\n if (!payment) {\n throw new Error(\n `PaymentIntent ${this.id}: referenced Payment '${args.paymentId}' does not exist`,\n );\n }\n if (payment.status !== PaymentStatus.COMPLETED) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${args.paymentId}' is not COMPLETED ` +\n `(status='${payment.status}'); cannot mark intent PAID against it`,\n );\n }\n\n // Reconcile the payment against the winning option — same rail, currency,\n // and amount (shared with the save-time guard so both gates agree).\n this.reconcilePaymentWithOption(payment, option, args.paymentId);\n\n this.markPaid({ backendId: args.backendId, paymentId: args.paymentId });\n }\n\n /**\n * Transition a `PAID` intent to `ISSUED` once the downstream rights\n * / contract / fulfillment have been created. Idempotent — calling\n * twice is a no-op so callers don't have to guard against retries.\n */\n markIssued(): void {\n if (this.status === PaymentIntentStatus.ISSUED) return;\n if (this.status !== PaymentIntentStatus.PAID) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot transition to ISSUED from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.ISSUED;\n this.issuedAt = new Date();\n }\n\n /**\n * Move an `AWAITING_PAYMENT` intent to `EXPIRED`. Safe to call on\n * an already-expired intent (no-op). Throws on terminal non-\n * expired states so a confused caller can't undo a paid / issued\n * intent.\n */\n expire(): void {\n if (this.status === PaymentIntentStatus.EXPIRED) return;\n if (this.status !== PaymentIntentStatus.AWAITING_PAYMENT) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot expire from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.EXPIRED;\n this.expiredAt = new Date();\n }\n\n /**\n * Cancel an open intent. Only valid from `AWAITING_PAYMENT` —\n * a paid / issued intent is no longer the buyer's to cancel; that\n * path is a refund, modelled separately on `Payment`.\n */\n cancel(reason?: string): void {\n if (this.status !== PaymentIntentStatus.AWAITING_PAYMENT) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot cancel from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.CANCELLED;\n this.cancelledAt = new Date();\n if (reason) {\n this.notes = this.notes\n ? `${this.notes}\\nCancelled: ${reason}`\n : `Cancelled: ${reason}`;\n }\n }\n\n /**\n * Mark a paid intent as retired — used when a previously-recorded\n * payment was reversed (chargeback, refund) and the intent should\n * not be considered \"satisfied\" anymore. Distinct from `cancel()`\n * which only applies to never-paid intents.\n */\n retire(reason?: string): void {\n if (\n this.status !== PaymentIntentStatus.PAID &&\n this.status !== PaymentIntentStatus.ISSUED\n ) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot retire from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.RETIRED;\n this.retiredAt = new Date();\n if (reason) {\n this.notes = this.notes\n ? `${this.notes}\\nRetired: ${reason}`\n : `Retired: ${reason}`;\n }\n }\n\n // -------- Option helpers --------\n\n /**\n * Return the option whose `backendId` matches, or `undefined` if\n * no such option is on this intent. Useful for routing inbound\n * payments to the right option's `nativeAmount` / `payTo` for\n * verification.\n */\n getOption(backendId: string): PaymentOption | undefined {\n return this.paymentOptions.find((o) => o.backendId === backendId);\n }\n\n /**\n * `true` once the intent is paid and the given option was NOT the\n * winning one. Consumers use this to flag inbound funds to retired\n * options for refund. Returns `false` for the winning option, for\n * unpaid intents, and for an unknown `backendId`.\n */\n isOptionRetired(backendId: string): boolean {\n if (!this.isPaid() && !this.isIssued() && !this.isRetired()) return false;\n if (!this.paidOptionBackendId) return false;\n if (!this.getOption(backendId)) return false;\n return backendId !== this.paidOptionBackendId;\n }\n\n /**\n * The options that were NOT selected at payment time. Returns an\n * empty array for unpaid intents so callers can iterate without\n * special-casing.\n */\n getRetiredOptions(): PaymentOption[] {\n if (!this.paidOptionBackendId) return [];\n return this.paymentOptions.filter(\n (o) => o.backendId !== this.paidOptionBackendId,\n );\n }\n\n // -------- Normalization helpers --------\n\n /**\n * Defensive coercion for `paymentOptions` input. Accepts either an\n * already-parsed array or a JSON string (e.g. from a row whose\n * column was hand-edited or migrated from a different schema).\n * Drops entries that don't carry the required `backendId` /\n * `currency` / `payTo` / `nativeAmount` quartet so downstream\n * routing code can rely on the invariant.\n *\n * `nativeAmount` must be a whole number of minor units (#2401): the\n * reconciliation in {@link reconcilePaymentWithOption} compares it to\n * `Payment.nativeAmount` with `!==`, so a fractional major-unit option could\n * never match the payment that arrives and would silently strand the intent.\n */\n private static normalizePaymentOptions(value: unknown): PaymentOption[] {\n if (value == null) return [];\n let parsed: unknown = value;\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value);\n } catch {\n return [];\n }\n }\n if (!Array.isArray(parsed)) return [];\n const out: PaymentOption[] = [];\n for (const raw of parsed) {\n if (!raw || typeof raw !== 'object') continue;\n const o = raw as Record<string, unknown>;\n const backendId = typeof o.backendId === 'string' ? o.backendId : '';\n const currency = typeof o.currency === 'string' ? o.currency : '';\n const payTo = typeof o.payTo === 'string' ? o.payTo : '';\n const nativeAmount =\n typeof o.nativeAmount === 'number' ? o.nativeAmount : Number.NaN;\n if (\n !backendId ||\n !currency ||\n !payTo ||\n !Number.isInteger(nativeAmount)\n ) {\n continue;\n }\n const option: PaymentOption = {\n backendId,\n currency,\n payTo,\n nativeAmount,\n };\n if (typeof o.chain === 'string' && o.chain) option.chain = o.chain;\n if (typeof o.memo === 'string' && o.memo) option.memo = o.memo;\n if (typeof o.x402Capable === 'boolean') {\n option.x402Capable = o.x402Capable;\n }\n if (typeof o.expiresAt === 'string' && o.expiresAt) {\n option.expiresAt = o.expiresAt;\n }\n out.push(option);\n }\n return out;\n }\n\n /**\n * Coerce a Date-ish input (Date / number / ISO string / null /\n * undefined) into a `Date | null`. The framework hands us strings\n * when hydrating from SQLite, numbers when round-tripping through\n * JSON, and Date instances from the application.\n */\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default PaymentIntent;\n","/**\n * PaymentIntentCollection — collection manager for {@link PaymentIntent}.\n *\n * Adds helpers for the common query shapes (find by idempotency key,\n * find still-open intents, find by licensee email, find expired) and a\n * `getOrCreateByIdempotencyKey` helper that callers use to implement\n * safe at-least-once retries from the marketplace's PaymentIntent\n * issuance handler.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { PaymentIntent } from '../models/PaymentIntent.js';\nimport { PaymentIntentStatus, type PaymentOption } from '../types/index.js';\n\nexport interface PaymentIntentIdempotencyArgs {\n /** Caller-supplied scope (Sku id / tier id / etc.) */\n offeringRef: string;\n /** Buyer email — second factor of the natural key */\n licenseeEmail: string;\n /** Caller-supplied idempotency key */\n idempotencyKey: string;\n /** Optional tenant scope, when called outside an active tenant context */\n tenantId?: string | null;\n}\n\nexport interface PaymentIntentSeed\n extends Partial<Omit<PaymentIntentIdempotencyArgs, 'tenantId'>> {\n tenantId?: string | null;\n skuId?: string;\n customerId?: string;\n paymentOptions: PaymentOption[];\n usdPriceLocked: number;\n priceLockWindowMs?: number;\n}\n\nexport class PaymentIntentCollection extends SmrtCollection<PaymentIntent> {\n static readonly _itemClass = PaymentIntent;\n\n /**\n * Look up an existing intent by its natural key. Returns `null` when\n * any of the three components are empty — empty natural-key inputs\n * disable dedup by design, so a `null` return tells the caller to\n * fall through to a fresh `create`.\n */\n async findByIdempotencyKey(\n args: PaymentIntentIdempotencyArgs,\n ): Promise<PaymentIntent | null> {\n if (!args.offeringRef || !args.licenseeEmail || !args.idempotencyKey) {\n return null;\n }\n const where: Record<string, unknown> = {\n offeringRef: args.offeringRef,\n licenseeEmail: args.licenseeEmail,\n idempotencyKey: args.idempotencyKey,\n };\n if (args.tenantId !== undefined) {\n where.tenantId = args.tenantId;\n }\n const results = await this.list({ where, limit: 1 });\n return results[0] ?? null;\n }\n\n /**\n * Idempotency-aware create. If an intent already exists with the\n * same `(tenantId, offeringRef, licenseeEmail, idempotencyKey)`\n * tuple, returns the existing row. Otherwise creates a new one\n * with the supplied seed.\n *\n * Returns a `{ intent, created }` pair so callers can branch on\n * whether they're handling a fresh quote or a replay (e.g. to\n * skip a duplicate \"intent created\" webhook emission).\n */\n async getOrCreateByIdempotencyKey(\n seed: PaymentIntentSeed,\n ): Promise<{ intent: PaymentIntent; created: boolean }> {\n const existing = await this.findByIdempotencyKey({\n offeringRef: seed.offeringRef ?? '',\n licenseeEmail: seed.licenseeEmail ?? '',\n idempotencyKey: seed.idempotencyKey ?? '',\n tenantId: seed.tenantId,\n });\n if (existing) {\n return { intent: existing, created: false };\n }\n return { intent: await this.create({ ...seed }), created: true };\n }\n\n /**\n * Open intents — `AWAITING_PAYMENT` and not yet past the price-\n * lock window. The price-lock check is `Date.now()`-based, so a\n * stale-but-not-yet-marked-EXPIRED row drops out of this list even\n * before a cleanup job advances its status.\n */\n async findOpen(): Promise<PaymentIntent[]> {\n const open = await this.list({\n where: { status: PaymentIntentStatus.AWAITING_PAYMENT },\n orderBy: 'created_at DESC',\n });\n const now = Date.now();\n return open.filter((p) => {\n if (!p.priceLockExpiresAt) return true;\n return p.priceLockExpiresAt.getTime() > now;\n });\n }\n\n /**\n * `AWAITING_PAYMENT` intents whose price-lock window has already\n * passed. The expected use is a periodic cleanup job that loads\n * this list, calls `intent.expire(); await intent.save()` on each,\n * and emits whatever downstream signal the application needs.\n */\n async findStale(): Promise<PaymentIntent[]> {\n const open = await this.list({\n where: { status: PaymentIntentStatus.AWAITING_PAYMENT },\n });\n const now = Date.now();\n return open.filter((p) => {\n if (!p.priceLockExpiresAt) return false;\n return p.priceLockExpiresAt.getTime() <= now;\n });\n }\n\n async findByLicenseeEmail(email: string): Promise<PaymentIntent[]> {\n return this.list({\n where: { licenseeEmail: email },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByOfferingRef(offeringRef: string): Promise<PaymentIntent[]> {\n return this.list({\n where: { offeringRef },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByStatus(status: PaymentIntentStatus): Promise<PaymentIntent[]> {\n return this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n async findByTenant(tenantId: string): Promise<PaymentIntent[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payment intents (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n */\n async findGlobal(): Promise<PaymentIntent[]> {\n return queryGlobal<PaymentIntent>(this);\n }\n\n /**\n * Find payment intents for a tenant including global intents.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n */\n async findWithGlobals(tenantId: string): Promise<PaymentIntent[]> {\n return queryWithGlobals<PaymentIntent>(\n this,\n tenantId,\n 'PaymentIntent.findWithGlobals',\n );\n }\n}\n","/**\n * Payout — operator-to-supplier outgoing remittance.\n *\n * A Payout sits on the opposite side of a {@link Payment}: it tracks\n * funds *leaving* the operator's wallet and arriving at a vendor's\n * payout address. The two are kept as distinct models (rather than\n * overloading `Payment` with a `direction` field) because the status\n * machine, chain semantics, and audit story differ.\n *\n * Industry-neutral: any operator that ingests one payment and remits\n * funds to a third party — marketplaces, payment processors with\n * Connect-style fan-out, royalty distributors, escrow services —\n * benefits.\n *\n * MVP scope: no currency conversion. The payout's amounts and currency\n * mirror the originating Payment's native currency, and the vendor\n * needs a matching `Vendor.payoutAddresses` entry to receive funds.\n * Filtering at quote time (`PaymentIntent.paymentOptions`) is the\n * consumer's job; the model just enforces the natural-key invariant\n * and the status machine.\n *\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport { type PayoutOptions, PayoutStatus } from '../types/index.js';\nimport { Vendor } from './Vendor.js';\n\n/*\n * There is deliberately no `PAYOUT_EPSILON` here any more (#2401).\n *\n * The gross/fee/net triple and the source `Payment.amount` it is capped\n * against are all integer minor units, so both the invariant and the cap are\n * exact. A payout that remits one cent more than its funding payment brought\n * in is over-remittance, not rounding, and the old `0.01` tolerance silently\n * allowed it.\n */\n\n/**\n * Legal status transitions for a Payout, keyed by the prior persisted status.\n * Mirrors the state machine enforced by `markSent` / `markConfirmed` /\n * `markFailed` / `resetFromFailed`, so a raw `status` mass-assignment on the\n * generated update route can't skip steps (e.g. `pending → confirmed`) or\n * revive a terminal-ish state outside the dedicated reset path.\n *\n * `pending → sent → confirmed`, with `failed` reachable from `pending` / `sent`\n * and `failed → pending` allowed only via `resetFromFailed()`.\n */\nconst PAYOUT_STATUS_TRANSITIONS: Record<PayoutStatus, PayoutStatus[]> = {\n [PayoutStatus.PENDING]: [PayoutStatus.SENT, PayoutStatus.FAILED],\n [PayoutStatus.SENT]: [PayoutStatus.CONFIRMED, PayoutStatus.FAILED],\n [PayoutStatus.CONFIRMED]: [],\n // FAILED is resettable to PENDING via resetFromFailed().\n [PayoutStatus.FAILED]: [PayoutStatus.PENDING],\n};\n\n/**\n * Module-scoped record of the status each Payout instance was loaded with, so\n * the save-time transition guard can compare the prior persisted status\n * against the one being written. WeakMap keeps it out of the schema and GCs\n * with the instance — same pattern as the other commerce models.\n */\nconst loadedPayoutStatus = new WeakMap<Payout, PayoutStatus>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): a Payout has NO safe generated write.\n // Its status drives an outgoing remittance and its amount triple\n // (grossAmount / operatorFee / supplierNet, all integer minor units) is the\n // integrity core — none may\n // be set by a caller. The only legitimate way to mint a payout is the\n // verified `PayoutCollection.createFromPayment()` domain path (which derives\n // the amounts from the source Payment and starts PENDING), and the only legal\n // status moves are the guarded `markSent` / `markConfirmed` / `markFailed` /\n // `resetFromFailed` helpers. So the generated surface is read-only — this\n // closes the \"forged CONFIRMED Payout via api.create\" vector (codex HIGH#4)\n // at the surface, not just in the save() guard.\n //\n // CLI parity (round 6, codex ROOT INSIGHT): the CLI is an independently\n // configured write surface. `cli: true` would still generate create/update/\n // delete commands that set status / the gross-fee-net triple, re-opening the\n // exact vector closed on api/mcp. The CLI is locked to the same read-only\n // surface — there is no safe generated payout write on ANY surface.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class Payout extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable so global / single-\n * tenant deployments work too.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * The source {@link Payment} that funded this payout. Plain string\n * reference (FK convention: `@foreignKey('ModelName')` would be\n * fine within the package, but `Payment` lives in this same\n * package — staying with a string keeps the dependency graph\n * clean and matches how `PaymentIntent` references `Payment`).\n */\n paymentId: string = '';\n\n /**\n * The destination {@link Vendor}. Foreign-key constrained because\n * Vendor lives in this same package and a hard reference catches\n * dangling payouts at insert time.\n */\n @foreignKey(Vendor)\n vendorId: string = '';\n\n /**\n * Total funds moved, in the originating payment's native currency, as\n * **integer minor units** (cents, satoshis) — the same convention as\n * `Payment.amount` / `Payment.nativeAmount` (#2401).\n */\n grossAmount: number = 0;\n\n /**\n * Operator's take from `grossAmount`, in integer minor units. Must equal\n * `grossAmount - supplierNet` **exactly** at save time — the model enforces\n * the invariant via `validateAmounts()`.\n */\n operatorFee: number = 0;\n\n /**\n * Net funds the supplier receives, in integer minor units. Must equal\n * `grossAmount - operatorFee` exactly at save time.\n *\n * Converts with the other two: the invariant is a subtraction over all\n * three, so a decimal member would reintroduce the fuzz the tolerance used\n * to hide (#2401).\n */\n supplierNet: number = 0;\n\n /**\n * Native currency the funds are denominated in — payout-rail-\n * qualified, matching `Payment.nativeCurrency` and\n * `Vendor.payoutAddresses` keys (`USDC-base`, `BTC`, `USD-stripe`,\n * etc.).\n */\n currency: string = '';\n\n /**\n * The `PaymentBackend` adapter id used to send the payout. For a\n * marketplace this typically matches the source `Payment.backendId`\n * (currency-match settlement, no conversion); other consumers can\n * use a different backend if they bridge currencies elsewhere.\n */\n backendId: string = '';\n\n /**\n * Outgoing chain transaction hash, gateway settlement id, or\n * (for not-yet-broadcast BTC payouts) a PSBT identifier. Set when\n * the payout transitions to `SENT`. Empty until then.\n */\n backendTxRef: string = '';\n\n /**\n * Current status — see {@link PayoutStatus} for the state machine.\n * Mutate via `markSent` / `markConfirmed` / `markFailed` /\n * `resetFromFailed` rather than direct assignment so the\n * transition invariants run.\n */\n status: PayoutStatus = PayoutStatus.PENDING;\n\n /**\n * When the payout transitioned to `SENT`.\n */\n sentAt: Date | null = null;\n\n /**\n * When the payout transitioned to `CONFIRMED`.\n */\n confirmedAt: Date | null = null;\n\n /**\n * When the payout transitioned to `FAILED`.\n */\n failedAt: Date | null = null;\n\n /**\n * Caller-supplied human-readable failure reason. Empty for non-\n * failed payouts. Cleared by `resetFromFailed()`.\n */\n failureReason: string = '';\n\n /**\n * Append-only operator notes — chargeback memos, manual-retry\n * justifications, etc.\n */\n notes: string = '';\n\n constructor(options: PayoutOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.vendorId !== undefined) this.vendorId = options.vendorId;\n if (options.grossAmount !== undefined)\n this.grossAmount = options.grossAmount;\n if (options.operatorFee !== undefined)\n this.operatorFee = options.operatorFee;\n if (options.supplierNet !== undefined)\n this.supplierNet = options.supplierNet;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.backendId !== undefined) this.backendId = options.backendId;\n if (options.backendTxRef !== undefined)\n this.backendTxRef = options.backendTxRef;\n if (options.status !== undefined) this.status = options.status;\n if (options.sentAt !== undefined)\n this.sentAt = Payout.coerceDate(options.sentAt);\n if (options.confirmedAt !== undefined)\n this.confirmedAt = Payout.coerceDate(options.confirmedAt);\n if (options.failedAt !== undefined)\n this.failedAt = Payout.coerceDate(options.failedAt);\n if (options.failureReason !== undefined)\n this.failureReason = options.failureReason;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Re-coerce timestamp fields after the framework reapplies raw option\n * values during initialization.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.sentAt = Payout.coerceDate(this.sentAt);\n this.confirmedAt = Payout.coerceDate(this.confirmedAt);\n this.failedAt = Payout.coerceDate(this.failedAt);\n if (await this.isSaved()) {\n loadedPayoutStatus.set(this, this.status);\n }\n return this;\n }\n\n // -------- Status predicates --------\n\n isPending(): boolean {\n return this.status === PayoutStatus.PENDING;\n }\n\n isSent(): boolean {\n return this.status === PayoutStatus.SENT;\n }\n\n isConfirmed(): boolean {\n return this.status === PayoutStatus.CONFIRMED;\n }\n\n isFailed(): boolean {\n return this.status === PayoutStatus.FAILED;\n }\n\n /**\n * Transition `PENDING → SENT`. Requires `backendTxRef` — without a\n * way to identify the outgoing transaction the payout cannot be\n * tracked further. Throws on any other source status so a confused\n * caller can't accidentally roll back a confirmed payout.\n */\n markSent(backendTxRef: string): void {\n if (this.status !== PayoutStatus.PENDING) {\n throw new Error(\n `Payout ${this.id}: cannot transition to SENT from status '${this.status}'`,\n );\n }\n if (!backendTxRef) {\n throw new Error(`Payout ${this.id}: markSent requires a backendTxRef`);\n }\n this.backendTxRef = backendTxRef;\n this.status = PayoutStatus.SENT;\n this.sentAt = new Date();\n }\n\n /**\n * Transition `SENT → CONFIRMED`. Throws if called from any other\n * status — confirmations on a pending payout would mean a chain\n * confirmation arrived before the application even recorded the\n * outgoing tx, which is a code bug worth surfacing rather than\n * silently fixing.\n */\n markConfirmed(): void {\n if (this.status !== PayoutStatus.SENT) {\n throw new Error(\n `Payout ${this.id}: cannot transition to CONFIRMED from status '${this.status}'`,\n );\n }\n this.status = PayoutStatus.CONFIRMED;\n this.confirmedAt = new Date();\n }\n\n /**\n * Move the payout to `FAILED`. Valid from `PENDING` (couldn't even\n * broadcast) or `SENT` (broadcast but chain rejected). Confirmed\n * payouts cannot fail — that path is a refund, modelled separately.\n */\n markFailed(reason: string): void {\n if (\n this.status !== PayoutStatus.PENDING &&\n this.status !== PayoutStatus.SENT\n ) {\n throw new Error(\n `Payout ${this.id}: cannot transition to FAILED from status '${this.status}'`,\n );\n }\n this.status = PayoutStatus.FAILED;\n this.failedAt = new Date();\n this.failureReason = reason ?? '';\n }\n\n /**\n * Operator-driven reset: move a `FAILED` payout back to `PENDING`\n * after fixing whatever broke. Clears the failure metadata and the\n * old `backendTxRef` (the next attempt will have a new one).\n * Distinct from `markFailed` reflexivity — the issue spec says\n * \"failed is terminal but allows manual reset via explicit method\".\n */\n resetFromFailed(): void {\n if (this.status !== PayoutStatus.FAILED) {\n throw new Error(\n `Payout ${this.id}: cannot reset from status '${this.status}' — only FAILED is resettable`,\n );\n }\n this.status = PayoutStatus.PENDING;\n this.sentAt = null;\n this.confirmedAt = null;\n this.failedAt = null;\n this.failureReason = '';\n // Drop the old tx ref so the next markSent attempt gets a fresh one.\n this.backendTxRef = '';\n }\n\n /**\n * Throws if the gross/fee/net invariant doesn't hold.\n *\n * Exact: all three are integer minor units, so `supplierNet` must equal\n * `grossAmount - operatorFee` on the nose (#2401).\n */\n validateAmounts(): void {\n const amounts = [\n ['grossAmount', this.grossAmount],\n ['operatorFee', this.operatorFee],\n ['supplierNet', this.supplierNet],\n ] as const;\n // Whole minor units first — the exact invariant below depends on it.\n assertIntegerMinorUnits('Payout', this.id ?? '<new>', amounts);\n // Non-negativity guard (S5 audit #1390). A negative operatorFee would let\n // an operator manufacture a supplierNet larger than the gross it took in\n // (paying out more than it received); negative gross/net are nonsensical.\n for (const [field, value] of amounts) {\n if (value < 0) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: ${field} must be a non-negative number (got ${value}).`,\n );\n }\n }\n const expectedNet = this.grossAmount - this.operatorFee;\n if (this.supplierNet !== expectedNet) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: amount invariant violated — ` +\n `gross=${this.grossAmount} fee=${this.operatorFee} net=${this.supplierNet} ` +\n `(expected net=${expectedNet})`,\n );\n }\n }\n\n /**\n * Save-time state-machine + settlement guard (S5 audit #1390 round 2).\n *\n * `status`, `backendTxRef`, and the amount triple are all mass-assignable on\n * the generated create/update routes. The amount invariant alone (round 1)\n * still let a caller raw-flip a persisted payout to `CONFIRMED` / `SENT`\n * (skipping the chain steps) or remit a `grossAmount` larger than the source\n * Payment actually brought in — money that never arrived.\n *\n * This guard adds:\n * - **Transitions on an existing row must be legal** per\n * {@link PAYOUT_STATUS_TRANSITIONS} (no `pending → confirmed` skip on a\n * raw update, no reviving a CONFIRMED payout). Brand-new rows may be\n * created in any status (collection/import/query fixtures), but advancing\n * a persisted row is constrained to the legal edges the helpers enforce.\n * - **SENT requires a backendTxRef** (matches `markSent`'s invariant) so a\n * sent payout is always traceable, regardless of how the status was set.\n * - **grossAmount is capped by the source Payment** when that Payment\n * resolves: a payout can never remit more than the funding Payment\n * settled. A `paymentId` that doesn't resolve (synthetic id, Payment-less\n * deployment) skips the cap — the source Payment is a plain-string,\n * cross-model reference by design and may legitimately be a manually\n * recorded (non-COMPLETED) row, so the cap is best-effort rather than a\n * hard existence requirement.\n *\n * The amount-invariant check (`validateAmounts`) still runs first as the\n * arithmetic floor.\n */\n override async save(): Promise<this> {\n this.validateAmounts();\n\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n\n // SENT *and* CONFIRMED both require a backendTxRef (S5 audit #1390 round 4):\n // a confirmed payout that carries no outgoing-transaction reference is\n // untraceable and indistinguishable from a forged confirmation. Round 3\n // only required it for SENT, leaving a raw CONFIRMED-with-no-txref hole.\n if (\n (this.status === PayoutStatus.SENT ||\n this.status === PayoutStatus.CONFIRMED) &&\n !this.backendTxRef\n ) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: a ${this.status} payout requires a ` +\n 'backendTxRef (use markSent()).',\n );\n }\n\n // CONFIRMED is only ever reachable from SENT (codex HIGH#4 / HIGH#3): an\n // existing row's transition is checked by assertStatusTransition, but a\n // brand-new row (no prior) would otherwise be allowed to start CONFIRMED.\n // A confirmation means the chain/gateway acknowledged a previously-sent tx,\n // so it can never be the genesis state.\n if (this.status === PayoutStatus.CONFIRMED && prior !== PayoutStatus.SENT) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: CONFIRMED is only reachable from SENT ` +\n '(use markConfirmed() after markSent()).',\n );\n }\n\n await this.assertCappedBySourcePayment();\n\n const result = (await super.save()) as this;\n loadedPayoutStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status from the database (S5 audit #1390\n * round 4). The {@link loadedPayoutStatus} WeakMap is only populated when\n * {@link initialize} hydrated the row, so a `create({ id: <existing>,\n * _skipLoad: true })` upsert produces an instance with a missing WeakMap\n * entry — trusting it would treat the write as a brand-new row and skip the\n * transition + CONFIRMED-genesis guards. Reading the persisted row directly\n * makes a create-onto-existing behave as an update. `undefined` means no row\n * exists (genuinely new).\n */\n private async resolvePriorStatus(): Promise<PayoutStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as PayoutStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedPayoutStatus.get(this);\n }\n\n /**\n * Reject an illegal status flip on an existing row. Brand-new payouts (no\n * prior) may start in any status — collection imports and query fixtures\n * legitimately seed SENT / CONFIRMED rows — but advancing a *persisted* row\n * is constrained to the legal edges the helpers enforce, so a raw update\n * can't skip `pending → confirmed` or revive a terminal CONFIRMED.\n */\n private assertStatusTransition(prior: PayoutStatus | undefined): void {\n if (prior === undefined) return; // new row\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYOUT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Payout ${this.id}: illegal status transition '${prior}' → ` +\n `'${this.status}'. Use the guarded helpers (markSent / markConfirmed ` +\n '/ markFailed / resetFromFailed).',\n );\n }\n }\n\n /**\n * Cap `grossAmount` by the source {@link Payment}'s settled funds. A payout\n * that remits more than the Payment that funded it brought in is money the\n * operator never received.\n *\n * HARD requirement (S5 audit #1390 round 4, codex HIGH#3): the source Payment\n * MUST resolve. Round 3 made the cap best-effort — a `paymentId` that didn't\n * resolve silently skipped the cap, so a caller could remit an arbitrary\n * `grossAmount` simply by pointing at a non-existent (or empty) payment.\n * A payout's whole purpose is to remit funds that *arrived* via a Payment;\n * without a resolvable source there is no funded amount to cap against, so we\n * reject rather than skip. `PayoutCollection.createFromPayment()` always wires\n * a real `paymentId`, so this never blocks the supported creation path.\n */\n private async assertCappedBySourcePayment(): Promise<void> {\n if (!this.paymentId) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: a paymentId is required — a payout must ` +\n 'reference the source Payment that funded it (use createFromPayment()).',\n );\n }\n\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: this.paymentId });\n if (!payment) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: referenced source Payment ` +\n `'${this.paymentId}' does not exist — refusing to remit against a ` +\n 'missing payment (the gross-amount cap cannot be enforced otherwise).',\n );\n }\n\n // Cap against the payment's funds. Prefer the native-rail figure when\n // present (volatile-currency rails), otherwise the settlement amount —\n // mirrors the PaymentIntent reconciliation convention.\n const settled =\n typeof payment.nativeAmount === 'number' && payment.nativeAmount > 0\n ? payment.nativeAmount\n : payment.amount;\n if (this.grossAmount > settled) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: grossAmount ${this.grossAmount} exceeds the ` +\n `source Payment '${this.paymentId}' funded amount ${settled}.`,\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default Payout;\n","/**\n * PayoutCollection — collection manager for {@link Payout} rows.\n *\n * Adds helpers for the common operator-side query shapes: payouts for\n * a specific vendor, payouts derived from a specific source payment,\n * status-bucketed lookups for the operator dashboard, and a\n * `createFromPayment` convenience that wires up the source-payment\n * link in one call.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport type { Payment } from '../models/Payment.js';\nimport { Payout } from '../models/Payout.js';\nimport { PayoutStatus } from '../types/index.js';\n\nexport interface CreatePayoutFromPaymentArgs {\n /** Source Payment row */\n payment: Payment;\n /** Destination vendor id */\n vendorId: string;\n /** Operator fee in the payment's native currency */\n operatorFee: number;\n /** Backend that will send the payout — defaults to the payment's */\n backendId?: string;\n /** Optional initial notes */\n notes?: string;\n}\n\nexport class PayoutCollection extends SmrtCollection<Payout> {\n static readonly _itemClass = Payout;\n\n /**\n * Build a `PENDING` payout linked to a source payment. The gross\n * defaults to the payment's `nativeAmount` (falling back to\n * `amount` if no backend rail was recorded), the currency defaults\n * to the payment's `nativeCurrency` (falling back to `currency`),\n * and `supplierNet` is computed as `gross - operatorFee`.\n *\n * The returned instance is initialized but not persisted. The caller\n * is still responsible for `await payout.save()` — keeping create /\n * save as separate steps lets callers stitch the payout into an\n * application-level transaction.\n */\n async createFromPayment(args: CreatePayoutFromPaymentArgs): Promise<Payout> {\n const { payment, vendorId, operatorFee } = args;\n const grossAmount = payment.nativeAmount || payment.amount || 0;\n const currency = payment.nativeCurrency || payment.currency || '';\n const backendId = args.backendId ?? payment.backendId ?? '';\n const payout = new Payout({\n ai: this.options.ai,\n db: this.db,\n _skipLoad: true,\n // Inherit the source payment's tenant. Payout is @TenantScoped, so when\n // this runs outside an active tenant context (e.g. a background payout\n // job iterating Payment rows), the interceptor's auto-populate can't\n // stamp a tenant — without carrying it explicitly the remittance would\n // save as global/null and drop out of tenant-filtered payout queries.\n tenantId: payment.tenantId,\n paymentId: payment.id ?? '',\n vendorId,\n grossAmount,\n operatorFee,\n supplierNet: grossAmount - operatorFee,\n currency,\n backendId,\n status: PayoutStatus.PENDING,\n notes: args.notes ?? '',\n });\n await payout.initialize();\n return payout;\n }\n\n async findByPayment(paymentId: string): Promise<Payout[]> {\n return this.list({\n where: { paymentId },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByVendor(vendorId: string): Promise<Payout[]> {\n return this.list({\n where: { vendorId },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByStatus(status: PayoutStatus): Promise<Payout[]> {\n return this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n async findPending(): Promise<Payout[]> {\n return this.findByStatus(PayoutStatus.PENDING);\n }\n\n async findFailed(): Promise<Payout[]> {\n return this.findByStatus(PayoutStatus.FAILED);\n }\n\n async findByBackendTxRef(backendTxRef: string): Promise<Payout | null> {\n if (!backendTxRef) return null;\n const results = await this.list({\n where: { backendTxRef },\n limit: 1,\n });\n return results[0] ?? null;\n }\n\n /**\n * Total funds delivered to a vendor in confirmed payouts. Useful for\n * a vendor-balance widget on the operator dashboard.\n */\n async getConfirmedTotalForVendor(vendorId: string): Promise<number> {\n const confirmed = await this.list({\n where: { vendorId, status: PayoutStatus.CONFIRMED },\n });\n return confirmed.reduce((sum, p) => sum + p.supplierNet, 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n async findByTenant(tenantId: string): Promise<Payout[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payouts (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n */\n async findGlobal(): Promise<Payout[]> {\n return queryGlobal<Payout>(this);\n }\n\n /**\n * Find payouts for a tenant including global payouts.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n */\n async findWithGlobals(tenantId: string): Promise<Payout[]> {\n return queryWithGlobals<Payout>(this, tenantId, 'Payout.findWithGlobals');\n }\n}\n","/**\n * VendorCollection - Collection manager for Vendor objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Vendor } from '../models/Vendor.js';\nimport { VendorStatus } from '../types/index.js';\n\nexport class VendorCollection extends SmrtCollection<Vendor> {\n static readonly _itemClass = Vendor;\n\n /**\n * Find vendors by profile ID\n *\n * @param profileId - Profile ID from smrt-profiles\n * @returns Array of vendors linked to this profile\n */\n async findByProfile(profileId: string): Promise<Vendor[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all active vendors\n *\n * @returns Array of active vendors\n */\n async findActive(): Promise<Vendor[]> {\n return await this.list({\n where: { status: VendorStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find vendors by status\n *\n * @param status - Vendor status\n * @returns Array of vendors\n */\n async findByStatus(status: VendorStatus): Promise<Vendor[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get or create a vendor for a profile\n *\n * @param profileId - Profile ID\n * @param defaults - Default values if creating\n * @returns Vendor\n */\n async getOrCreateForProfile(\n profileId: string,\n defaults: Partial<{\n leadTimeDays: number;\n minimumOrderAmount: number;\n paymentTerms: string;\n }> = {},\n ): Promise<Vendor> {\n const existing = await this.findByProfile(profileId);\n if (existing.length > 0) {\n return existing[0];\n }\n\n const vendor = await this.create({\n profileId,\n leadTimeDays: defaults.leadTimeDays ?? 0,\n minimumOrderAmount: defaults.minimumOrderAmount ?? 0,\n paymentTerms: defaults.paymentTerms ?? '',\n status: VendorStatus.ACTIVE,\n });\n await vendor.save();\n return vendor;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all vendors belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of vendors for the tenant\n */\n async findByTenant(tenantId: string): Promise<Vendor[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global vendors (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global vendors\n */\n async findGlobal(): Promise<Vendor[]> {\n return queryGlobal<Vendor>(this);\n }\n\n /**\n * Find vendors for a tenant including global vendors.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global vendors\n */\n async findWithGlobals(tenantId: string): Promise<Vendor[]> {\n return queryWithGlobals<Vendor>(this, tenantId, 'Vendor.findWithGlobals');\n }\n}\n","/**\n * Commerce money columns: floating-point major units → integer minor units.\n *\n * Ships as an explicit, opt-in migration rather than a schema-differ upgrade\n * because REAL→INTEGER is not information-preserving: an automatic\n * `ALTER … TYPE integer` would truncate a stored `19.99` to `19` and destroy 99\n * cents on every row. The values have to be rescaled by the currency's\n * minor-unit factor *during* the type change (#2401).\n *\n * Run it from one deploy process, after the writers are upgraded:\n *\n * ```ts\n * import { getDatabase } from '@happyvertical/sql';\n * import {\n * preflightCommerceMoneyMinorUnits,\n * migrateCommerceMoneyToMinorUnits,\n * } from '@happyvertical/smrt-commerce';\n *\n * const db = await getDatabase({ type: 'postgres', url: process.env.DATABASE_URL! });\n *\n * // 1. Read-only. Prints rows that would be rounded or exceed JavaScript's\n * // safe-integer range when hydrated.\n * console.log((await preflightCommerceMoneyMinorUnits(db)).summary);\n *\n * // 2. Convert. Idempotent — a second run is a no-op.\n * await migrateCommerceMoneyToMinorUnits(db);\n * ```\n *\n * Engine notes:\n *\n * - **PostgreSQL / DuckDB** — the column type changes in place with\n * `USING round(col * 100)`.\n * - **SQLite** — values are rescaled, but the *declared* type cannot be altered\n * in place. SQLite's declared type is only an affinity, so the column already\n * behaves as integer minor units; bringing the declaration into line needs\n * the table-rebuild path (#2370). The result reports those columns in\n * `declaredTypeChangePending`.\n *\n * Fresh PostgreSQL and DuckDB INTEGER columns materialize as BIGINT. The\n * preflight lists values that would exceed JavaScript's safe-integer range\n * after rescaling, because SMRT rejects rather than rounds those on hydration.\n * Existing PostgreSQL `int4` columns require the explicit widening in #2424.\n *\n * ## Two things this migration deliberately does NOT convert\n *\n * Both hold amounts in a **native asset's** minor unit rather than the\n * deployment's currency, so there is no single scale that is correct for every\n * row and a blanket ×100 would silently destroy data:\n *\n * - **`payments.native_amount`** — satoshis on a BTC rail (×1e8), cents on a\n * fiat or stablecoin rail (×1e2), in the same column, discriminated only by\n * `native_currency`. See {@link COMMERCE_NATIVE_UNIT_COLUMNS} for the\n * two-step conversion.\n * - **`payment_intents.payment_options[].nativeAmount`** — the same problem\n * inside a JSON column, per option. Because an intent's price lock defaults\n * to 15 minutes, the supported answer is to **let open intents expire and be\n * re-quoted**: an unconverted option simply fails to reconcile against the\n * payment that arrives (the check is an exact `!==`), which is a safe, loud\n * failure rather than a silently mis-scaled quote.\n */\n\nimport {\n type DatabaseInterface,\n type MinorUnitsPreflightResult,\n type MinorUnitsRescaleResult,\n type MoneyColumnTarget,\n preflightMinorUnitsRescale,\n rescaleMoneyColumnsToMinorUnits,\n} from '@happyvertical/smrt-core/migrations';\n\n/** Marker recorded in `_smrt_backfills` once the column rescale has run. */\nexport const COMMERCE_MONEY_MINOR_UNITS_BACKFILL =\n '@happyvertical/smrt-commerce:money-minor-units:v1';\n\n/**\n * Every commerce money column denominated in the deployment's own currency,\n * i.e. everything a single scale converts correctly.\n *\n * `invoices`, `invoice_line_items` and `payment_allocations` are absent on\n * purpose: those were already INTEGER (#2361), so the preflight reports them as\n * converted and there is nothing to rescale. Rate columns (`tax_rate`) are\n * absent for the opposite reason — they are genuinely fractional and must stay\n * DECIMAL. `payments.native_amount` is absent because its scale is per-asset;\n * see {@link COMMERCE_NATIVE_UNIT_COLUMNS}.\n */\nexport const COMMERCE_MONEY_COLUMNS: MoneyColumnTarget[] = [\n { table: 'contracts', columns: ['subtotal', 'tax_amount', 'total_amount'] },\n {\n table: 'contract_line_items',\n columns: ['unit_price', 'discount', 'amount'],\n },\n {\n table: 'payments',\n columns: ['amount', 'usd_at_quote', 'usd_at_confirmation'],\n },\n {\n table: 'payouts',\n columns: ['gross_amount', 'operator_fee', 'supplier_net'],\n },\n { table: 'vendors', columns: ['minimum_order_amount'] },\n { table: 'customers', columns: ['credit_limit'] },\n { table: 'payment_intents', columns: ['usd_price_locked'] },\n];\n\n/**\n * Columns whose minor-unit scale is a property of the **row's** asset, not of\n * the deployment — so no single `scale` converts them and they are excluded\n * from {@link migrateCommerceMoneyToMinorUnits}.\n *\n * `payments.native_amount` holds \"the amount the backend actually moved, in the\n * native currency's own minor units\": satoshis for `BTC` (×1e8), cents for\n * `USDC-base` or `USD-stripe` (×1e2). A blanket ×100 would store 1 satoshi-ish\n * nonsense for a 0.00713 BTC payment whose correct value is 713 000 sats, and\n * a round amount like 0.01 BTC would even pass the preflight's integrality\n * check while being wrong by six orders of magnitude.\n *\n * Convert it in two deliberate steps, from one deploy process, while the column\n * is still floating-point:\n *\n * ```ts\n * // 1. Normalise each row to its own asset's minor units, in the REAL column.\n * for (const [currency, scale] of Object.entries({ BTC: 1e8, 'USDC-base': 100 })) {\n * await db.query(\n * 'UPDATE payments SET native_amount = round(native_amount * ?) WHERE native_currency = ?',\n * scale,\n * currency,\n * );\n * }\n *\n * // 2. Change the column type with no further scaling.\n * await rescaleMoneyColumnsToMinorUnits(db, COMMERCE_NATIVE_UNIT_COLUMNS, {\n * scale: 1,\n * backfillName: '<your-app>:payments-native-amount-minor-units:v1',\n * });\n * ```\n *\n * Step 1 is deliberately yours to write: only the deployment knows which\n * `native_currency` values it has used and what each one's exponent is.\n */\nexport const COMMERCE_NATIVE_UNIT_COLUMNS: MoneyColumnTarget[] = [\n { table: 'payments', columns: ['native_amount'] },\n];\n\n/**\n * Report what {@link migrateCommerceMoneyToMinorUnits} would do, without\n * writing anything.\n *\n * @param db - Root database handle.\n * @param options - `scale` (default 100) and `engineHint`.\n * @returns Per-column state plus the rows that would lose information.\n */\nexport function preflightCommerceMoneyMinorUnits(\n db: DatabaseInterface,\n options: { scale?: number; engineHint?: string } = {},\n): Promise<MinorUnitsPreflightResult> {\n return preflightMinorUnitsRescale(db, COMMERCE_MONEY_COLUMNS, options);\n}\n\n/**\n * Convert every single-scale commerce money column from floating-point major\n * units to integer minor units.\n *\n * Idempotent: guarded by a `_smrt_backfills` marker, so re-running can never\n * multiply a table by the scale twice.\n *\n * Does **not** touch `payments.native_amount` or the `paymentOptions` JSON —\n * see this module's header and {@link COMMERCE_NATIVE_UNIT_COLUMNS}.\n *\n * @param db - Root database handle.\n * @param options - `scale` (default 100 — cents), `engineHint`, and `force` to\n * convert despite rows the preflight flagged as lossy.\n * @throws `MinorUnitsPreflightError` when a row would be rounded or exceed the\n * JavaScript safe-integer range and `force` was not set. Print\n * {@link preflightCommerceMoneyMinorUnits}'s summary, fix the data, retry.\n */\nexport function migrateCommerceMoneyToMinorUnits(\n db: DatabaseInterface,\n options: { scale?: number; engineHint?: string; force?: boolean } = {},\n): Promise<MinorUnitsRescaleResult> {\n return rescaleMoneyColumnsToMinorUnits(db, COMMERCE_MONEY_COLUMNS, {\n ...options,\n backfillName: COMMERCE_MONEY_MINOR_UNITS_BACKFILL,\n packageName: '@happyvertical/smrt-commerce',\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["requested","sequence","seq","payment"],"sources":["../src/__smrt-register__.ts","../src/billing/provider.ts","../src/models/Customer.ts","../src/collections/CustomerCollection.ts","../src/models/PaymentInstrument.ts","../src/collections/PaymentInstrumentCollection.ts","../src/billing/cards.ts","../src/billing/credits.ts","../src/models/Payment.ts","../src/collections/PaymentCollection.ts","../src/billing/top-up.ts","../src/billing/stripe.ts","../src/billing/crypto.ts","../src/billing/btcpay.ts","../src/billing/cycles.ts","../src/models/PaymentAllocation.ts","../src/collections/PaymentAllocationCollection.ts","../src/billing/invoice-payments.ts","../src/billing/jobs.ts","../src/models/Invoice.ts","../src/collections/InvoiceCollection.ts","../src/models/billing.ts","../src/billing/payment-attempts.ts","../src/billing/period-close.ts","../src/models/InvoiceLineItem.ts","../src/collections/InvoiceLineItemCollection.ts","../src/billing/events.ts","../src/billing/runtime.ts","../src/models/Vendor.ts","../src/models/Contract.ts","../src/collections/ContractCollection.ts","../src/models/ContractLineItem.ts","../src/collections/ContractLineItemCollection.ts","../src/models/Fulfillment.ts","../src/collections/FulfillmentCollection.ts","../src/models/FulfillmentLineItem.ts","../src/collections/FulfillmentLineItemCollection.ts","../src/models/PaymentIntent.ts","../src/collections/PaymentIntentCollection.ts","../src/models/Payout.ts","../src/collections/PayoutCollection.ts","../src/collections/VendorCollection.ts","../src/migrations/invoiceEditorStorage.ts","../src/migrations/moneyMinorUnits.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * The provider-neutral port period close, webhook intake, and credit\n * purchases talk to (#3060).\n *\n * Every amount crossing this port is **integer minor units** of the named\n * currency, like the rest of commerce. Adapters (see\n * {@link createStripeBillingProvider}) own the conversion to the provider's\n * representation and every provider call; this package never calls a payment\n * provider directly.\n */\nimport type { SubscriptionStatus } from '@happyvertical/smrt-subscriptions';\nimport type { Address } from '../types/index.js';\n\nexport interface BillingProviderCustomerInput {\n /** The local billing account id, recorded on the provider customer. */\n accountId: string;\n /** Existing provider customer id; omitted to create one. */\n providerCustomerId?: string;\n name: string;\n email?: string;\n /** Tax location used by provider-calculated tax. */\n billingAddress?: Address;\n taxExempt: boolean;\n}\n\nexport interface BillingProviderInvoiceLine {\n description: string;\n /** Signed line amount in minor units; discounts are negative lines. */\n amount: number;\n periodStart?: Date;\n periodEnd?: Date;\n /**\n * Product tax code for provider-calculated tax (Stripe Tax `txcd_...`,\n * #3194). Omitted: the provider account's default.\n */\n taxCode?: string;\n}\n\n/**\n * How the provider collects an invoice. `charge_automatically` charges the\n * payer's saved default payment method after the invoice is sent, on the\n * provider's own schedule; the outcome arrives as a `paid` or\n * `payment_failed` event. A provider that cannot pull funds from a saved\n * method must reject it, never fall back to `send_invoice`.\n */\nexport type BillingCollectionMethod = 'send_invoice' | 'charge_automatically';\n\nexport interface BillingProviderInvoiceInput {\n /** Local invoice id; the provider tags its invoice with it. */\n invoiceId: string;\n invoiceNumber: string;\n providerCustomerId: string;\n currency: string;\n issueDate: Date;\n dueDate: Date;\n lines: BillingProviderInvoiceLine[];\n /** Sum of `lines`, in minor units. */\n subtotal: number;\n /** Stable key for this logical invoice; reused on every retry. */\n idempotencyKey: string;\n /** Ask the provider to calculate tax from the customer's tax location. */\n automaticTax: boolean;\n /** Default `send_invoice` (#3139). */\n collectionMethod?: BillingCollectionMethod;\n memo?: string;\n}\n\n/**\n * `uncollectible` is an open balance the seller wrote off (#3139); it can\n * still be paid or voided later.\n */\nexport type BillingProviderInvoiceStatus =\n | 'draft'\n | 'open'\n | 'paid'\n | 'void'\n | 'uncollectible';\n\nexport interface BillingProviderInvoiceState {\n providerInvoiceId: string;\n status: BillingProviderInvoiceStatus;\n /**\n * The invoice was closed as paid outside the provider (for example by a\n * crypto payment rail, #3138). The rail that took the money records it; the\n * provider's own `paid` event must not record a second payment.\n */\n paidOutOfBand?: boolean;\n currency: string;\n /** Pre-tax subtotal, minor units. */\n subtotal: number;\n /** Provider-calculated tax, minor units. */\n taxAmount: number;\n total: number;\n amountPaid: number;\n amountDue: number;\n}\n\nexport interface BillingProviderSubscriptionState {\n providerSubscriptionId: string;\n status: SubscriptionStatus;\n currentPeriodStart?: Date;\n currentPeriodEnd?: Date;\n cancelAtPeriodEnd: boolean;\n canceledAt?: Date;\n trialEndsAt?: Date;\n}\n\nexport interface BillingProviderCheckoutInput {\n /** Stable key reused when retrying the same checkout creation. */\n idempotencyKey: string;\n providerCustomerId?: string;\n customerEmail?: string;\n currency: string;\n /** Amount charged, minor units. */\n amount: number;\n description: string;\n successUrl: string;\n cancelUrl: string;\n /** Opaque values returned verbatim on the completion event. */\n metadata: Record<string, string>;\n /**\n * Charge provider-calculated tax on top of `amount` (#3139). The buyer's\n * tax location is collected at checkout when the customer has none.\n */\n automaticTax?: boolean;\n /**\n * Product tax code for the checkout line (Stripe Tax `txcd_...`, #3194).\n * Omitted: the provider account's default.\n */\n taxCode?: string;\n /**\n * Also save the payment method for later off-session charges (for example\n * automatic top-ups). Requires `providerCustomerId`.\n */\n savePaymentMethod?: boolean;\n}\n\nexport interface BillingProviderCheckoutSession {\n sessionId: string;\n url: string | null;\n}\n\n/** A checkout that saves a payment method without charging (#3139). */\nexport interface BillingProviderSetupCheckoutInput {\n /** Stable key reused when retrying the same checkout creation. */\n idempotencyKey: string;\n providerCustomerId: string;\n /** The currency the saved method will be charged in. */\n currency: string;\n successUrl: string;\n cancelUrl: string;\n /** Collect the billing address (tax location) and save it to the customer. */\n collectBillingAddress: boolean;\n /** Opaque values returned verbatim on the completion event. */\n metadata: Record<string, string>;\n}\n\n/** A checkout session as the provider reports it now (#3139). */\nexport interface BillingProviderCheckoutState {\n sessionId: string;\n mode: 'payment' | 'setup' | 'other';\n /** The session completed (paid, or a payment method was saved). */\n complete: boolean;\n /** A payment-mode session collected its money. */\n paid: boolean;\n providerCustomerId?: string;\n /** The payment method the session collected. */\n paymentMethodId?: string;\n currency?: string;\n /** Line amount before discounts and tax, minor units. */\n amountSubtotal?: number;\n /** Provider-calculated tax, minor units. */\n amountTax?: number;\n /** Amount collected, minor units. */\n amountTotal?: number;\n /**\n * The customer's billing address after a completed session that was asked\n * to collect one.\n */\n billingAddress?: Address;\n}\n\n/**\n * The outcome of an off-session charge (#3139). `processing` settles later\n * through a `payment` event; `requires_action` (the issuer wants the payer\n * present), `failed`, and `canceled` charged nothing.\n */\nexport type BillingChargeStatus =\n | 'succeeded'\n | 'processing'\n | 'requires_action'\n | 'failed'\n | 'canceled';\n\nexport interface BillingProviderChargeInput {\n providerCustomerId: string;\n /** Positive integer minor units. */\n amount: number;\n currency: string;\n /**\n * Stable key for this logical charge. Every retry with it returns the\n * original charge; a new attempt needs a new key.\n */\n idempotencyKey: string;\n description?: string;\n metadata?: Record<string, string>;\n /**\n * Add provider-calculated tax on top of `amount` from the customer's tax\n * location (#3194): `amount` is then the pre-tax subtotal. A customer whose\n * tax location cannot be determined yields a `failed` result with\n * `failureCode: 'customer_tax_location_invalid'`, charging nothing. A\n * provider that cannot calculate tax must throw, never charge untaxed.\n * Every retry of a charge key must pass the same value.\n */\n automaticTax?: boolean;\n /** Product tax code (requires `automaticTax`). */\n taxCode?: string;\n}\n\nexport interface BillingProviderChargeResult {\n status: BillingChargeStatus;\n /** The provider payment id, when one was created. */\n providerPaymentId?: string;\n /** Minor units charged, including tax for an `automaticTax` charge. */\n amount: number;\n currency: string;\n /** `automaticTax` charges: the pre-tax amount, minor units. */\n subtotal?: number;\n /** `automaticTax` charges: the provider-calculated tax, minor units. */\n tax?: number;\n failureCode?: string;\n failureMessage?: string;\n}\n\n/** Invoice lifecycle changes this package acts on. */\nexport type BillingInvoiceEventType =\n | 'paid'\n | 'payment_failed'\n | 'overdue'\n | 'uncollectible'\n | 'voided';\n\n/** A verified provider event, normalized and stripped of customer data. */\nexport type BillingProviderEvent =\n | {\n kind: 'invoice';\n eventId: string;\n type: BillingInvoiceEventType;\n providerInvoiceId: string;\n }\n | {\n kind: 'subscription';\n eventId: string;\n providerSubscriptionId: string;\n }\n | {\n kind: 'checkout_completed';\n eventId: string;\n sessionId: string;\n /** `payment` (credit purchase) or `setup` (card on file, #3139). */\n mode?: 'payment' | 'setup';\n /** Only `paid` sessions settle a credit purchase. */\n paid: boolean;\n currency: string;\n /**\n * Line-item amount before discounts and tax, minor units. Omitted by a\n * provider whose event amounts are not minor units; `observe()` then\n * re-reads the session with `getCheckout()`.\n */\n amountSubtotal?: number;\n /** Provider-calculated tax, minor units. */\n amountTax?: number;\n /** Amount actually collected (after discounts, with tax), minor units. */\n amountTotal?: number;\n metadata: Record<string, string>;\n }\n | {\n /** An off-session charge made by `chargeSavedPaymentMethod` (#3139). */\n kind: 'payment';\n eventId: string;\n /** The `idempotencyKey` the charge was made with. */\n chargeKey: string;\n status: BillingChargeStatus;\n providerPaymentId: string;\n providerCustomerId?: string;\n /**\n * Minor units charged (with tax for an `automaticTax` charge), when the\n * provider amount converts exactly.\n */\n amount?: number;\n currency?: string;\n /** `automaticTax` charges: the pre-tax amount, minor units (#3194). */\n subtotal?: number;\n /** `automaticTax` charges: the provider-calculated tax, minor units. */\n tax?: number;\n failureCode?: string;\n }\n | {\n /**\n * A payment rail's checkout changed (#3138). The event carries ids\n * only; `observe()` re-reads the attempt with `getPaymentAttempt()`.\n */\n kind: 'payment_attempt';\n eventId: string;\n checkoutId: string;\n }\n | { kind: 'ignored'; eventId: string; type: string };\n\n/** What a provider can do beyond the core port (#3138). */\nexport interface BillingProviderCapabilities {\n /**\n * The provider issues, taxes, and sends invoices (`pushInvoice` …). A\n * payment rail (for example a crypto checkout) does not, and cannot be a\n * runtime's issuing provider.\n */\n issuesInvoices: boolean;\n /** The provider reports checkouts through `getPaymentAttempt()`. */\n paymentAttempts: boolean;\n}\n\n/** A rail checkout's lifecycle, normalized by the provider (#3138). */\nexport type BillingPaymentAttemptStatus =\n | 'open'\n | 'confirming'\n | 'settled'\n | 'expired'\n | 'invalid';\n\nexport type BillingPaymentAttemptException =\n | 'none'\n | 'underpaid'\n | 'overpaid'\n | 'paid_late'\n | 'manually_marked';\n\nexport interface BillingPaymentAttemptPayment {\n id: string;\n /** `onchain`, `lightning`, … */\n rail: string;\n /** The asset paid (`BTC`). */\n asset: string;\n /** Amount in the asset's minor units (satoshis for BTC). */\n amount: number;\n /** Fee in the asset's minor units, when reported. */\n fee?: number;\n status: 'confirming' | 'settled' | 'invalid';\n transactionId?: string;\n receivedAt?: string;\n}\n\n/**\n * A rail checkout as the provider reports it now. Amounts are minor units;\n * `metadata` holds only `smrt_*` keys whose signature verified, and is empty\n * for a checkout this package did not create.\n */\nexport interface BillingPaymentAttemptState {\n checkoutId: string;\n /** The rail order id the checkout was created for. */\n orderId?: string;\n status: BillingPaymentAttemptStatus;\n exception: BillingPaymentAttemptException;\n /** The locked fiat price. */\n amount: number;\n currency: string;\n /** Fiat value received at the locked rate, rounded down. */\n amountPaid: number;\n metadata: Record<string, string>;\n /** The asset the price was quoted in (`BTC`). */\n nativeCurrency?: string;\n /** Asset minor units (satoshis). */\n nativeAmountDue?: number;\n nativeAmountPaid?: number;\n /** Decimal price of one unit of `nativeCurrency` in `currency`. */\n rate?: string;\n rateSource?: string;\n checkoutUrl?: string;\n expiresAt?: Date;\n payments: BillingPaymentAttemptPayment[];\n}\n\n/**\n * A payment provider as period close sees it. Implementations must be\n * replay-safe: `pushInvoice` with the same `idempotencyKey` returns the same\n * provider invoice however often it is retried.\n */\nexport interface BillingProvider {\n /** Provider name recorded on local rows (`stripe`). */\n readonly name: string;\n /**\n * Defaults to an issuing provider without payment attempts (Stripe's\n * shape) when omitted.\n */\n readonly capabilities?: BillingProviderCapabilities;\n syncCustomer(\n input: BillingProviderCustomerInput,\n ): Promise<{ providerCustomerId: string }>;\n pushInvoice(\n input: BillingProviderInvoiceInput,\n ): Promise<{ providerInvoiceId: string }>;\n /** Finalize (if still a draft) and send an invoice for payment. */\n sendInvoice(providerInvoiceId: string): Promise<void>;\n getInvoice(providerInvoiceId: string): Promise<BillingProviderInvoiceState>;\n getSubscription(\n providerSubscriptionId: string,\n ): Promise<BillingProviderSubscriptionState>;\n createCheckout(\n input: BillingProviderCheckoutInput,\n ): Promise<BillingProviderCheckoutSession>;\n /**\n * Verify a webhook signature and normalize the event. Rejects with\n * {@link BillingWebhookVerificationError} when the signature is invalid;\n * anything verified but not actionable is returned as `ignored`.\n */\n verifyWebhook(\n payload: string,\n signature: string,\n /** The request headers, for providers that sign with their own header. */\n headers?: Headers | Record<string, string | undefined>,\n ): Promise<BillingProviderEvent>;\n\n // Optional capabilities (#3139). A provider that cannot do one omits the\n // method, and callers detect the capability by its presence.\n\n /**\n * Charge the customer's saved default payment method off-session,\n * idempotently by `input.idempotencyKey`. A decline or an authentication\n * requirement is a result, not a throw.\n */\n chargeSavedPaymentMethod?(\n input: BillingProviderChargeInput,\n ): Promise<BillingProviderChargeResult>;\n /** Start a checkout that saves a payment method without charging. */\n createSetupCheckout?(\n input: BillingProviderSetupCheckoutInput,\n ): Promise<BillingProviderCheckoutSession>;\n /** Re-read a checkout session, with amounts in minor units. */\n getCheckout?(sessionId: string): Promise<BillingProviderCheckoutState>;\n /** Make a saved payment method the customer's default. Idempotent. */\n setDefaultPaymentMethod?(\n providerCustomerId: string,\n paymentMethodId: string,\n ): Promise<void>;\n /** Write an open invoice off as uncollectible. Idempotent. */\n markInvoiceUncollectible?(providerInvoiceId: string): Promise<void>;\n /**\n * Close an issued invoice as paid outside the provider (#3138). Must be\n * idempotent: an invoice already closed this way is a no-op. Callers check\n * `getInvoice()` first and never close an invoice the provider collected\n * itself (a provider may throw for one).\n */\n markInvoicePaidOutOfBand?(providerInvoiceId: string): Promise<void>;\n /**\n * Read a rail checkout's current state (#3138). Returns null for a\n * checkout this provider did not create.\n */\n getPaymentAttempt?(\n checkoutId: string,\n ): Promise<BillingPaymentAttemptState | null>;\n}\n\n/** The provider's capabilities, defaulted for providers that omit them. */\nexport function providerCapabilities(\n provider: BillingProvider,\n): BillingProviderCapabilities {\n return (\n provider.capabilities ?? { issuesInvoices: true, paymentAttempts: false }\n );\n}\n\n/** A provider was asked for something it does not do (#3138). */\nexport class BillingProviderUnsupportedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BillingProviderUnsupportedError';\n }\n}\n\n/**\n * Why a payer's payment request was refused (#3185). New codes may be added\n * in a minor release; treat an unknown code like `not_payable`.\n *\n * - `not_found`: no such invoice for this payer, or no active balance policy.\n * - `not_payable`: the invoice exists but cannot be paid now (not sent and\n * unpaid, or not issued by this runtime's provider).\n * - `payment_in_progress`: another payment for the invoice is still open.\n * - `nothing_due`: the invoice has no amount left to pay.\n * - `invalid_amount`: the requested amount is not positive minor units.\n * - `below_minimum`: the amount is under the rail's minimum.\n * - `no_account`: the payer has no billing account with this seller.\n */\nexport type BillingPaymentRefusalCode =\n | 'not_found'\n | 'not_payable'\n | 'payment_in_progress'\n | 'nothing_due'\n | 'invalid_amount'\n | 'below_minimum'\n | 'no_account';\n\n/**\n * A payment request refused for a reason the payer can act on (#3185): show\n * the message as a 4xx. Anything else thrown by the same call (provider,\n * database, or configuration failures) is a server error.\n * `TenantIsolationError` (a caller who is not the payer) stays separate.\n */\nexport class BillingPaymentRefusedError extends Error {\n readonly code: BillingPaymentRefusalCode;\n\n constructor(code: BillingPaymentRefusalCode, message: string) {\n super(message);\n this.name = 'BillingPaymentRefusedError';\n this.code = code;\n }\n}\n\nexport class BillingWebhookVerificationError extends Error {\n constructor(message = 'Billing webhook signature verification failed.') {\n super(message);\n this.name = 'BillingWebhookVerificationError';\n }\n}\n","/**\n * Customer model - links to Profile with customer-specific data\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type Address,\n type CustomerOptions,\n CustomerStatus,\n CustomerType,\n} from '../types/index.js';\n\n/**\n * Customer represents the customer role for a Profile.\n *\n * One Profile can have multiple Customer records (e.g., different business contexts).\n * Customer stores customer-specific data like credit limits and payment terms.\n *\n * @example\n * ```typescript\n * const customer = await customers.create({\n * profileId: 'profile-uuid',\n * creditLimit: 10000,\n * paymentTerms: 'Net 30'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Customer extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global customers\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Reference to smrt-profiles Profile\n * Plain string for cross-package reference\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n profileId: string = '';\n\n /**\n * Maximum credit extended to this customer, in **integer minor units**\n * (cents, satoshis).\n *\n * Converts with the rest of commerce because\n * {@link Customer.hasAvailableCredit} compares it directly against a money\n * amount; a dollars-vs-cents split there would approve every request off by\n * a factor of 100 (#2401).\n */\n creditLimit: number = 0;\n\n /**\n * Payment terms (e.g., \"Net 30\", \"Due on receipt\", \"2/10 Net 30\")\n */\n paymentTerms: string = '';\n\n /**\n * Whether customer is exempt from tax\n */\n taxExempt: boolean = false;\n\n /**\n * Tax identification number\n *\n * Sensitive (#1540): PII excluded from generated API/MCP responses and\n * rejected as a `where` filter key.\n */\n @field({ sensitive: true })\n taxId: string = '';\n\n /**\n * Default shipping address\n */\n defaultShippingAddress: Address = {};\n\n /**\n * Default billing address\n */\n defaultBillingAddress: Address = {};\n\n /**\n * Customer status\n */\n status: CustomerStatus = CustomerStatus.ACTIVE;\n\n /**\n * Customer classification — DTC, wholesale, or retail.\n *\n * Drives channel access (wholesale portal vs. storefront vs. POS), default\n * price tier, and credit terms. Defaults to DTC for backwards compatibility.\n */\n customerType: CustomerType = CustomerType.DTC;\n\n /**\n * Internal notes about this customer\n */\n notes: string = '';\n\n constructor(options: CustomerOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.creditLimit !== undefined)\n this.creditLimit = options.creditLimit;\n if (options.paymentTerms !== undefined)\n this.paymentTerms = options.paymentTerms;\n if (options.taxExempt !== undefined) this.taxExempt = options.taxExempt;\n if (options.taxId !== undefined) this.taxId = options.taxId;\n if (options.defaultShippingAddress !== undefined)\n this.defaultShippingAddress = options.defaultShippingAddress;\n if (options.defaultBillingAddress !== undefined)\n this.defaultBillingAddress = options.defaultBillingAddress;\n if (options.status !== undefined) this.status = options.status;\n // Treat `null` the same as `undefined` here. Customers loaded from\n // rows written before the `customer_type` column existed will\n // hydrate with the field as SQL `NULL`; without this guard the\n // SmrtObject loader would overwrite the `CustomerType.DTC`\n // initializer with `null`, breaking the documented\n // backwards-compatible default. Explicit string values (including\n // any future custom flavour) still take precedence.\n if (options.customerType !== undefined && options.customerType !== null) {\n this.customerType = options.customerType;\n }\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Convenience predicate: is this a wholesale customer?\n */\n isWholesale(): boolean {\n return this.customerType === CustomerType.WHOLESALE;\n }\n\n /**\n * Check if customer is active\n */\n isActive(): boolean {\n return this.status === CustomerStatus.ACTIVE;\n }\n\n /**\n * Check if customer has available credit\n */\n hasAvailableCredit(amount: number): boolean {\n // In a full implementation, this would check against outstanding balances\n return this.creditLimit >= amount;\n }\n\n /**\n * Get the profile ID for external lookup.\n *\n * To get the actual Profile object, use the ProfileCollection from smrt-profiles:\n * ```typescript\n * const profiles = await ProfileCollection.create(options);\n * const profile = await profiles.get({ id: customer.profileId });\n * ```\n *\n * @returns The profile ID or empty string if not set\n */\n getProfileId(): string {\n return this.profileId;\n }\n}\n\nexport default Customer;\n","/**\n * CustomerCollection - Collection manager for Customer objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Customer } from '../models/Customer.js';\nimport { CustomerStatus } from '../types/index.js';\n\nexport class CustomerCollection extends SmrtCollection<Customer> {\n static readonly _itemClass = Customer;\n\n /**\n * Find customers by profile ID\n *\n * @param profileId - Profile ID from smrt-profiles\n * @returns Array of customers linked to this profile\n */\n async findByProfile(profileId: string): Promise<Customer[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all active customers\n *\n * @returns Array of active customers\n */\n async findActive(): Promise<Customer[]> {\n return await this.list({\n where: { status: CustomerStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find customers by status\n *\n * @param status - Customer status\n * @returns Array of customers\n */\n async findByStatus(status: CustomerStatus): Promise<Customer[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get or create a customer for a profile\n *\n * @param profileId - Profile ID\n * @param defaults - Default values if creating\n * @returns Customer\n */\n async getOrCreateForProfile(\n profileId: string,\n defaults: Partial<{\n creditLimit: number;\n paymentTerms: string;\n }> = {},\n ): Promise<Customer> {\n const existing = await this.findByProfile(profileId);\n if (existing.length > 0) {\n return existing[0];\n }\n\n const customer = await this.create({\n profileId,\n creditLimit: defaults.creditLimit ?? 0,\n paymentTerms: defaults.paymentTerms ?? '',\n status: CustomerStatus.ACTIVE,\n });\n await customer.save();\n return customer;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all customers belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of customers for the tenant\n */\n async findByTenant(tenantId: string): Promise<Customer[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global customers (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global customers\n */\n async findGlobal(): Promise<Customer[]> {\n return queryGlobal<Customer>(this);\n }\n\n /**\n * Find customers for a tenant including global customers.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global customers\n */\n async findWithGlobals(tenantId: string): Promise<Customer[]> {\n return queryWithGlobals<Customer>(\n this,\n tenantId,\n 'Customer.findWithGlobals',\n );\n }\n}\n","/**\n * PaymentInstrument model - a reusable saved payment method (\"card on file\").\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type PaymentInstrumentOptions,\n PaymentInstrumentStatus,\n} from '../types/index.js';\n\n/**\n * PaymentInstrument stores a reusable payment method a customer has saved with\n * a payment backend (e.g. a card vaulted by Stripe), so it can be charged later\n * without the customer present.\n *\n * It holds only **references and non-sensitive display metadata** — the\n * provider customer id (`cus_...`), the provider payment-method id (`pm_...`),\n * and card display fields (brand / last4 / expiry). The actual card number is\n * vaulted by the payment backend and is never stored here.\n *\n * @example\n * ```typescript\n * const instrument = await instruments.create({\n * customerId: customer.id,\n * backendId: 'stripe',\n * providerCustomerId: 'cus_123',\n * providerPaymentMethodId: 'pm_123',\n * brand: 'visa',\n * last4: '4242',\n * expMonth: 12,\n * expYear: 2030,\n * isDefault: true,\n * });\n * await instrument.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: {\n include: ['list', 'get', 'create', 'update'],\n // `isDefault` is deliberately EXCLUDED from the generated write surface:\n // the single-default-per-customer invariant is enforced by\n // {@link PaymentInstrumentCollection.setDefaultForCustomer}, which clears\n // the flag on the customer's other instruments. Letting a generated\n // create/update route set `isDefault` directly would let a client mark\n // several instruments default (or flip one without clearing the others).\n writable: [\n 'customerId',\n 'backendId',\n 'providerCustomerId',\n 'providerPaymentMethodId',\n 'type',\n 'brand',\n 'last4',\n 'expMonth',\n 'expYear',\n 'status',\n ],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class PaymentInstrument extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable to support both\n * tenant-scoped and global instruments.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Customer this saved method belongs to.\n */\n @foreignKey('Customer')\n customerId: string = '';\n\n /**\n * Stable identifier of the `PaymentBackend` adapter that vaulted the method —\n * e.g. `stripe`. Mirrors {@link Payment.backendId}.\n */\n backendId: string = '';\n\n /**\n * Provider customer reference (e.g. a Stripe `cus_...`). A reference, not\n * card data.\n */\n providerCustomerId: string = '';\n\n /**\n * Provider payment-method reference (e.g. a Stripe `pm_...`). Used, together\n * with {@link providerCustomerId}, to charge the saved method off-session. A\n * reference, not card data.\n */\n providerPaymentMethodId: string = '';\n\n /**\n * Instrument type as reported by the backend — e.g. `card`,\n * `us_bank_account`.\n */\n type: string = 'card';\n\n /**\n * Card brand for display (e.g. `visa`). Display-only, not PCI-sensitive.\n */\n brand: string = '';\n\n /**\n * Last four digits for display. Not the full card number.\n */\n last4: string = '';\n\n /**\n * Card expiry month (1–12). `0` when unknown / not a card.\n */\n expMonth: number = 0;\n\n /**\n * Card expiry year (four digits). `0` when unknown / not a card.\n */\n expYear: number = 0;\n\n /**\n * Whether this is the customer's default instrument. Use\n * {@link PaymentInstrumentCollection.setDefaultForCustomer} to flip the\n * default so only one instrument per customer is marked default.\n */\n isDefault: boolean = false;\n\n /**\n * Lifecycle status — `active` while chargeable, `expired` once the card has\n * lapsed, `removed` when the customer detaches it.\n */\n status: PaymentInstrumentStatus = PaymentInstrumentStatus.ACTIVE;\n\n constructor(options: PaymentInstrumentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.backendId !== undefined) this.backendId = options.backendId;\n if (options.providerCustomerId !== undefined)\n this.providerCustomerId = options.providerCustomerId;\n if (options.providerPaymentMethodId !== undefined)\n this.providerPaymentMethodId = options.providerPaymentMethodId;\n if (options.type !== undefined) this.type = options.type;\n if (options.brand !== undefined) this.brand = options.brand;\n if (options.last4 !== undefined) this.last4 = options.last4;\n if (options.expMonth !== undefined) this.expMonth = options.expMonth;\n if (options.expYear !== undefined) this.expYear = options.expYear;\n if (options.isDefault !== undefined) this.isDefault = options.isDefault;\n if (options.status !== undefined) this.status = options.status;\n }\n\n /**\n * True while the instrument is active (chargeable).\n */\n isActive(): boolean {\n return this.status === PaymentInstrumentStatus.ACTIVE;\n }\n\n /**\n * True once the instrument has been marked expired.\n */\n isExpired(): boolean {\n return this.status === PaymentInstrumentStatus.EXPIRED;\n }\n\n /**\n * True once the instrument has been removed/detached.\n */\n isRemoved(): boolean {\n return this.status === PaymentInstrumentStatus.REMOVED;\n }\n\n /**\n * Mark the instrument expired (e.g. the card lapsed). A removed instrument\n * is not revived by this.\n */\n markExpired(): void {\n if (this.status === PaymentInstrumentStatus.REMOVED) return;\n this.status = PaymentInstrumentStatus.EXPIRED;\n }\n\n /**\n * Mark the instrument removed/detached and clear its default flag.\n */\n markRemoved(): void {\n this.status = PaymentInstrumentStatus.REMOVED;\n this.isDefault = false;\n }\n}\n\nexport default PaymentInstrument;\n","/**\n * PaymentInstrumentCollection - Collection manager for PaymentInstrument objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { PaymentInstrument } from '../models/PaymentInstrument.js';\nimport { PaymentInstrumentStatus } from '../types/index.js';\n\nexport class PaymentInstrumentCollection extends SmrtCollection<PaymentInstrument> {\n static readonly _itemClass = PaymentInstrument;\n\n /**\n * Find all instruments saved by a customer, newest first.\n *\n * @param customerId - Customer ID\n * @returns Array of instruments\n */\n async findByCustomer(customerId: string): Promise<PaymentInstrument[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find a customer's active instruments, newest first.\n *\n * @param customerId - Customer ID\n * @returns Array of active instruments\n */\n async findActiveByCustomer(customerId: string): Promise<PaymentInstrument[]> {\n return await this.list({\n where: { customerId, status: PaymentInstrumentStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find a customer's default instrument, or null if none is set.\n *\n * @param customerId - Customer ID\n * @returns The default instrument or null\n */\n async findDefaultForCustomer(\n customerId: string,\n ): Promise<PaymentInstrument | null> {\n const results = await this.list({\n where: { customerId, isDefault: true },\n orderBy: 'created_at DESC',\n });\n // Skip any instrument left flagged default after removal, and pick\n // deterministically (newest) if more than one row is somehow marked.\n return results.find((instrument) => !instrument.isRemoved()) ?? null;\n }\n\n /**\n * Find an instrument by its provider payment-method reference (e.g. a Stripe\n * `pm_...`).\n *\n * @param providerPaymentMethodId - Provider payment-method id\n * @returns The instrument or null\n */\n async findByProviderPaymentMethodId(\n providerPaymentMethodId: string,\n ): Promise<PaymentInstrument | null> {\n const results = await this.list({\n where: { providerPaymentMethodId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Make one of a customer's instruments the default, clearing the flag on the\n * others so only a single default is ever set. Only rows whose flag actually\n * changes are re-saved.\n *\n * @param customerId - Customer ID\n * @param instrumentId - The instrument to make default\n */\n async setDefaultForCustomer(\n customerId: string,\n instrumentId: string,\n ): Promise<void> {\n const instruments = await this.findByCustomer(customerId);\n const target = instruments.find(\n (instrument) => instrument.id === instrumentId,\n );\n // Fail fast rather than clearing every default — leaving the customer with\n // none — when the target isn't one of this customer's instruments.\n if (!target) {\n throw new Error(\n `PaymentInstrument ${instrumentId} not found for customer ${customerId}`,\n );\n }\n if (target.isRemoved()) {\n throw new Error(\n `PaymentInstrument ${instrumentId} is removed and cannot be made default`,\n );\n }\n for (const instrument of instruments) {\n const shouldBeDefault = instrument.id === instrumentId;\n if (instrument.isDefault !== shouldBeDefault) {\n instrument.isDefault = shouldBeDefault;\n await instrument.save();\n }\n }\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all instruments belonging to a specific tenant.\n *\n * @param tenantId - Tenant ID\n * @returns Array of instruments for the tenant\n */\n async findByTenant(tenantId: string): Promise<PaymentInstrument[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global instruments (not associated with any tenant). Routes\n * through the shared tenant-global helper so it does not throw under an\n * active tenant context. (#1600)\n *\n * @returns Array of global instruments\n */\n async findGlobal(): Promise<PaymentInstrument[]> {\n return queryGlobal<PaymentInstrument>(this);\n }\n\n /**\n * Find instruments for a tenant including global instruments. Fails closed if\n * an active tenant context requests a different tenant's rows. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global instruments\n */\n async findWithGlobals(tenantId: string): Promise<PaymentInstrument[]> {\n return queryWithGlobals<PaymentInstrument>(\n this,\n tenantId,\n 'PaymentInstrument.findWithGlobals',\n );\n }\n}\n","/**\n * Card on file (#3139): a provider checkout that saves a payment method\n * without charging, and the settlement that makes it the payer's default.\n *\n * A completed setup (or a credit purchase that saved its card) is applied\n * from the provider's checkout event: `observe()` re-reads the session and\n * makes the saved method the provider customer's default; `project()`\n * records it as the payer customer's default commerce `PaymentInstrument`\n * and adopts the billing address collected at checkout as the tax location.\n */\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport {\n getTenantId,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport { CustomerCollection } from '../collections/CustomerCollection.js';\nimport { PaymentInstrumentCollection } from '../collections/PaymentInstrumentCollection.js';\nimport type { BillingAccount } from '../models/billing.js';\nimport type { PaymentInstrument } from '../models/PaymentInstrument.js';\nimport { PaymentInstrumentStatus } from '../types/index.js';\nimport type {\n BillingProviderCheckoutSession,\n BillingProviderCheckoutState,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n canonicalTenantId,\n deterministicId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport const CARD_SETUP_PURPOSE = 'card_setup';\n/** Checkout metadata flag: the session saves its payment method. */\nexport const SAVE_CARD_METADATA = 'smrt_save_card';\n/**\n * Checkout metadata flag: the session collects the billing address, which is\n * adopted as the payer's tax location when the card is saved.\n */\nexport const COLLECT_ADDRESS_METADATA = 'smrt_collect_address';\n\nexport interface CreateCardSetupCheckoutInput {\n payerTenantId: string;\n /** The currency the saved card will be charged in. */\n currency: string;\n successUrl: string;\n cancelUrl: string;\n /**\n * Caller-owned id for this setup attempt. A retried request with the same\n * id returns the same checkout; a new attempt (for example after a declined\n * card) needs a new id.\n */\n setupId: string;\n /**\n * Collect the billing address at checkout and adopt it as the payer's tax\n * location. Default: when the account is taxed and has no tax location.\n */\n collectBillingAddress?: boolean;\n}\n\n/** Only the payer (or a system/operator context) manages its card. */\nexport function assertPayerContext(payerTenantId: string): void {\n if (\n !isSystemContext() &&\n !isSuperAdminBypass() &&\n tenantKey(getTenantId()) !== payerTenantId\n ) {\n throw new TenantIsolationError(\n 'Only the payer can manage its payment methods.',\n );\n }\n}\n\nexport async function createCardSetupCheckout(\n runtime: BillingRuntime,\n input: CreateCardSetupCheckoutInput,\n): Promise<BillingProviderCheckoutSession> {\n const createSetupCheckout = runtime.provider.createSetupCheckout;\n if (!createSetupCheckout) {\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot save a payment method.`,\n );\n }\n assertCanSaveCards(runtime);\n if (!input.setupId) throw new Error('setupId is required.');\n const payer = canonicalTenantId(input.payerTenantId, 'payerTenantId');\n assertPayerContext(payer);\n const account = await runtime.getAccount(payer);\n if (!account?.id) throw new Error(`No billing account for payer ${payer}.`);\n // The address may be collected at checkout, so the tax location is not\n // required yet; period close still requires one before invoicing.\n const synced = await runtime.ensureProviderCustomer(account, {\n requireTaxLocation: false,\n });\n const key = await deterministicId([\n 'billing-card-setup',\n runtime.sellerTenantId,\n String(account.id),\n input.setupId,\n ]);\n const collectBillingAddress =\n input.collectBillingAddress ??\n (synced.automaticTax && !synced.hasTaxLocation);\n return createSetupCheckout.call(runtime.provider, {\n idempotencyKey: `smrt-card-setup:${key}`,\n providerCustomerId: synced.providerCustomerId,\n currency: normalizeCurrency(input.currency),\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n collectBillingAddress,\n metadata: {\n smrt_purpose: CARD_SETUP_PURPOSE,\n smrt_seller: runtime.sellerTenantId,\n smrt_payer: payer,\n smrt_account: String(account.id),\n ...(collectBillingAddress ? { [COLLECT_ADDRESS_METADATA]: '1' } : {}),\n },\n });\n}\n\n/**\n * What a completed checkout saved for one of this seller's accounts: a card\n * (setup, or a purchase with `savePaymentMethod`) and/or a billing address it\n * was asked to collect. Validated against the account.\n */\nexport interface SavedCard {\n account: BillingAccount;\n providerCustomerId: string;\n paymentMethodId?: string;\n billingAddress?: BillingProviderCheckoutState['billingAddress'];\n}\n\n/** Whether the provider can apply a saved card from a checkout event. */\nexport function assertCanSaveCards(runtime: BillingRuntime): void {\n if (\n !runtime.provider.getCheckout ||\n !runtime.provider.setDefaultPaymentMethod\n ) {\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot apply a saved payment method (getCheckout and setDefaultPaymentMethod are required).`,\n );\n }\n}\n\n/**\n * The card and/or collected address a completed checkout saved for one of\n * this seller's accounts, or null when it saved neither. Throws when the\n * session and its (verified) metadata disagree, so the event dead-letters\n * visibly.\n */\nexport async function savedCardFromCheckout(\n runtime: BillingRuntime,\n metadata: Record<string, string>,\n state: BillingProviderCheckoutState,\n savesCard: boolean,\n): Promise<SavedCard | null> {\n if (!state.complete) return null;\n const paymentMethodId = savesCard ? state.paymentMethodId : undefined;\n // Only an address the checkout was asked to collect replaces the local\n // tax location; otherwise the provider's copy is the one synced from it.\n const billingAddress =\n metadata[COLLECT_ADDRESS_METADATA] === '1' && state.billingAddress?.country\n ? state.billingAddress\n : undefined;\n if (!paymentMethodId && !billingAddress) return null;\n if (tenantKey(metadata.smrt_seller) !== runtime.sellerTenantId) return null;\n const account = await runtime.accounts.get(metadata.smrt_account ?? '');\n if (\n !account?.id ||\n tenantKey(account.sellerTenantId) !== runtime.sellerTenantId ||\n tenantKey(account.payerTenantId) !== tenantKey(metadata.smrt_payer)\n ) {\n throw new Error(\n `Checkout ${state.sessionId} does not belong to the billing account in its metadata.`,\n );\n }\n if (\n account.provider !== runtime.provider.name ||\n !account.providerCustomerId ||\n state.providerCustomerId !== account.providerCustomerId\n ) {\n // The account's provider customer changed while the checkout was open\n // (#3152). A saved card would attach to the wrong customer, so that\n // fails visibly; an address collected for a credit purchase is skipped\n // (the next checkout or card setup collects it again) so the credit the\n // payer paid for still settles.\n if (!paymentMethodId) return null;\n throw new Error(\n `Checkout ${state.sessionId} saved a card for provider customer ${state.providerCustomerId ?? '(none)'}, not its billing account's.`,\n );\n }\n return {\n account,\n providerCustomerId: account.providerCustomerId,\n paymentMethodId,\n billingAddress,\n };\n}\n\n/**\n * Record a saved card as the customer's default instrument, and adopt a\n * collected billing address. Idempotent; runs in the event transaction.\n */\nexport async function recordSavedCard(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n card: SavedCard,\n): Promise<void> {\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n if (card.paymentMethodId) await recordInstrument(runtime, db, card);\n if (card.billingAddress?.country) {\n const customers = await CustomerCollection.create({ db });\n const customer = await customers.get(card.account.customerId);\n if (customer) {\n customer.defaultBillingAddress = card.billingAddress;\n await customer.save();\n }\n }\n });\n}\n\nasync function recordInstrument(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n card: SavedCard,\n): Promise<void> {\n const paymentMethodId = String(card.paymentMethodId);\n const instruments = await PaymentInstrumentCollection.create({ db });\n const id = await deterministicId([\n 'billing-payment-instrument',\n runtime.provider.name,\n runtime.sellerTenantId,\n paymentMethodId,\n ]);\n let instrument = await instruments.get(id);\n if (!instrument) {\n instrument = await instruments.create({\n id,\n tenantId: runtime.sellerTenantId,\n customerId: card.account.customerId,\n backendId: runtime.provider.name,\n providerCustomerId: card.providerCustomerId,\n providerPaymentMethodId: paymentMethodId,\n type: 'card',\n status: PaymentInstrumentStatus.ACTIVE,\n _insertOnly: true,\n });\n } else if (instrument.customerId !== card.account.customerId) {\n throw new Error(\n `Payment method ${paymentMethodId} is already saved for another customer.`,\n );\n } else if (!instrument.isActive()) {\n instrument.status = PaymentInstrumentStatus.ACTIVE;\n await instrument.save();\n }\n await instruments.setDefaultForCustomer(\n card.account.customerId,\n String(instrument.id),\n );\n}\n\n/**\n * The payer's default card when it is with this runtime's provider customer,\n * else null: what `autoChargeInvoices` bills and automatic top-ups charge.\n */\nexport async function defaultCardInstrument(\n runtime: BillingRuntime,\n account: BillingAccount,\n providerCustomerId: string,\n): Promise<PaymentInstrument | null> {\n const instrument = await withTenant(\n { tenantId: runtime.sellerTenantId },\n () => runtime.instruments.findDefaultForCustomer(account.customerId),\n );\n return instrument?.isActive() &&\n instrument.backendId === runtime.provider.name &&\n instrument.providerCustomerId === providerCustomerId\n ? instrument\n : null;\n}\n\n/** Whether {@link defaultCardInstrument} finds a card. */\nexport async function defaultCardFor(\n runtime: BillingRuntime,\n account: BillingAccount,\n providerCustomerId: string,\n): Promise<boolean> {\n return Boolean(\n await defaultCardInstrument(runtime, account, providerCustomerId),\n );\n}\n\n/** Whether a payer has a card on file, and since when (#3187). */\nexport interface BillingCardOnFile {\n /**\n * The payer has a default card with this runtime's provider customer: the\n * card automatically charged invoices bill and automatic top-ups charge.\n */\n onFile: boolean;\n /** When that card was first recorded. */\n savedAt?: Date;\n}\n\n/** See {@link BillingRuntime.cardOnFile}. */\nexport async function cardOnFile(\n runtime: BillingRuntime,\n payerTenantId: string,\n): Promise<BillingCardOnFile> {\n const payer = canonicalTenantId(payerTenantId, 'payerTenantId');\n assertPayerContext(payer);\n const account = await runtime.getAccount(payer);\n if (\n !account?.id ||\n account.provider !== runtime.provider.name ||\n !account.providerCustomerId\n ) {\n return { onFile: false };\n }\n const instrument = await defaultCardInstrument(\n runtime,\n account,\n account.providerCustomerId,\n );\n if (!instrument) return { onFile: false };\n const created = instrument.created_at;\n const savedAt =\n created instanceof Date ? created : created ? new Date(created) : null;\n return savedAt && Number.isFinite(savedAt.getTime())\n ? { onFile: true, savedAt }\n : { onFile: true };\n}\n","/**\n * Prepaid credit purchases through provider checkout (#3060). A paid\n * checkout credits the balance policy through smrt-subscriptions'\n * `grantCredit()`, keyed by the checkout session so it is credited once.\n * Provider-calculated tax is charged on top of the credit (#3139).\n */\nimport {\n getTenantId,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withSystemContext,\n} from '@happyvertical/smrt-tenancy';\nimport {\n assertCanSaveCards,\n COLLECT_ADDRESS_METADATA,\n SAVE_CARD_METADATA,\n} from './cards.js';\nimport {\n BillingPaymentRefusedError,\n type BillingProviderCheckoutSession,\n providerCapabilities,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n canonicalTenantId,\n deterministicId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport const CREDIT_PURCHASE_PURPOSE = 'credit_purchase';\n\nexport interface CreateCreditCheckoutInput {\n /** The `period: 'balance'` spending policy to credit. */\n spendingPolicyId: string;\n /** Credit to buy, in integer minor units of the policy currency. */\n amount: number;\n successUrl: string;\n cancelUrl: string;\n /**\n * Caller-owned id for this purchase attempt (for example a cart id). A\n * retried request with the same id returns the same checkout.\n */\n purchaseId: string;\n /** Checkout line description (default `Prepaid credit`). */\n description?: string;\n /**\n * Charge provider-calculated tax on top of the credit (#3139). Default:\n * the payer's account setting (`automaticTax`, off for tax-exempt\n * customers). The credit granted is always `amount`; tax is booked to the\n * tax account.\n */\n automaticTax?: boolean;\n /**\n * Also save the card as the payer's default for automatic top-ups and\n * automatically charged invoices (#3139).\n */\n savePaymentMethod?: boolean;\n /**\n * The payment rail to pay with (a provider name from the runtime's\n * `provider` or `paymentProviders`; default the issuing provider, #3138).\n */\n provider?: string;\n}\n\n/** The checkout metadata a completed purchase is settled from. */\nexport interface CreditPurchaseMetadata {\n purpose: string;\n sellerTenantId: string;\n payerTenantId: string;\n billingAccountId: string;\n spendingPolicyId: string;\n grantedByTenantId: string;\n amount: number;\n currency: string;\n}\n\nfunction encodeMetadata(\n metadata: CreditPurchaseMetadata,\n): Record<string, string> {\n const encoded: Record<string, string> = {\n smrt_purpose: metadata.purpose,\n smrt_seller: metadata.sellerTenantId,\n smrt_payer: metadata.payerTenantId,\n smrt_account: metadata.billingAccountId,\n smrt_policy: metadata.spendingPolicyId,\n smrt_granted_by: metadata.grantedByTenantId,\n smrt_amount: String(metadata.amount),\n smrt_currency: metadata.currency,\n };\n // Providers drop empty metadata values (Stripe treats \"\" as unset), so an\n // empty key is omitted rather than signed and then lost in transit.\n for (const [key, value] of Object.entries(encoded)) {\n if (!value) delete encoded[key];\n }\n return encoded;\n}\n\n/** Parse checkout metadata written by {@link createCreditCheckout}. */\nexport function creditMetadata(\n raw: Record<string, string>,\n): CreditPurchaseMetadata | null {\n if (raw.smrt_purpose !== CREDIT_PURCHASE_PURPOSE) return null;\n const amount = Number(raw.smrt_amount);\n if (\n !raw.smrt_seller ||\n !raw.smrt_payer ||\n !raw.smrt_account ||\n !raw.smrt_policy ||\n !raw.smrt_currency ||\n !Number.isSafeInteger(amount) ||\n amount <= 0\n ) {\n throw new Error('Credit purchase metadata is incomplete.');\n }\n return {\n purpose: raw.smrt_purpose,\n sellerTenantId: tenantKey(raw.smrt_seller),\n payerTenantId: tenantKey(raw.smrt_payer),\n billingAccountId: raw.smrt_account,\n spendingPolicyId: raw.smrt_policy,\n grantedByTenantId: tenantKey(raw.smrt_granted_by),\n amount,\n currency: normalizeCurrency(raw.smrt_currency),\n };\n}\n\nexport async function createCreditCheckout(\n runtime: BillingRuntime,\n input: CreateCreditCheckoutInput,\n): Promise<BillingProviderCheckoutSession> {\n if (!Number.isSafeInteger(input.amount) || input.amount <= 0) {\n throw new BillingPaymentRefusedError(\n 'invalid_amount',\n 'Credit amount must be positive integer minor units.',\n );\n }\n if (!input.purchaseId) throw new Error('purchaseId is required.');\n const policy = await withSystemContext(() =>\n runtime.policies.get(input.spendingPolicyId),\n );\n if (!policy?.id || policy.period !== 'balance' || !policy.active) {\n throw new BillingPaymentRefusedError(\n // One code whether it is missing or inactive: this runs before the\n // caller is known to be the payer.\n 'not_found',\n `Spending policy ${input.spendingPolicyId} is not an active balance policy.`,\n );\n }\n // A delegated balance is funded only by the parent that set it.\n const grantedBy = tenantKey(policy.setByTenantId);\n const payer = canonicalTenantId(\n grantedBy || String(policy.tenantId),\n 'payerTenantId',\n );\n if (\n !isSystemContext() &&\n !isSuperAdminBypass() &&\n tenantKey(getTenantId()) !== payer\n ) {\n throw new TenantIsolationError(\n 'Only the payer of a balance policy can buy credit for it.',\n );\n }\n const account = await runtime.getAccount(payer);\n if (!account?.id) {\n throw new BillingPaymentRefusedError(\n 'no_account',\n `No billing account for payer ${payer}.`,\n );\n }\n const currency = normalizeCurrency(policy.currency);\n const provider = runtime.providerFor(input.provider);\n const issuing = provider === runtime.provider;\n if (!issuing && input.savePaymentMethod) {\n throw new Error(`The ${provider.name} payment rail cannot save a card.`);\n }\n // Saving a card needs a provider customer to attach it to; the address may\n // still be collected at checkout.\n // Saving a card is settled from the checkout event, so refuse a provider\n // that could take the money but not record the card.\n if (input.savePaymentMethod) assertCanSaveCards(runtime);\n const synced = input.savePaymentMethod\n ? await runtime.ensureProviderCustomer(account, {\n requireTaxLocation: false,\n })\n : null;\n const metadata = encodeMetadata({\n purpose: CREDIT_PURCHASE_PURPOSE,\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId: payer,\n billingAccountId: String(account.id),\n spendingPolicyId: String(policy.id),\n grantedByTenantId: grantedBy,\n amount: input.amount,\n currency,\n });\n const key = await deterministicId([\n 'billing-credit-checkout',\n runtime.sellerTenantId,\n String(policy.id),\n input.purchaseId,\n // The issuing provider keeps the pre-#3138 key, so retries of existing\n // purchases still return their checkout.\n ...(issuing ? [] : [provider.name]),\n ]);\n const providerCustomerId =\n synced?.providerCustomerId ??\n (account.provider === provider.name ? account.providerCustomerId : '');\n const automaticTax =\n input.automaticTax ?? (await runtime.accountIsTaxed(account));\n if (!issuing && automaticTax) {\n // A payment rail cannot calculate tax (#3138). Selling untaxed credit on\n // it is the host's explicit decision: pass `automaticTax: false`.\n throw new Error(\n `The ${provider.name} payment rail cannot charge tax; pass automaticTax: false to sell this payer untaxed credit on it.`,\n );\n }\n if (synced) metadata[SAVE_CARD_METADATA] = '1';\n // A taxed checkout for a provider customer collects the address onto that\n // customer; with no local tax location yet, it becomes the payer's (as a\n // card setup does), so period close can tax the payer too. A checkout\n // without a provider customer keeps no customer to read it from.\n if (\n automaticTax &&\n providerCustomerId &&\n provider.getCheckout &&\n !(await runtime.hasTaxLocation(account))\n ) {\n metadata[COLLECT_ADDRESS_METADATA] = '1';\n }\n const session = await provider.createCheckout({\n idempotencyKey: `smrt-credit-checkout:${key}`,\n providerCustomerId: providerCustomerId || undefined,\n customerEmail: providerCustomerId ? undefined : account.email || undefined,\n currency,\n amount: input.amount,\n description: input.description || 'Prepaid credit',\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n metadata,\n automaticTax,\n ...(automaticTax && runtime.taxCodes.credit\n ? { taxCode: runtime.taxCodes.credit }\n : {}),\n ...(input.savePaymentMethod ? { savePaymentMethod: true } : {}),\n });\n if (providerCapabilities(provider).paymentAttempts) {\n await runtime.recordPaymentAttemptStart({\n orderId: `smrt-credit-checkout:${key}`,\n provider: provider.name,\n checkoutId: session.sessionId,\n checkoutUrl: session.url ?? '',\n purpose: 'credit_purchase',\n payerTenantId: payer,\n billingAccountId: String(account.id),\n spendingPolicyId: String(policy.id),\n amount: input.amount,\n currency,\n });\n }\n return session;\n}\n","/**\n * Payment model - tracks payments with ledger integration\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Journal } from '@happyvertical/smrt-ledgers';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport {\n PaymentMethod,\n type PaymentOptions,\n PaymentStatus,\n type RecordPaymentOptions,\n} from '../types/index.js';\n\n/**\n * Legal status transitions for a Payment, keyed by the prior persisted status.\n * A status mapping to itself (no-op re-save) is always permitted and handled\n * separately. Transitioning *into* COMPLETED is additionally gated on the\n * verified `recordPayment()` settlement path (see {@link Payment.save}) — it is\n * never reachable by raw mass-assignment even though it appears here as a\n * structurally legal edge out of PENDING.\n *\n * Forward path: PENDING → COMPLETED, with FAILED / CANCELLED as alternate\n * terminal exits from PENDING and REFUNDED reachable from COMPLETED.\n */\nconst PAYMENT_STATUS_TRANSITIONS: Record<PaymentStatus, PaymentStatus[]> = {\n [PaymentStatus.PENDING]: [\n PaymentStatus.COMPLETED,\n PaymentStatus.FAILED,\n PaymentStatus.CANCELLED,\n ],\n // A completed payment can only be reversed via refund.\n [PaymentStatus.COMPLETED]: [PaymentStatus.REFUNDED],\n // Terminal states.\n [PaymentStatus.FAILED]: [],\n [PaymentStatus.REFUNDED]: [],\n [PaymentStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each Payment instance was loaded with,\n * so the save-time transition guard can compare the prior persisted status\n * against the one being written without adding a persisted column. WeakMap\n * keeps it out of the schema and GCs with the instance — same pattern as the\n * other commerce models.\n */\nconst loadedPaymentStatus = new WeakMap<Payment, PaymentStatus>();\n\n/**\n * Marks instances whose transition into COMPLETED is being driven by the\n * verified `recordPayment()` settlement path (which posts a balanced journal\n * before flipping the status). The save-time guard consults this set so that\n * COMPLETED can only be reached through that path, never via raw\n * mass-assignment on the generated update route. Cleared after the save runs.\n */\nconst settlementInProgress = new WeakSet<Payment>();\n\n/**\n * Payment represents a financial transaction against a contract.\n *\n * Payments can be integrated with smrt-ledgers to automatically\n * create balanced journal entries for proper accounting.\n *\n * @example\n * ```typescript\n * // Create a payment\n * const payment = await payments.create({\n * contractId: order.id,\n * customerId: customer.id,\n * amount: 1500.00,\n * method: PaymentMethod.CREDIT_CARD,\n * transactionId: 'stripe_pi_123456'\n * });\n *\n * // Record with ledger integration\n * await payment.recordPayment({\n * ledgerId: ledger.id,\n * receivablesAccountId: arAccount.id,\n * cashAccountId: bankAccount.id\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): restrict the generated create/update\n // write surface so privileged / settlement-derived fields can NEVER be set\n // by a generated REST/MCP route — closing the status-mass-assignment vector\n // at the surface rather than relying solely on the save()-time guard.\n //\n // `writable` is an allowlist: only these fields survive applyWritablePolicy()\n // on BOTH create and update (#1540). Deliberately EXCLUDED:\n // - `status` — a COMPLETED Payment is settlement proof downstream\n // (PaymentIntent's PAID verification trusts it). It is only ever set by\n // the verified `recordPayment()` settlement path, never by a caller. This\n // closes the \"forged COMPLETED Payment via api.create\" vector (codex HIGH#1).\n // - `journalId` / `paidAt` — settlement-derived, written by recordPayment().\n // - `syncedAt` — provider-sync bookkeeping, written by the sync path.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'contractId',\n 'customerId',\n 'amount',\n 'currency',\n 'method',\n 'transactionId',\n 'reference',\n 'notes',\n 'externalId',\n 'externalProvider',\n 'backendId',\n 'backendTxRef',\n 'nativeAmount',\n 'nativeCurrency',\n 'usdAtQuote',\n 'usdAtConfirmation',\n ],\n },\n // NOTE: `recordPayment` is intentionally NOT exposed over MCP. It posts a\n // balanced journal into smrt-ledgers (moves money in the books) and flips\n // the payment to COMPLETED — not safe as an unguarded MCP tool that carries\n // no authz. Financial mutations must go through application code that\n // enforces permissions. The MCP create/update tools honor the same\n // `api.writable` allowlist above, so `status` is unsettable over MCP too.\n // See S5 audit #1390.\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Payment extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global payments\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Contract this payment is for\n */\n @foreignKey('Contract')\n contractId: string | null = null;\n\n /**\n * Customer who made the payment\n */\n @foreignKey('Customer')\n customerId: string | null = null;\n\n /**\n * Payment amount, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. This is the figure\n * `PaymentAllocation.save()` caps allocations against and `Invoice.amountPaid`\n * is summed from, so it must be in the same exact units as both (#2401).\n */\n amount: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n currency: string = 'USD';\n\n /**\n * Payment method used\n */\n method: PaymentMethod = PaymentMethod.BANK_TRANSFER;\n\n /**\n * Current payment status\n */\n status: PaymentStatus = PaymentStatus.PENDING;\n\n /**\n * External transaction ID (from payment processor)\n */\n transactionId: string = '';\n\n /**\n * Internal reference number\n */\n reference: string = '';\n\n /**\n * Link to smrt-ledgers Journal (created by recordPayment)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n journalId: string = '';\n\n /**\n * When the payment was completed\n */\n paidAt: Date | null = null;\n\n /**\n * Notes about the payment\n */\n notes: string = '';\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * External ID in accounting provider (e.g., QBO payment ID)\n */\n externalId: string = '';\n\n /**\n * Accounting provider name ('quickbooks' | 'stripe' | 'paypal' | etc.)\n */\n externalProvider: string = '';\n\n /**\n * When payment was last synced to provider\n */\n syncedAt: Date | null = null;\n\n // ============================================================================\n // Payment Backend Identity\n // ============================================================================\n //\n // When a `PaymentBackend` adapter (Stripe, x402, BTC RPC, etc.) brokers the\n // payment rather than a human recording it manually, we capture the\n // backend identity, its native-currency amount, and the USD valuation at\n // both quote and confirmation time. The classic `amount` / `currency`\n // pair stays as the canonical settlement number; the backend fields\n // describe *how* the funds arrived and let downstream accounting close\n // the loop on volatile-currency drift.\n //\n // All fields default to empty / zero so existing Payment consumers that\n // pre-date the marketplace adapter machinery are unaffected.\n\n /**\n * Stable identifier of the `PaymentBackend` adapter that served this\n * payment — e.g. `base-usdc`, `solana-usdc`, `btc`, `stripe`, `paypal`.\n *\n * Distinct from {@link externalProvider}: `backendId` names the SMRT\n * payment-rail adapter, while `externalProvider` names a downstream\n * accounting destination (QuickBooks, Stripe-the-accounting-source).\n * The same Stripe payment will have `backendId: 'stripe'` AND\n * `externalProvider: 'stripe'`; a crypto payment synced to QBO will\n * have `backendId: 'base-usdc'` and `externalProvider: 'quickbooks'`.\n */\n backendId: string = '';\n\n /**\n * Chain transaction hash or backend aggregator's reference id. For\n * on-chain payments this is the tx hash (`0x...` on EVM, base58 sig on\n * Solana, txid on Bitcoin); for fiat-rail backends it's the gateway's\n * own reference. Kept distinct from {@link transactionId} so consumers\n * that already populate `transactionId` with a provider-internal id\n * don't have to overload it.\n */\n backendTxRef: string = '';\n\n /**\n * The amount the backend actually moved, in the native currency's own\n * **integer minor units** — satoshis for BTC, cents for a fiat rail. For\n * stablecoin rails this typically equals `amount`; for volatile-currency\n * rails it's the satoshi/wei figure the chain recorded, independent of any\n * USD valuation.\n *\n * Fresh PostgreSQL and DuckDB INTEGER columns materialize as BIGINT. SMRT\n * rejects values outside JavaScript's safe-integer range during hydration;\n * existing PostgreSQL `int4` columns require the explicit widening in #2424.\n */\n nativeAmount: number = 0;\n\n /**\n * Code identifying the native currency `nativeAmount` is denominated\n * in. Mirrors the `backendId` namespacing convention — `USDC-base`,\n * `BTC`, `ETH`, `USD-stripe`. Empty string means \"use {@link currency}\"\n * (i.e. the payment was already quoted and settled in the same\n * currency).\n */\n nativeCurrency: string = '';\n\n /**\n * USD valuation of the payment at the moment the price was quoted to\n * the buyer (typically the moment a `PaymentIntent` was issued), in\n * **integer minor units** (US cents). A default of `0` means no USD-quote\n * snapshot was taken (the payment was already USD-denominated or the\n * backend doesn't require drift accounting).\n *\n * This is money describing the same funds as {@link Payment.amount}, so it\n * converts with it — a dollars-here / cents-there split would make\n * {@link Payment.usdDrift} meaningless (#2401).\n */\n usdAtQuote: number = 0;\n\n /**\n * USD valuation of the payment at the moment it was confirmed on the\n * backend (chain confirmation, gateway settlement, etc.), in integer minor\n * units (US cents). The delta between {@link usdAtQuote} and\n * `usdAtConfirmation` is the USD drift the operator absorbs (positive or\n * negative) when accepting payment in a volatile native currency (#2401).\n */\n usdAtConfirmation: number = 0;\n\n constructor(options: PaymentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.method !== undefined) this.method = options.method;\n if (options.status !== undefined) this.status = options.status;\n if (options.transactionId !== undefined)\n this.transactionId = options.transactionId;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.journalId !== undefined) this.journalId = options.journalId;\n if (options.paidAt !== undefined) this.paidAt = options.paidAt;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.externalProvider !== undefined)\n this.externalProvider = options.externalProvider;\n if (options.syncedAt !== undefined) this.syncedAt = options.syncedAt;\n if (options.backendId !== undefined) this.backendId = options.backendId;\n if (options.backendTxRef !== undefined)\n this.backendTxRef = options.backendTxRef;\n if (options.nativeAmount !== undefined)\n this.nativeAmount = options.nativeAmount;\n if (options.nativeCurrency !== undefined)\n this.nativeCurrency = options.nativeCurrency;\n if (options.usdAtQuote !== undefined) this.usdAtQuote = options.usdAtQuote;\n if (options.usdAtConfirmation !== undefined)\n this.usdAtConfirmation = options.usdAtConfirmation;\n }\n\n /**\n * Capture the persisted status the row was loaded with, so the save-time\n * transition guard can reject illegal status flips made via raw field\n * assignment (mass-assignment on the generated update route, a stale caller,\n * etc.). Freshly-constructed (not-yet-saved) payments have no prior status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedPaymentStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Save-time state-machine guard (S5 audit #1390).\n *\n * `status` is mass-assignable on the generated update/create routes, and a\n * COMPLETED Payment is treated as settlement proof downstream — e.g.\n * {@link PaymentIntent}'s PAID verification trusts a COMPLETED Payment row.\n * A forged `status: 'completed'` (with arbitrary amounts and no journal)\n * would therefore satisfy that check without any money having moved.\n *\n * This guard enforces two things:\n * - **Transitions must be legal** per {@link PAYMENT_STATUS_TRANSITIONS}.\n * - **Reaching COMPLETED requires the verified settlement path — on\n * creation AND on update.** Only `recordPayment()` (which posts a\n * balanced journal and links `journalId`) may write a Payment into\n * COMPLETED; it announces itself via {@link settlementInProgress}. A raw\n * `status: 'completed'` is rejected whether the row is brand-new (a\n * GENESIS `create({ status: 'completed' })`) or already persisted — that\n * is the exact path a forged COMPLETED would take to satisfy\n * PaymentIntent's PAID verification without any money having moved (codex\n * HIGH#1, #1390 round 5). There is deliberately NO import/fixture\n * carve-out: a COMPLETED Payment is settlement proof downstream, so it is\n * only ever reachable through `recordPayment()`, on every surface\n * (REST/MCP/CLI/direct). Fixtures/migrations that need a completed payment\n * must drive it through `recordPayment()` (or start non-COMPLETED).\n */\n override async save(): Promise<this> {\n // Whole minor units on every surface (#2401). `amount` is what\n // `PaymentAllocation.save()` and `Payout.assertCappedBySourcePayment()` cap\n // against with exact integer comparisons, so a fractional major-unit write\n // here would silently break both.\n assertIntegerMinorUnits('Payment', this.id || '<new>', [\n ['amount', this.amount],\n ['nativeAmount', this.nativeAmount],\n ['usdAtQuote', this.usdAtQuote],\n ['usdAtConfirmation', this.usdAtConfirmation],\n ]);\n\n const priorRow = await this.loadPersistedRow();\n const prior =\n priorRow && priorRow.status != null\n ? (priorRow.status as PaymentStatus)\n : loadedPaymentStatus.get(this);\n this.assertStatusTransition(prior);\n\n // Reaching COMPLETED is only legal via the verified settlement path,\n // regardless of whether this is a create (no persisted prior) or an\n // update of an existing row. A GENESIS create with status: COMPLETED would\n // otherwise forge settlement proof, so we close it here at the model level\n // — this covers EVERY surface (REST/MCP/CLI/direct), independent of any\n // writable-allowlist gap on a given surface.\n const reachingCompleted =\n this.status === PaymentStatus.COMPLETED &&\n prior !== PaymentStatus.COMPLETED;\n if (reachingCompleted && !settlementInProgress.has(this)) {\n throw new Error(\n `Payment ${this.id || '<new>'}: cannot set status to COMPLETED via ` +\n 'raw assignment — a Payment is only completed through ' +\n 'recordPayment(), which posts a balanced settlement journal. Use ' +\n 'recordPayment() instead of setting status directly.',\n );\n }\n\n // Freeze the settled monetary fields once the row is already COMPLETED in\n // the database (S5 audit #1390 round 6 — codex ROOT INSIGHT; mirrors the\n // PaymentIntent PAID backing-field freeze). A COMPLETED Payment is\n // settlement proof: its amount was reconciled against a balanced journal\n // and is trusted downstream — PaymentIntent's PAID verification binds the\n // winning option's `nativeAmount` to `Payment.amount` / `nativeAmount`. The\n // only legal exit from COMPLETED is COMPLETED → REFUNDED, and a refund must\n // NOT alter the settled figures. Closing this at the model level covers\n // EVERY surface (REST/MCP/CLI/direct), independent of the per-surface\n // writable allowlist (`amount`/`currency`/`nativeAmount`/... are writable\n // on a *new* PENDING payment, but must be immutable once settled).\n if (priorRow && priorRow.status === PaymentStatus.COMPLETED) {\n this.assertSettledAmountsUnchanged(priorRow);\n }\n\n try {\n const result = (await super.save()) as this;\n loadedPaymentStatus.set(this, this.status);\n return result;\n } finally {\n settlementInProgress.delete(this);\n }\n }\n\n /**\n * Reject an illegal status flip. Compares the about-to-be-written status\n * against the status the row was loaded with. No-op transitions (status\n * unchanged) and brand-new rows (no prior) are always allowed — the\n * COMPLETED-specific settlement requirement is enforced separately in\n * {@link save}.\n */\n private assertStatusTransition(prior: PaymentStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status (subject to the COMPLETED gate)\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYMENT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Payment ${this.id}: illegal status transition '${prior}' → '${this.status}'. ` +\n 'Use the guarded helpers (recordPayment / markFailed / cancel).',\n );\n }\n }\n\n /**\n * Load the AUTHORITATIVE persisted row (S5 audit #1390 round 4, codex HIGH#1;\n * extended round 6 to carry the full row, not just `status`). The WeakMap is\n * only populated when {@link initialize} loaded the row from the DB; it is\n * empty for an instance built via\n * `collection.create({ id: <existing>, _skipLoad: true })` — the upsert path\n * that lets a caller write onto an existing row without hydrating it. Trusting\n * an empty WeakMap there would treat the write as a brand-new row and skip the\n * transition guard entirely (a poisonable prior-state).\n *\n * So when this instance carries an `id`, read the persisted row straight from\n * the database and use it as the prior — a create-onto-existing is an update.\n * Returns `undefined` when no row exists (truly new), so callers fall back to\n * the WeakMap (which is also empty then). Settled-amount columns are\n * snake_case (`native_amount`, etc.); the `status` column is single-word.\n */\n private async loadPersistedRow(): Promise<\n Record<string, unknown> | undefined\n > {\n if (!this.id) return undefined;\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n return row ?? undefined;\n } catch {\n // DB not ready / table absent — treat as new (in-memory fallback in save).\n return undefined;\n }\n }\n\n /**\n * Reject any change to the settled monetary fields of an already-COMPLETED\n * Payment (S5 audit #1390 round 6 — codex ROOT INSIGHT). Mirrors the\n * PaymentIntent PAID backing-field freeze. A COMPLETED Payment's economic\n * identity is settled: the `amount` was posted into a balanced journal and is\n * the figure PaymentIntent's PAID verification reconciles against, so it (and\n * the native/USD valuation fields that describe the same money) must not\n * drift. The only legal status move out of COMPLETED is REFUNDED, which\n * reverses the funds without rewriting how much they were. Compared against\n * the AUTHORITATIVE persisted row so the freeze holds on every surface, even\n * for an un-hydrated create-onto-existing upsert.\n */\n private assertSettledAmountsUnchanged(\n priorRow: Record<string, unknown>,\n ): void {\n const frozen: Array<[string, number | string, number | string]> = [\n ['amount', Number(priorRow.amount ?? 0), Number(this.amount ?? 0)],\n [\n 'currency',\n String(priorRow.currency ?? ''),\n String(this.currency ?? ''),\n ],\n [\n 'nativeAmount',\n Number(priorRow.native_amount ?? 0),\n Number(this.nativeAmount ?? 0),\n ],\n [\n 'nativeCurrency',\n String(priorRow.native_currency ?? ''),\n String(this.nativeCurrency ?? ''),\n ],\n [\n 'usdAtQuote',\n Number(priorRow.usd_at_quote ?? 0),\n Number(this.usdAtQuote ?? 0),\n ],\n [\n 'usdAtConfirmation',\n Number(priorRow.usd_at_confirmation ?? 0),\n Number(this.usdAtConfirmation ?? 0),\n ],\n ];\n for (const [field, prior, next] of frozen) {\n if (prior !== next) {\n throw new Error(\n `Payment ${this.id}: '${field}' is frozen once the payment is ` +\n `COMPLETED (was '${prior}', got '${next}'). A settled payment's ` +\n 'monetary fields cannot be mutated — issue a refund instead.',\n );\n }\n }\n }\n\n /**\n * USD drift between quote time and confirmation time — what the\n * operator gained (positive) or lost (negative) by accepting a\n * volatile-currency payment. Returns `0` when either side of the\n * comparison is missing or zero, so callers don't have to special-case\n * fiat-rail / stablecoin payments.\n */\n usdDrift(): number {\n if (!this.usdAtQuote || !this.usdAtConfirmation) return 0;\n return this.usdAtConfirmation - this.usdAtQuote;\n }\n\n /**\n * Check if payment is pending\n */\n isPending(): boolean {\n return this.status === PaymentStatus.PENDING;\n }\n\n /**\n * Check if payment is completed\n */\n isCompleted(): boolean {\n return this.status === PaymentStatus.COMPLETED;\n }\n\n /**\n * Check if payment is refunded\n */\n isRefunded(): boolean {\n return this.status === PaymentStatus.REFUNDED;\n }\n\n /**\n * Record the payment and create a balanced journal entry.\n *\n * Creates a journal entry in smrt-ledgers:\n * - Debit: Cash/Bank account (assets increase)\n * - Credit: Accounts Receivable (receivables decrease)\n *\n * Both sides carry `this.amount`, so the journal posted from commerce is\n * denominated in **integer minor units** like the rest of this package\n * (#2401). smrt-ledgers is unit-agnostic — its `BALANCE_EPSILON` only checks\n * that debits equal credits — and integer entries balance exactly, so this is\n * strictly tighter than the major-unit journals commerce used to post.\n *\n * @param options - Ledger and account configuration\n * @returns The created journal\n *\n * @example\n * ```typescript\n * const journal = await payment.recordPayment({\n * ledgerId: ledger.id,\n * receivablesAccountId: arAccount.id,\n * cashAccountId: bankAccount.id\n * });\n * console.log(`Created journal: ${journal.number}`);\n * ```\n */\n async recordPayment(options: RecordPaymentOptions): Promise<Journal> {\n if (this.status === PaymentStatus.COMPLETED) {\n throw new Error('Payment already recorded');\n }\n\n if (this.amount <= 0) {\n throw new Error('Payment amount must be positive');\n }\n\n // Dynamic import to avoid hard dependency on smrt-ledgers\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n\n // Create the journal collection\n const journalCollection = await JournalCollection.create(this.options);\n\n // Create a new journal for this payment\n const journal = await journalCollection.create({\n date: new Date(),\n description: `Payment received for contract ${this.contractId}`,\n sourceModule: 'smrt-commerce',\n sourceRef: this.id,\n });\n await journal.save();\n\n // Add balanced entries:\n // Debit Cash/Bank (assets increase)\n await journal.addEntry({\n accountId: options.cashAccountId,\n debit: this.amount,\n memo: `Payment ${this.reference || this.id}`,\n });\n\n // Credit Accounts Receivable (receivables decrease)\n await journal.addEntry({\n accountId: options.receivablesAccountId,\n credit: this.amount,\n memo: `Payment ${this.reference || this.id}`,\n });\n\n // Post the journal (validates balance and finalizes)\n await journal.post();\n\n // Update payment record. Announce the verified settlement to the save-time\n // guard so the COMPLETED transition is accepted (the guard rejects any\n // other route into COMPLETED). A posted journal always carries an id;\n // fail fast rather than persist an empty-string FK if that invariant breaks.\n if (!journal.id) {\n throw new Error(\n 'Payment.recordPayment: journal was posted but has no id',\n );\n }\n this.journalId = journal.id;\n this.status = PaymentStatus.COMPLETED;\n this.paidAt = new Date();\n settlementInProgress.add(this);\n await this.save();\n\n return journal;\n }\n\n /**\n * Get the linked journal entry (if recorded)\n */\n async getJournal(): Promise<Journal | null> {\n if (!this.journalId) return null;\n\n try {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const collection = await JournalCollection.create(this.options);\n return await collection.get({ id: this.journalId });\n } catch {\n // smrt-ledgers not available\n return null;\n }\n }\n\n /**\n * Mark payment as failed\n */\n markFailed(reason?: string): void {\n this.status = PaymentStatus.FAILED;\n if (reason) {\n this.notes = `${this.notes ? `${this.notes}\\n` : ''}Failed: ${reason}`;\n }\n }\n\n /**\n * Cancel the payment\n */\n cancel(): void {\n if (this.status === PaymentStatus.COMPLETED) {\n throw new Error('Cannot cancel a completed payment. Use refund instead.');\n }\n this.status = PaymentStatus.CANCELLED;\n }\n}\n\nexport default Payment;\n","/**\n * PaymentCollection - Collection manager for Payment objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Payment } from '../models/Payment.js';\nimport { type PaymentMethod, PaymentStatus } from '../types/index.js';\n\nexport class PaymentCollection extends SmrtCollection<Payment> {\n static readonly _itemClass = Payment;\n\n /**\n * Find payments by contract\n *\n * @param contractId - Contract ID\n * @returns Array of payments\n */\n async findByContract(contractId: string): Promise<Payment[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find payments by customer\n *\n * @param customerId - Customer ID\n * @returns Array of payments\n */\n async findByCustomer(customerId: string): Promise<Payment[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find payments by status\n *\n * @param status - Payment status\n * @returns Array of payments\n */\n async findByStatus(status: PaymentStatus): Promise<Payment[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find payments by method\n *\n * @param method - Payment method\n * @returns Array of payments\n */\n async findByMethod(method: PaymentMethod): Promise<Payment[]> {\n return await this.list({\n where: { method },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all pending payments\n *\n * @returns Array of pending payments\n */\n async findPending(): Promise<Payment[]> {\n return await this.findByStatus(PaymentStatus.PENDING);\n }\n\n /**\n * Find all completed payments\n *\n * @returns Array of completed payments\n */\n async findCompleted(): Promise<Payment[]> {\n return await this.findByStatus(PaymentStatus.COMPLETED);\n }\n\n /**\n * Find payment by transaction ID\n *\n * @param transactionId - External transaction ID\n * @returns Payment or null\n */\n async findByTransactionId(transactionId: string): Promise<Payment | null> {\n const results = await this.list({\n where: { transactionId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find payments in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of payments\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Payment[]> {\n return await this.list({\n where: {\n 'paidAt >=': startDate.toISOString(),\n 'paidAt <=': endDate.toISOString(),\n },\n orderBy: 'paidAt DESC',\n });\n }\n\n /**\n * Calculate total payments for a contract\n *\n * @param contractId - Contract ID\n * @returns Total amount paid\n */\n async getTotalForContract(contractId: string): Promise<number> {\n const payments = await this.list({\n where: {\n contractId,\n status: PaymentStatus.COMPLETED,\n },\n });\n\n return payments.reduce((sum, payment) => sum + payment.amount, 0);\n }\n\n /**\n * Find payments linked to a ledger journal\n *\n * @param journalId - Journal ID from smrt-ledgers\n * @returns Payment or null\n */\n async findByJournal(journalId: string): Promise<Payment | null> {\n const results = await this.list({\n where: { journalId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all payments belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of payments for the tenant\n */\n async findByTenant(tenantId: string): Promise<Payment[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payments (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global payments\n */\n async findGlobal(): Promise<Payment[]> {\n return queryGlobal<Payment>(this);\n }\n\n /**\n * Find payments for a tenant including global payments.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global payments\n */\n async findWithGlobals(tenantId: string): Promise<Payment[]> {\n return queryWithGlobals<Payment>(this, tenantId, 'Payment.findWithGlobals');\n }\n}\n","/**\n * Automatic prepaid-credit top-ups through an off-session charge of the\n * payer's saved payment method (#3139).\n *\n * smrt-subscriptions' `SpendingPolicyEvaluator` calls an `autoTopUp` hook\n * when a pending charge would exhaust a balance policy; it never calls a\n * payment provider itself. {@link createAutoTopUpHook} is that hook for a\n * {@link BillingRuntime} whose provider can charge a saved method.\n *\n * Exactly once:\n * - Each attempt is a `PENDING` commerce `Payment` in the seller's books whose\n * id is derived from the policy and the attempt's sequence number, inserted\n * before the provider is called. Two evaluations racing for the same top-up\n * derive the same id; only one insert wins, and only the winner charges.\n * - The charge's idempotency key is derived from that payment id, so a retry\n * (a re-drive of an unsettled attempt) returns the original charge.\n * - The credit grant is keyed by the charge key, so the synchronous result and\n * the provider's later `payment` webhook credit the balance once.\n */\nimport { isUniqueViolationError } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport {\n type AutoTopUpGrant,\n type AutoTopUpHook,\n type AutoTopUpRequest,\n SpendingPolicyEvaluator,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { PaymentCollection } from '../collections/PaymentCollection.js';\nimport type { Payment } from '../models/Payment.js';\nimport { PaymentMethod, PaymentStatus } from '../types/index.js';\nimport { defaultCardFor } from './cards.js';\nimport type { BillingChargeStatus } from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n canonicalTenantId,\n deterministicId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\n/** Prefix of every auto top-up charge key (the provider idempotency key). */\nexport const AUTO_TOP_UP_CHARGE_PREFIX = 'smrt-auto-top-up:';\n/** `Payment.reference` of an auto top-up attempt: prefix + policy id. */\nexport const AUTO_TOP_UP_REFERENCE_PREFIX = 'auto-top-up:';\n\nconst DEFAULT_RETRY_AFTER_MS = 60 * 60 * 1000;\nconst DEFAULT_RECHECK_AFTER_MS = 60 * 1000;\n\n/** A top-up charge that did not succeed. Nothing was credited. */\nexport interface AutoTopUpFailure {\n sellerTenantId: string;\n payerTenantId: string;\n spendingPolicyId: string;\n /** The attempt's commerce `Payment` id. */\n paymentId: string;\n /**\n * `requires_action`: the issuer wants the payer present (re-save the card\n * with a card setup checkout); `failed`: declined or no usable card;\n * `canceled`: the provider canceled it.\n */\n status: Exclude<BillingChargeStatus, 'succeeded' | 'processing'>;\n failureCode?: string;\n amount: number;\n currency: string;\n /**\n * The settlement transaction. Host writes through it commit or roll back\n * with the attempt's closing.\n */\n db: DatabaseInterface;\n}\n\n/**\n * Host hook for a failed top-up, for example to ask the payer to update their\n * card. It runs inside the transaction that closes the attempt, once per\n * attempt, but may run again if that transaction is retried, so it must be\n * idempotent.\n */\nexport type AutoTopUpFailureHook = (\n failure: AutoTopUpFailure,\n) => void | Promise<void>;\n\n/**\n * Why a needed top-up will not be charged (#3189). New reasons may be added in\n * a minor release; treat an unknown reason like `no_card`.\n *\n * - `taxed_account`: the payer's account is taxed and the hook was built with\n * `taxedAccounts: 'skip'`.\n * - `no_card`: the payer has no default card with this provider customer\n * (see `BillingRuntime.cardOnFile`).\n * - `no_account`: the payer has no billing account or provider customer yet.\n * - `tax_location_invalid`: a taxed payer has no tax location (billing\n * address country), so tax cannot be calculated (#3194). A location the\n * provider rejects instead fails the charge with `failureCode:\n * 'customer_tax_location_invalid'` through `onAutoTopUpFailed`.\n */\nexport type AutoTopUpSkipReason =\n | 'taxed_account'\n | 'no_card'\n | 'no_account'\n | 'tax_location_invalid';\n\n/** A top-up the balance needed that will not be charged. */\nexport interface AutoTopUpSkip {\n sellerTenantId: string;\n /** Who would have been charged (the delegating parent, if delegated). */\n payerTenantId: string;\n spendingPolicyId: string;\n currency: string;\n /** The shortfall still to cover, recomputed at the skip, minor units. */\n shortfall: number;\n reason: AutoTopUpSkipReason;\n}\n\n/**\n * Host hook for a skipped top-up, for example to ask the payer to top up by\n * hand. It runs on the spending evaluation's path, on every evaluation that\n * needed a top-up and was skipped, so dedupe notifications and keep it\n * cheap; an error it throws fails the evaluation.\n */\nexport type AutoTopUpSkipHook = (skip: AutoTopUpSkip) => void | Promise<void>;\n\nexport interface AutoTopUpHookOptions {\n /**\n * How much to charge, in integer minor units of the policy currency.\n * Default: the request's shortfall, recomputed against the current balance. Return 0 or nothing to skip the top-up\n * (the policy's own behavior then applies).\n */\n amount?: (\n request: AutoTopUpRequest,\n ) => number | null | undefined | Promise<number | null | undefined>;\n /**\n * After a failed charge, wait this long before charging the policy again\n * (default one hour), so a declined card is not retried on every request.\n */\n retryAfterMs?: number;\n /**\n * An attempt still `processing` (or interrupted before its outcome was\n * saved) is re-read from the provider at most this often (default one\n * minute); its outcome also arrives as a `payment` webhook.\n */\n recheckAfterMs?: number;\n /** Charge description shown to the payer where supported. */\n description?: string;\n /**\n * How a payer whose account is taxed (`automaticTax`, not tax-exempt) is\n * topped up (#3194). `'charge_taxed'` (the default) adds\n * provider-calculated tax on top of the credit, as a checkout purchase of\n * the same credit does: the payer is charged credit plus tax, the balance\n * is credited the credit, and the tax is booked to tax payable.\n * `'skip'` does not top them up (reported as `taxed_account`);\n * `'charge_untaxed'` charges them without tax, for sellers that account\n * for tax on this credit elsewhere. Untaxed accounts are never taxed.\n */\n taxedAccounts?: 'charge_taxed' | 'skip' | 'charge_untaxed';\n /**\n * Called when no charge will be made for a needed top-up because of the\n * payer's account (#3189): see {@link AutoTopUpSkipReason}. It is not\n * called while an attempt is still processing, while a declined card waits\n * out `retryAfterMs` (`onAutoTopUpFailed` already reported it), when the\n * recomputed shortfall is already covered, or when `amount` returns 0\n * (which is evaluated first, as for a charge).\n */\n onAutoTopUpSkipped?: AutoTopUpSkipHook;\n}\n\n/** The outcome of a charge, from the provider's result or its webhook. */\nexport interface AutoTopUpChargeOutcome {\n status: BillingChargeStatus;\n providerPaymentId?: string;\n providerCustomerId?: string;\n /** Minor units charged (with tax for a taxed charge), when reported. */\n amount?: number;\n currency?: string;\n /** A taxed charge's pre-tax amount: the credit (#3194). */\n subtotal?: number;\n /** A taxed charge's provider-calculated tax (#3194). */\n tax?: number;\n failureCode?: string;\n}\n\n/**\n * Marks an attempt charged with provider tax (#3194) in its `notes`, so a\n * re-drive of the same charge key repeats the same request whatever the\n * account's tax status is by then.\n */\nconst TAXED_ATTEMPT_NOTE = ' (automatic tax)';\n\nexport function autoTopUpChargeKey(paymentId: string): string {\n return `${AUTO_TOP_UP_CHARGE_PREFIX}${paymentId}`;\n}\n\n/** The attempt payment id in a charge key, or null for any other key. */\nexport function autoTopUpPaymentId(chargeKey: string): string | null {\n if (!chargeKey.startsWith(AUTO_TOP_UP_CHARGE_PREFIX)) return null;\n return chargeKey.slice(AUTO_TOP_UP_CHARGE_PREFIX.length) || null;\n}\n\nfunction positiveMs(value: number | undefined, fallback: number): number {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value) || value < 0) {\n throw new Error('Auto top-up intervals must be non-negative numbers.');\n }\n return value;\n}\n\n/**\n * Build the `autoTopUp` hook for `SpendingPolicyEvaluator.create()`.\n *\n * The hook returns a grant only when the charge `succeeded`. A `processing`\n * charge returns nothing now and is credited when its `payment` webhook is\n * processed; `requires_action`, `failed`, and `canceled` credit nothing. A\n * provider without `chargeSavedPaymentMethod` (a push-payment rail) never\n * tops up. Provider errors other than a charge outcome propagate.\n */\nexport function createAutoTopUpHook(\n runtime: BillingRuntime,\n options: AutoTopUpHookOptions = {},\n): AutoTopUpHook {\n const retryAfterMs = positiveMs(options.retryAfterMs, DEFAULT_RETRY_AFTER_MS);\n const recheckAfterMs = positiveMs(\n options.recheckAfterMs,\n DEFAULT_RECHECK_AFTER_MS,\n );\n return async (request) => {\n const charge = runtime.provider.chargeSavedPaymentMethod;\n if (!charge) return null;\n const attempt = await withSystemContext(() =>\n claimAttempt(runtime, request, options, retryAfterMs, recheckAfterMs),\n );\n if (attempt && 'skip' in attempt) {\n await options.onAutoTopUpSkipped?.(attempt.skip);\n return null;\n }\n if (!attempt) return null;\n const { payment, payerTenantId, providerCustomerId } = attempt;\n const paymentId = String(payment.id);\n const automaticTax = isTaxedAttempt(payment);\n const result = await charge.call(runtime.provider, {\n providerCustomerId,\n amount: payment.amount,\n currency: payment.currency,\n idempotencyKey: autoTopUpChargeKey(paymentId),\n description: options.description || 'Prepaid credit top-up',\n metadata: {\n smrt_purpose: 'auto_top_up',\n smrt_seller: runtime.sellerTenantId,\n smrt_payer: payerTenantId,\n smrt_policy: request.policyId,\n },\n ...(automaticTax\n ? {\n automaticTax: true,\n ...(runtime.taxCodes.credit\n ? { taxCode: runtime.taxCodes.credit }\n : {}),\n }\n : {}),\n });\n const outcome: AutoTopUpChargeOutcome = {\n status: result.status,\n providerPaymentId: result.providerPaymentId,\n providerCustomerId,\n amount: result.amount,\n currency: result.currency,\n subtotal: result.subtotal,\n tax: result.tax,\n failureCode: result.failureCode,\n };\n // Settle in the seller's books, like event processing does.\n const settled = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n inTransaction(runtime.db, (tx) =>\n applyAutoTopUpOutcome(runtime, tx, paymentId, outcome),\n ),\n );\n return settled.grant;\n };\n}\n\ninterface ClaimedAttempt {\n payment: Payment;\n payerTenantId: string;\n providerCustomerId: string;\n}\n\ninterface SkippedAttempt {\n skip: AutoTopUpSkip;\n}\n\nasync function claimAttempt(\n runtime: BillingRuntime,\n request: AutoTopUpRequest,\n options: AutoTopUpHookOptions,\n retryAfterMs: number,\n recheckAfterMs: number,\n): Promise<ClaimedAttempt | SkippedAttempt | null> {\n const policy = await runtime.policies.get(request.policyId);\n if (!policy?.id || policy.period !== 'balance' || !policy.active) {\n return null;\n }\n const currency = normalizeCurrency(policy.currency);\n if (normalizeCurrency(request.currency) !== currency) return null;\n // A delegated balance is funded only by the parent that set it.\n const payerTenantId = canonicalTenantId(\n tenantKey(policy.setByTenantId) || String(policy.tenantId),\n 'payerTenantId',\n );\n // What a top-up would charge now, or null when none is needed: another\n // evaluation may have topped the balance up since this one read it (its\n // attempt is completed, so its grant is visible), and `amount` may decline.\n // A skip is reported only for a top-up that is still needed (#3189).\n const needed = async (): Promise<{\n shortfall: number;\n requested: number;\n } | null> => {\n const shortfall = request.currentShortfall\n ? await request.currentShortfall()\n : request.shortfall;\n if (shortfall <= 0) return null;\n const requested = options.amount\n ? await options.amount({ ...request, shortfall })\n : shortfall;\n if (!requested) return null;\n if (!Number.isSafeInteger(requested) || requested <= 0) {\n throw new Error(\n 'Auto top-up amount must be positive integer minor units.',\n );\n }\n return { shortfall, requested };\n };\n const skipped = (\n reason: AutoTopUpSkipReason,\n shortfall: number,\n ): SkippedAttempt => ({\n skip: {\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId,\n spendingPolicyId: String(policy.id),\n currency,\n shortfall,\n reason,\n },\n });\n const payments = await PaymentCollection.create({ db: runtime.db });\n const where = {\n tenantId: runtime.sellerTenantId,\n reference: `${AUTO_TOP_UP_REFERENCE_PREFIX}${policy.id}`,\n externalProvider: runtime.provider.name,\n };\n // Attempts are numbered 0, 1, 2, … and a number is inserted only after the\n // count reached it (rows are never deleted), so the count is the next\n // number and the previous attempt is `sequence - 1`. Everything below is\n // decided from that one read: two evaluations that read the same count\n // derive the same id and only one insert wins; one that reads a later\n // count sees the earlier attempt.\n const attemptId = (sequence: number) =>\n deterministicId([\n 'billing-auto-top-up',\n runtime.provider.name,\n runtime.sellerTenantId,\n String(policy.id),\n String(sequence),\n ]);\n const sequence = await payments.count({ where });\n const latest =\n sequence > 0 ? await payments.get(await attemptId(sequence - 1)) : null;\n if (sequence > 0 && !latest) {\n throw new Error(`Auto top-up attempt ${sequence - 1} is missing.`);\n }\n const now = Date.now();\n const account = await runtime.getAccount(payerTenantId);\n const providerCustomerId =\n account?.id && account.provider === runtime.provider.name\n ? account.providerCustomerId\n : '';\n if (latest?.status === PaymentStatus.PENDING) {\n // One live attempt per policy: re-drive it (same key, same amount) only\n // when it has not been checked recently. Never a reported skip (#3189).\n if (now - timeOf(latest) < recheckAfterMs || !providerCustomerId) {\n return null;\n }\n return { payment: latest, payerTenantId, providerCustomerId };\n }\n if (\n latest?.status === PaymentStatus.FAILED &&\n now - timeOf(latest) < retryAfterMs\n ) {\n return null;\n }\n\n const need = await needed();\n if (!need) return null;\n if (!account?.id || !providerCustomerId) {\n // No provider customer, so no saved card to charge.\n return skipped('no_account', need.shortfall);\n }\n // New charges only (an attempt already charged is re-driven above with\n // the tax decision it was made with).\n const taxedAccounts = options.taxedAccounts ?? 'charge_taxed';\n const taxed = await runtime.accountIsTaxed(account);\n if (taxed && taxedAccounts === 'skip') {\n return skipped('taxed_account', need.shortfall);\n }\n // Only a payer with a card on file is charged (no failed attempt per\n // shortfall for payers who never saved one).\n if (!(await defaultCardFor(runtime, account, providerCustomerId))) {\n return skipped('no_card', need.shortfall);\n }\n const automaticTax = taxed && taxedAccounts === 'charge_taxed';\n // Without a tax location the provider cannot tax the charge: no attempt\n // (and no failed charge per shortfall) until the payer adds one.\n if (automaticTax && !(await runtime.hasTaxLocation(account))) {\n return skipped('tax_location_invalid', need.shortfall);\n }\n const requested = need.requested;\n const id = await attemptId(sequence);\n try {\n const payment = await payments.create({\n id,\n tenantId: runtime.sellerTenantId,\n customerId: account.customerId,\n amount: requested,\n currency,\n method: PaymentMethod.CREDIT_CARD,\n status: PaymentStatus.PENDING,\n reference: where.reference,\n externalProvider: runtime.provider.name,\n backendId: runtime.provider.name,\n notes: `attempt ${sequence}${automaticTax ? TAXED_ATTEMPT_NOTE : ''}`,\n _insertOnly: true,\n });\n return { payment, payerTenantId, providerCustomerId };\n } catch (error) {\n // Another evaluation claimed this attempt and is charging it.\n if (isUniqueViolationError(error) || (await payments.get(id))) return null;\n throw error;\n }\n}\n\nfunction inTransaction<T>(\n db: DatabaseInterface,\n operation: (tx: DatabaseInterface) => Promise<T>,\n): Promise<T> {\n if (!db.transaction) {\n throw new Error(\n 'Auto top-up requires a database adapter with transaction().',\n );\n }\n let result: T | undefined;\n return db\n .transaction(async (tx) => {\n result = await operation(tx);\n })\n .then(() => result as T);\n}\n\nfunction isTaxedAttempt(payment: Payment): boolean {\n return (\n payment.notes.startsWith('attempt ') &&\n payment.notes.includes(TAXED_ATTEMPT_NOTE)\n );\n}\n\nfunction timeOf(payment: Payment): number {\n const at = payment.updated_at ?? payment.created_at;\n return at instanceof Date ? at.getTime() : new Date(String(at)).getTime();\n}\n\n/**\n * Apply a charge outcome to its attempt: credit and record the payment when it\n * succeeded, close the attempt when it failed. Idempotent, and safe to run\n * concurrently from the hook and the `payment` webhook: `db` must be a\n * transaction (the event transaction, or one the hook opens), and the first\n * statement takes the attempt's row lock with a conditional update, so only\n * one caller settles a pending attempt and the rest see it settled. Runs in\n * the seller's tenant context (payments and journals are the seller's).\n */\nexport async function applyAutoTopUpOutcome(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n paymentId: string,\n outcome: AutoTopUpChargeOutcome,\n): Promise<{ grant: AutoTopUpGrant | null }> {\n const payments = await PaymentCollection.create({ db });\n // Lock a pending attempt for this transaction. A concurrent settler blocks\n // here until this one commits, then matches nothing (PostgreSQL re-checks\n // the status); SQLite serializes the transactions.\n // Reviewed raw SQL on a tenant-scoped table: filtered to the seller's\n // tenant by hand, and routed through the collection so its cache is busted.\n const locked = await payments.query(\n `UPDATE ${payments.tableName}\n SET updated_at = ?\n WHERE id = ? AND tenant_id = ? AND status = ?\n RETURNING id`,\n [\n new Date().toISOString(),\n paymentId,\n runtime.sellerTenantId,\n PaymentStatus.PENDING,\n ],\n { allowRawOnTenantScoped: true },\n );\n const pending = locked.length === 1;\n const payment = await payments.get(paymentId);\n if (\n !payment?.id ||\n tenantKey(payment.tenantId) !== runtime.sellerTenantId ||\n payment.externalProvider !== runtime.provider.name ||\n !payment.reference.startsWith(AUTO_TOP_UP_REFERENCE_PREFIX)\n ) {\n throw new Error(`Auto top-up attempt ${paymentId} was not found.`);\n }\n const policyId = payment.reference.slice(AUTO_TOP_UP_REFERENCE_PREFIX.length);\n // The policy and account belong to the payer: a reviewed system read.\n const policy = await withSystemContext(() => runtime.policies.get(policyId));\n if (!policy?.id) {\n throw new Error(`Auto top-up policy ${policyId} was not found.`);\n }\n const setBy = tenantKey(policy.setByTenantId);\n const payerTenantId = canonicalTenantId(\n setBy || String(policy.tenantId),\n 'payerTenantId',\n );\n const account = await withSystemContext(() =>\n runtime.getAccount(payerTenantId),\n );\n // The attempt row holds the credit: a taxed charge is compared by its\n // pre-tax subtotal, and its total must be that plus the tax (#3194). An\n // untaxed attempt never accepts tax (the payer was charged more than the\n // credit it asked for).\n const tax = outcome.tax ?? 0;\n const taxedAttempt = isTaxedAttempt(payment);\n if (\n !Number.isSafeInteger(tax) ||\n tax < 0 ||\n // The total must be exact minor units, never a rounded float sum.\n !Number.isSafeInteger(payment.amount + tax) ||\n (tax > 0 && !taxedAttempt) ||\n (outcome.subtotal !== undefined && outcome.subtotal !== payment.amount) ||\n (outcome.amount !== undefined && outcome.amount !== payment.amount + tax) ||\n (outcome.currency !== undefined &&\n normalizeCurrency(outcome.currency) !==\n normalizeCurrency(payment.currency)) ||\n (outcome.providerCustomerId !== undefined &&\n outcome.providerCustomerId !== account?.providerCustomerId)\n ) {\n throw new Error(\n `Charge for auto top-up ${paymentId} does not match it: ` +\n `${outcome.amount} ${outcome.currency} (tax ${tax}) for ${payment.amount} ${payment.currency}.`,\n );\n }\n\n if (outcome.status === 'succeeded') {\n if (taxedAttempt && outcome.tax === undefined) {\n // A taxed charge that reports no tax cannot be booked correctly: fail\n // visibly rather than leave the collected tax out of the ledger.\n throw new Error(\n `Taxed auto top-up ${paymentId} succeeded without a reported tax amount.`,\n );\n }\n if (payment.status === PaymentStatus.FAILED) {\n // The attempt was closed as failed, then the provider collected: an\n // operator must reconcile it (credit or refund).\n throw new Error(\n `Auto top-up ${paymentId} was recorded as failed but its charge succeeded.`,\n );\n }\n const grant: AutoTopUpGrant = {\n amount: payment.amount,\n source: `${runtime.provider.name}-auto-top-up`,\n sourceId: autoTopUpChargeKey(paymentId),\n reason: 'Automatic prepaid credit top-up',\n };\n if (pending) {\n // Keyed by the charge: the evaluator recording the returned grant\n // again finds this one.\n await withSystemContext(async () => {\n const evaluator = await SpendingPolicyEvaluator.create({ db });\n await evaluator.grantCredit({\n spendingPolicyId: policyId,\n amount: grant.amount,\n reason: grant.reason,\n source: grant.source,\n sourceId: grant.sourceId,\n ...(setBy ? { grantedByTenantId: setBy } : {}),\n });\n });\n if (outcome.providerPaymentId) {\n payment.externalId = outcome.providerPaymentId;\n }\n if (tax > 0) payment.notes = `${payment.notes}; tax ${tax}`;\n await payment.recordPayment({\n ledgerId: '',\n cashAccountId: runtime.ledger.cashAccountId,\n receivablesAccountId: runtime.ledger.prepaidCreditAccountId,\n });\n if (tax > 0) {\n // The payer paid credit plus tax: the tax is the seller's to remit,\n // booked once with the settlement it belongs to.\n const journals = await JournalCollection.create({ db });\n const journal = await journals.create({\n date: new Date(),\n description: `Tax collected on automatic top-up ${paymentId}`,\n sourceModule: 'smrt-commerce',\n sourceRef: `${paymentId}:tax`,\n });\n await journal.save();\n await journal.addEntry({\n accountId: runtime.ledger.cashAccountId,\n debit: tax,\n memo: `Tax on top-up ${policyId}`,\n });\n await journal.addEntry({\n accountId: runtime.ledger.taxAccountId,\n credit: tax,\n memo: `Tax on top-up ${policyId}`,\n });\n await journal.post();\n }\n }\n return { grant };\n }\n\n if (!pending) return { grant: null }; // Already settled.\n\n if (outcome.status === 'processing') {\n // The lock's update refreshed `updated_at`, which paces re-drives.\n if (outcome.providerPaymentId && !payment.externalId) {\n payment.externalId = outcome.providerPaymentId;\n await payment.save();\n }\n return { grant: null };\n }\n\n // requires_action, failed, canceled: nothing was charged.\n payment.status = PaymentStatus.FAILED;\n if (outcome.providerPaymentId && !payment.externalId) {\n payment.externalId = outcome.providerPaymentId;\n }\n // Appended, never replaced: the notes carry the attempt's tax decision,\n // which a later webhook for the same charge is checked against.\n payment.notes = `${payment.notes}; ${outcome.status}${outcome.failureCode ? `: ${outcome.failureCode}` : ''}`;\n await payment.save();\n await runtime.onAutoTopUpFailed?.({\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId,\n spendingPolicyId: policyId,\n paymentId,\n status: outcome.status,\n failureCode: outcome.failureCode,\n amount: payment.amount,\n currency: normalizeCurrency(payment.currency),\n db,\n });\n return { grant: null };\n}\n","/**\n * Stripe adapter for the {@link BillingProvider} port (#3060).\n *\n * Every provider call goes through `@happyvertical/accounting`'s Stripe\n * provider; this module only maps between commerce's integer minor units and\n * the SDK's major-unit contract, and normalizes verified webhook events. The\n * SDK's `*Minor` fields (checkout prices, session totals, off-session charges)\n * are already ISO minor units and pass through unconverted (#3139).\n */\nimport type {\n StripeAccountingProvider,\n StripeSubscriptionStatus,\n WebhookEvent,\n} from '@happyvertical/accounting';\nimport type { SubscriptionStatus } from '@happyvertical/smrt-subscriptions';\nimport { CARD_SETUP_PURPOSE, COLLECT_ADDRESS_METADATA } from './cards.js';\nimport { CREDIT_PURCHASE_PURPOSE } from './credits.js';\nimport {\n type BillingChargeStatus,\n type BillingInvoiceEventType,\n type BillingProvider,\n type BillingProviderChargeInput,\n type BillingProviderChargeResult,\n type BillingProviderCheckoutInput,\n type BillingProviderCheckoutSession,\n type BillingProviderCheckoutState,\n type BillingProviderCustomerInput,\n type BillingProviderEvent,\n type BillingProviderInvoiceInput,\n type BillingProviderInvoiceState,\n type BillingProviderInvoiceStatus,\n type BillingProviderSetupCheckoutInput,\n type BillingProviderSubscriptionState,\n BillingWebhookVerificationError,\n} from './provider.js';\nimport { AUTO_TOP_UP_CHARGE_PREFIX } from './top-up.js';\nimport {\n majorToMinorUnits,\n minorToMajorUnits,\n normalizeCurrency,\n} from './units.js';\n\nexport interface StripeBillingProviderOptions {\n /** A Stripe provider from `getAccountingProvider({ type: 'stripe', … })`. */\n stripe: StripeAccountingProvider;\n /** Webhook endpoint signing secret (`whsec_…`). */\n webhookSecret: string;\n}\n\nconst INVOICE_EVENTS: Record<string, BillingInvoiceEventType> = {\n 'invoice.paid': 'paid',\n 'invoice.payment_succeeded': 'paid',\n 'invoice.payment_failed': 'payment_failed',\n 'invoice.overdue': 'overdue',\n 'invoice.marked_uncollectible': 'uncollectible',\n 'invoice.voided': 'voided',\n};\n\nconst SUBSCRIPTION_EVENTS = new Set([\n 'customer.subscription.created',\n 'customer.subscription.updated',\n 'customer.subscription.deleted',\n 'customer.subscription.paused',\n 'customer.subscription.resumed',\n]);\n\nconst CHECKOUT_EVENTS = new Set([\n 'checkout.session.completed',\n 'checkout.session.async_payment_succeeded',\n]);\n\nconst PAYMENT_EVENTS = new Set([\n 'payment_intent.succeeded',\n 'payment_intent.processing',\n 'payment_intent.payment_failed',\n 'payment_intent.canceled',\n]);\n\nconst CHARGE_STATUSES = new Set<BillingChargeStatus>([\n 'succeeded',\n 'processing',\n 'requires_action',\n 'failed',\n 'canceled',\n]);\n\nconst INVOICE_STATUSES: Record<string, BillingProviderInvoiceStatus> = {\n draft: 'draft',\n paid: 'paid',\n voided: 'void',\n uncollectible: 'uncollectible',\n};\n\nconst SUBSCRIPTION_STATUSES: Record<\n StripeSubscriptionStatus,\n SubscriptionStatus\n> = {\n active: 'active',\n trialing: 'trialing',\n past_due: 'past_due',\n unpaid: 'unpaid',\n canceled: 'canceled',\n incomplete: 'incomplete',\n incomplete_expired: 'canceled',\n paused: 'unpaid',\n};\n\nasync function sha256Hex(value: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(value),\n );\n return Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n}\n\nexport function createStripeBillingProvider(\n options: StripeBillingProviderOptions,\n): BillingProvider {\n const { stripe, webhookSecret } = options;\n if (!webhookSecret) {\n throw new Error('A Stripe webhook signing secret is required.');\n }\n // Checkout amounts are settled from a re-read session (Stripe's webhook\n // amounts are in Stripe's unit, not ISO minor units), so the SDK must be\n // able to retrieve one (@happyvertical/accounting >= 0.92).\n const retrieveCheckoutSession = stripe.billing.retrieveCheckoutSession?.bind(\n stripe.billing,\n );\n if (!retrieveCheckoutSession) {\n throw new Error(\n 'The Stripe provider cannot retrieve checkout sessions; upgrade @happyvertical/accounting to 0.92 or later.',\n );\n }\n const chargeSaved = stripe.payments.chargeSavedPaymentMethod?.bind(\n stripe.payments,\n );\n const setDefault = stripe.billing.setDefaultPaymentMethod?.bind(\n stripe.billing,\n );\n const markUncollectible = stripe.invoices.markUncollectible?.bind(\n stripe.invoices,\n );\n const markPaidOutOfBand = stripe.invoices.markPaidOutOfBand?.bind(\n stripe.invoices,\n );\n\n const provider: BillingProvider = {\n name: 'stripe',\n\n async syncCustomer(input: BillingProviderCustomerInput) {\n const customer = {\n id: input.accountId,\n externalId: input.providerCustomerId || undefined,\n name: input.name,\n email: input.email || undefined,\n billingAddress: input.billingAddress,\n taxExempt: input.taxExempt,\n };\n if (customer.externalId) {\n const result = await stripe.customers.sync(customer);\n return { providerCustomerId: result.externalId };\n }\n // Creation is idempotent per account (sdk#1268): a retry returns the\n // customer an earlier attempt created, found by Stripe's key replay or,\n // after its window, by the account id the SDK tags on the customer. The\n // key also covers the contents, so a retry with edited details is a new\n // request (Stripe refuses a reused key with other parameters) that the\n // SDK still resolves to the first customer by that tag.\n const digest = await sha256Hex(JSON.stringify(customer));\n const created = await stripe.customers.sync({\n ...customer,\n idempotencyKey: `smrt-billing-customer:${input.accountId}:${digest.slice(0, 32)}`,\n });\n // A replayed or found customer carries the first attempt's details;\n // write the current ones.\n await stripe.customers.sync({\n ...customer,\n externalId: created.externalId,\n });\n return { providerCustomerId: created.externalId };\n },\n\n async pushInvoice(input: BillingProviderInvoiceInput) {\n const currency = normalizeCurrency(input.currency);\n const subtotal = minorToMajorUnits(input.subtotal, currency);\n const result = await stripe.invoices.push({\n id: input.invoiceId,\n invoiceNumber: input.invoiceNumber,\n customerId: input.providerCustomerId,\n customerExternalId: input.providerCustomerId,\n issueDate: input.issueDate,\n dueDate: input.dueDate,\n lineItems: input.lines.map((line) => ({\n description: line.description,\n quantity: 1,\n unitPrice: minorToMajorUnits(line.amount, currency),\n periodStart: line.periodStart,\n periodEnd: line.periodEnd,\n ...(line.taxCode ? { taxCode: line.taxCode } : {}),\n })),\n subtotal,\n taxAmount: 0,\n totalAmount: subtotal,\n currency: currency.toLowerCase(),\n memo: input.memo,\n idempotencyKey: input.idempotencyKey,\n automaticTax: input.automaticTax,\n collectionMethod: input.collectionMethod ?? 'send_invoice',\n });\n return { providerInvoiceId: result.externalId };\n },\n\n async sendInvoice(providerInvoiceId: string) {\n await stripe.invoices.send(providerInvoiceId);\n },\n\n async getInvoice(\n providerInvoiceId: string,\n ): Promise<BillingProviderInvoiceState> {\n const invoice = await stripe.invoices.pull(providerInvoiceId);\n const currency = normalizeCurrency(invoice.currency);\n // sent, viewed, and overdue are all open (unpaid) at the provider.\n const status: BillingProviderInvoiceStatus =\n INVOICE_STATUSES[invoice.status] ?? 'open';\n return {\n providerInvoiceId: invoice.externalId,\n status,\n currency,\n subtotal: majorToMinorUnits(invoice.subtotal, currency),\n taxAmount: majorToMinorUnits(invoice.taxAmount, currency),\n total: majorToMinorUnits(invoice.totalAmount, currency),\n // Closed out of band, Stripe collected nothing; the invoice is\n // settled in full by whoever did (#3138).\n amountPaid:\n invoice.paidOutOfBand && status === 'paid'\n ? majorToMinorUnits(invoice.totalAmount, currency)\n : majorToMinorUnits(invoice.amountPaid, currency),\n amountDue:\n invoice.paidOutOfBand && status === 'paid'\n ? 0\n : majorToMinorUnits(invoice.balance, currency),\n ...(invoice.paidOutOfBand ? { paidOutOfBand: true } : {}),\n };\n },\n\n async getSubscription(\n providerSubscriptionId: string,\n ): Promise<BillingProviderSubscriptionState> {\n const subscription = await stripe.billing.retrieveSubscriptionStatus(\n providerSubscriptionId,\n );\n const status = SUBSCRIPTION_STATUSES[subscription.status];\n if (!status) {\n throw new Error(\n `Unknown Stripe subscription status '${subscription.status}'.`,\n );\n }\n return {\n providerSubscriptionId: subscription.externalId,\n status,\n currentPeriodStart: subscription.currentPeriodStart,\n currentPeriodEnd: subscription.currentPeriodEnd,\n cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,\n canceledAt: subscription.canceledAt,\n trialEndsAt: subscription.trialEnd,\n };\n },\n\n async createCheckout(\n input: BillingProviderCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n const currency = normalizeCurrency(input.currency);\n if (!Number.isSafeInteger(input.amount) || input.amount <= 0) {\n throw new Error(\n 'Checkout amount must be positive integer minor units.',\n );\n }\n const customerExternalId = input.providerCustomerId || undefined;\n if (input.savePaymentMethod && !customerExternalId) {\n throw new Error(\n 'Saving a payment method requires a provider customer.',\n );\n }\n const session = await stripe.billing.createCheckoutSession({\n mode: 'payment',\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n customerExternalId,\n customerEmail: customerExternalId\n ? undefined\n : input.customerEmail || undefined,\n lineItems: [\n {\n quantity: 1,\n priceData: {\n currency: currency.toLowerCase(),\n // ISO minor units; the SDK converts to Stripe's unit (sdk#1269).\n unitAmountMinor: input.amount,\n productName: input.description,\n ...(input.taxCode ? { taxCode: input.taxCode } : {}),\n },\n },\n ],\n ...(input.automaticTax\n ? {\n automaticTax: true,\n // Stripe Tax needs the buyer's location: Checkout collects it\n // and saves it to an existing customer.\n ...(customerExternalId\n ? { customerUpdate: { address: 'auto' as const } }\n : { billingAddressCollection: 'required' as const }),\n }\n : {}),\n ...(input.savePaymentMethod\n ? { setupFutureUsage: 'off_session' as const }\n : {}),\n metadata: await signMetadata(input.metadata, webhookSecret),\n idempotencyKey: input.idempotencyKey,\n });\n return { sessionId: session.externalId, url: session.url };\n },\n\n async createSetupCheckout(\n input: BillingProviderSetupCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n if (!input.providerCustomerId) {\n throw new Error('A setup checkout requires a provider customer.');\n }\n const session = await stripe.billing.createCheckoutSession({\n mode: 'setup',\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n customerExternalId: input.providerCustomerId,\n currency: normalizeCurrency(input.currency).toLowerCase(),\n ...(input.collectBillingAddress\n ? {\n billingAddressCollection: 'required' as const,\n customerUpdate: { address: 'auto' as const },\n }\n : {}),\n metadata: await signMetadata(input.metadata, webhookSecret),\n idempotencyKey: input.idempotencyKey,\n });\n return { sessionId: session.externalId, url: session.url };\n },\n\n async getCheckout(\n sessionId: string,\n ): Promise<BillingProviderCheckoutState> {\n const session = await retrieveCheckoutSession(sessionId);\n const mode =\n session.mode === 'payment' || session.mode === 'setup'\n ? session.mode\n : 'other';\n const complete = session.status === 'complete';\n const state: BillingProviderCheckoutState = {\n sessionId: session.externalId,\n mode,\n complete,\n paid: session.paymentStatus === 'paid',\n providerCustomerId: session.customerExternalId,\n paymentMethodId: session.paymentMethodExternalId,\n currency: session.currency,\n amountSubtotal: session.amountSubtotalMinor,\n amountTax: session.amountTaxMinor,\n amountTotal: session.amountTotalMinor,\n };\n if (\n complete &&\n session.customerExternalId &&\n session.metadata[COLLECT_ADDRESS_METADATA] === '1'\n ) {\n // A session asked to collect the address saved it to the customer\n // (customer_update[address]=auto). Only those pay for this read.\n const customer = await stripe.customers.pull(\n session.customerExternalId,\n );\n if (customer.billingAddress?.country) {\n state.billingAddress = customer.billingAddress;\n }\n }\n return state;\n },\n\n async verifyWebhook(\n payload: string,\n signature: string,\n ): Promise<BillingProviderEvent> {\n if (!stripe.webhooks.verify(payload, signature, webhookSecret)) {\n throw new BillingWebhookVerificationError();\n }\n return normalizeStripeEvent(\n stripe.webhooks.parse(payload),\n webhookSecret,\n );\n },\n };\n\n if (chargeSaved) {\n provider.chargeSavedPaymentMethod = async (\n input: BillingProviderChargeInput,\n ): Promise<BillingProviderChargeResult> => {\n const result = await chargeSaved({\n customerExternalId: input.providerCustomerId,\n amountMinor: input.amount,\n currency: normalizeCurrency(input.currency),\n idempotencyKey: input.idempotencyKey,\n description: input.description,\n metadata: input.metadata,\n ...(input.automaticTax\n ? {\n automaticTax: true,\n ...(input.taxCode ? { taxCode: input.taxCode } : {}),\n }\n : {}),\n });\n return {\n status: result.status,\n providerPaymentId: result.paymentExternalId,\n amount: result.amountMinor,\n currency: result.currency,\n ...(result.subtotalMinor !== undefined\n ? { subtotal: result.subtotalMinor }\n : {}),\n ...(result.taxMinor !== undefined ? { tax: result.taxMinor } : {}),\n failureCode: result.failureCode,\n failureMessage: result.failureMessage,\n };\n };\n }\n if (setDefault) {\n provider.setDefaultPaymentMethod = (providerCustomerId, paymentMethodId) =>\n setDefault(providerCustomerId, paymentMethodId);\n }\n if (markUncollectible) {\n provider.markInvoiceUncollectible = (providerInvoiceId) =>\n markUncollectible(providerInvoiceId);\n }\n if (markPaidOutOfBand) {\n // Closes an invoice a payment rail collected (#3138): Stripe stops its\n // emails, dunning, and automatic charges.\n provider.markInvoicePaidOutOfBand = (providerInvoiceId) =>\n markPaidOutOfBand(providerInvoiceId);\n }\n return provider;\n}\n\n/** Normalize a verified, parsed Stripe event. Exported for tests. */\nexport async function normalizeStripeEvent(\n event: WebhookEvent,\n webhookSecret: string,\n): Promise<BillingProviderEvent> {\n const eventId = event.id;\n if (!eventId) {\n throw new BillingWebhookVerificationError(\n 'Stripe webhook event has no id; it cannot be deduplicated.',\n );\n }\n // After verification, anything this package cannot act on is `ignored`\n // (acknowledged, not stored) rather than thrown: a throw here would fail\n // the host's webhook response for an event that is not ours.\n const ignored = { kind: 'ignored' as const, eventId, type: event.type };\n const invoiceType = INVOICE_EVENTS[event.type];\n if (invoiceType) {\n if (!event.resourceId) return ignored;\n return {\n kind: 'invoice',\n eventId,\n type: invoiceType,\n providerInvoiceId: event.resourceId,\n };\n }\n if (SUBSCRIPTION_EVENTS.has(event.type)) {\n const id = stripeObject(event).id;\n if (typeof id !== 'string' || !id) return ignored;\n return { kind: 'subscription', eventId, providerSubscriptionId: id };\n }\n if (CHECKOUT_EVENTS.has(event.type)) {\n const session = stripeObject(event);\n // Only sessions this package created are stored, and only their smrt_*\n // metadata: other integrations' checkouts never enter the inbox. The\n // metadata is signed with the endpoint secret at creation, so another\n // integration on the same account cannot forge a credit purchase or\n // attach a card to another payer.\n const metadata = smrtMetadata(session.metadata);\n const id = session.id;\n const mode = session.mode;\n const expected =\n (mode === 'payment' &&\n metadata.smrt_purpose === CREDIT_PURCHASE_PURPOSE) ||\n (mode === 'setup' && metadata.smrt_purpose === CARD_SETUP_PURPOSE);\n if (\n !expected ||\n !(await metadataSignatureValid(metadata, webhookSecret)) ||\n typeof id !== 'string' ||\n !id ||\n (mode === 'payment' && typeof session.currency !== 'string')\n ) {\n // A session that claims to be ours but fails these checks is reported\n // distinctly so the host can alert on it.\n if (!expected) return ignored;\n return {\n ...ignored,\n type:\n mode === 'payment'\n ? `${event.type}:unverified_credit_purchase`\n : `${event.type}:unverified_card_setup`,\n };\n }\n // Amounts are not taken from the event: Stripe reports them in its own\n // unit, which differs from ISO minor units for some currencies (ISK,\n // UGX). `observe()` re-reads the session through the SDK, which converts.\n return {\n kind: 'checkout_completed',\n eventId,\n sessionId: id,\n mode,\n paid: session.payment_status === 'paid',\n // Validated when the event is applied, so a bad value dead-letters\n // visibly instead of failing intake.\n currency:\n typeof session.currency === 'string'\n ? session.currency.toUpperCase()\n : '',\n metadata,\n };\n }\n if (PAYMENT_EVENTS.has(event.type)) {\n // Only off-session charges this package made (#3139); the charge key is\n // the SDK's `hv_charge_key` metadata. Settlement checks it against the\n // local attempt row, amount, currency, and customer.\n const payment = event.payment;\n const chargeKey = payment?.chargeKey;\n if (\n !payment ||\n !chargeKey?.startsWith(AUTO_TOP_UP_CHARGE_PREFIX) ||\n !CHARGE_STATUSES.has(payment.status) ||\n !payment.paymentExternalId\n ) {\n return ignored;\n }\n return {\n kind: 'payment',\n eventId,\n chargeKey,\n status: payment.status,\n providerPaymentId: payment.paymentExternalId,\n providerCustomerId: payment.customerExternalId,\n amount: payment.amountMinor,\n currency: payment.currency,\n ...(payment.subtotalMinor !== undefined\n ? { subtotal: payment.subtotalMinor }\n : {}),\n ...(payment.taxMinor !== undefined ? { tax: payment.taxMinor } : {}),\n failureCode: payment.failureCode,\n };\n }\n return ignored;\n}\n\nfunction stripeObject(event: WebhookEvent): Record<string, unknown> {\n const payload = event.payload as { data?: { object?: unknown } } | null;\n const object = payload?.data?.object;\n return object && typeof object === 'object'\n ? (object as Record<string, unknown>)\n : {};\n}\n\n/** Metadata key holding the `smrt_*` HMAC (shared with crypto rails, #3138). */\nexport const SIGNATURE_KEY = 'smrt_sig';\n\nasync function hmacHex(secret: string, message: string): Promise<string> {\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const digest = await crypto.subtle.sign('HMAC', key, encoder.encode(message));\n return Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n}\n\nfunction canonicalMetadata(metadata: Record<string, string>): string {\n return JSON.stringify(\n Object.entries(metadata)\n // Empty values are dropped by the provider, so they are never signed.\n .filter(\n ([key, value]) =>\n key.startsWith('smrt_') && key !== SIGNATURE_KEY && value !== '',\n )\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n );\n}\n\nexport async function signMetadata(\n metadata: Record<string, string>,\n secret: string,\n): Promise<Record<string, string>> {\n return {\n ...metadata,\n [SIGNATURE_KEY]: await hmacHex(\n `smrt-checkout:${secret}`,\n canonicalMetadata(metadata),\n ),\n };\n}\n\nexport async function metadataSignatureValid(\n metadata: Record<string, string>,\n secret: string,\n): Promise<boolean> {\n const given = metadata[SIGNATURE_KEY];\n if (!given) return false;\n const expected = await hmacHex(\n `smrt-checkout:${secret}`,\n canonicalMetadata(metadata),\n );\n if (given.length !== expected.length) return false;\n let diff = 0;\n for (let index = 0; index < given.length; index += 1) {\n diff |= given.charCodeAt(index) ^ expected.charCodeAt(index);\n }\n return diff === 0;\n}\n\nexport function smrtMetadata(value: unknown): Record<string, string> {\n const result: Record<string, string> = {};\n if (!value || typeof value !== 'object') return result;\n for (const [key, entry] of Object.entries(value)) {\n if (key.startsWith('smrt_') && typeof entry === 'string') {\n result[key] = entry;\n }\n }\n return result;\n}\n","/**\n * A crypto payment rail for the {@link BillingProvider} port (#3138).\n *\n * The rail runs short-lived, fiat-priced checkouts through a\n * `CryptoCheckoutGateway` from `@happyvertical/payments` — BTCPay at launch,\n * any other implementation of the port later. It never issues invoices: the\n * runtime's issuing provider (Stripe) does, and this rail pays them or buys\n * prepaid credit. Settlement is the gateway's decision (its confirmation\n * policy); this package never counts confirmations.\n */\nimport {\n type CryptoCheckout,\n type CryptoCheckoutGateway,\n CryptoCheckoutNotOwnedError,\n PaymentVerificationError,\n} from '@happyvertical/payments';\nimport {\n type BillingPaymentAttemptPayment,\n type BillingPaymentAttemptState,\n BillingPaymentRefusedError,\n type BillingProvider,\n type BillingProviderCheckoutInput,\n type BillingProviderCheckoutSession,\n type BillingProviderEvent,\n BillingProviderUnsupportedError,\n BillingWebhookVerificationError,\n} from './provider.js';\nimport {\n metadataSignatureValid,\n SIGNATURE_KEY,\n signMetadata,\n smrtMetadata,\n} from './stripe.js';\nimport { currencyMinorUnitExponent, normalizeCurrency } from './units.js';\n\nexport interface CryptoBillingProviderOptions {\n /** A checkout gateway, for example `createBtcpayCheckoutGateway()`. */\n gateway: CryptoCheckoutGateway;\n /**\n * Secret the `smrt_*` checkout metadata is signed with. Rotating it\n * strands checkouts created before the rotation.\n */\n metadataSecret: string;\n /** Provider name on local rows and in the inbox (default the gateway id). */\n name?: string;\n /**\n * Smallest checkout accepted, per currency, in minor units (fee and dust\n * economics). A currency without an entry has no minimum.\n */\n minimumAmount?: Record<string, number>;\n}\n\n/** Minor-unit exponents of the settlement assets this rail records. */\nconst ASSET_EXPONENTS: Record<string, number> = { BTC: 8 };\n\nexport function createCryptoBillingProvider(\n options: CryptoBillingProviderOptions,\n): BillingProvider {\n const { gateway, metadataSecret } = options;\n if (!gateway) throw new Error('A crypto checkout gateway is required.');\n if (!metadataSecret) {\n throw new Error('A crypto rail metadata secret is required.');\n }\n const name = options.name ?? gateway.id;\n if (!/^[a-z][a-z0-9-]*$/.test(name)) {\n throw new Error(`Invalid crypto rail name '${name}'.`);\n }\n const minimums = new Map<string, number>();\n for (const [currency, amount] of Object.entries(\n options.minimumAmount ?? {},\n )) {\n if (!Number.isSafeInteger(amount) || amount < 0) {\n throw new Error(`minimumAmount for ${currency} must be minor units.`);\n }\n minimums.set(normalizeCurrency(currency), amount);\n }\n const unsupported = (what: string) => async () => {\n throw new BillingProviderUnsupportedError(\n `The ${name} payment rail does not ${what}; the runtime's issuing provider does.`,\n );\n };\n\n return {\n name,\n capabilities: { issuesInvoices: false, paymentAttempts: true },\n\n async syncCustomer(input) {\n // No provider customer exists; the account id identifies the payer.\n return { providerCustomerId: `${name}:${input.accountId}` };\n },\n pushInvoice: unsupported('issue invoices'),\n sendInvoice: unsupported('send invoices'),\n getInvoice: unsupported('issue invoices'),\n getSubscription: unsupported('manage subscriptions'),\n\n async createCheckout(\n input: BillingProviderCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n const currency = normalizeCurrency(input.currency);\n if (!Number.isSafeInteger(input.amount) || input.amount <= 0) {\n throw new Error(\n 'Checkout amount must be positive integer minor units.',\n );\n }\n if (input.automaticTax || input.savePaymentMethod) {\n throw new BillingProviderUnsupportedError(\n `The ${name} payment rail cannot charge tax or save a card.`,\n );\n }\n const minimum = minimums.get(currency) ?? 0;\n if (input.amount < minimum) {\n throw new BillingPaymentRefusedError(\n 'below_minimum',\n `The ${name} payment rail accepts at least ${minimum} ${currency} minor units.`,\n );\n }\n const checkout = await gateway.createCheckout({\n orderId: input.idempotencyKey,\n amount: input.amount,\n currency,\n description: input.description,\n buyerEmail: input.customerEmail || undefined,\n metadata: await signMetadata(\n smrtMetadata(input.metadata),\n metadataSecret,\n ),\n redirectUrl: input.successUrl,\n });\n return { sessionId: checkout.id, url: checkout.checkoutUrl ?? null };\n },\n\n async verifyWebhook(\n payload: string,\n _signature: string,\n headers?: Headers | Record<string, string | undefined>,\n ): Promise<BillingProviderEvent> {\n let event: ReturnType<CryptoCheckoutGateway['verifyWebhook']>;\n try {\n event = gateway.verifyWebhook(payload, headers ?? {});\n } catch (error) {\n if (error instanceof PaymentVerificationError) {\n throw new BillingWebhookVerificationError(error.message);\n }\n throw error;\n }\n if (!event.checkoutId) {\n return { kind: 'ignored', eventId: event.eventId, type: event.type };\n }\n return {\n kind: 'payment_attempt',\n eventId: event.eventId,\n checkoutId: event.checkoutId,\n };\n },\n\n async getPaymentAttempt(\n checkoutId: string,\n ): Promise<BillingPaymentAttemptState | null> {\n let checkout: CryptoCheckout;\n try {\n checkout = await gateway.getCheckout(checkoutId);\n } catch (error) {\n if (error instanceof CryptoCheckoutNotOwnedError) return null;\n throw error;\n }\n const metadata = smrtMetadata(checkout.metadata);\n const verified =\n Boolean(metadata[SIGNATURE_KEY]) &&\n (await metadataSignatureValid(metadata, metadataSecret));\n // A checkout without our signed metadata is not ours to record, even\n // if it settles in an asset this rail cannot map.\n if (!verified) return null;\n return toAttemptState(checkout, metadata);\n },\n };\n}\n\nfunction toAttemptState(\n checkout: CryptoCheckout,\n metadata: Record<string, string>,\n): BillingPaymentAttemptState {\n const currency = normalizeCurrency(checkout.currency);\n // Validates the fiat currency's exponent (throws for unknown codes).\n currencyMinorUnitExponent(currency);\n const asset = checkout.settlementAsset?.toUpperCase();\n const exponent = asset ? ASSET_EXPONENTS[asset] : undefined;\n if (asset && exponent === undefined) {\n throw new Error(\n `Checkout ${checkout.id} settles in ${asset}, which this rail cannot record.`,\n );\n }\n const native = (value: string | undefined) =>\n value === undefined || exponent === undefined\n ? undefined\n : decimalToAtomic(value, exponent);\n const payments: BillingPaymentAttemptPayment[] = checkout.payments.map(\n (payment) => {\n const paymentExponent = ASSET_EXPONENTS[payment.asset.toUpperCase()];\n if (paymentExponent === undefined) {\n throw new Error(\n `Checkout ${checkout.id} has a ${payment.asset} payment this rail cannot record.`,\n );\n }\n return {\n id: payment.id,\n rail: payment.rail,\n asset: payment.asset.toUpperCase(),\n amount: decimalToAtomic(payment.amount, paymentExponent),\n ...(payment.fee !== undefined\n ? { fee: decimalToAtomic(payment.fee, paymentExponent) }\n : {}),\n status: payment.status,\n ...(payment.transactionId\n ? { transactionId: payment.transactionId }\n : {}),\n ...(payment.receivedAt\n ? { receivedAt: payment.receivedAt.toISOString() }\n : {}),\n };\n },\n );\n return {\n checkoutId: checkout.id,\n orderId: checkout.orderId,\n status: checkout.status,\n exception: checkout.exception,\n amount: checkout.amount,\n currency,\n amountPaid: checkout.amountPaid,\n metadata,\n nativeCurrency: asset,\n nativeAmountDue: native(checkout.nativeAmountDue),\n nativeAmountPaid: native(checkout.nativeAmountPaid),\n rate: checkout.rate,\n rateSource: checkout.rateSource,\n checkoutUrl: checkout.checkoutUrl,\n expiresAt: checkout.expiresAt,\n payments,\n };\n}\n\n/** Exact decimal string → integer atomic units; rejects excess precision. */\nexport function decimalToAtomic(value: string, exponent: number): number {\n const match = /^(\\d+)(?:\\.(\\d+))?$/.exec(String(value).trim());\n if (!match) throw new Error(`Invalid decimal amount '${value}'.`);\n const fraction = (match[2] ?? '').replace(/0+$/, '');\n if (fraction.length > exponent) {\n throw new Error(`Amount ${value} has more than ${exponent} decimals.`);\n }\n const atomic = BigInt(`${match[1]}${fraction.padEnd(exponent, '0')}`);\n if (atomic > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new Error(`Amount ${value} exceeds the safe integer range.`);\n }\n return Number(atomic);\n}\n","/**\n * BTCPay Server as a crypto payment rail (#3138): sugar over\n * {@link createCryptoBillingProvider} with the SDK's BTCPay checkout gateway.\n * One BTCPay store per seller; keys and secrets come from the host's secret\n * store, never from this package.\n */\nimport {\n BtcpayClient,\n type BtcpayClientOptions,\n type BtcpaySpeedPolicy,\n createBtcpayCheckoutGateway,\n} from '@happyvertical/payments/btcpay';\nimport { createCryptoBillingProvider } from './crypto.js';\nimport type { BillingProvider } from './provider.js';\n\nexport interface BtcPayBillingProviderOptions {\n /** Server origin, for example `https://btcpay.example.com`. */\n baseUrl: string;\n /** Store-scoped key: `canviewinvoices` + `cancreateinvoice` suffice. */\n apiKey: string;\n storeId: string;\n /** The store webhook's secret. */\n webhookSecret: string;\n /** Secret signing the `smrt_*` checkout metadata. */\n metadataSecret: string;\n /**\n * Confirmations before BTCPay settles on-chain: `HighSpeed` 0,\n * `MediumSpeed` 1, `LowMediumSpeed` 2 (default, the owner's settlement\n * policy), `LowSpeed` 6.\n */\n speedPolicy?: BtcpaySpeedPolicy;\n /** Default `['BTC-CHAIN']`; add `BTC-LN` once the store has Lightning. */\n paymentMethods?: string[];\n expirationMinutes?: number;\n monitoringMinutes?: number;\n /** BTCPay's own underpayment tolerance, percent (default 0). */\n paymentTolerance?: number;\n /** Label of the store's rate source, recorded on every payment. */\n rateSource?: string;\n /** Minimum checkout per currency, minor units. */\n minimumAmount?: Record<string, number>;\n /** Provider name (default `btcpay`). */\n name?: string;\n fetch?: BtcpayClientOptions['fetch'];\n}\n\nexport function createBtcPayBillingProvider(\n options: BtcPayBillingProviderOptions,\n): BillingProvider {\n const gateway = createBtcpayCheckoutGateway({\n client: new BtcpayClient({\n baseUrl: options.baseUrl,\n apiKey: options.apiKey,\n storeId: options.storeId,\n fetch: options.fetch,\n }),\n webhookSecret: options.webhookSecret,\n speedPolicy: options.speedPolicy ?? 'LowMediumSpeed',\n paymentMethods: options.paymentMethods,\n expirationMinutes: options.expirationMinutes,\n monitoringMinutes: options.monitoringMinutes,\n paymentTolerance: options.paymentTolerance,\n rateSource: options.rateSource,\n });\n return createCryptoBillingProvider({\n gateway,\n metadataSecret: options.metadataSecret,\n name: options.name ?? 'btcpay',\n minimumAmount: options.minimumAmount,\n });\n}\n","/**\n * Billing-cycle schedule and proration math (#3116).\n *\n * A payer's billing periods are a pure function of its account's\n * `billingAnchorAt`:\n *\n * - no anchor: UTC calendar months (the default, and the only schedule before\n * #3116);\n * - an anchor `A`: monthly periods `[A + k months, A + (k + 1) months)` at\n * `A`'s UTC time of day. A day past the end of a shorter month is clamped to\n * its last day, always computed from `A` itself, so an anchor on the 31st\n * runs Jan 31 → Feb 28 (29) → Mar 31 without drifting. Before `A` the\n * schedule stays calendar months, and the month `A` falls in ends early at\n * `A` (a *stub* period), so an account that moves from calendar months to an\n * anchor has no gap and no overlap.\n *\n * Every period carries the full cycle it belongs to (`cycleStart`/`cycleEnd`);\n * that cycle's length is the proration denominator, so a stub is billed as the\n * fraction of its calendar month it covers.\n */\nimport type { BillingPeriod } from './period-close.js';\n\n/** A period of a payer's schedule and the full cycle it prorates against. */\nexport interface ScheduledBillingPeriod extends BillingPeriod {\n /** Start of the full monthly cycle (equals `periodStart` except for a stub). */\n cycleStart: Date;\n /** End of the full monthly cycle (equals `periodEnd` except for a stub). */\n cycleEnd: Date;\n}\n\nfunction daysInMonth(year: number, month: number): number {\n return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();\n}\n\n/**\n * `anchor` plus `months` calendar months at the anchor's UTC time of day,\n * with the day clamped to the target month's last day.\n */\nexport function addBillingMonths(anchor: Date, months: number): Date {\n const total = anchor.getUTCFullYear() * 12 + anchor.getUTCMonth() + months;\n const year = Math.floor(total / 12);\n const month = total - year * 12;\n const day = Math.min(anchor.getUTCDate(), daysInMonth(year, month));\n return new Date(\n Date.UTC(\n year,\n month,\n day,\n anchor.getUTCHours(),\n anchor.getUTCMinutes(),\n anchor.getUTCSeconds(),\n anchor.getUTCMilliseconds(),\n ),\n );\n}\n\nfunction monthStart(date: Date): Date {\n return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));\n}\n\nfunction calendarMonthContaining(at: Date): ScheduledBillingPeriod {\n const periodStart = monthStart(at);\n const periodEnd = new Date(\n Date.UTC(periodStart.getUTCFullYear(), periodStart.getUTCMonth() + 1, 1),\n );\n return {\n periodStart,\n periodEnd,\n cycleStart: periodStart,\n cycleEnd: periodEnd,\n };\n}\n\nfunction assertValidDate(date: Date, name: string): void {\n if (!(date instanceof Date) || !Number.isFinite(date.getTime())) {\n throw new Error(`${name} must be a valid date.`);\n }\n}\n\n/**\n * The period of a schedule (calendar months when `anchor` is null) that\n * contains `at`.\n */\nexport function billingPeriodContaining(\n anchor: Date | null | undefined,\n at: Date,\n): ScheduledBillingPeriod {\n assertValidDate(at, 'at');\n if (!anchor) return calendarMonthContaining(at);\n assertValidDate(anchor, 'billingAnchorAt');\n const time = at.getTime();\n if (time < anchor.getTime()) {\n const anchorMonth = monthStart(anchor);\n if (time >= anchorMonth.getTime()) {\n // The stub between the anchor's month start and the anchor itself.\n const cycle = calendarMonthContaining(anchorMonth);\n return {\n periodStart: anchorMonth,\n periodEnd: anchor,\n cycleStart: cycle.cycleStart,\n cycleEnd: cycle.cycleEnd,\n };\n }\n return calendarMonthContaining(at);\n }\n let k =\n (at.getUTCFullYear() - anchor.getUTCFullYear()) * 12 +\n (at.getUTCMonth() - anchor.getUTCMonth());\n while (k > 0 && addBillingMonths(anchor, k).getTime() > time) k -= 1;\n while (addBillingMonths(anchor, k + 1).getTime() <= time) k += 1;\n const periodStart = addBillingMonths(anchor, k);\n const periodEnd = addBillingMonths(anchor, k + 1);\n return {\n periodStart,\n periodEnd,\n cycleStart: periodStart,\n cycleEnd: periodEnd,\n };\n}\n\n/**\n * The most recent period of a schedule that has ended by `now` — what a\n * period close with no explicit period bills. With no anchor this is the\n * previous calendar month.\n */\nexport function lastEndedBillingPeriod(\n anchor: Date | null | undefined,\n now: Date = new Date(),\n): ScheduledBillingPeriod {\n const current = billingPeriodContaining(anchor, now);\n return billingPeriodContaining(\n anchor,\n new Date(current.periodStart.getTime() - 1),\n );\n}\n\n/**\n * `amount × coveredMs / cycleMs` in integer minor units, rounded half up\n * (exact integer arithmetic, no floating point). Coverage of the whole cycle\n * or more returns `amount` unchanged; no coverage returns 0.\n */\nexport function prorateMinorUnits(\n amount: number,\n coveredMs: number,\n cycleMs: number,\n): number {\n if (!Number.isSafeInteger(amount) || amount < 0) {\n throw new Error('A prorated amount must be a non-negative safe integer.');\n }\n if (!Number.isSafeInteger(cycleMs) || cycleMs <= 0) {\n throw new Error('A billing cycle must have a positive length.');\n }\n if (!Number.isSafeInteger(coveredMs) || coveredMs <= 0) return 0;\n if (coveredMs >= cycleMs) return amount;\n const numerator = BigInt(amount) * BigInt(coveredMs) * 2n + BigInt(cycleMs);\n return Number(numerator / (BigInt(cycleMs) * 2n));\n}\n","/**\n * PaymentAllocation model - maps payments to invoices\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport type { PaymentAllocationOptions } from '../types/index.js';\n\n/*\n * There is deliberately no `ALLOCATION_EPSILON` here any more (#2401).\n *\n * `Payment.amount`, `Invoice.totalAmount` and this row's `amount` are all\n * integer minor units, so \"does this allocation over-apply the payment\" is an\n * exact integer question. The old `0.01` tolerance let an allocation exceed its\n * payment by up to a cent — real money, not rounding fuzz — and only existed\n * because the payment side was a float.\n */\n\n/**\n * PaymentAllocation tracks how payments are applied to invoices.\n *\n * This enables:\n * - Partial payments (one payment partially covering an invoice)\n * - Split payments (one payment split across multiple invoices)\n * - Payment history per invoice\n *\n * **Note**: This model does not automatically validate that allocations\n * don't exceed the payment amount or invoice amount due. Use\n * `PaymentAllocationCollection.getUnallocatedFromPayment()` before creating\n * allocations to ensure sufficient funds are available.\n *\n * @example\n * ```typescript\n * import { PaymentAllocationCollection } from '@happyvertical/smrt-commerce';\n *\n * // Get the collection\n * const allocations = await PaymentAllocationCollection.create(options);\n *\n * // Check available funds before allocating (all amounts are minor units)\n * const available = await allocations.getUnallocatedFromPayment(\n * payment.id,\n * payment.amount\n * );\n *\n * if (available >= amountToAllocate) {\n * const allocation = await allocations.create({\n * paymentId: payment.id,\n * invoiceId: invoice.id,\n * amount: amountToAllocate,\n * allocatedBy: 'user-uuid'\n * });\n *\n * // Update invoice payment status\n * const totalAllocated = await allocations.getTotalAllocatedToInvoice(invoice.id);\n * invoice.updatePaymentStatus(totalAllocated);\n * await invoice.save();\n * }\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // NOTE: `create` and `delete` are intentionally NOT exposed over the\n // generated REST/MCP surface (S5 audit #1390). Allocation rows directly\n // determine an invoice's amountPaid/status and a payment's remaining funds;\n // a generated `create` lets a caller forge an allocation that over-applies a\n // payment, and a generated `delete` lets a caller silently un-apply funds —\n // both falsify balances with no authz. Allocations must be created/removed\n // through application code that re-derives invoice status and respects the\n // payment-amount cap (the cap is also enforced in `save()` as defence in\n // depth).\n //\n // CLI parity (round 6, codex ROOT INSIGHT): the CLI is an independently\n // configured write surface. `cli: true` would still generate create/update/\n // delete commands that forge or un-apply allocations, re-opening the exact\n // vector closed on api/mcp. The CLI is locked to the same read-only surface.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class PaymentAllocation extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global payment allocations\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Payment being allocated\n */\n @foreignKey('Payment')\n paymentId: string = '';\n\n /**\n * Invoice receiving the allocation\n */\n @foreignKey('Invoice')\n invoiceId: string = '';\n\n /**\n * Amount allocated from payment to invoice, in **integer minor units**\n * (cents, satoshis).\n *\n * Matches `Invoice.amountPaid` (derived by summing these rows) and\n * `Payment.amount` (which `save()` caps against). All three converted\n * together in #2401 — the cap is a subtraction across the pair, so a\n * dollars-vs-cents split there would reject every allocation as\n * over-applying.\n */\n amount: number = 0;\n\n /**\n * When the allocation was made\n */\n allocatedAt: Date = new Date();\n\n /**\n * User/agent who made the allocation\n */\n allocatedBy: string = '';\n\n /**\n * Notes about the allocation\n */\n notes: string = '';\n\n constructor(options: PaymentAllocationOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.invoiceId !== undefined) this.invoiceId = options.invoiceId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.allocatedAt !== undefined)\n this.allocatedAt = options.allocatedAt;\n if (options.allocatedBy !== undefined)\n this.allocatedBy = options.allocatedBy;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Save-time integrity guard (S5 audit #1390 + follow-up):\n * - allocation `amount` must be a finite, positive number,\n * - the sum of all allocations against the referenced Payment (this row\n * included) must not exceed the Payment's amount — over-applying a\n * payment across invoices would falsify both payment and invoice\n * balances, and\n * - the sum of all allocations against the referenced Invoice (this row\n * included) must not exceed the Invoice's `totalAmount`.\n *\n * The Payment-amount cap is enforced against the persisted Payment row. An\n * allocation always carries a `@foreignKey('Payment')` paymentId, so a\n * `paymentId` that doesn't resolve to a real Payment is a **hard error** (S5\n * audit #1390 round 2): previously a missing Payment silently skipped the cap\n * entirely, letting a caller over-apply (or fabricate) funds simply by\n * pointing at a non-existent payment. An empty `paymentId` is still rejected\n * by the underlying FK requirement; the positivity check always applies.\n *\n * The Invoice-total cap (follow-up) closes a complementary hole: the\n * per-Payment cap lets allocations from *different* payments each pass their\n * own check while jointly summing above the invoice total. The next\n * `Invoice.save()` would then recompute `amountPaid` from these allocations,\n * trip `assertNonNegativeAmounts` (amountPaid > totalAmount), and leave the\n * invoice permanently unsaveable while the over-allocations persist. Capping\n * here keeps allocations from ever exceeding what the invoice owes. The cap\n * is skipped only when the invoice row can't be resolved (e.g. ledger-less /\n * not-yet-persisted) so it never blocks an otherwise-valid allocation.\n */\n override async save(): Promise<this> {\n // Whole minor units first — every cap below is an exact integer comparison\n // and is only sound if the value really is an integer (#2401).\n assertIntegerMinorUnits('PaymentAllocation', this.id ?? '<new>', [\n ['amount', this.amount],\n ]);\n if (this.amount <= 0) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: amount must be a positive number (got ${this.amount}).`,\n );\n }\n\n if (this.paymentId) {\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: this.paymentId });\n if (!payment) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: referenced Payment ` +\n `'${this.paymentId}' does not exist — refusing to allocate against a ` +\n 'missing payment (the payment-amount cap cannot be enforced otherwise).',\n );\n }\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocations = await PaymentAllocationCollection.create(\n this.options,\n );\n const existing = await allocations.findByPayment(this.paymentId);\n const otherTotal = existing.reduce(\n (sum: number, alloc: PaymentAllocation) =>\n alloc.id === this.id ? sum : sum + alloc.amount,\n 0,\n );\n if (otherTotal + this.amount > payment.amount) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: allocating ${this.amount} would over-apply ` +\n `payment '${this.paymentId}' — already allocated ${otherTotal} of ${payment.amount}.`,\n );\n }\n }\n\n if (this.invoiceId) {\n const { InvoiceCollection } = await import(\n '../collections/InvoiceCollection.js'\n );\n const invoices = await InvoiceCollection.create(this.options);\n const invoice = await invoices.get({ id: this.invoiceId });\n // Only enforce when the invoice resolves and carries a real positive\n // total — a missing/zero-total invoice (not yet persisted, ledger-less\n // test fixture) skips the cap rather than blocking a valid allocation.\n // `Number.isFinite(0)` is `true`, so an `isFinite` guard here wrongly\n // capped a freshly-created total=0 invoice at 0 and rejected every\n // allocation against it; gate on `> 0` to match the documented\n // \"zero/missing total skips the cap\" intent.\n if (invoice && invoice.totalAmount > 0) {\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocations = await PaymentAllocationCollection.create(\n this.options,\n );\n const existingForInvoice = await allocations.findByInvoice(\n this.invoiceId,\n );\n const otherInvoiceTotal = existingForInvoice.reduce(\n (sum: number, alloc: PaymentAllocation) =>\n alloc.id === this.id ? sum : sum + alloc.amount,\n 0,\n );\n if (otherInvoiceTotal + this.amount > invoice.totalAmount) {\n throw new Error(\n `PaymentAllocation ${this.id ?? '<new>'}: allocating ${this.amount} would over-pay ` +\n `invoice '${this.invoiceId}' — already allocated ${otherInvoiceTotal} of ` +\n `${invoice.totalAmount}.`,\n );\n }\n }\n }\n\n return super.save() as Promise<this>;\n }\n}\n\nexport default PaymentAllocation;\n","/**\n * PaymentAllocationCollection - Collection manager for PaymentAllocation objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { PaymentAllocation } from '../models/PaymentAllocation.js';\n\nexport class PaymentAllocationCollection extends SmrtCollection<PaymentAllocation> {\n static readonly _itemClass = PaymentAllocation;\n\n /**\n * Find allocations by payment\n *\n * @param paymentId - Payment ID\n * @returns Array of allocations\n */\n async findByPayment(paymentId: string): Promise<PaymentAllocation[]> {\n return await this.list({\n where: { paymentId },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n /**\n * Find allocations by invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Array of allocations\n */\n async findByInvoice(invoiceId: string): Promise<PaymentAllocation[]> {\n return await this.list({\n where: { invoiceId },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n /**\n * Get total amount allocated to an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Total allocated amount\n */\n async getTotalAllocatedToInvoice(invoiceId: string): Promise<number> {\n const allocations = await this.findByInvoice(invoiceId);\n return allocations.reduce((sum, alloc) => sum + alloc.amount, 0);\n }\n\n /**\n * Get total amount allocated from a payment\n *\n * @param paymentId - Payment ID\n * @returns Total allocated amount\n */\n async getTotalAllocatedFromPayment(paymentId: string): Promise<number> {\n const allocations = await this.findByPayment(paymentId);\n return allocations.reduce((sum, alloc) => sum + alloc.amount, 0);\n }\n\n /**\n * Get remaining unallocated amount from a payment.\n *\n * @param paymentId - Payment ID\n * @param paymentAmount - Total payment amount\n * @returns Unallocated amount\n * @throws Error if payment is over-allocated (indicates data integrity issue)\n */\n async getUnallocatedFromPayment(\n paymentId: string,\n paymentAmount: number,\n ): Promise<number> {\n const allocated = await this.getTotalAllocatedFromPayment(paymentId);\n const remaining = paymentAmount - allocated;\n\n if (remaining < 0) {\n throw new Error(\n `Over-allocated payment detected for paymentId=${paymentId}: ` +\n `allocated amount (${allocated}) exceeds payment amount (${paymentAmount})`,\n );\n }\n\n return remaining;\n }\n\n /**\n * Find allocation by payment and invoice\n *\n * @param paymentId - Payment ID\n * @param invoiceId - Invoice ID\n * @returns Allocation or null\n */\n async findByPaymentAndInvoice(\n paymentId: string,\n invoiceId: string,\n ): Promise<PaymentAllocation | null> {\n const results = await this.list({\n where: { paymentId, invoiceId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find allocations by allocator\n *\n * @param allocatedBy - User/agent ID who made the allocation\n * @returns Array of allocations\n */\n async findByAllocator(allocatedBy: string): Promise<PaymentAllocation[]> {\n return await this.list({\n where: { allocatedBy },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n /**\n * Find allocations in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of allocations\n */\n async findByDateRange(\n startDate: Date,\n endDate: Date,\n ): Promise<PaymentAllocation[]> {\n return await this.list({\n where: {\n 'allocatedAt >=': startDate.toISOString(),\n 'allocatedAt <=': endDate.toISOString(),\n },\n orderBy: 'allocatedAt DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all payment allocations belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of payment allocations for the tenant\n */\n async findByTenant(tenantId: string): Promise<PaymentAllocation[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payment allocations (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global payment allocations\n */\n async findGlobal(): Promise<PaymentAllocation[]> {\n return queryGlobal<PaymentAllocation>(this);\n }\n\n /**\n * Find payment allocations for a tenant including global allocations.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global payment allocations\n */\n async findWithGlobals(tenantId: string): Promise<PaymentAllocation[]> {\n return queryWithGlobals<PaymentAllocation>(\n this,\n tenantId,\n 'PaymentAllocation.findWithGlobals',\n );\n }\n}\n","/**\n * Paying an issued invoice on another payment rail (#3138).\n *\n * The issuing provider (Stripe) issues, taxes, and emails the invoice. A payer\n * may instead pay it on a payment rail such as a crypto checkout: this opens a\n * rail checkout for the invoice's amount due, and settlement records the\n * payment locally and closes the issuing provider's invoice as paid out of\n * band so it stops collecting.\n */\nimport {\n getTenantId,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport { PaymentAllocationCollection } from '../collections/PaymentAllocationCollection.js';\nimport { InvoiceStatus } from '../types/index.js';\nimport {\n BillingPaymentRefusedError,\n type BillingProviderCheckoutSession,\n providerCapabilities,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n deterministicId,\n isUuid,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport const INVOICE_PAYMENT_PURPOSE = 'invoice_payment';\n\n/**\n * Invoices a rail payment may be opened for. A written-off invoice is still\n * payable, as it is at the issuer (#3188): settlement records the payment,\n * closes the issuer's invoice out of band, and returns the payer to\n * `current`; the invoice keeps its `WRITTEN_OFF` status.\n */\nconst PAYABLE = new Set<string>([\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.WRITTEN_OFF,\n]);\n\nexport interface CreateInvoicePaymentInput {\n invoiceId: string;\n /** The payment rail (a provider name from `paymentProviders`). */\n provider: string;\n /**\n * Caller-owned id for this attempt; a retry with the same id (and the same\n * amount due) returns the same checkout.\n */\n purchaseId: string;\n successUrl: string;\n cancelUrl: string;\n description?: string;\n}\n\nexport interface InvoicePaymentMetadata {\n purpose: string;\n sellerTenantId: string;\n payerTenantId: string;\n billingAccountId: string;\n invoiceId: string;\n amount: number;\n currency: string;\n}\n\n/** Parse checkout metadata written by {@link createInvoicePayment}. */\nexport function invoicePaymentMetadata(\n raw: Record<string, string>,\n): InvoicePaymentMetadata | null {\n if (raw.smrt_purpose !== INVOICE_PAYMENT_PURPOSE) return null;\n const amount = Number(raw.smrt_amount);\n if (\n !raw.smrt_seller ||\n !raw.smrt_payer ||\n !raw.smrt_account ||\n !raw.smrt_invoice ||\n !raw.smrt_currency ||\n !Number.isSafeInteger(amount) ||\n amount <= 0\n ) {\n throw new Error('Invoice payment metadata is incomplete.');\n }\n return {\n purpose: raw.smrt_purpose,\n sellerTenantId: tenantKey(raw.smrt_seller),\n payerTenantId: tenantKey(raw.smrt_payer),\n billingAccountId: raw.smrt_account,\n invoiceId: raw.smrt_invoice,\n amount,\n currency: normalizeCurrency(raw.smrt_currency),\n };\n}\n\nexport async function createInvoicePayment(\n runtime: BillingRuntime,\n input: CreateInvoicePaymentInput,\n): Promise<BillingProviderCheckoutSession> {\n if (!input.purchaseId) throw new Error('purchaseId is required.');\n const rail = runtime.providerFor(input.provider);\n if (!providerCapabilities(rail).paymentAttempts) {\n throw new Error(\n `Provider ${rail.name} cannot take payments for issued invoices.`,\n );\n }\n // Authorize before anything about the invoice is revealed: a payer may\n // only learn about, and pay, its own invoices.\n const notFound = () =>\n new BillingPaymentRefusedError(\n 'not_found',\n `Invoice ${input.invoiceId} was not found for this payer.`,\n );\n // Billing invoice ids are UUIDs: anything else cannot be one, and must not\n // reach a UUID column as a failed cast. A failed lookup is a server error,\n // never reported to the payer as \"not found\" (#3185).\n if (!isUuid(input.invoiceId)) throw notFound();\n const invoice = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n runtime.invoices.get(input.invoiceId),\n );\n if (!invoice?.id) throw notFound();\n const [close] = await runtime.closes.list({\n where: {\n invoiceId: String(invoice.id),\n sellerTenantId: runtime.sellerTenantId,\n },\n limit: 1,\n });\n const account = close?.billingAccountId\n ? await runtime.accounts.get(close.billingAccountId)\n : null;\n if (!account?.id) throw notFound();\n const payer = tenantKey(account.payerTenantId);\n if (\n !isSystemContext() &&\n !isSuperAdminBypass() &&\n tenantKey(getTenantId()) !== payer\n ) {\n throw new TenantIsolationError('Only the payer of an invoice can pay it.');\n }\n if (!PAYABLE.has(invoice.status)) {\n throw new BillingPaymentRefusedError(\n 'not_payable',\n `Invoice ${invoice.invoiceNumber} is ${invoice.status}; only sent, unpaid invoices can be paid.`,\n );\n }\n if (invoice.externalProvider !== runtime.provider.name) {\n throw new BillingPaymentRefusedError(\n 'not_payable',\n `Invoice ${invoice.invoiceNumber} was not issued by ${runtime.provider.name}.`,\n );\n }\n // Fail closed: without it a paid invoice would stay open at the issuer.\n if (!runtime.provider.markInvoicePaidOutOfBand) {\n throw new Error(\n `${runtime.provider.name} cannot close an invoice paid on another rail.`,\n );\n }\n const allocated = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () =>\n (\n await PaymentAllocationCollection.create({ db: runtime.db })\n ).getTotalAllocatedToInvoice(String(invoice.id)),\n );\n const amountDue = invoice.totalAmount - allocated;\n if (!Number.isSafeInteger(amountDue) || amountDue <= 0) {\n throw new BillingPaymentRefusedError(\n 'nothing_due',\n `Invoice ${invoice.invoiceNumber} has nothing due.`,\n );\n }\n const currency = normalizeCurrency(invoice.currency);\n const key = await deterministicId([\n 'billing-invoice-payment',\n runtime.sellerTenantId,\n rail.name,\n String(invoice.id),\n String(amountDue),\n input.purchaseId,\n ]);\n const orderId = `smrt-invoice-payment:${key}`;\n // One live payment per invoice: a second checkout could be paid too and\n // collect the invoice twice. The payer finishes (or lets expire) the one\n // already open; retrying the same purchase returns it.\n const live = (\n await runtime.listPaymentAttempts({ invoiceId: String(invoice.id) })\n ).filter(\n (row) =>\n (row.status === 'open' || row.status === 'confirming') && !row.settledAt,\n );\n // Rows without an order id (created before it was recorded) are left to\n // the gateway, whose creation is idempotent by order id.\n const busy = live.find((row) => row.orderId && row.orderId !== orderId);\n if (busy) {\n throw new BillingPaymentRefusedError(\n 'payment_in_progress',\n `Invoice ${invoice.invoiceNumber} already has a payment in progress on ${busy.provider}.`,\n );\n }\n const metadata: Record<string, string> = {\n smrt_purpose: INVOICE_PAYMENT_PURPOSE,\n smrt_seller: runtime.sellerTenantId,\n smrt_payer: payer,\n smrt_account: String(account.id),\n smrt_invoice: String(invoice.id),\n smrt_amount: String(amountDue),\n smrt_currency: currency,\n };\n const session = await rail.createCheckout({\n idempotencyKey: orderId,\n customerEmail: account.email || undefined,\n currency,\n amount: amountDue,\n description: input.description || `Invoice ${invoice.invoiceNumber}`,\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n metadata,\n });\n await runtime.recordPaymentAttemptStart({\n orderId,\n provider: rail.name,\n checkoutId: session.sessionId,\n checkoutUrl: session.url ?? '',\n purpose: 'invoice_payment',\n payerTenantId: payer,\n billingAccountId: String(account.id),\n invoiceId: String(invoice.id),\n amount: amountDue,\n currency,\n });\n return session;\n}\n","/**\n * smrt-jobs integration for billing (#3060).\n *\n * A runtime holds live dependencies (the payment provider, the relationship\n * reader) that cannot be serialized into a job row, so jobs name a runtime\n * registered in this process and carry only plain arguments.\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { type SmrtJob, SmrtJobCollection } from '@happyvertical/smrt-jobs';\nimport type { PeriodCloseResult } from './period-close.js';\nimport type { BillingRuntime } from './runtime.js';\n\nconst REGISTRY_KEY = Symbol.for(\n '@happyvertical/smrt-commerce:billing-runtimes',\n);\n\nfunction registry(): Map<string, BillingRuntime> {\n const holder = globalThis as unknown as Record<\n symbol,\n Map<string, BillingRuntime> | undefined\n >;\n let runtimes = holder[REGISTRY_KEY];\n if (!runtimes) {\n runtimes = new Map();\n holder[REGISTRY_KEY] = runtimes;\n }\n return runtimes;\n}\n\n/** Make a runtime available to billing jobs under `name`. */\nexport function registerBillingRuntime(\n name: string,\n runtime: BillingRuntime,\n): void {\n if (!name) throw new Error('A billing runtime name is required.');\n registry().set(name, runtime);\n}\n\nexport function unregisterBillingRuntime(name: string): void {\n registry().delete(name);\n}\n\nexport function getBillingRuntime(name: string): BillingRuntime {\n const runtime = registry().get(name);\n if (!runtime) {\n throw new Error(`No billing runtime is registered as '${name}'.`);\n }\n return runtime;\n}\n\nfunction runtimeFrom(args: Record<string, unknown>): BillingRuntime {\n if (typeof args.runtime !== 'string') {\n throw new Error('Billing jobs require a runtime name.');\n }\n return getBillingRuntime(args.runtime);\n}\n\nfunction optionalDate(value: unknown, name: string): Date | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n const date = new Date(String(value));\n if (!Number.isFinite(date.getTime())) {\n throw new Error(`${name} must be an ISO date.`);\n }\n return date;\n}\n\nexport async function runBillingPeriodCloseJob(\n args: Record<string, unknown>,\n): Promise<PeriodCloseResult> {\n return runtimeFrom(args).closePeriod({\n periodStart: optionalDate(args.periodStart, 'periodStart'),\n periodEnd: optionalDate(args.periodEnd, 'periodEnd'),\n });\n}\n\nexport async function runBillingEventsJob(\n args: Record<string, unknown>,\n): Promise<{ processed: number }> {\n const limit = args.limit === undefined ? 25 : Number(args.limit);\n return { processed: await runtimeFrom(args).processEvents(limit) };\n}\n\nexport interface EnqueueBillingJobOptions {\n /** Registered runtime name. */\n runtime: string;\n runAt?: Date;\n queue?: string;\n maxAttempts?: number;\n}\n\nfunction jobObjectType(): string {\n return (\n ObjectRegistry.getClass('BillingPeriodClose')?.qualifiedName ??\n '@happyvertical/smrt-commerce:BillingPeriodClose'\n );\n}\n\nasync function enqueue(\n runtime: BillingRuntime,\n method: string,\n args: Record<string, unknown>,\n options: EnqueueBillingJobOptions,\n): Promise<SmrtJob> {\n const jobs = await SmrtJobCollection.create({ db: runtime.db });\n return jobs.enqueueJob({\n tenantId: null,\n queue: options.queue ?? 'billing',\n objectType: jobObjectType(),\n objectId: null,\n method,\n args: { runtime: options.runtime, ...args },\n runAt: options.runAt,\n maxAttempts: options.maxAttempts ?? 5,\n });\n}\n\n/**\n * Queue a period close. Without a period the job closes each payer's last\n * ended period on its own schedule when it runs (the previous calendar month,\n * or the last anchor-to-anchor period for an anchored account, #3116), so\n * enqueueing it daily is safe.\n */\nexport function enqueueBillingPeriodClose(\n options: EnqueueBillingJobOptions & { periodStart?: Date; periodEnd?: Date },\n): Promise<SmrtJob> {\n const runtime = getBillingRuntime(options.runtime);\n return enqueue(\n runtime,\n 'runPeriodClose',\n {\n ...(options.periodStart\n ? { periodStart: options.periodStart.toISOString() }\n : {}),\n ...(options.periodEnd\n ? { periodEnd: options.periodEnd.toISOString() }\n : {}),\n },\n options,\n );\n}\n\n/** Queue processing of received provider events. */\nexport function enqueueBillingEvents(\n options: EnqueueBillingJobOptions & { limit?: number },\n): Promise<SmrtJob> {\n if (\n options.limit !== undefined &&\n (!Number.isSafeInteger(options.limit) || options.limit <= 0)\n ) {\n return Promise.reject(new Error('limit must be a positive integer.'));\n }\n const runtime = getBillingRuntime(options.runtime);\n return enqueue(\n runtime,\n 'processBillingEvents',\n options.limit !== undefined ? { limit: options.limit } : {},\n options,\n );\n}\n","/**\n * Invoice model - billing document sent to customers\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Journal, JournalEntryData } from '@happyvertical/smrt-ledgers';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { minorToMajorUnits } from '../billing/units.js';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport {\n type AccountingInvoiceInput,\n type InvoiceCollectionMethod,\n type InvoiceOptions,\n InvoiceStatus,\n type RecognizeRevenueOptions,\n} from '../types/index.js';\nimport type { InvoiceLineItem } from './InvoiceLineItem.js';\n\n/*\n * There is deliberately no `INVOICE_EPSILON` here any more (#2401).\n *\n * Every amount on this model is integer minor units, so `subtotal + taxAmount`\n * is exact, the sum of allocations is exact, and every comparison below is a\n * plain integer comparison. The old `0.01` tolerance existed because the values\n * were floats; keeping it against integers would silently forgive a genuine\n * one-cent discrepancy, and the \"snap `totalAmount` to `subtotal + taxAmount`\n * afterwards\" workaround it required (to stay inside the tighter smrt-ledgers\n * `BALANCE_EPSILON`) is gone with it — exact arithmetic needs no snap.\n */\n\n/**\n * Legal status transitions for an Invoice. Keyed by the prior (persisted)\n * status; the value is the set of statuses it may move to. A status mapping\n * to itself (no-op re-save) is always permitted and handled separately.\n *\n * Forward path: DRAFT → SENT → VIEWED → PARTIAL → PAID, with OVERDUE reachable\n * from any open status and CANCELLED / WRITTEN_OFF as terminal exits. Payment\n * progress (SENT/VIEWED ↔ PARTIAL ↔ PAID) is driven by `updatePaymentStatus`,\n * which can move both forward and backward as allocations change, so those\n * edges are bidirectional here.\n */\nconst INVOICE_STATUS_TRANSITIONS: Record<InvoiceStatus, InvoiceStatus[]> = {\n [InvoiceStatus.DRAFT]: [\n InvoiceStatus.SENT,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.SENT]: [\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.VIEWED]: [\n InvoiceStatus.SENT,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.PARTIAL]: [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.OVERDUE]: [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n // PAID can fall back to PARTIAL / VIEWED / SENT when a payment allocation is\n // removed or reversed (handled by updatePaymentStatus). It can also be\n // written off as bad debt in unusual reconciliation flows.\n [InvoiceStatus.PAID]: [\n InvoiceStatus.PARTIAL,\n InvoiceStatus.VIEWED,\n InvoiceStatus.SENT,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.WRITTEN_OFF,\n ],\n // Terminal states: no outbound transitions.\n [InvoiceStatus.CANCELLED]: [],\n [InvoiceStatus.WRITTEN_OFF]: [],\n};\n\n/**\n * Module-scoped record of the status each Invoice instance was loaded with.\n * Used by the save-time status-transition guard to compare the prior\n * persisted status against the one being written, without adding a\n * persisted column. A WeakMap keyed by the instance keeps it out of the\n * schema and garbage-collects with the instance — same pattern LicenseSale\n * uses for its rights snapshot.\n */\nconst loadedInvoiceStatus = new WeakMap<Invoice, InvoiceStatus>();\n\n/**\n * Invoice represents a bill sent to a customer for goods or services.\n *\n * Invoices are distinct from Contracts - a Contract is an agreement,\n * while an Invoice is the billing document requesting payment.\n *\n * Invoices integrate with:\n * - `@happyvertical/smrt-ledgers` for revenue recognition (double-entry accounting)\n * - `@happyvertical/accounting` SDK for syncing with external providers (QBO, Stripe)\n *\n * @example\n * ```typescript\n * // Create an invoice\n * const invoice = await invoices.create({\n * customerId: customer.id,\n * invoiceNumber: await invoices.generateInvoiceNumber(),\n * issueDate: new Date(),\n * dueDate: addDays(new Date(), 30),\n * subtotal: 1000,\n * taxAmount: 50,\n * totalAmount: 1050,\n * currency: 'CAD'\n * });\n *\n * // Recognize revenue in ledger\n * await invoice.recognizeRevenue({\n * arAccountId: arAccount.id,\n * revenueAccountId: revenueAccount.id,\n * taxAccountId: taxAccount.id\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): the generated create/update surface may\n // only set the billing-document descriptors (who/when/identifiers). The\n // derived/integrity amounts (`subtotal`, `taxAmount`, `totalAmount`,\n // `amountPaid`) and `status` are EXCLUDED — totals are derived from line\n // items, amountPaid from PaymentAllocations, and status from\n // updatePaymentStatus()/the lifecycle helpers. A caller can therefore create\n // a DRAFT invoice (customer / dueDate / line-item refs) but can never forge a\n // total, an amountPaid, or a PAID status through a generated route. Ledger\n // journal refs and communication timestamps are server-managed too.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'customerId',\n 'contractId',\n 'invoiceNumber',\n 'reference',\n 'issueDate',\n 'dueDate',\n 'currency',\n 'notes',\n 'customerNotes',\n 'terms',\n 'externalId',\n 'customerExternalId',\n 'externalProvider',\n ],\n },\n // NOTE: `send` and `recognizeRevenue` are intentionally NOT exposed over\n // MCP. `recognizeRevenue` posts a balanced journal into smrt-ledgers\n // (i.e. moves money in the books) and `send` transmits a billing document\n // to a customer — neither is safe as an unauthenticated/unguarded MCP tool.\n // Generated MCP tools carry no authz; financial mutations must go through\n // application code that enforces permissions. MCP create/update honor the\n // same `api.writable` allowlist, so status/amounts are unsettable over MCP\n // too. See S5 audit #1390.\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Invoice extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global invoices\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Customer this invoice is for\n */\n @foreignKey('Customer')\n customerId: string = '';\n\n /**\n * Optional link to Contract (cross-package reference)\n */\n @foreignKey('Contract')\n contractId: string = '';\n\n // ============================================================================\n // Identification\n // ============================================================================\n\n /**\n * Invoice number (e.g., INV-2025-0001)\n * Generated via InvoiceCollection.generateInvoiceNumber()\n */\n invoiceNumber: string = '';\n\n /**\n * External reference (e.g., customer PO number)\n */\n reference: string = '';\n\n // ============================================================================\n // Dates\n // ============================================================================\n\n /**\n * Date invoice was issued\n */\n issueDate: Date = new Date();\n\n /**\n * Payment due date\n */\n dueDate: Date = new Date();\n\n /**\n * Date invoice was fully paid\n */\n paidDate: Date | null = null;\n\n // ============================================================================\n // Amounts\n // ============================================================================\n\n /**\n * Subtotal before tax, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so it is stored\n * as minor units and never as a float — `$19.99` is `1999`, not `19.99`.\n * Writing a fractional major-unit value here is the bug; PostgreSQL rejects\n * it with `22P02` while SQLite's affinity silently stores it (#2361).\n */\n subtotal: number = 0;\n\n /**\n * Tax amount, in integer minor units (#2361). With line items this is the\n * sum of their rate-based tax plus {@link providerTaxAmount}.\n */\n taxAmount: number = 0;\n\n /**\n * Tax calculated by the payment provider (for example Stripe Tax) from the\n * customer's tax location, in integer minor units (#3060). Server-managed:\n * billing-period close records it after the provider finalizes the invoice.\n * It is added to the line items' tax, so `taxAmount` and `totalAmount` stay\n * authoritative from line items plus this one provider figure.\n */\n providerTaxAmount: number = 0;\n\n /**\n * Total amount due (subtotal + tax), in integer minor units (#2361).\n */\n totalAmount: number = 0;\n\n /**\n * Amount paid (sum of PaymentAllocations), in integer minor units (#2361).\n */\n amountPaid: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n currency: string = 'CAD';\n\n // ============================================================================\n // Status\n // ============================================================================\n\n /**\n * Current invoice status\n */\n status: InvoiceStatus = InvoiceStatus.DRAFT;\n\n // ============================================================================\n // Ledger Integration\n // ============================================================================\n\n /**\n * Journal ID for AR recognition (cross-package ref to smrt-ledgers)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n arJournalId: string = '';\n\n /**\n * Journal ID for revenue recognition (cross-package ref to smrt-ledgers)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n revenueJournalId: string = '';\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * External ID in accounting provider (e.g., QBO invoice ID)\n */\n externalId: string = '';\n\n /**\n * Customer's external ID in accounting provider.\n * Used to link invoice to customer in external system.\n */\n customerExternalId: string = '';\n\n /**\n * Accounting provider name ('quickbooks' | 'stripe' | etc.)\n */\n externalProvider: string = '';\n\n /**\n * When invoice was last synced to provider\n */\n syncedAt: Date | null = null;\n\n /**\n * How the provider collects this invoice (#3190), decided once by billing\n * period close before its first push and reused on every retry, so it is\n * what the provider was asked for. Server-managed. A host must not offer\n * another payment rail on a `charge_automatically` invoice the provider is\n * still collecting.\n */\n collectionMethod: InvoiceCollectionMethod = '';\n\n // ============================================================================\n // Communication\n // ============================================================================\n\n /**\n * When invoice was sent to customer\n */\n sentAt: Date | null = null;\n\n /**\n * When customer viewed the invoice\n */\n viewedAt: Date | null = null;\n\n /**\n * Number of payment reminders sent\n */\n remindersSent: number = 0;\n\n /**\n * When last reminder was sent\n */\n lastReminderAt: Date | null = null;\n\n // ============================================================================\n // Notes\n // ============================================================================\n\n /**\n * Internal notes (not shown to customer)\n */\n notes: string = '';\n\n /**\n * Notes shown to customer on invoice\n */\n customerNotes: string = '';\n\n /**\n * Payment terms text\n */\n terms: string = '';\n\n constructor(options: InvoiceOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.invoiceNumber !== undefined)\n this.invoiceNumber = options.invoiceNumber;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.issueDate !== undefined) this.issueDate = options.issueDate;\n if (options.dueDate !== undefined) this.dueDate = options.dueDate;\n if (options.paidDate !== undefined) this.paidDate = options.paidDate;\n if (options.subtotal !== undefined) this.subtotal = options.subtotal;\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.providerTaxAmount !== undefined)\n this.providerTaxAmount = options.providerTaxAmount;\n if (options.totalAmount !== undefined)\n this.totalAmount = options.totalAmount;\n if (options.amountPaid !== undefined) this.amountPaid = options.amountPaid;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.status !== undefined) this.status = options.status;\n if (options.arJournalId !== undefined)\n this.arJournalId = options.arJournalId;\n if (options.revenueJournalId !== undefined)\n this.revenueJournalId = options.revenueJournalId;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.customerExternalId !== undefined)\n this.customerExternalId = options.customerExternalId;\n if (options.externalProvider !== undefined)\n this.externalProvider = options.externalProvider;\n if (options.syncedAt !== undefined) this.syncedAt = options.syncedAt;\n if (options.collectionMethod !== undefined)\n this.collectionMethod = options.collectionMethod;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt;\n if (options.viewedAt !== undefined) this.viewedAt = options.viewedAt;\n if (options.remindersSent !== undefined)\n this.remindersSent = options.remindersSent;\n if (options.lastReminderAt !== undefined)\n this.lastReminderAt = options.lastReminderAt;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.customerNotes !== undefined)\n this.customerNotes = options.customerNotes;\n if (options.terms !== undefined) this.terms = options.terms;\n }\n\n /**\n * Capture the persisted status the row was loaded with, so the save-time\n * transition guard can reject illegal status flips made via raw field\n * assignment (mass-assignment on a generated update route, a stale caller,\n * etc.). Only rows that already exist in the database carry a \"prior\"\n * status — freshly-constructed (not-yet-saved) invoices have no prior and\n * may start in any status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedInvoiceStatus.set(this, this.status);\n }\n return this;\n }\n\n // ============================================================================\n // Save-time financial-integrity guard (S5 audit #1390)\n // ============================================================================\n\n /**\n * Recompute and validate financial fields before every write so forged\n * totals, negative amounts, and illegal status flips can't be persisted via\n * raw mass-assignment on the generated CRUD routes.\n *\n * Behaviour:\n * - **Totals are authoritative from line items.** When the invoice is\n * persisted and has line items, `subtotal` / `taxAmount` / `totalAmount`\n * are recomputed from those items, overriding whatever the caller sent.\n * This blocks \"create real line items but claim a tiny total\" forgery.\n * - **Without line items**, the caller-supplied totals are accepted but the\n * `totalAmount === subtotal + taxAmount` arithmetic invariant is enforced.\n * - **amountPaid is derived/validated.** When persisted, it is recomputed\n * from PaymentAllocations rather than trusted from the caller. It may\n * never exceed `totalAmount`.\n * - **Whole minor units and non-negativity** are enforced on all four\n * amounts. The integer check runs FIRST (#2401), before the\n * `totalAmount === subtotal + taxAmount` invariant, so a caller who wrote\n * major units gets \"money is minor units\" rather than a confusing\n * arithmetic-mismatch error from float addition.\n * - **Status transitions** are validated against the prior persisted status.\n */\n override async save(): Promise<this> {\n const persisted = await this.isSaved();\n const prior = await this.resolvePriorStatus(persisted);\n\n this.assertAmountsAreMinorUnits();\n await this.recomputeAmountsForSave(persisted);\n this.assertNonNegativeAmounts();\n this.assertStatusTransition(prior);\n this.assertPaymentStatusConsistent();\n\n const result = (await super.save()) as this;\n // Once written, the current status becomes the new \"prior\" for the next\n // save in this instance's lifetime.\n loadedInvoiceStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status (S5 audit #1390 round 4). The\n * {@link loadedInvoiceStatus} WeakMap is only populated when {@link initialize}\n * hydrated the row, so a `create({ id: <existing>, _skipLoad: true })` upsert\n * yields an instance whose WeakMap entry is missing — trusting it would treat\n * the write as a brand-new row and skip the transition guard entirely. Read\n * the persisted row's status directly so a create-onto-existing is correctly\n * treated as an update. `undefined` means no row exists (genuinely new).\n */\n private async resolvePriorStatus(\n persisted: boolean,\n ): Promise<InvoiceStatus | undefined> {\n if (persisted && this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as InvoiceStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedInvoiceStatus.get(this);\n }\n\n /**\n * Recompute subtotal/tax/total from line items (when present) and derive\n * amountPaid from PaymentAllocations. Falls back to the arithmetic invariant\n * when the invoice has no line items. Tolerant of smrt-ledgers being absent\n * (the dynamic imports stay inside the package).\n */\n private async recomputeAmountsForSave(persisted: boolean): Promise<void> {\n if (persisted && this.id) {\n const { InvoiceLineItemCollection } = await import(\n '../collections/InvoiceLineItemCollection.js'\n );\n const lineItemCollection = await InvoiceLineItemCollection.create(\n this.options,\n );\n const lineItems = await lineItemCollection.findByInvoice(this.id);\n\n if (lineItems.length > 0) {\n const subtotal = lineItems.reduce(\n (sum: number, item: InvoiceLineItem) => sum + item.getSubtotal(),\n 0,\n );\n const taxAmount =\n lineItems.reduce(\n (sum: number, item: InvoiceLineItem) => sum + item.getTaxAmount(),\n 0,\n ) + this.providerTaxAmount;\n // Totals are AUTHORITATIVE from the line items (S5 audit #1390). Any\n // caller-supplied subtotal / tax / total is ignored and overwritten —\n // that is the security goal (a forged total can never be persisted)\n // AND it self-heals a stale stored total (e.g. one left behind after a\n // line item changed). We deliberately do NOT throw on a caller\n // mismatch: throwing blocked legitimate self-heal of stale totals, and\n // since the values are overwritten regardless, the throw added no\n // protection. Non-negativity is still enforced downstream by\n // assertNonNegativeAmounts().\n const computedTotal = subtotal + taxAmount;\n this.subtotal = subtotal;\n this.taxAmount = taxAmount;\n this.totalAmount = computedTotal;\n } else {\n this.assertTotalArithmetic();\n }\n\n // amountPaid is the sum of PaymentAllocations — never trust the caller.\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocationCollection = await PaymentAllocationCollection.create(\n this.options,\n );\n const allocated = await allocationCollection.getTotalAllocatedToInvoice(\n this.id,\n );\n this.amountPaid = allocated;\n } else {\n // Not-yet-persisted invoice: no line items / allocations exist yet, so\n // enforce the arithmetic invariant on the caller-supplied totals.\n this.assertTotalArithmetic();\n\n // A brand-new invoice cannot already be paid: there are no\n // PaymentAllocations behind it yet, so a caller-supplied amountPaid (or a\n // PAID/PARTIAL starting status) would be unbacked money. Force it to\n // start unpaid; payment progress is only ever driven by allocations via\n // updatePaymentStatus(). (Re-derive on the first persisted save once\n // allocations exist.)\n this.amountPaid = 0;\n if (\n this.status === InvoiceStatus.PAID ||\n this.status === InvoiceStatus.PARTIAL\n ) {\n throw new Error(\n `Invoice ${this.invoiceNumber || '<new>'}: a new invoice cannot start ` +\n `in status '${this.status}' — payment status is derived from ` +\n 'PaymentAllocations, not set at creation. Create the invoice unpaid ' +\n '(DRAFT / SENT) and record payments via allocations.',\n );\n }\n }\n }\n\n /**\n * Whether `amountPaid` covers `totalAmount`. This is the SINGLE source of\n * truth for the **PAID** decision — both {@link updatePaymentStatus} (which\n * decides PAID) and {@link assertPaymentStatusConsistent} (which validates\n * PAID on save) call it, so the PAID-deciding comparison and the\n * PAID-validating comparison can never drift apart (S5 audit #1390\n * follow-up).\n *\n * With integer minor units this is a plain `>=` (#2401). The float-era\n * hazard it used to guard against — a total summed from `0.1 × 3` line items\n * reading as \"not covered\" by `>=` but \"covered\" by an epsilon-tolerant\n * guard, leaving a genuinely-paid invoice unsaveable — cannot arise when both\n * sides are exact integers. That also removes the documented PAID/PARTIAL\n * asymmetry: {@link updatePaymentStatus} and {@link isPartiallyPaid} now\n * agree that any `amountPaid > 0` is a partial payment, because there is no\n * such thing as sub-cent dust.\n */\n private isFullyPaid(): boolean {\n return this.amountPaid >= this.totalAmount;\n }\n\n /**\n * Whether `amountPaid` is a non-trivial partial payment of `totalAmount`.\n * Derived from {@link isFullyPaid} so the two stay mutually exclusive.\n */\n private isPartiallyPaid(): boolean {\n return this.amountPaid > 0 && !this.isFullyPaid();\n }\n\n /**\n * After amounts are recomputed and amountPaid is re-derived from allocations,\n * assert the persisted `status` is consistent with the derived\n * amountPaid-vs-totalAmount relationship. This blocks the raw\n * `SENT → PAID with amountPaid=0` flip (and its inverse, claiming PARTIAL/SENT\n * while fully allocated) that the status-transition map alone permits because\n * SENT → PAID is a structurally legal edge.\n *\n * Only enforced for the payment-derived statuses (PAID / PARTIAL and the\n * unpaid open states SENT / VIEWED). Lifecycle statuses that aren't a\n * function of amountPaid — DRAFT, OVERDUE, CANCELLED, WRITTEN_OFF — are left\n * to their own transition rules.\n */\n private assertPaymentStatusConsistent(): void {\n const label = this.invoiceNumber || this.id || '<new>';\n const fullyPaid = this.isFullyPaid();\n const partiallyPaid = this.isPartiallyPaid();\n\n if (this.status === InvoiceStatus.PAID && !fullyPaid) {\n throw new Error(\n `Invoice ${label}: status is PAID but derived amountPaid ${this.amountPaid} ` +\n `does not cover totalAmount ${this.totalAmount}. Payment status is ` +\n 'derived from PaymentAllocations — use updatePaymentStatus().',\n );\n }\n if (this.status === InvoiceStatus.PARTIAL && !partiallyPaid) {\n throw new Error(\n `Invoice ${label}: status is PARTIAL but derived amountPaid ${this.amountPaid} ` +\n `is not a partial payment of totalAmount ${this.totalAmount}. Payment ` +\n 'status is derived from PaymentAllocations — use updatePaymentStatus().',\n );\n }\n // Claiming an unpaid open status (SENT / VIEWED) while the invoice is in\n // fact fully covered by allocations is also inconsistent.\n if (\n (this.status === InvoiceStatus.SENT ||\n this.status === InvoiceStatus.VIEWED) &&\n this.totalAmount > 0 &&\n fullyPaid\n ) {\n throw new Error(\n `Invoice ${label}: status is '${this.status}' but derived amountPaid ` +\n `${this.amountPaid} fully covers totalAmount ${this.totalAmount}. ` +\n 'Use updatePaymentStatus() so the status reflects the allocations.',\n );\n }\n }\n\n /**\n * Enforce `totalAmount === subtotal + taxAmount` exactly. Used when the\n * invoice has no line items to recompute from.\n *\n * Exact because every term is integer minor units (#2401). The float era\n * needed a `0.01` tolerance here and then had to SNAP `totalAmount` to the\n * recomputed value afterwards, because a total that passed the invoice's\n * loose tolerance could still fail smrt-ledgers' tighter `BALANCE_EPSILON`\n * and void the journal `recognizeRevenue` builds from it. Integer arithmetic\n * removes both halves of that workaround: what the caller supplied either is\n * the sum or is rejected, and any journal built from it balances exactly.\n */\n private assertTotalArithmetic(): void {\n const expected = this.subtotal + this.taxAmount;\n if (this.totalAmount !== expected) {\n throw new Error(\n `Invoice ${this.invoiceNumber || this.id || '<new>'}: totalAmount ${this.totalAmount} ` +\n `must equal subtotal + taxAmount (${expected}).`,\n );\n }\n }\n\n /**\n * Reject amounts that are not whole minor units, negative financial values,\n * and an amountPaid that exceeds the total (overpayment is modelled\n * elsewhere, not by letting amountPaid float above the invoice total).\n */\n private assertAmountsAreMinorUnits(): void {\n assertIntegerMinorUnits(\n 'Invoice',\n this.invoiceNumber || this.id || '<new>',\n [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['providerTaxAmount', this.providerTaxAmount],\n ['totalAmount', this.totalAmount],\n ['amountPaid', this.amountPaid],\n ],\n );\n }\n\n private assertNonNegativeAmounts(): void {\n const label = this.invoiceNumber || this.id || '<new>';\n const amounts = [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['providerTaxAmount', this.providerTaxAmount],\n ['totalAmount', this.totalAmount],\n ['amountPaid', this.amountPaid],\n ] as const;\n // Re-checked after the recompute: line-item sums and the allocation total\n // feed these, so integer-ness has to hold for the derived values too — it\n // is what lets every comparison below drop its epsilon.\n assertIntegerMinorUnits('Invoice', label, amounts);\n for (const [field, value] of amounts) {\n if (value < 0) {\n throw new Error(\n `Invoice ${label}: ${field} must be a non-negative number (got ${value}).`,\n );\n }\n }\n if (this.amountPaid > this.totalAmount) {\n throw new Error(\n `Invoice ${label}: amountPaid ${this.amountPaid} cannot exceed totalAmount ${this.totalAmount}.`,\n );\n }\n }\n\n /**\n * Reject an illegal status flip done via raw field assignment. Compares the\n * about-to-be-written status against the status the row was loaded with.\n * No-op transitions (status unchanged) and brand-new rows are always allowed.\n */\n private assertStatusTransition(prior: InvoiceStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = INVOICE_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Invoice ${this.invoiceNumber || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'. Use the guarded transition helpers ` +\n '(markSent / markViewed / updatePaymentStatus / cancel / writeOff).',\n );\n }\n }\n\n // ============================================================================\n // Status Helpers\n // ============================================================================\n\n /**\n * Check if invoice is a draft\n */\n isDraft(): boolean {\n return this.status === InvoiceStatus.DRAFT;\n }\n\n /**\n * Check if invoice has been sent\n */\n isSent(): boolean {\n return this.status === InvoiceStatus.SENT;\n }\n\n /**\n * Check if invoice is fully paid\n */\n isPaid(): boolean {\n return this.status === InvoiceStatus.PAID;\n }\n\n /**\n * Check if invoice is partially paid\n */\n isPartial(): boolean {\n return this.status === InvoiceStatus.PARTIAL;\n }\n\n /**\n * Check if invoice is overdue\n */\n isOverdue(): boolean {\n if (this.status === InvoiceStatus.OVERDUE) return true;\n if (this.isPaid() || this.status === InvoiceStatus.CANCELLED) return false;\n return new Date() > this.dueDate;\n }\n\n /**\n * Get remaining amount due\n */\n getAmountDue(): number {\n return Math.max(0, this.totalAmount - this.amountPaid);\n }\n\n // ============================================================================\n // Status Transitions\n // ============================================================================\n\n /**\n * Mark invoice as sent\n */\n markSent(): void {\n if (this.status !== InvoiceStatus.DRAFT) {\n throw new Error(\n `Cannot send invoice with status '${this.status}'. Only draft invoices can be sent.`,\n );\n }\n this.status = InvoiceStatus.SENT;\n this.sentAt = new Date();\n }\n\n /**\n * Record that the invoice was viewed by the customer.\n *\n * This method is idempotent - calling it multiple times will only\n * record the first view time. Status only transitions to VIEWED if\n * the current status is SENT (other statuses like PARTIAL or PAID\n * are preserved).\n */\n markViewed(): void {\n if (this.viewedAt) return; // Already viewed\n this.viewedAt = new Date();\n if (this.status === InvoiceStatus.SENT) {\n this.status = InvoiceStatus.VIEWED;\n }\n }\n\n /**\n * Update payment amount and status.\n *\n * Called when PaymentAllocations change. Handles both forward transitions\n * (SENT → PARTIAL → PAID) and reversals (PAID → PARTIAL → original status).\n *\n * Terminal statuses (CANCELLED, WRITTEN_OFF) are not modified.\n */\n updatePaymentStatus(amountPaid: number): void {\n this.amountPaid = amountPaid;\n\n // Do not change payment status for terminal states\n if (\n this.status === InvoiceStatus.CANCELLED ||\n this.status === InvoiceStatus.WRITTEN_OFF\n ) {\n return;\n }\n\n // Use the SAME epsilon-tolerant \"fully paid\" test the save-time guard\n // (assertPaymentStatusConsistent) applies, so a float-summed total paid\n // exactly (e.g. 0.1×3 line items paid 0.3) is treated as PAID by both —\n // not PARTIAL here and then rejected on save (S5 audit #1390 follow-up).\n if (this.isFullyPaid()) {\n this.status = InvoiceStatus.PAID;\n this.paidDate = new Date();\n } else if (amountPaid > 0) {\n this.status = InvoiceStatus.PARTIAL;\n this.paidDate = null; // Clear paidDate if no longer fully paid\n } else {\n // No payment remaining: revert to the appropriate pre-payment status\n this.paidDate = null;\n if (this.sentAt) {\n this.status = this.viewedAt ? InvoiceStatus.VIEWED : InvoiceStatus.SENT;\n } else {\n this.status = InvoiceStatus.DRAFT;\n }\n }\n }\n\n /**\n * Cancel the invoice\n */\n cancel(): void {\n if (this.isPaid()) {\n throw new Error(\n `Cannot cancel invoice with status '${this.status}'. Paid invoices cannot be cancelled.`,\n );\n }\n this.status = InvoiceStatus.CANCELLED;\n }\n\n /**\n * Write off the invoice (bad debt)\n */\n writeOff(): void {\n this.status = InvoiceStatus.WRITTEN_OFF;\n }\n\n // ============================================================================\n // Ledger Integration\n // ============================================================================\n\n /**\n * Recognize revenue and create AR journal entry.\n *\n * Creates a balanced journal entry in smrt-ledgers:\n * - Debit: Accounts Receivable (assets increase)\n * - Credit: Revenue (revenue increases)\n * - Credit: Tax Payable (liability increases, if taxAmount > 0)\n *\n * **Note**: This method saves the invoice after setting arJournalId.\n *\n * @param options - Account IDs for the journal entry\n * @returns The created journal\n *\n * @example\n * ```typescript\n * const journal = await invoice.recognizeRevenue({\n * arAccountId: '1120',\n * revenueAccountId: '4100',\n * taxAccountId: '2130'\n * });\n * ```\n */\n async recognizeRevenue(options: RecognizeRevenueOptions): Promise<Journal> {\n if (this.arJournalId) {\n throw new Error(\n `Revenue already recognized for invoice ${this.invoiceNumber} (journal ID: ${this.arJournalId})`,\n );\n }\n\n // Recompute/validate the invoice's authoritative totals BEFORE building any\n // journal (S5 audit #1390 round 2). Round 1 built and posted the journal\n // off stale/forged in-memory totals, then called save() — which recomputes\n // from line items and could throw, leaving an orphaned posted journal with\n // the wrong amount already in the books. Recomputing first means the\n // journal is built from the same authoritative figures save() would accept,\n // and a forged total is rejected before any ledger mutation happens.\n const persisted = await this.isSaved();\n await this.recomputeAmountsForSave(persisted);\n this.assertNonNegativeAmounts();\n\n if (this.totalAmount <= 0) {\n throw new Error(\n `Cannot recognize revenue for invoice ${this.invoiceNumber} with non-positive total: ${this.totalAmount}`,\n );\n }\n\n if (this.taxAmount > 0 && !options.taxAccountId) {\n throw new Error(\n `Invoice ${this.invoiceNumber} has taxAmount ${this.taxAmount} but no taxAccountId provided`,\n );\n }\n\n // Dynamic import to avoid hard dependency on smrt-ledgers\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n\n const journalCollection = await JournalCollection.create(this.options);\n\n // Build entries array from the now-authoritative totals.\n const entries: JournalEntryData[] = [\n // Debit AR (assets increase)\n {\n accountId: options.arAccountId,\n debit: this.totalAmount,\n memo: `Invoice ${this.invoiceNumber}`,\n },\n // Credit Revenue\n {\n accountId: options.revenueAccountId,\n credit: this.subtotal,\n memo: `Invoice ${this.invoiceNumber}`,\n },\n ];\n\n // Credit Tax Payable if there's tax\n if (this.taxAmount > 0 && options.taxAccountId) {\n entries.push({\n accountId: options.taxAccountId,\n credit: this.taxAmount,\n memo: `Tax on Invoice ${this.invoiceNumber}`,\n });\n }\n\n // Create journal with entries\n const journal = await journalCollection.createWithEntries({\n description: `Revenue: Invoice ${this.invoiceNumber}`,\n sourceModule: 'smrt-commerce',\n sourceRef: this.id,\n entries,\n });\n\n // Post the journal, then link + persist the invoice as one logical unit. If\n // the invoice save fails after the journal is posted, void the journal so\n // the books don't retain a posted-but-unlinked entry (compensating action —\n // smrt-ledgers has no cross-row transaction primitive, and a posted journal\n // is immutable except via void()).\n await journal.post();\n // A posted journal always carries an id; fail fast rather than persist an\n // empty-string FK if that invariant breaks.\n if (!journal.id) {\n throw new Error(\n 'Invoice.recognizeRevenue: journal was posted but has no id',\n );\n }\n this.arJournalId = journal.id;\n try {\n await this.save();\n } catch (err) {\n try {\n await journal.void(\n `Reverted: invoice ${this.invoiceNumber} save failed during revenue recognition`,\n );\n } catch {\n // Best-effort compensation; surface the original failure regardless.\n }\n this.arJournalId = '';\n throw err;\n }\n\n return journal;\n }\n\n /**\n * Get the AR journal entry (if revenue was recognized)\n */\n async getArJournal(): Promise<Journal | null> {\n if (!this.arJournalId) return null;\n\n try {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const collection = await JournalCollection.create(this.options);\n return await collection.get({ id: this.arJournalId });\n } catch {\n // smrt-ledgers not available\n return null;\n }\n }\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * Convert to InvoiceInput for SDK accounting provider sync.\n *\n * Fetches line items and maps all fields to the format expected\n * by @happyvertical/accounting providers. The SDK takes currency **major**\n * units (`12.34` USD), so every amount is converted from this package's\n * integer minor units with the currency's ISO 4217 exponent (#3060).\n *\n * @returns InvoiceInput compatible with @happyvertical/accounting\n *\n * @example\n * ```typescript\n * const provider = await getAccountingProvider({ type: 'quickbooks', ... });\n * const input = await invoice.toAccountingInput();\n * const result = await provider.invoices.push(input);\n * invoice.externalId = result.externalId;\n * invoice.syncedAt = result.syncedAt;\n * await invoice.save();\n * ```\n */\n async toAccountingInput(): Promise<AccountingInvoiceInput> {\n // Dynamically import to avoid circular dependency\n const { InvoiceLineItemCollection } = await import(\n '../collections/InvoiceLineItemCollection.js'\n );\n\n const lineItemCollection = await InvoiceLineItemCollection.create(\n this.options,\n );\n // A synced invoice is always persisted; fail fast rather than query line\n // items under an empty id or push an invalid invoice to the provider.\n if (!this.id) {\n throw new Error(\n 'Invoice.toAccountingInput requires a persisted invoice (missing id)',\n );\n }\n const invoiceId = this.id;\n const lineItems = await lineItemCollection.findByInvoice(invoiceId);\n\n return {\n id: invoiceId,\n externalId: this.externalId || undefined,\n invoiceNumber: this.invoiceNumber,\n customerId: this.customerId,\n customerExternalId: this.customerExternalId || undefined,\n issueDate: this.issueDate,\n dueDate: this.dueDate,\n lineItems: lineItems.map((item: InvoiceLineItem) =>\n item.toAccountingLineItem(this.currency),\n ),\n subtotal: minorToMajorUnits(this.subtotal, this.currency),\n taxAmount: minorToMajorUnits(this.taxAmount, this.currency),\n totalAmount: minorToMajorUnits(this.totalAmount, this.currency),\n currency: this.currency,\n reference: this.reference || undefined,\n memo: this.customerNotes || undefined,\n };\n }\n}\n\nexport default Invoice;\n","/**\n * InvoiceCollection - Collection manager for Invoice objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus } from '../types/index.js';\n\n/**\n * Statuses that indicate an invoice has outstanding balance.\n * Used for calculating outstanding amounts and finding unpaid invoices.\n */\nexport const UNPAID_STATUSES = [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.OVERDUE,\n] as const;\n\n/**\n * Options for generating invoice numbers\n */\nexport interface InvoiceNumberOptions {\n /** Prefix for invoice numbers (default: 'INV') */\n prefix?: string;\n /** Format: 'prefix-year-seq' or 'prefix-seq' (default: 'prefix-year-seq') */\n format?: 'prefix-year-seq' | 'prefix-seq';\n}\n\nexport class InvoiceCollection extends SmrtCollection<Invoice> {\n static readonly _itemClass = Invoice;\n\n /**\n * Find invoices by customer\n *\n * @param customerId - Customer ID\n * @returns Array of invoices\n */\n async findByCustomer(customerId: string): Promise<Invoice[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoices by contract\n *\n * @param contractId - Contract ID\n * @returns Array of invoices\n */\n async findByContract(contractId: string): Promise<Invoice[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoices by status\n *\n * @param status - Invoice status\n * @returns Array of invoices\n */\n async findByStatus(status: InvoiceStatus): Promise<Invoice[]> {\n return await this.list({\n where: { status },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find all draft invoices\n *\n * @returns Array of draft invoices\n */\n async findDrafts(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.DRAFT);\n }\n\n /**\n * Find all sent invoices\n *\n * @returns Array of sent invoices\n */\n async findSent(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.SENT);\n }\n\n /**\n * Find all paid invoices\n *\n * @returns Array of paid invoices\n */\n async findPaid(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.PAID);\n }\n\n /**\n * Find all partial invoices\n *\n * @returns Array of partially paid invoices\n */\n async findPartial(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.PARTIAL);\n }\n\n /**\n * Find overdue invoices (past due date, not paid).\n *\n * **Note**: This method finds invoices by date, but does not automatically\n * update their status to OVERDUE. Use a background job or scheduled task\n * to periodically update invoice statuses based on due dates.\n *\n * @returns Array of overdue invoices\n */\n async findOverdue(): Promise<Invoice[]> {\n const now = new Date().toISOString();\n\n // Get invoices that are sent/viewed/partial and past due\n const candidates = await this.list({\n where: {\n 'dueDate <': now,\n },\n orderBy: 'dueDate ASC',\n });\n\n return candidates.filter((inv) =>\n (UNPAID_STATUSES as readonly InvoiceStatus[]).includes(inv.status),\n );\n }\n\n /**\n * Find invoices in date range (by issue date)\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of invoices\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Invoice[]> {\n return await this.list({\n where: {\n 'issueDate >=': startDate.toISOString(),\n 'issueDate <=': endDate.toISOString(),\n },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoice by external ID (from accounting provider)\n *\n * @param externalId - External ID\n * @returns Invoice or null\n */\n async findByExternalId(externalId: string): Promise<Invoice | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find invoices needing sync to external provider\n * (no externalId or stale syncedAt)\n *\n * @returns Array of invoices needing sync\n */\n async findNeedingSync(): Promise<Invoice[]> {\n // Get all non-draft invoices without externalId\n const allInvoices = await this.list({\n orderBy: 'issueDate DESC',\n });\n\n return allInvoices.filter(\n (inv) =>\n inv.status !== InvoiceStatus.DRAFT &&\n inv.status !== InvoiceStatus.CANCELLED &&\n !inv.externalId,\n );\n }\n\n /**\n * Find invoice by invoice number\n *\n * @param invoiceNumber - Invoice number\n * @returns Invoice or null\n */\n async findByNumber(invoiceNumber: string): Promise<Invoice | null> {\n const results = await this.list({\n where: { invoiceNumber },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Count invoices for a given year\n *\n * @param year - Year to count\n * @returns Number of invoices\n */\n async countForYear(year: number): Promise<number> {\n const startDate = new Date(year, 0, 1).toISOString();\n const endDate = new Date(year + 1, 0, 1).toISOString();\n\n const invoices = await this.list({\n where: {\n 'issueDate >=': startDate,\n 'issueDate <': endDate,\n },\n });\n\n return invoices.length;\n }\n\n /**\n * Generate the next invoice number.\n *\n * **Warning**: This method has a potential race condition if multiple\n * invoices are created concurrently. For high-concurrency environments,\n * consider using a database sequence or atomic counter instead.\n *\n * @param options - Optional configuration for number format\n * @returns Generated invoice number (e.g., 'INV-2025-0001')\n *\n * @example\n * ```typescript\n * // Default format: INV-2025-0001\n * const num = await invoices.generateInvoiceNumber();\n *\n * // Custom prefix: BILL-2025-0001\n * const num = await invoices.generateInvoiceNumber({ prefix: 'BILL' });\n *\n * // Simple sequential: INV-000001\n * const num = await invoices.generateInvoiceNumber({ format: 'prefix-seq' });\n * ```\n */\n async generateInvoiceNumber(\n options: InvoiceNumberOptions = {},\n ): Promise<string> {\n const prefix = options.prefix || 'INV';\n const format = options.format || 'prefix-year-seq';\n\n const year = new Date().getFullYear();\n\n if (format === 'prefix-year-seq') {\n const count = await this.countForYear(year);\n const seq = String(count + 1).padStart(4, '0');\n return `${prefix}-${year}-${seq}`;\n }\n\n // Simple sequential format\n const allInvoices = await this.list({});\n const seq = String(allInvoices.length + 1).padStart(6, '0');\n return `${prefix}-${seq}`;\n }\n\n /**\n * Get total outstanding (unpaid) amount for a customer\n *\n * @param customerId - Customer ID\n * @returns Total amount due\n */\n async getTotalOutstandingForCustomer(customerId: string): Promise<number> {\n const invoices = await this.list({\n where: { customerId },\n });\n\n return invoices\n .filter((inv) =>\n (UNPAID_STATUSES as readonly InvoiceStatus[]).includes(inv.status),\n )\n .reduce((sum, inv) => sum + inv.getAmountDue(), 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all invoices belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of invoices for the tenant\n */\n async findByTenant(tenantId: string): Promise<Invoice[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global invoices (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global invoices\n */\n async findGlobal(): Promise<Invoice[]> {\n return queryGlobal<Invoice>(this);\n }\n\n /**\n * Find invoices for a tenant including global invoices.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global invoices\n */\n async findWithGlobals(tenantId: string): Promise<Invoice[]> {\n return queryWithGlobals<Invoice>(this, tenantId, 'Invoice.findWithGlobals');\n }\n}\n","/**\n * Billing-period close records (#3060).\n *\n * These are system tables managed by `BillingRuntime`, like smrt-tenancy's\n * `_smrt_billing_relationships`: they span a seller and its payers, so they\n * are not tenant-scoped and have no generated API, CLI, or MCP surface.\n */\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n backgroundEligible,\n type JobExecutionContext,\n} from '@happyvertical/smrt-jobs';\nimport { canonicalTenantId, normalizeCurrency } from '../billing/units.js';\n\n/**\n * Who a runtime bills. `provider` closes the platform's `ClientCharge`s,\n * their `BillingAdjustment`s, and the platform's flat plans; `reseller`\n * closes the `RetailCharge`s a reseller's children owe it and the reseller's\n * own flat plans.\n */\nexport type BillingCloseKind = 'provider' | 'reseller';\n\n/** A payer's standing with its seller, driven by provider invoice events. */\nexport type BillingStanding = 'current' | 'past_due' | 'uncollectible';\n\n/**\n * `collecting` → `invoiced` → `pushed` → `sent` → `completed`. Every step is\n * resumable: a retry continues from the persisted status. A close whose\n * claimed sources net to a credit ends `carried_forward`: its `subtotal` is\n * billed as a credit line by the payer's next close.\n */\nexport type BillingPeriodCloseStatus =\n | 'collecting'\n | 'invoiced'\n | 'pushed'\n | 'sent'\n | 'completed'\n | 'carried_forward';\n\nexport type BillingLineSourceType =\n | 'client_charge'\n | 'billing_adjustment'\n | 'retail_charge'\n | 'subscription_period'\n | 'credit_carry_forward';\n\nconst STANDINGS: readonly BillingStanding[] = [\n 'current',\n 'past_due',\n 'uncollectible',\n];\n\n/**\n * One payer's account with one seller: the commerce `Customer` invoices are\n * issued to, the provider customer they are pushed to, and the payer's\n * billing terms. The seller's tax location for the payer is the customer's\n * `defaultBillingAddress`.\n */\n@smrt({\n tableName: '_smrt_billing_accounts',\n conflictColumns: ['seller_tenant_id', 'payer_tenant_id'],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingAccount extends SmrtObject {\n /** The tenant that issues invoices (the provider or a reseller). */\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n /** The billing owner that pays them. */\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n payerTenantId: string = '';\n @foreignKey('Customer')\n customerId: string = '';\n /** Display name sent to the provider customer. */\n name: string = '';\n email: string = '';\n /** Provider name (`stripe`) once a provider customer exists. */\n provider: string = '';\n providerCustomerId: string = '';\n /** Let the provider calculate tax from the customer's tax location. */\n automaticTax: boolean = true;\n /** Discount on flat plan lines, in basis points (10000 = 100%). */\n flatDiscountBasisPoints: number = 0;\n paymentTermsDays: number = 30;\n standing: BillingStanding = 'current';\n /**\n * Billing-cycle anchor (#3116). Null bills UTC calendar months; a date bills\n * monthly periods from it (its day and UTC time of day, clamped to shorter\n * months), with calendar months before it and a stub period from its month\n * start to the anchor. Moving it never bills time twice: flat-plan claims\n * record the time they cover.\n */\n @field({ type: 'datetime', nullable: true })\n billingAnchorAt: Date | null = null;\n /**\n * Prorate flat plans to the time they were active inside a period\n * (#3116): a subscription starting, leaving trial, or canceled mid-period\n * is billed `price × covered / cycle`, rounded half up to minor units.\n * Off, a flat plan active at any point of a period is billed in full (the\n * pre-#3116 behavior).\n */\n prorateFlatPlans: boolean = false;\n\n protected async validateBeforeSave(): Promise<void> {\n await super.validateBeforeSave();\n this.sellerTenantId = canonicalTenantId(\n this.sellerTenantId,\n 'sellerTenantId',\n );\n this.payerTenantId = canonicalTenantId(this.payerTenantId, 'payerTenantId');\n if (this.sellerTenantId === this.payerTenantId) {\n throw new Error('A tenant cannot bill itself.');\n }\n if (!this.customerId) {\n throw new Error('Billing accounts require a customerId.');\n }\n if (\n !Number.isSafeInteger(this.flatDiscountBasisPoints) ||\n this.flatDiscountBasisPoints < 0 ||\n this.flatDiscountBasisPoints > 10_000\n ) {\n throw new Error(\n 'flatDiscountBasisPoints must be an integer from 0 to 10000.',\n );\n }\n if (\n !Number.isSafeInteger(this.paymentTermsDays) ||\n this.paymentTermsDays < 0 ||\n this.paymentTermsDays > 365\n ) {\n throw new Error('paymentTermsDays must be an integer from 0 to 365.');\n }\n if (!STANDINGS.includes(this.standing)) {\n throw new Error('standing must be current, past_due, or uncollectible.');\n }\n if (\n this.billingAnchorAt !== null &&\n (!(this.billingAnchorAt instanceof Date) ||\n !Number.isFinite(this.billingAnchorAt.getTime()))\n ) {\n throw new Error('billingAnchorAt must be a valid date or null.');\n }\n }\n}\n\n/**\n * The idempotency anchor for one payer's invoice in one currency for one\n * period. Its id is derived from that identity, so a retried close always\n * resumes the same row, invoice, and provider invoice.\n */\n@smrt({\n tableName: '_smrt_billing_period_closes',\n conflictColumns: [\n 'seller_tenant_id',\n 'kind',\n 'payer_tenant_id',\n 'currency',\n 'period_start',\n 'period_end',\n ],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingPeriodClose extends SmrtObject {\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n kind: BillingCloseKind = 'provider';\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n payerTenantId: string = '';\n currency: string = 'USD';\n periodStart: Date = new Date(0);\n periodEnd: Date = new Date(0);\n @foreignKey('BillingAccount')\n billingAccountId: string = '';\n status: BillingPeriodCloseStatus = 'collecting';\n @foreignKey('Invoice')\n invoiceId: string = '';\n providerInvoiceId: string = '';\n /** Invoice subtotal in integer minor units once invoiced. */\n subtotal: number = 0;\n lineCount: number = 0;\n /** When the invoice was issued; fixed at first collection. */\n issuedAt: Date = new Date(0);\n @field({ type: 'text', nullable: true })\n leaseToken: string | null = null;\n @field({ type: 'datetime', nullable: true })\n leaseExpiresAt: Date | null = null;\n attempts: number = 0;\n lastError: string = '';\n\n protected async validateBeforeSave(): Promise<void> {\n await super.validateBeforeSave();\n this.sellerTenantId = canonicalTenantId(\n this.sellerTenantId,\n 'sellerTenantId',\n );\n this.payerTenantId = canonicalTenantId(this.payerTenantId, 'payerTenantId');\n this.currency = normalizeCurrency(this.currency);\n if (this.kind !== 'provider' && this.kind !== 'reseller') {\n throw new Error('Billing close kind must be provider or reseller.');\n }\n if (!(this.periodEnd.getTime() > this.periodStart.getTime())) {\n throw new Error('A billing period must end after it starts.');\n }\n }\n\n /**\n * smrt-jobs entry point: close a period for a registered billing runtime.\n * Args: `{ runtime, periodStart?, periodEnd? }` (ISO strings); without a\n * period each payer's last ended period on its own schedule is closed\n * (#3116), so a daily schedule is safe.\n */\n @backgroundEligible()\n async runPeriodClose(\n args: Record<string, unknown> = {},\n _context?: JobExecutionContext,\n ): Promise<unknown> {\n const { runBillingPeriodCloseJob } = await import('../billing/jobs.js');\n return runBillingPeriodCloseJob(args);\n }\n\n /**\n * smrt-jobs entry point: apply queued provider webhook events for a\n * registered billing runtime. Args: `{ runtime, limit? }`.\n */\n @backgroundEligible()\n async processBillingEvents(\n args: Record<string, unknown> = {},\n _context?: JobExecutionContext,\n ): Promise<unknown> {\n const { runBillingEventsJob } = await import('../billing/jobs.js');\n return runBillingEventsJob(args);\n }\n}\n\n/**\n * A claim that one billable source — a charge, an adjustment, or a window of\n * a flat plan — is billed on exactly one period close. The id is derived\n * from `(sourceType, sourceId)`, so a source can never be claimed twice. A\n * flat plan's claims record the window they cover and are numbered per\n * subscription (`<subscriptionId>:seq:<n>`, #3116): only one claimer can take\n * the next number, so no two cover the same time. Claims written before #3116\n * are `<subscriptionId>:<periodStart>` with number 0.\n */\n@smrt({\n tableName: '_smrt_billing_line_sources',\n conflictColumns: ['source_type', 'source_id'],\n // Flat-plan claims are read per subscription line, newest number first\n // (#3116).\n indexes: [\n {\n name: '_smrt_billing_line_sources_line_key_chain_sequence_idx',\n columns: ['lineKey', 'chainSequence'],\n },\n ],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingLineSource extends SmrtObject {\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n @foreignKey('BillingPeriodClose')\n periodCloseId: string = '';\n sourceType: BillingLineSourceType = 'client_charge';\n sourceId: string = '';\n /** Groups sources into one invoice line. */\n lineKey: string = '';\n lineDescription: string = '';\n /** Source amount in integer minor units (signed for adjustments). */\n amount: number = 0;\n /** Discount on this source in integer minor units (flat plans only). */\n discount: number = 0;\n /** Metered quantity, informational; fractional like the charge's. */\n quantity: number = 0.0;\n /**\n * A flat-plan claim's number within its subscription (1, 2, …; #3116). 0\n * for other sources and for flat-plan claims written before #3116.\n */\n chainSequence: number = 0;\n currency: string = 'USD';\n /**\n * The billed window. For a flat plan this is the time the claim covers\n * (#3116), which later claims for the same subscription never overlap.\n */\n periodStart: Date | null = null;\n periodEnd: Date | null = null;\n}\n\n/** Why a payment attempt was taken (#3138). */\nexport type BillingPaymentAttemptPurpose =\n | 'credit_purchase'\n | 'invoice_payment';\n\n/**\n * Something about an attempt that an operator must resolve (#3138). Empty\n * when nothing needs attention.\n */\nexport type BillingPaymentAttemptFlag =\n | ''\n | 'overpaid'\n | 'underpaid'\n | 'paid_late'\n | 'manually_marked'\n | 'invalidated_after_settlement'\n | 'invoice_already_paid'\n | 'amount_mismatch'\n /** The issuer's invoice was closed out of band but the payment never settled. */\n | 'out_of_band_without_settlement';\n\n/** One entry of an attempt's append-only status history. */\nexport interface BillingPaymentAttemptTransition {\n at: string;\n status: string;\n exception: string;\n source: 'created' | 'webhook' | 'poll';\n}\n\n/**\n * One payment attempt on a payment rail (#3138): a short-lived provider\n * checkout (for example a BTCPay invoice) paying a credit purchase or an\n * issued invoice. It records everything the provider reported — the locked\n * fiat price, the native amounts, rate and source, each payment's txid and\n * fee — plus a timeline of status transitions and what settlement produced.\n * Its id is derived from `(provider, checkoutId)`.\n */\n@smrt({\n tableName: '_smrt_billing_payment_attempts',\n conflictColumns: ['provider', 'checkout_id'],\n api: false,\n cli: false,\n mcp: false,\n})\nexport class BillingPaymentAttempt extends SmrtObject {\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n sellerTenantId: string = '';\n @crossPackageRef('@happyvertical/smrt-users:Tenant')\n @field({ required: true })\n payerTenantId: string = '';\n billingAccountId: string = '';\n purpose: BillingPaymentAttemptPurpose = 'credit_purchase';\n /** The invoice paid (`invoice_payment`). */\n invoiceId: string = '';\n /** The balance credited (`credit_purchase`). */\n spendingPolicyId: string = '';\n /** Provider (rail) name, for example `btcpay`. */\n provider: string = '';\n /** The rail order id (checkout idempotency key) the checkout was made for. */\n orderId: string = '';\n checkoutId: string = '';\n status: string = 'open';\n exception: string = 'none';\n flag: BillingPaymentAttemptFlag = '';\n /** Locked fiat price, integer minor units. */\n amount: number = 0;\n currency: string = 'USD';\n /** Fiat value received at the locked rate, minor units (rounded down). */\n amountPaid: number = 0;\n nativeCurrency: string = '';\n /** Native minor units (satoshis). */\n nativeAmountDue: number = 0;\n nativeAmountPaid: number = 0;\n rate: string = '';\n rateSource: string = '';\n checkoutUrl: string = '';\n @field({ type: 'datetime', nullable: true })\n expiresAt: Date | null = null;\n /** The provider's payments (JSON). */\n payments: Record<string, unknown>[] = [];\n /** Append-only status history (JSON). */\n timeline: BillingPaymentAttemptTransition[] = [];\n @field({ type: 'datetime', nullable: true })\n settledAt: Date | null = null;\n /** The commerce `Payment` settlement recorded. */\n paymentId: string = '';\n /** The credit grant settlement recorded. */\n creditGrantId: string = '';\n /** An operator's note when resolving a flag. */\n resolution: string = '';\n @field({ type: 'datetime', nullable: true })\n resolvedAt: Date | null = null;\n /**\n * Set before the issuer's invoice is closed out of band, so the issuer's\n * own `paid` event waits for this settlement instead of recording one.\n */\n @field({ type: 'datetime', nullable: true })\n outOfBandRequestedAt: Date | null = null;\n /**\n * The most severe standing an issuer event asked for while this payment\n * was confirming (dunning paused); re-applied if it ends unsettled.\n */\n pausedStanding: string = '';\n /** Fiat received above the price, booked as customer credit (minor units). */\n excessAmount: number = 0;\n /** Number of excess adjustments posted (keys each adjustment journal). */\n excessRevision: number = 0;\n /**\n * How settlement booked the price: `credit` (prepaid credit, or customer\n * credit for an invoice already paid elsewhere — refundable) or `applied`\n * (allocated to its invoice — not refundable here).\n */\n settlementOutcome: string = '';\n /** Refunds recorded by operators, total (minor units). */\n refundedAmount: number = 0;\n /** The part of `refundedAmount` taken from the price, not the excess. */\n refundedPrincipal: number = 0;\n /** Refund records (JSON): reference, amount, basis, journal, at. */\n refunds: Record<string, unknown>[] = [];\n\n protected async validateBeforeSave(): Promise<void> {\n await super.validateBeforeSave();\n this.sellerTenantId = canonicalTenantId(\n this.sellerTenantId,\n 'sellerTenantId',\n );\n this.payerTenantId = canonicalTenantId(this.payerTenantId, 'payerTenantId');\n this.currency = normalizeCurrency(this.currency);\n if (\n this.purpose !== 'credit_purchase' &&\n this.purpose !== 'invoice_payment'\n ) {\n throw new Error('Payment attempt purpose is invalid.');\n }\n if (!this.provider || !this.checkoutId) {\n throw new Error('Payment attempts require a provider and checkoutId.');\n }\n for (const value of [\n this.amount,\n this.amountPaid,\n this.nativeAmountDue,\n this.nativeAmountPaid,\n this.excessAmount,\n this.refundedAmount,\n this.refundedPrincipal,\n ]) {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new Error('Payment attempt amounts must be minor units.');\n }\n }\n }\n\n /** The timeline, tolerating a JSON string from the database. */\n get transitions(): BillingPaymentAttemptTransition[] {\n return jsonArray(this.timeline) as BillingPaymentAttemptTransition[];\n }\n\n /** The recorded payments, tolerating a JSON string from the database. */\n get paymentRecords(): Record<string, unknown>[] {\n return jsonArray(this.payments) as Record<string, unknown>[];\n }\n\n /** The recorded refunds, tolerating a JSON string from the database. */\n get refundRecords(): Record<string, unknown>[] {\n return jsonArray(this.refunds) as Record<string, unknown>[];\n }\n}\n\nfunction jsonArray(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (typeof value === 'string' && value) {\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n }\n return [];\n}\n\nexport class BillingAccountCollection extends SmrtCollection<BillingAccount> {\n static readonly _itemClass = BillingAccount;\n}\nexport class BillingPeriodCloseCollection extends SmrtCollection<BillingPeriodClose> {\n static readonly _itemClass = BillingPeriodClose;\n}\nexport class BillingLineSourceCollection extends SmrtCollection<BillingLineSource> {\n static readonly _itemClass = BillingLineSource;\n}\nexport class BillingPaymentAttemptCollection extends SmrtCollection<BillingPaymentAttempt> {\n static readonly _itemClass = BillingPaymentAttempt;\n}\n","/**\n * Payment-rail attempts (#3138): per-seller policy, settlement of a rail\n * checkout into credit or an invoice payment, and operator refund and\n * conversion journals.\n *\n * The rail's gateway decides when a checkout is settled (its confirmation\n * policy); this module never counts confirmations. It records everything the\n * rail reports on a `BillingPaymentAttempt` and settles at most once, keyed by\n * the checkout id.\n */\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport { SpendingPolicyEvaluator } from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { InvoiceCollection } from '../collections/InvoiceCollection.js';\nimport { PaymentAllocationCollection } from '../collections/PaymentAllocationCollection.js';\nimport { PaymentCollection } from '../collections/PaymentCollection.js';\nimport {\n type BillingPaymentAttempt,\n BillingPaymentAttemptCollection,\n type BillingPaymentAttemptFlag,\n type BillingPaymentAttemptPurpose,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus, PaymentMethod, PaymentStatus } from '../types/index.js';\nimport { CREDIT_PURCHASE_PURPOSE, creditMetadata } from './credits.js';\nimport {\n INVOICE_PAYMENT_PURPOSE,\n invoicePaymentMetadata,\n} from './invoice-payments.js';\nimport type { BillingPaymentAttemptState } from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport { deterministicId, normalizeCurrency, tenantKey } from './units.js';\n\n/** Per-seller rules for rail payments. Every default is conservative. */\nexport interface BillingPaymentPolicy {\n /**\n * Underpayment accepted as payment in full, in basis points of the price\n * (default 0). The shortfall is not written off in the ledger.\n */\n underpaymentToleranceBps: number;\n /** Overpayment not flagged for an operator, in basis points (default 0). */\n overpaymentToleranceBps: number;\n /**\n * A payment received after the rate lock expired: `review` (default)\n * flags it and settles nothing; `accept` settles when it covers the price.\n */\n latePaymentPolicy: 'review' | 'accept';\n /** Settle checkouts an operator marked paid at the rail (default false). */\n acceptManuallyMarked: boolean;\n /** Hold received crypto (default) or hand it to `conversionHandler`. */\n cryptoTreatment: 'hold' | 'convert';\n /**\n * Called after a settlement when `cryptoTreatment` is `convert`. It runs\n * inside the event transaction and may run again on retry: enqueue work\n * (for example a job) rather than calling an exchange directly.\n */\n conversionHandler?: (settlement: CryptoSettlement) => void | Promise<void>;\n /** Refund basis recorded when a refund does not name one. */\n refundBasis: 'original_native' | 'fiat_at_refund';\n}\n\nexport interface CryptoSettlement {\n sellerTenantId: string;\n attemptId: string;\n paymentId: string;\n provider: string;\n amount: number;\n currency: string;\n nativeCurrency: string;\n nativeAmount: number;\n db: DatabaseInterface;\n}\n\nexport function normalizePaymentPolicy(\n input: Partial<BillingPaymentPolicy> = {},\n): BillingPaymentPolicy {\n const policy: BillingPaymentPolicy = {\n underpaymentToleranceBps: input.underpaymentToleranceBps ?? 0,\n overpaymentToleranceBps: input.overpaymentToleranceBps ?? 0,\n latePaymentPolicy: input.latePaymentPolicy ?? 'review',\n acceptManuallyMarked: input.acceptManuallyMarked ?? false,\n cryptoTreatment: input.cryptoTreatment ?? 'hold',\n conversionHandler: input.conversionHandler,\n refundBasis: input.refundBasis ?? 'original_native',\n };\n for (const key of [\n 'underpaymentToleranceBps',\n 'overpaymentToleranceBps',\n ] as const) {\n const value = policy[key];\n if (!Number.isSafeInteger(value) || value < 0 || value > 10_000) {\n throw new Error(`${key} must be an integer from 0 to 10000.`);\n }\n }\n if (!['review', 'accept'].includes(policy.latePaymentPolicy)) {\n throw new Error('latePaymentPolicy must be review or accept.');\n }\n if (!['hold', 'convert'].includes(policy.cryptoTreatment)) {\n throw new Error('cryptoTreatment must be hold or convert.');\n }\n if (policy.cryptoTreatment === 'convert' && !policy.conversionHandler) {\n throw new Error('cryptoTreatment convert requires a conversionHandler.');\n }\n if (!['original_native', 'fiat_at_refund'].includes(policy.refundBasis)) {\n throw new Error('refundBasis must be original_native or fiat_at_refund.');\n }\n return policy;\n}\n\n// ---------------------------------------------------------------------------\n// Deciding\n// ---------------------------------------------------------------------------\n\nexport type AttemptDecision =\n | { action: 'settle'; flag: BillingPaymentAttemptFlag }\n | { action: 'none'; flag: BillingPaymentAttemptFlag };\n\n/**\n * What a rail checkout's state means for billing. Pure: the same state and\n * policy always decide the same way, so `observe()` and `project()` agree.\n */\nexport function decideAttempt(\n state: BillingPaymentAttemptState,\n policy: BillingPaymentPolicy,\n expected: { amount: number; currency: string },\n): AttemptDecision {\n if (\n normalizeCurrency(state.currency) !== expected.currency ||\n state.amount !== expected.amount\n ) {\n return { action: 'none', flag: 'amount_mismatch' };\n }\n const shortfall = Math.floor(\n (expected.amount * policy.underpaymentToleranceBps) / 10_000,\n );\n const excess = Math.floor(\n (expected.amount * policy.overpaymentToleranceBps) / 10_000,\n );\n const covered = state.amountPaid >= expected.amount - shortfall;\n const over: BillingPaymentAttemptFlag =\n state.amountPaid > expected.amount + excess ? 'overpaid' : '';\n // Every counted payment must itself be settled by the rail.\n const allSettled = state.payments.every(\n (payment) => payment.status !== 'confirming',\n );\n\n if (state.exception === 'manually_marked') {\n return state.status === 'settled' && policy.acceptManuallyMarked\n ? { action: 'settle', flag: '' }\n : { action: 'none', flag: 'manually_marked' };\n }\n if (state.exception === 'paid_late') {\n return policy.latePaymentPolicy === 'accept' &&\n covered &&\n allSettled &&\n (state.status === 'settled' || state.status === 'expired')\n ? { action: 'settle', flag: over }\n : { action: 'none', flag: 'paid_late' };\n }\n switch (state.status) {\n case 'settled':\n return covered\n ? { action: 'settle', flag: over }\n : { action: 'none', flag: 'underpaid' };\n case 'expired':\n if (state.exception === 'underpaid' || state.amountPaid > 0) {\n // Within tolerance and settled at the rail: treat as paid in full.\n return covered && allSettled && state.amountPaid > 0\n ? { action: 'settle', flag: over }\n : { action: 'none', flag: 'underpaid' };\n }\n return { action: 'none', flag: '' };\n default:\n return { action: 'none', flag: '' };\n }\n}\n\n/** The purpose and settlement target an attempt's verified metadata names. */\nexport interface AttemptTarget {\n purpose: BillingPaymentAttemptPurpose;\n payerTenantId: string;\n billingAccountId: string;\n amount: number;\n currency: string;\n invoiceId: string;\n spendingPolicyId: string;\n grantedByTenantId: string;\n /**\n * The issuer reported the invoice paid by its own collection (for example\n * a card charge whose event has not been applied yet): the rail's money is\n * kept as customer credit, never allocated to the invoice.\n */\n issuerCollected?: boolean;\n}\n\n/** Read the target from verified metadata; null when not this seller's. */\nexport function attemptTarget(\n runtime: BillingRuntime,\n metadata: Record<string, string>,\n): AttemptTarget | null {\n if (metadata.smrt_purpose === CREDIT_PURCHASE_PURPOSE) {\n const credit = creditMetadata(metadata);\n if (!credit || credit.sellerTenantId !== runtime.sellerTenantId) {\n return null;\n }\n return {\n purpose: 'credit_purchase',\n payerTenantId: credit.payerTenantId,\n billingAccountId: credit.billingAccountId,\n amount: credit.amount,\n currency: credit.currency,\n invoiceId: '',\n spendingPolicyId: credit.spendingPolicyId,\n grantedByTenantId: credit.grantedByTenantId,\n };\n }\n if (metadata.smrt_purpose === INVOICE_PAYMENT_PURPOSE) {\n const invoice = invoicePaymentMetadata(metadata);\n if (!invoice || invoice.sellerTenantId !== runtime.sellerTenantId) {\n return null;\n }\n return {\n purpose: 'invoice_payment',\n payerTenantId: invoice.payerTenantId,\n billingAccountId: invoice.billingAccountId,\n amount: invoice.amount,\n currency: invoice.currency,\n invoiceId: invoice.invoiceId,\n spendingPolicyId: '',\n grantedByTenantId: '',\n };\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Applying (inside the event transaction)\n// ---------------------------------------------------------------------------\n\nexport interface AttemptOutcome {\n attempt: BillingPaymentAttempt;\n /** The attempt left `confirming` without settling. */\n endedUnsettled: boolean;\n /** This application settled it. */\n settled: boolean;\n}\n\n/**\n * Record the rail's current state on the attempt and settle it once when the\n * decision says so. `invoiceHook` applies standing for invoice payments.\n */\nexport async function applyPaymentAttempt(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n providerName: string,\n state: BillingPaymentAttemptState,\n target: AttemptTarget,\n source: 'webhook' | 'poll',\n): Promise<AttemptOutcome> {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n const id = await runtime.paymentAttemptId(providerName, state.checkoutId);\n if (target.purpose === 'invoice_payment') {\n // Invoice before attempt, as the issuer's events lock them: one order\n // for every transaction touching both.\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const invoices = await InvoiceCollection.create({ db });\n await db.query(\n `UPDATE ${invoices.tableName} SET updated_at = updated_at WHERE id = ?`,\n target.invoiceId,\n );\n });\n }\n // Lock before reading, so a concurrent writer (a refund, a paused\n // standing) is never overwritten by this full-row save.\n await lockAttempt(db, attempts.tableName, id);\n const attempt =\n (await attempts.get(id)) ??\n (await attempts.create({\n id,\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId: target.payerTenantId,\n billingAccountId: target.billingAccountId,\n purpose: target.purpose,\n invoiceId: target.invoiceId,\n spendingPolicyId: target.spendingPolicyId,\n provider: providerName,\n orderId: state.orderId ?? '',\n checkoutId: state.checkoutId,\n amount: target.amount,\n currency: target.currency,\n status: 'open',\n exception: 'none',\n timeline: [],\n _insertOnly: true,\n }));\n const wasConfirming = attempt.status === 'confirming';\n if (!attempt.orderId && state.orderId) attempt.orderId = state.orderId;\n const changed =\n attempt.status !== state.status || attempt.exception !== state.exception;\n const timeline = attempt.transitions;\n if (changed || timeline.length === 0) {\n timeline.push({\n at: new Date().toISOString(),\n status: state.status,\n exception: state.exception,\n source,\n });\n }\n attempt.timeline = timeline;\n attempt.status = state.status;\n attempt.exception = state.exception;\n attempt.amountPaid = state.amountPaid;\n attempt.nativeCurrency = state.nativeCurrency ?? '';\n attempt.nativeAmountDue = state.nativeAmountDue ?? 0;\n attempt.nativeAmountPaid = state.nativeAmountPaid ?? 0;\n attempt.rate = state.rate ?? '';\n attempt.rateSource = state.rateSource ?? '';\n if (state.checkoutUrl) attempt.checkoutUrl = state.checkoutUrl;\n attempt.expiresAt = state.expiresAt ?? attempt.expiresAt;\n attempt.payments = state.payments.map((payment) => ({ ...payment }));\n\n const decision = decideAttempt(state, runtime.paymentPolicy, {\n amount: target.amount,\n currency: target.currency,\n });\n let settled = false;\n if (attempt.settledAt) {\n // Settled before; a later invalidation needs an operator, never an\n // automatic reversal.\n if (state.status === 'invalid')\n attempt.flag = 'invalidated_after_settlement';\n // Excess follows what the rail reports in both directions: money after\n // settlement is booked; a reversed payment (reorg, RBF) is unbooked.\n const excess = Math.max(0, state.amountPaid - target.amount);\n const delta = excess - attempt.excessAmount;\n if (delta !== 0 && attempt.paymentId) {\n const holdings =\n runtime.ledger.cryptoHoldingsAccountId || runtime.ledger.cashAccountId;\n const credit = runtime.ledger.prepaidCreditAccountId;\n await postOnce(\n runtime,\n db,\n `overpayment:${attempt.paymentId}:r${attempt.excessRevision + 1}`,\n {\n description: `Excess on ${providerName} checkout ${state.checkoutId}: ${attempt.excessAmount} -> ${excess}`,\n entries:\n delta > 0\n ? [\n { accountId: holdings, debit: delta },\n { accountId: credit, credit: delta },\n ]\n : [\n { accountId: credit, debit: -delta },\n { accountId: holdings, credit: -delta },\n ],\n },\n );\n attempt.excessAmount = excess;\n attempt.excessRevision += 1;\n if (delta < 0) attempt.flag = 'invalidated_after_settlement';\n else if (!attempt.flag) attempt.flag = 'overpaid';\n }\n } else if (decision.action === 'settle') {\n const result = await settleAttempt(runtime, db, attempt, state, target);\n attempt.paymentId = result.paymentId;\n attempt.creditGrantId = result.creditGrantId;\n attempt.excessAmount = result.excess;\n attempt.settlementOutcome =\n target.purpose === 'credit_purchase' ||\n result.flag === 'invoice_already_paid'\n ? 'credit'\n : 'applied';\n attempt.settledAt = new Date();\n attempt.flag = result.flag || decision.flag;\n settled = true;\n } else if (decision.flag) {\n attempt.flag = decision.flag;\n }\n\n // Ended without settling: it stopped confirming and this did not settle\n // it (expired, invalid, or reported settled but refused by the policy).\n let endedUnsettled =\n wasConfirming && !attempt.settledAt && state.status !== 'confirming';\n if (\n attempt.outOfBandRequestedAt &&\n !attempt.settledAt &&\n (state.status === 'expired' || state.status === 'invalid')\n ) {\n // The issuer was told the invoice was paid, but the money never settled:\n // an operator must reopen it at the issuer (then resolve this flag).\n // Standing is re-derived from the invoice either way.\n attempt.flag = 'out_of_band_without_settlement';\n endedUnsettled = true;\n }\n await attempt.save();\n if (\n settled &&\n runtime.paymentPolicy.cryptoTreatment === 'convert' &&\n runtime.paymentPolicy.conversionHandler\n ) {\n await runtime.paymentPolicy.conversionHandler({\n sellerTenantId: runtime.sellerTenantId,\n attemptId: String(attempt.id),\n paymentId: attempt.paymentId,\n provider: providerName,\n amount: target.amount,\n currency: target.currency,\n nativeCurrency: attempt.nativeCurrency,\n nativeAmount: attempt.nativeAmountPaid,\n db,\n });\n }\n return { attempt, endedUnsettled, settled };\n}\n\nasync function settleAttempt(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n attempt: BillingPaymentAttempt,\n state: BillingPaymentAttemptState,\n target: AttemptTarget,\n): Promise<{\n paymentId: string;\n creditGrantId: string;\n excess: number;\n flag: BillingPaymentAttemptFlag;\n}> {\n const ledger = runtime.ledger;\n const holdings = ledger.cryptoHoldingsAccountId || ledger.cashAccountId;\n const txids = [\n ...new Set(\n state.payments\n .filter((payment) => payment.status === 'settled')\n .map((payment) => payment.transactionId)\n .filter((txid): txid is string => Boolean(txid)),\n ),\n ];\n const payments = await PaymentCollection.create({ db });\n const paymentId = await deterministicId([\n 'billing-attempt-payment',\n attempt.provider,\n state.checkoutId,\n ]);\n\n let invoice: Invoice | null = null;\n let flag: BillingPaymentAttemptFlag = '';\n let receivables = ledger.prepaidCreditAccountId;\n let customerId = '';\n if (target.purpose === 'invoice_payment') {\n invoice = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () => {\n const invoices = await InvoiceCollection.create({ db });\n // Serialize settlements of one invoice: two rail payments projected\n // at once must not both read it unpaid and both allocate.\n await db.query(\n `UPDATE ${invoices.tableName} SET updated_at = updated_at WHERE id = ?`,\n target.invoiceId,\n );\n return invoices.get(target.invoiceId);\n },\n );\n if (!invoice?.id) {\n throw new Error(`Invoice ${target.invoiceId} for an attempt is missing.`);\n }\n customerId = invoice.customerId;\n // A written-off invoice keeps its status when paid (#3188), so \"paid\n // elsewhere\" is read from its allocations.\n const settledElsewhere =\n invoice.status === InvoiceStatus.WRITTEN_OFF &&\n (await withTenant({ tenantId: runtime.sellerTenantId }, async () =>\n (\n await PaymentAllocationCollection.create({ db })\n ).getTotalAllocatedToInvoice(String(invoice?.id)),\n )) >= invoice.totalAmount;\n if (\n invoice.status === InvoiceStatus.PAID ||\n target.issuerCollected ||\n settledElsewhere\n ) {\n // Paid elsewhere meanwhile: keep the money as customer credit for an\n // operator to refund or apply.\n flag = 'invoice_already_paid';\n } else {\n receivables = ledger.arAccountId;\n }\n } else {\n const account = await runtime.accounts.get(target.billingAccountId);\n if (\n !account ||\n tenantKey(account.sellerTenantId) !== runtime.sellerTenantId ||\n tenantKey(account.payerTenantId) !== target.payerTenantId\n ) {\n throw new Error(`Checkout ${state.checkoutId} has no matching account.`);\n }\n customerId = account.customerId;\n }\n\n const payment = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () => {\n const existing = await payments.get(paymentId);\n const row =\n existing ??\n (await payments.create({\n id: paymentId,\n tenantId: runtime.sellerTenantId,\n customerId,\n amount: target.amount,\n currency: target.currency,\n method: PaymentMethod.CRYPTO,\n reference:\n target.purpose === 'invoice_payment'\n ? (invoice?.invoiceNumber ?? '')\n : `credit:${target.spendingPolicyId}`,\n externalId: state.checkoutId,\n externalProvider: attempt.provider,\n backendId: (state.nativeCurrency ?? '').toLowerCase(),\n backendTxRef: txids.join(','),\n nativeAmount: state.nativeAmountPaid ?? 0,\n nativeCurrency: state.nativeCurrency ?? '',\n notes: [\n `rate ${state.rate ?? 'unknown'} ${target.currency}/${state.nativeCurrency ?? '?'}`,\n state.rateSource ? `source ${state.rateSource}` : '',\n `received ${state.amountPaid} ${target.currency} minor units`,\n ]\n .filter(Boolean)\n .join('; '),\n _insertOnly: true,\n }));\n if (row.status !== PaymentStatus.COMPLETED) {\n await row.recordPayment({\n ledgerId: '',\n cashAccountId: holdings,\n receivablesAccountId: receivables,\n });\n }\n return row;\n },\n );\n\n // Money above the price is the payer's: book it as customer credit (in the\n // prepaid-credit liability) for an operator to refund or apply.\n const excess = Math.max(0, state.amountPaid - target.amount);\n if (excess > 0) {\n await postOnce(runtime, db, `overpayment:${paymentId}`, {\n description: `Overpayment on ${attempt.provider} checkout ${state.checkoutId}`,\n entries: [\n { accountId: holdings, debit: excess },\n { accountId: ledger.prepaidCreditAccountId, credit: excess },\n ],\n });\n }\n\n let creditGrantId = '';\n if (target.purpose === 'credit_purchase') {\n const grant = await withSystemContext(async () => {\n const evaluator = await SpendingPolicyEvaluator.create({ db });\n return evaluator.grantCredit({\n spendingPolicyId: target.spendingPolicyId,\n amount: target.amount,\n reason: 'Prepaid credit purchase',\n source: `${attempt.provider}-checkout`,\n sourceId: state.checkoutId,\n ...(target.grantedByTenantId\n ? { grantedByTenantId: target.grantedByTenantId }\n : {}),\n });\n });\n creditGrantId = String(grant.id ?? '');\n } else if (invoice && flag !== 'invoice_already_paid') {\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const allocations = await PaymentAllocationCollection.create({ db });\n const already = await allocations.getTotalAllocatedToInvoice(\n String(invoice.id),\n );\n const room = Math.max(0, invoice.totalAmount - already);\n const allocationId = await deterministicId([\n 'billing-attempt-allocation',\n paymentId,\n String(invoice.id),\n ]);\n if (room > 0 && !(await allocations.get(allocationId))) {\n await allocations.create({\n id: allocationId,\n tenantId: runtime.sellerTenantId,\n paymentId,\n invoiceId: String(invoice.id),\n amount: Math.min(room, target.amount),\n allocatedBy: `${attempt.provider}-checkout`,\n _insertOnly: true,\n });\n }\n invoice.updatePaymentStatus(\n await allocations.getTotalAllocatedToInvoice(String(invoice.id)),\n );\n await invoice.save();\n });\n }\n return { paymentId: String(payment.id), creditGrantId, excess, flag };\n}\n\n// ---------------------------------------------------------------------------\n// Operator journals\n// ---------------------------------------------------------------------------\n\nexport interface ManualRefundInput {\n /** The settled rail `Payment` refunded. */\n paymentId: string;\n /** Fiat value of the refund, minor units of the payment currency. */\n fiatAmount: number;\n /** Native amount sent back (satoshis), when known. */\n nativeAmount?: number;\n /** Default: the seller's `paymentPolicy.refundBasis`. */\n basis?: 'original_native' | 'fiat_at_refund';\n /** The operator's refund reference (for example the refund txid). */\n reference: string;\n reason: string;\n}\n\nexport interface ManualRefundResult {\n journalId: string;\n creditGrantId: string;\n}\n\n/**\n * Record a refund an operator made outside billing. Refunds come from what\n * the payer is owed: first any overpayment, then — for a credit purchase, or\n * an invoice payment kept as credit because the invoice was paid elsewhere —\n * the price. An invoice payment applied to its invoice is not refundable\n * here (reverse the invoice at the issuer first). Refunds are capped\n * cumulatively per payment and are idempotent by `reference`.\n */\nexport async function recordManualRefund(\n runtime: BillingRuntime,\n input: ManualRefundInput,\n): Promise<ManualRefundResult> {\n if (!Number.isSafeInteger(input.fiatAmount) || input.fiatAmount <= 0) {\n throw new Error('fiatAmount must be positive integer minor units.');\n }\n const reference = input.reference?.trim();\n if (!reference) throw new Error('A reference is required.');\n const basis = input.basis ?? runtime.paymentPolicy.refundBasis;\n const [found] = await runtime.attempts.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n paymentId: input.paymentId,\n },\n limit: 1,\n });\n if (!found?.id) {\n throw new Error(\n `Payment ${input.paymentId} is not a settled payment-rail payment of this seller.`,\n );\n }\n const inTransaction = <T>(work: (db: DatabaseInterface) => Promise<T>) =>\n runtime.db.transaction ? runtime.db.transaction(work) : work(runtime.db);\n return withSystemContext(() =>\n inTransaction(async (db) => {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n // Serialize refunds of one payment: the cumulative cap reads, then writes.\n await lockAttempt(db, attempts.tableName, String(found.id));\n const attempt = await attempts.get(String(found.id));\n if (!attempt?.id || !attempt.settledAt) {\n throw new Error(\n `Payment ${input.paymentId} is not a settled payment-rail payment of this seller.`,\n );\n }\n const done = attempt.refundRecords.find(\n (record) => record.reference === reference,\n );\n if (done) {\n return {\n journalId: String(done.journalId ?? ''),\n creditGrantId: String(done.creditGrantId ?? ''),\n };\n }\n // The settlement's own classification, not the mutable flag.\n const principalRefundable =\n attempt.settlementOutcome === 'credit' ? attempt.amount : 0;\n const refundedExcess = attempt.refundedAmount - attempt.refundedPrincipal;\n const excessLeft = attempt.excessAmount - refundedExcess;\n const principalLeft = principalRefundable - attempt.refundedPrincipal;\n if (input.fiatAmount > excessLeft + principalLeft) {\n throw new Error(\n `A refund of ${input.fiatAmount} would exceed the ${excessLeft + principalLeft} ${attempt.currency} still refundable on this payment.`,\n );\n }\n const fromExcess = Math.min(input.fiatAmount, excessLeft);\n const fromPrincipal = input.fiatAmount - fromExcess;\n const ledger = runtime.ledger;\n const holdings = ledger.cryptoHoldingsAccountId || ledger.cashAccountId;\n const journalId = await postOnce(\n runtime,\n db,\n `refund:${attempt.id}:${reference}`,\n {\n description: `Refund ${reference} (${basis}${\n input.nativeAmount ? `, ${input.nativeAmount} native` : ''\n }): ${input.reason}`,\n entries: [\n // Excess and credit kept for an already-paid invoice both sit in\n // the prepaid-credit liability, as does purchased credit.\n {\n accountId: ledger.prepaidCreditAccountId,\n debit: input.fiatAmount,\n },\n { accountId: holdings, credit: input.fiatAmount },\n ],\n },\n );\n let creditGrantId = '';\n if (\n fromPrincipal > 0 &&\n attempt.purpose === 'credit_purchase' &&\n attempt.spendingPolicyId\n ) {\n const grant = await (\n await SpendingPolicyEvaluator.create({ db })\n ).grantCredit({\n spendingPolicyId: attempt.spendingPolicyId,\n amount: -fromPrincipal,\n reason: `Refund: ${input.reason}`,\n source: `${attempt.provider}-refund`,\n sourceId: reference,\n });\n creditGrantId = String(grant.id ?? '');\n }\n const refunds = attempt.refundRecords;\n refunds.push({\n reference,\n amount: input.fiatAmount,\n principal: fromPrincipal,\n nativeAmount: input.nativeAmount ?? null,\n basis,\n reason: input.reason,\n journalId,\n creditGrantId,\n at: new Date().toISOString(),\n });\n attempt.refunds = refunds;\n attempt.refundedAmount += input.fiatAmount;\n attempt.refundedPrincipal += fromPrincipal;\n attempt.resolution = [\n attempt.resolution,\n `refunded ${input.fiatAmount} ${attempt.currency} (${basis}) ref ${reference}`,\n ]\n .filter(Boolean)\n .join('; ');\n attempt.resolvedAt = new Date();\n await attempt.save();\n return { journalId, creditGrantId };\n }),\n );\n}\n\nexport interface CryptoConversionInput {\n /** Operator reference (for example the exchange trade id). */\n reference: string;\n currency: string;\n nativeCurrency: string;\n /** Native amount converted (satoshis). */\n nativeAmount: number;\n /** Book value of the converted holdings, minor units of `currency`. */\n carryingValue: number;\n /** Cash received, minor units. */\n proceeds: number;\n /** Fees paid, minor units (default 0). */\n fees?: number;\n}\n\nexport async function recordCryptoConversion(\n runtime: BillingRuntime,\n input: CryptoConversionInput,\n): Promise<{ journalId: string }> {\n const ledger = runtime.ledger;\n if (!ledger.cryptoHoldingsAccountId || !ledger.fxGainLossAccountId) {\n throw new Error(\n 'Conversions need cryptoHoldingsAccountId and fxGainLossAccountId ledger accounts.',\n );\n }\n const fees = input.fees ?? 0;\n for (const [key, value] of Object.entries({\n nativeAmount: input.nativeAmount,\n carryingValue: input.carryingValue,\n proceeds: input.proceeds,\n fees,\n })) {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new Error(`${key} must be non-negative integer minor units.`);\n }\n }\n if (fees > 0 && !ledger.feesAccountId) {\n throw new Error('Conversion fees need a feesAccountId ledger account.');\n }\n if (!input.reference?.trim()) throw new Error('A reference is required.');\n const gain = input.proceeds + fees - input.carryingValue;\n const entries: JournalLine[] = [\n { accountId: ledger.cashAccountId, debit: input.proceeds },\n { accountId: ledger.cryptoHoldingsAccountId, credit: input.carryingValue },\n ];\n if (fees > 0 && ledger.feesAccountId) {\n entries.push({ accountId: ledger.feesAccountId, debit: fees });\n }\n if (gain > 0) {\n entries.push({ accountId: ledger.fxGainLossAccountId, credit: gain });\n } else if (gain < 0) {\n entries.push({ accountId: ledger.fxGainLossAccountId, debit: -gain });\n }\n const inTransaction = <T>(work: (db: DatabaseInterface) => Promise<T>) =>\n runtime.db.transaction ? runtime.db.transaction(work) : work(runtime.db);\n const journalId = await inTransaction((db) =>\n postOnce(runtime, db, `conversion:${input.reference.trim()}`, {\n description: `Convert ${input.nativeAmount} ${normalizeCurrency(\n input.nativeCurrency,\n )} minor units to ${normalizeCurrency(input.currency)} (${input.reference})`,\n entries: entries.filter(\n (entry) => (entry.debit ?? 0) > 0 || (entry.credit ?? 0) > 0,\n ),\n }),\n );\n return { journalId };\n}\n\nasync function lockAttempt(\n db: DatabaseInterface,\n tableName: string,\n id: string,\n): Promise<void> {\n await db.query(\n `UPDATE ${tableName} SET updated_at = updated_at WHERE id = ?`,\n id,\n );\n}\n\ninterface JournalLine {\n accountId: string;\n debit?: number;\n credit?: number;\n}\n\n/** Post a balanced journal once per `sourceRef` in the seller's books. */\nasync function postOnce(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n sourceRef: string,\n journal: { description: string; entries: JournalLine[] },\n): Promise<string> {\n return withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const journals = await JournalCollection.create({ db });\n const [posted] = await journals.list({\n where: { sourceModule: 'smrt-commerce', sourceRef, status: 'posted' },\n limit: 1,\n });\n if (posted?.id) return String(posted.id);\n // A deterministic id makes a concurrent second writer fail on insert\n // instead of posting a duplicate journal.\n const id = await deterministicId([\n 'billing-journal',\n runtime.sellerTenantId,\n sourceRef,\n ]);\n const existing = await journals.get(id);\n if (existing) {\n if (existing.status === 'posted') return id;\n throw new Error(\n `Journal ${sourceRef} was started but not posted; an operator must complete or void it.`,\n );\n }\n let created: Awaited<ReturnType<typeof journals.create>>;\n try {\n created = await journals.create({\n id,\n date: new Date(),\n description: journal.description,\n sourceModule: 'smrt-commerce',\n sourceRef,\n _insertOnly: true,\n });\n } catch (error) {\n if (await journals.get(id)) {\n throw new Error(\n `Journal ${sourceRef} is being recorded concurrently; retry to read its result.`,\n { cause: error },\n );\n }\n throw error;\n }\n await created.save();\n for (const entry of journal.entries) {\n await created.addEntry({ ...entry, memo: sourceRef });\n }\n await created.post();\n return String(created.id);\n });\n}\n","/**\n * Billing-period close (#3060): approved charges and flat plan fees per\n * billing owner → commerce invoice → provider invoice (with provider tax) →\n * ledger.\n *\n * Replay safety comes from identity, not from a transaction spanning the\n * provider call:\n *\n * - each payer/currency/period has one {@link BillingPeriodClose} whose id is\n * derived from that identity, and a lease so two workers never advance the\n * same close at once;\n * - each billable source is claimed by one {@link BillingLineSource} whose id\n * is derived from the source, so a charge is billed at most once however\n * many periods or workers race for it; a flat plan's claims record the time\n * they cover and are numbered per subscription (#3116), so no two claims\n * for one subscription ever cover the same time, whatever schedule each\n * payer is on;\n * - the invoice and its lines have ids derived from the close, and the\n * provider invoice uses the close id as its idempotency key;\n * - every step persists its outcome before the next, so a retry resumes where\n * the last attempt stopped.\n */\nimport type {\n ClientCharge,\n RetailCharge,\n SubscriptionPlan,\n TenantSubscription,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport type {\n BillingAccount,\n BillingLineSource,\n BillingLineSourceType,\n BillingPeriodClose,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus } from '../types/index.js';\nimport { defaultCardFor } from './cards.js';\nimport {\n billingPeriodContaining,\n lastEndedBillingPeriod,\n prorateMinorUnits,\n type ScheduledBillingPeriod,\n} from './cycles.js';\nimport type { BillingProviderInvoiceLine } from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport {\n deterministicId,\n isTenantId,\n normalizeCurrency,\n tenantKey,\n} from './units.js';\n\nexport interface BillingPeriod {\n /** Inclusive start. */\n periodStart: Date;\n /** Exclusive end; charges approved before it are billable. */\n periodEnd: Date;\n}\n\n/**\n * Without a period, each payer's most recent ended period on its own schedule\n * is closed (#3116): the previous calendar month for a payer without a\n * billing anchor, the last anchor-to-anchor period for one with. With a\n * period, that exact period is closed for every calendar-schedule payer (flat\n * plans only when it is a calendar month) and for each anchored payer whose\n * schedule contains exactly that period.\n */\nexport interface ClosePeriodInput {\n periodStart?: Date;\n periodEnd?: Date;\n /** Clock override for tests; defaults to now. */\n now?: Date;\n}\n\nexport type PeriodCloseOutcome =\n | 'completed'\n | 'empty'\n | 'busy'\n | 'carried_forward'\n | 'failed';\n\nexport interface PeriodCloseGroupResult {\n payerTenantId: string;\n currency: string;\n /** The payer's period this group closed (#3116). */\n periodStart: Date;\n periodEnd: Date;\n closeId?: string;\n invoiceId?: string;\n providerInvoiceId?: string;\n outcome: PeriodCloseOutcome;\n error?: string;\n}\n\n/**\n * `periodStart`/`periodEnd` are the explicit period, or the previous calendar\n * month for a close without one; each group carries the payer's own period.\n */\nexport interface PeriodCloseResult extends BillingPeriod {\n sellerTenantId: string;\n groups: PeriodCloseGroupResult[];\n}\n\n/** Thrown when one or more payers could not be closed; the rest were. */\nexport class BillingPeriodCloseError extends Error {\n constructor(readonly result: PeriodCloseResult) {\n const failed = result.groups.filter((group) => group.outcome === 'failed');\n super(\n `Billing period close failed for ${failed.length} payer group(s): ${failed\n .map(\n (group) => `${group.payerTenantId}/${group.currency}: ${group.error}`,\n )\n .join('; ')}`,\n );\n this.name = 'BillingPeriodCloseError';\n }\n}\n\ninterface Candidate {\n sourceType: BillingLineSourceType;\n sourceId: string;\n payerTenantId: string;\n currency: string;\n lineKey: string;\n lineDescription: string;\n amount: number;\n quantity: number;\n /** Flat-plan sources take the payer account's discount at claim time. */\n discountable: boolean;\n periodStart: Date | null;\n periodEnd: Date | null;\n /** A flat plan's billable window, claimed by `claimFlatWindow()`. */\n flat?: FlatWindow;\n}\n\ninterface FlatWindow {\n subscriptionId: string;\n /** The plan's full-cycle price in minor units. */\n planAmount: number;\n /** Plan name and tenant suffix for the line description. */\n label: string;\n windowStart: Date;\n windowEnd: Date;\n cycleMs: number;\n}\n\ninterface CandidateGroup {\n payerTenantId: string;\n currency: string;\n closeId: string;\n period: ScheduledBillingPeriod;\n candidates: Candidate[];\n}\n\n/** How one payer is closed in this run. */\ninterface PayerSchedule {\n /** The period to close, or null when the payer is not closed this run. */\n period: ScheduledBillingPeriod | null;\n /** Whether flat plans are billed for that period. */\n billsFlatPlans: boolean;\n prorate: boolean;\n}\n\ntype CloseMode =\n | { kind: 'due'; now: Date }\n | { kind: 'explicit'; period: BillingPeriod };\n\n/** The previous calendar month in UTC relative to `now`. */\nexport function previousCalendarMonth(now = new Date()): BillingPeriod {\n const periodEnd = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n );\n const periodStart = new Date(\n Date.UTC(periodEnd.getUTCFullYear(), periodEnd.getUTCMonth() - 1, 1),\n );\n return { periodStart, periodEnd };\n}\n\nfunction isCalendarMonth(period: BillingPeriod): boolean {\n const { periodStart: start, periodEnd: end } = period;\n return (\n start.getUTCDate() === 1 &&\n start.getUTCHours() === 0 &&\n start.getUTCMinutes() === 0 &&\n start.getUTCSeconds() === 0 &&\n start.getUTCMilliseconds() === 0 &&\n end.getTime() ===\n Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1)\n );\n}\n\nfunction resolveMode(input: ClosePeriodInput): CloseMode {\n const now = input.now ?? new Date();\n if (!input.periodStart && !input.periodEnd) return { kind: 'due', now };\n if (!input.periodStart || !input.periodEnd) {\n throw new Error('Pass both periodStart and periodEnd, or neither.');\n }\n const period = { periodStart: input.periodStart, periodEnd: input.periodEnd };\n if (\n !Number.isFinite(period.periodStart.getTime()) ||\n !(period.periodEnd.getTime() > period.periodStart.getTime())\n ) {\n throw new Error('A billing period must end after it starts.');\n }\n if (period.periodEnd.getTime() > now.getTime()) {\n throw new Error('A billing period cannot be closed before it ends.');\n }\n return { kind: 'explicit', period };\n}\n\nexport function periodCloseId(\n runtime: Pick<BillingRuntime, 'sellerTenantId' | 'kind'>,\n payerTenantId: string,\n currency: string,\n period: BillingPeriod,\n): Promise<string> {\n return deterministicId([\n 'billing-period-close',\n runtime.sellerTenantId,\n runtime.kind,\n tenantKey(payerTenantId),\n normalizeCurrency(currency),\n period.periodStart.toISOString(),\n period.periodEnd.toISOString(),\n ]);\n}\n\nfunction sourceId(type: BillingLineSourceType, id: string): Promise<string> {\n return deterministicId(['billing-line-source', type, id]);\n}\n\nfunction samePeriod(a: BillingPeriod, b: BillingPeriod): boolean {\n return (\n a.periodStart.getTime() === b.periodStart.getTime() &&\n a.periodEnd.getTime() === b.periodEnd.getTime()\n );\n}\n\n/** Each payer's schedule for one run, read once from its billing account. */\nclass PayerSchedules {\n private readonly cache = new Map<string, Promise<PayerSchedule>>();\n\n constructor(\n private readonly runtime: BillingRuntime,\n private readonly mode: CloseMode,\n ) {}\n\n get(payerTenantId: string): Promise<PayerSchedule> {\n const key = tenantKey(payerTenantId);\n let schedule = this.cache.get(key);\n if (!schedule) {\n schedule = this.resolve(key);\n this.cache.set(key, schedule);\n }\n return schedule;\n }\n\n private async resolve(payer: string): Promise<PayerSchedule> {\n // A payer without an account keeps the calendar schedule; its close then\n // fails with a clear \"no billing account\" error, as before #3116.\n const account = isTenantId(payer)\n ? await this.runtime.getAccount(payer)\n : null;\n const anchor = account?.billingAnchorAt ?? null;\n const prorate = Boolean(account?.prorateFlatPlans);\n if (this.mode.kind === 'due') {\n return {\n period: lastEndedBillingPeriod(anchor, this.mode.now),\n billsFlatPlans: true,\n prorate,\n };\n }\n const explicit = this.mode.period;\n if (!anchor) {\n return {\n period: {\n ...explicit,\n cycleStart: explicit.periodStart,\n cycleEnd: explicit.periodEnd,\n },\n billsFlatPlans: isCalendarMonth(explicit),\n prorate,\n };\n }\n // An anchored payer is closed only for a period of its own schedule.\n const scheduled = billingPeriodContaining(anchor, explicit.periodStart);\n const matches = samePeriod(scheduled, explicit);\n return {\n period: matches ? scheduled : null,\n billsFlatPlans: matches,\n prorate,\n };\n }\n}\n\n/**\n * Close billing periods for a runtime's seller: each payer's last ended\n * period, or one explicit period (see {@link ClosePeriodInput}). Idempotent:\n * re-running it resumes unfinished payers and bills nothing twice.\n */\nexport async function closeBillingPeriod(\n runtime: BillingRuntime,\n input: ClosePeriodInput = {},\n): Promise<PeriodCloseResult> {\n const mode = resolveMode(input);\n const now = input.now ?? new Date();\n const schedules = new PayerSchedules(runtime, mode);\n const groups = await withSystemContext(() =>\n collectGroups(runtime, mode, schedules),\n );\n // Closes still in progress, including ones whose every source was already\n // claimed (a collection query no longer returns them).\n const unfinished = await runtime.closes.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n kind: runtime.kind,\n 'status !=': 'completed',\n },\n });\n const results: PeriodCloseGroupResult[] = [];\n const seen = new Set<string>();\n for (const group of groups) {\n seen.add(group.closeId);\n results.push(await closeGroup(runtime, group, now));\n }\n // Resume unfinished closes: with an explicit period, those of that period\n // whose sources were all claimed on an earlier attempt; without one, every\n // unfinished close whose period has ended, whatever the payer's schedule is\n // now — a close stranded by a re-anchor would otherwise hold its claims\n // (time later closes treat as billed) without ever invoicing them.\n for (const close of unfinished) {\n if (\n !close.id ||\n seen.has(close.id) ||\n close.status === 'completed' ||\n close.status === 'carried_forward'\n ) {\n continue;\n }\n const resumable =\n mode.kind === 'explicit'\n ? samePeriod(close, mode.period)\n : close.periodEnd.getTime() <= now.getTime();\n const period: ScheduledBillingPeriod | null = resumable\n ? {\n periodStart: close.periodStart,\n periodEnd: close.periodEnd,\n cycleStart: close.periodStart,\n cycleEnd: close.periodEnd,\n }\n : null;\n if (!period) continue;\n results.push(\n await closeGroup(\n runtime,\n {\n payerTenantId: close.payerTenantId,\n currency: close.currency,\n closeId: close.id,\n period,\n candidates: [],\n },\n now,\n ),\n );\n }\n const result: PeriodCloseResult = {\n sellerTenantId: runtime.sellerTenantId,\n ...(mode.kind === 'explicit' ? mode.period : previousCalendarMonth(now)),\n groups: results,\n };\n if (results.some((group) => group.outcome === 'failed')) {\n throw new BillingPeriodCloseError(result);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Collection\n// ---------------------------------------------------------------------------\n\nasync function collectGroups(\n runtime: BillingRuntime,\n mode: CloseMode,\n schedules: PayerSchedules,\n): Promise<CandidateGroup[]> {\n // Nothing after this instant is billable to any payer this run; each\n // collector narrows it to the payer's own period end.\n const bound = mode.kind === 'explicit' ? mode.period.periodEnd : mode.now;\n const candidates: Candidate[] = [];\n if (runtime.kind === 'provider') {\n candidates.push(...(await collectClientCharges(runtime, bound, schedules)));\n candidates.push(...(await collectAdjustments(runtime, bound, schedules)));\n } else {\n candidates.push(...(await collectRetailCharges(runtime, bound, schedules)));\n }\n candidates.push(...(await collectFlatPlans(runtime, bound, schedules)));\n candidates.push(...(await collectCarriedCredits(runtime, schedules)));\n\n const byGroup = new Map<string, CandidateGroup>();\n for (const candidate of candidates) {\n const key = `${candidate.payerTenantId}|${candidate.currency}`;\n let group = byGroup.get(key);\n if (!group) {\n const { period } = await schedules.get(candidate.payerTenantId);\n if (!period) continue;\n group = {\n payerTenantId: candidate.payerTenantId,\n currency: candidate.currency,\n closeId: await periodCloseId(\n runtime,\n candidate.payerTenantId,\n candidate.currency,\n period,\n ),\n period,\n candidates: [],\n };\n byGroup.set(key, group);\n }\n group.candidates.push(candidate);\n }\n\n // Drop sources another close already claimed; keep this close's own claims\n // so a retry rebuilds the same invoice.\n const groups = [...byGroup.values()].sort((a, b) =>\n `${a.payerTenantId}|${a.currency}`.localeCompare(\n `${b.payerTenantId}|${b.currency}`,\n ),\n );\n const flatClaims = await loadFlatClaims(\n runtime,\n candidates.flatMap((candidate) => (candidate.flat ? [candidate.flat] : [])),\n );\n for (const group of groups) {\n const ids = await Promise.all(\n group.candidates.map((candidate) =>\n candidate.flat\n ? ''\n : sourceId(candidate.sourceType, candidate.sourceId),\n ),\n );\n const claimed = new Map<string, string>();\n const lookup = ids.filter(Boolean);\n for (let offset = 0; offset < lookup.length; offset += runtime.pageSize) {\n const rows = await runtime.sources.list({\n where: { id: lookup.slice(offset, offset + runtime.pageSize) },\n });\n for (const row of rows) {\n if (row.id) claimed.set(row.id, row.periodCloseId);\n }\n }\n group.candidates = group.candidates.filter((candidate, index) => {\n if (candidate.flat) {\n const claims = flatClaims.get(candidate.flat.subscriptionId) ?? [];\n return (\n claims.some((claim) => claim.periodCloseId === group.closeId) ||\n uncovered(candidate.flat, claims).length > 0\n );\n }\n const owner = claimed.get(ids[index]);\n return owner === undefined || owner === group.closeId;\n });\n }\n // A carried credit alone is not new activity: it stays claimable at its\n // original close until the payer is next billed.\n return groups.filter((group) =>\n group.candidates.some(\n (candidate) => candidate.sourceType !== 'credit_carry_forward',\n ),\n );\n}\n\nasync function listAll<T>(\n pageSize: number,\n page: (limit: number, offset: number) => Promise<T[]>,\n): Promise<T[]> {\n const all: T[] = [];\n for (let offset = 0; ; offset += pageSize) {\n const rows = await page(pageSize, offset);\n all.push(...rows);\n if (rows.length < pageSize) return all;\n }\n}\n\ninterface UnclaimedRow {\n id: string;\n payer: string;\n at: Date;\n}\n\n/**\n * Rows in `table` matching `where` that no close has claimed yet, with their\n * payer (`tenant_id`) and billing instant. The anti-join keeps each run\n * proportional to unbilled work rather than to the whole charge history.\n */\nasync function unclaimedRows(\n runtime: BillingRuntime,\n table: string,\n sourceType: BillingLineSourceType,\n atColumn: string,\n where: string,\n params: unknown[],\n): Promise<UnclaimedRow[]> {\n const rows: UnclaimedRow[] = [];\n for (let offset = 0; ; offset += runtime.pageSize) {\n const result = await runtime.db.query(\n `SELECT c.id AS id, c.tenant_id AS payer, c.${atColumn} AS at\n FROM ${table} c\n LEFT JOIN _smrt_billing_line_sources s\n ON s.source_type = ? AND s.source_id = CAST(c.id AS TEXT)\n WHERE s.id IS NULL AND ${where}\n ORDER BY c.id\n LIMIT ? OFFSET ?`,\n sourceType,\n ...params,\n runtime.pageSize,\n offset,\n );\n for (const row of result.rows) {\n rows.push({\n id: String(row.id),\n payer: tenantKey(row.payer as string | null),\n at: new Date(row.at as string | Date),\n });\n }\n if (result.rows.length < runtime.pageSize) return rows;\n }\n}\n\n/** Ids of rows billable in their payer's period this run. */\nasync function billableIds(\n rows: UnclaimedRow[],\n schedules: PayerSchedules,\n): Promise<string[]> {\n const ids: string[] = [];\n for (const row of rows) {\n const { period } = await schedules.get(row.payer);\n if (period && row.at.getTime() < period.periodEnd.getTime()) {\n ids.push(row.id);\n }\n }\n return ids;\n}\n\n/** Load rows by id in pages through a collection. */\nasync function loadByIds<T>(\n pageSize: number,\n ids: string[],\n load: (batch: string[]) => Promise<T[]>,\n): Promise<T[]> {\n const rows: T[] = [];\n for (let offset = 0; offset < ids.length; offset += pageSize) {\n rows.push(...(await load(ids.slice(offset, offset + pageSize))));\n }\n return rows;\n}\n\nfunction usageDescription(\n metricKey: string,\n serviceKey: string,\n quantity: number,\n usageTenantId?: string,\n): string {\n const service = serviceKey ? ` (${serviceKey})` : '';\n const tenant = usageTenantId ? ` for tenant ${usageTenantId}` : '';\n const units = Number.isFinite(quantity)\n ? ` — ${Number(quantity.toFixed(6))} units`\n : '';\n return `Usage: ${metricKey}${service}${tenant}${units}`;\n}\n\n/** Wholesale books this seller publishes; charges priced by others are not ours. */\nasync function sellerBookIds(runtime: BillingRuntime): Promise<Set<string>> {\n const books = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.books.list({\n where: { tenantId: runtime.sellerTenantId },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n return new Set(books.map((book) => String(book.id)));\n}\n\nfunction isSellerCharge(charge: ClientCharge, books: Set<string>): boolean {\n return !charge.priceBookId || books.has(String(charge.priceBookId));\n}\n\nasync function collectClientCharges(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const books = await sellerBookIds(runtime);\n const ids = await billableIds(\n await unclaimedRows(\n runtime,\n '_smrt_client_charges',\n 'client_charge',\n 'approved_at',\n \"c.status IN ('approved', 'adjusted') AND c.approved_at < ?\",\n [bound.toISOString()],\n ),\n schedules,\n );\n const charges = await loadByIds(runtime.pageSize, ids, (batch) =>\n runtime.charges.list({ where: { id: batch } }),\n );\n return charges\n .filter((charge) => charge.id && isSellerCharge(charge, books))\n .filter((charge) => tenantKey(charge.tenantId) !== runtime.sellerTenantId)\n .map((charge) => {\n const payer = tenantKey(charge.tenantId);\n const usageTenant = tenantKey(charge.usageTenantId);\n const child = usageTenant && usageTenant !== payer ? usageTenant : '';\n return {\n sourceType: 'client_charge' as const,\n sourceId: String(charge.id),\n payerTenantId: payer,\n currency: normalizeCurrency(charge.currency),\n lineKey: `usage|${charge.serviceKey}|${charge.metricKey}|${child}`,\n lineDescription: usageDescription(\n charge.metricKey,\n charge.serviceKey,\n Number(charge.quantity) || 0,\n child,\n ),\n amount: Number(charge.amount),\n quantity: Number(charge.quantity) || 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n };\n });\n}\n\nasync function collectAdjustments(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const books = await sellerBookIds(runtime);\n const ids = await billableIds(\n await unclaimedRows(\n runtime,\n '_smrt_billing_adjustments',\n 'billing_adjustment',\n 'created_at',\n 'c.created_at < ?',\n [bound.toISOString()],\n ),\n schedules,\n );\n const adjustments = await loadByIds(runtime.pageSize, ids, (batch) =>\n runtime.adjustments.list({ where: { id: batch } }),\n );\n const chargeIds = [\n ...new Set(adjustments.map((row) => String(row.clientChargeId))),\n ].filter(Boolean);\n const charges = new Map<string, ClientCharge>();\n for (let offset = 0; offset < chargeIds.length; offset += runtime.pageSize) {\n const rows = await runtime.charges.list({\n where: { id: chargeIds.slice(offset, offset + runtime.pageSize) },\n });\n for (const row of rows) charges.set(String(row.id), row);\n }\n const candidates: Candidate[] = [];\n for (const adjustment of adjustments) {\n const charge = charges.get(String(adjustment.clientChargeId));\n if (\n !adjustment.id ||\n !charge ||\n !isSellerCharge(charge, books) ||\n (charge.status !== 'approved' && charge.status !== 'adjusted')\n ) {\n continue;\n }\n const payer = tenantKey(adjustment.tenantId);\n if (!payer || payer === runtime.sellerTenantId) continue;\n candidates.push({\n sourceType: 'billing_adjustment',\n sourceId: String(adjustment.id),\n payerTenantId: payer,\n currency: normalizeCurrency(adjustment.currency),\n lineKey: 'adjustment',\n lineDescription: 'Billing adjustments',\n amount: Number(adjustment.amount),\n quantity: 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n });\n }\n return candidates;\n}\n\nasync function collectRetailCharges(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const ids = await billableIds(\n await unclaimedRows(\n runtime,\n '_smrt_retail_charges',\n 'retail_charge',\n 'approved_at',\n \"c.reseller_tenant_id = ? AND c.status = 'approved' AND c.approved_at < ?\",\n [runtime.sellerTenantId, bound.toISOString()],\n ),\n schedules,\n );\n const charges = await loadByIds(runtime.pageSize, ids, (batch) =>\n runtime.retailCharges.list({ where: { id: batch } }),\n );\n return charges\n .filter((charge: RetailCharge) => charge.id)\n .map((charge: RetailCharge) => ({\n sourceType: 'retail_charge' as const,\n sourceId: String(charge.id),\n payerTenantId: tenantKey(charge.tenantId),\n currency: normalizeCurrency(charge.currency),\n lineKey: `retail|${charge.serviceKey}|${charge.metricKey}`,\n lineDescription: usageDescription(\n charge.metricKey,\n charge.serviceKey,\n Number(charge.quantity) || 0,\n ),\n amount: Number(charge.amount),\n quantity: Number(charge.quantity) || 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n }));\n}\n\n/** Net credits carried forward by this seller's earlier closes. */\nasync function collectCarriedCredits(\n runtime: BillingRuntime,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n // Only unconsumed credits: a close is marked completed once a later close\n // claims its credit, so this scan stays proportional to open credits.\n const carried = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.closes.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n kind: runtime.kind,\n status: 'carried_forward',\n },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n const candidates: Candidate[] = [];\n for (const close of carried) {\n if (!close.id || !(Number(close.subtotal) < 0)) continue;\n const { period } = await schedules.get(close.payerTenantId);\n if (!period || close.periodEnd.getTime() > period.periodStart.getTime()) {\n continue;\n }\n candidates.push({\n sourceType: 'credit_carry_forward' as const,\n sourceId: String(close.id),\n payerTenantId: tenantKey(close.payerTenantId),\n currency: normalizeCurrency(close.currency),\n lineKey: `carry-forward|${close.id}`,\n lineDescription: `Credit carried forward from ${isoDate(\n close.periodStart,\n )} to ${isoDate(new Date(close.periodEnd.getTime() - 1))}`,\n amount: Number(close.subtotal),\n quantity: 0,\n discountable: false,\n periodStart: null,\n periodEnd: null,\n });\n }\n return candidates;\n}\n\nfunction isoDate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\n/**\n * Flat monthly plan fees, billed in arrears for each payer's period,\n * including the period a subscription is canceled in. Provider-managed\n * subscriptions (a provider subscription id) are billed by the provider\n * itself and skipped; incomplete subscriptions are not billed, and trials\n * are not billed (with proration, only the trial's time is not billed).\n */\nasync function collectFlatPlans(\n runtime: BillingRuntime,\n bound: Date,\n schedules: PayerSchedules,\n): Promise<Candidate[]> {\n const subscriptions = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.subscriptions.list({\n where: {\n // A subscription canceled during the period still owes that period.\n status: ['active', 'past_due', 'unpaid', 'canceled'],\n subscriberKind: 'tenant',\n 'startedAt <': bound.toISOString(),\n },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n const planIds = [\n ...new Set(subscriptions.map((row) => String(row.planId))),\n ].filter(Boolean);\n const plans = new Map<string, SubscriptionPlan>();\n for (let offset = 0; offset < planIds.length; offset += runtime.pageSize) {\n const rows = await runtime.plans.list({\n where: { id: planIds.slice(offset, offset + runtime.pageSize) },\n });\n for (const row of rows) plans.set(String(row.id), row);\n }\n\n const candidates: Candidate[] = [];\n for (const subscription of subscriptions) {\n const plan = plans.get(String(subscription.planId));\n if (!subscription.id || !plan || !isFlatPlan(subscription, plan)) continue;\n const planSeller = tenantKey(plan.tenantId);\n const subscriber = tenantKey(subscription.tenantId);\n let payer: string;\n if (runtime.kind === 'provider') {\n if (planSeller && planSeller !== runtime.sellerTenantId) continue;\n const relationship =\n await runtime.billingRelationships.getRelationship(subscriber);\n payer = relationship ? relationship.billingOwnerTenantId : subscriber;\n } else {\n if (planSeller !== runtime.sellerTenantId) continue;\n const relationship =\n await runtime.billingRelationships.getRelationship(subscriber);\n if (relationship?.resellerTenantId !== runtime.sellerTenantId) continue;\n payer = subscriber;\n }\n payer = tenantKey(payer);\n if (!payer || payer === runtime.sellerTenantId) continue;\n const schedule = await schedules.get(payer);\n if (!schedule.period || !schedule.billsFlatPlans) continue;\n const window = flatWindow(subscription, schedule.period, schedule.prorate);\n if (!window) continue;\n const forTenant = payer !== subscriber ? ` for tenant ${subscriber}` : '';\n candidates.push({\n sourceType: 'subscription_period',\n // Assigned when claimed: the claim's number for its subscription.\n sourceId: '',\n payerTenantId: payer,\n currency: normalizeCurrency(plan.currency),\n lineKey: flatLineKey(String(subscription.id)),\n lineDescription: '',\n amount: 0,\n quantity: 1,\n discountable: true,\n periodStart: window.start,\n periodEnd: window.end,\n flat: {\n subscriptionId: String(subscription.id),\n planAmount: Number(plan.priceAmount),\n label: `${plan.name || plan.planKey}${forTenant}`,\n windowStart: window.start,\n windowEnd: window.end,\n cycleMs:\n schedule.period.cycleEnd.getTime() -\n schedule.period.cycleStart.getTime(),\n },\n });\n }\n return candidates;\n}\n\n/** Period-independent eligibility of a flat plan subscription. */\nfunction isFlatPlan(\n subscription: TenantSubscription,\n plan: SubscriptionPlan,\n): boolean {\n if (subscription.stripeSubscriptionId) return false;\n if (plan.billingInterval !== 'month') return false;\n if (!Number.isSafeInteger(plan.priceAmount) || plan.priceAmount <= 0) {\n return false;\n }\n if (subscription.status === 'canceled' && !subscription.canceledAt) {\n return false;\n }\n return true;\n}\n\n/**\n * The part of `period` a subscription is billed for, or null. Without\n * proration that is the whole period if the subscription was active in it\n * and out of trial at its start; with proration it is exactly the time the\n * subscription was active and out of trial.\n */\nfunction flatWindow(\n subscription: TenantSubscription,\n period: BillingPeriod,\n prorate: boolean,\n): { start: Date; end: Date } | null {\n const start = period.periodStart.getTime();\n const end = period.periodEnd.getTime();\n const startedAt = subscription.startedAt.getTime();\n const canceledAt = subscription.canceledAt?.getTime();\n const trialEndsAt = subscription.trialEndsAt?.getTime();\n if (!(startedAt < end)) return null;\n if (canceledAt !== undefined && canceledAt <= start) return null;\n if (!prorate) {\n if (trialEndsAt !== undefined && trialEndsAt > start) return null;\n return { start: period.periodStart, end: period.periodEnd };\n }\n const from = Math.max(start, startedAt, trialEndsAt ?? start);\n const to = Math.min(end, canceledAt ?? end);\n return to > from ? { start: new Date(from), end: new Date(to) } : null;\n}\n\n// ---------------------------------------------------------------------------\n// Flat-plan coverage\n// ---------------------------------------------------------------------------\n\nfunction flatLineKey(subscriptionId: string): string {\n return `subscription|${subscriptionId}`;\n}\n\n/**\n * Flat-plan claims of each subscription that overlap the windows being\n * billed (ending after the earliest start and starting before the latest\n * end), by subscription id. The read is bounded by those windows, not by\n * billing history.\n */\nasync function loadFlatClaims(\n runtime: BillingRuntime,\n flats: FlatWindow[],\n): Promise<Map<string, BillingLineSource[]>> {\n const bySubscription = new Map<string, [number, number]>();\n for (const flat of flats) {\n const start = flat.windowStart.getTime();\n const end = flat.windowEnd.getTime();\n const known = bySubscription.get(flat.subscriptionId);\n bySubscription.set(\n flat.subscriptionId,\n known\n ? [Math.min(known[0], start), Math.max(known[1], end)]\n : [start, end],\n );\n }\n const entries = [...bySubscription.entries()];\n const claims = new Map<string, BillingLineSource[]>();\n for (let offset = 0; offset < entries.length; offset += runtime.pageSize) {\n const batch = entries.slice(offset, offset + runtime.pageSize);\n const after = Math.min(...batch.map(([, [start]]) => start));\n const before = Math.max(...batch.map(([, [, end]]) => end));\n const rows = await runtime.sources.list({\n where: {\n sourceType: 'subscription_period',\n lineKey: batch.map(([subscriptionId]) => flatLineKey(subscriptionId)),\n 'periodEnd >': new Date(after).toISOString(),\n 'periodStart <': new Date(before).toISOString(),\n },\n });\n for (const row of rows) {\n const subscriptionId = row.lineKey.slice('subscription|'.length);\n const list = claims.get(subscriptionId) ?? [];\n list.push(row);\n claims.set(subscriptionId, list);\n }\n }\n return claims;\n}\n\n/** The parts of a flat window no existing claim covers, in order. */\nfunction uncovered(\n flat: FlatWindow,\n claims: BillingLineSource[],\n): Array<[number, number]> {\n const start = flat.windowStart.getTime();\n const end = flat.windowEnd.getTime();\n const covered = claims\n .filter((claim) => claim.periodStart && claim.periodEnd)\n .map(\n (claim) =>\n [\n (claim.periodStart as Date).getTime(),\n (claim.periodEnd as Date).getTime(),\n ] as [number, number],\n )\n .filter(([from, to]) => from < end && to > start)\n .sort((a, b) => a[0] - b[0]);\n const gaps: Array<[number, number]> = [];\n let cursor = start;\n for (const [from, to] of covered) {\n if (from > cursor) gaps.push([cursor, Math.min(from, end)]);\n cursor = Math.max(cursor, to);\n if (cursor >= end) break;\n }\n if (cursor < end) gaps.push([cursor, end]);\n return gaps.filter(([from, to]) => to > from);\n}\n\n/** The subscription's highest claim sequence number (0 before its first). */\nasync function lastFlatSequence(\n runtime: BillingRuntime,\n subscriptionId: string,\n): Promise<number> {\n const [last] = await runtime.sources.list({\n where: {\n sourceType: 'subscription_period',\n lineKey: flatLineKey(subscriptionId),\n },\n orderBy: 'chainSequence DESC',\n limit: 1,\n });\n return Number(last?.chainSequence) || 0;\n}\n\nfunction flatSourceId(subscriptionId: string, sequence: number): string {\n return `${subscriptionId}:seq:${sequence}`;\n}\n\nconst MAX_FLAT_CLAIM_CONFLICTS = 8;\n\n/**\n * Claim every uncovered part of a flat window for `closeId`, one claim per\n * gap, each priced as the plan amount × gap length / cycle length (rounded\n * half up) and recording exactly the time it bills.\n *\n * A subscription's claims are numbered 1, 2, 3, … and a claim's id is derived\n * from its number, so only one claimer can ever take number n + 1. A claimer\n * reads the last number n first and the coverage second; every claim up to n\n * was committed before claim n was, so the coverage read sees them all, and a\n * claim written after the first read takes n + 1 and makes this insert\n * conflict. A conflicting claimer re-reads and retries.\n */\nasync function claimFlatWindow(\n runtime: BillingRuntime,\n closeId: string,\n candidate: Candidate,\n flat: FlatWindow,\n account: BillingAccount,\n): Promise<void> {\n let conflicts = 0;\n for (;;) {\n const sequence = (await lastFlatSequence(runtime, flat.subscriptionId)) + 1;\n const claims =\n (await loadFlatClaims(runtime, [flat])).get(flat.subscriptionId) ?? [];\n const gap = uncovered(flat, claims)\n .map(([from, to]) => ({\n from,\n to,\n amount: prorateMinorUnits(flat.planAmount, to - from, flat.cycleMs),\n }))\n .find((part) => part.amount > 0);\n if (!gap) return;\n const billedStart = new Date(gap.from);\n const billedEnd = new Date(gap.to);\n const claimSourceId = flatSourceId(flat.subscriptionId, sequence);\n const id = await sourceId('subscription_period', claimSourceId);\n const prorated = gap.to - gap.from < flat.cycleMs ? ' (prorated)' : '';\n try {\n await runtime.sources.create({\n id,\n sellerTenantId: runtime.sellerTenantId,\n periodCloseId: closeId,\n sourceType: 'subscription_period',\n sourceId: claimSourceId,\n chainSequence: sequence,\n lineKey: candidate.lineKey,\n lineDescription: `${flat.label} — ${isoDate(billedStart)} to ${isoDate(\n new Date(billedEnd.getTime() - 1),\n )}${prorated}`,\n amount: gap.amount,\n discount: flatDiscount(gap.amount, account),\n quantity: 1,\n currency: candidate.currency,\n periodStart: billedStart,\n periodEnd: billedEnd,\n _insertOnly: true,\n });\n } catch (error) {\n // Another claim took this number: re-read and retry.\n if (!(await runtime.sources.get(id))) throw error;\n conflicts += 1;\n if (conflicts >= MAX_FLAT_CLAIM_CONFLICTS) {\n throw new Error(\n `Could not claim subscription ${flat.subscriptionId}: its billed coverage kept changing.`,\n );\n }\n }\n }\n}\n\nfunction flatDiscount(amount: number, account: BillingAccount): number {\n return Math.round(\n (amount * Number(account.flatDiscountBasisPoints)) / 10_000,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Per-payer close\n// ---------------------------------------------------------------------------\n\nasync function closeGroup(\n runtime: BillingRuntime,\n group: CandidateGroup,\n now: Date,\n): Promise<PeriodCloseGroupResult> {\n const { period } = group;\n const base = {\n payerTenantId: group.payerTenantId,\n currency: group.currency,\n periodStart: period.periodStart,\n periodEnd: period.periodEnd,\n closeId: group.closeId,\n };\n let leaseToken: string | null = null;\n try {\n const existing = await runtime.closes.get(group.closeId);\n const account = await runtime.getAccount(group.payerTenantId);\n if (!account?.id) {\n throw new Error(\n `No billing account for payer ${group.payerTenantId}; create one with upsertAccount().`,\n );\n }\n const close =\n existing ?? (await createClose(runtime, period, group, account, now));\n if (close.status === 'completed' || close.status === 'carried_forward') {\n return resultOf(base, close, close.status);\n }\n leaseToken = await runtime.acquireCloseLease(String(close.id), now);\n if (!leaseToken) return { ...base, outcome: 'busy' };\n const fresh = (await runtime.closes.get(String(close.id))) ?? close;\n const outcome = await advance(runtime, fresh, group.candidates, account);\n return resultOf(base, fresh, outcome);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n await recordFailure(runtime, group.closeId, message);\n return { ...base, outcome: 'failed', error: message };\n } finally {\n if (leaseToken) {\n await runtime.releaseCloseLease(group.closeId, leaseToken);\n }\n }\n}\n\nfunction resultOf(\n base: Omit<PeriodCloseGroupResult, 'outcome'>,\n close: BillingPeriodClose,\n outcome: PeriodCloseOutcome,\n): PeriodCloseGroupResult {\n return {\n ...base,\n outcome,\n invoiceId: close.invoiceId || undefined,\n providerInvoiceId: close.providerInvoiceId || undefined,\n };\n}\n\nasync function recordFailure(\n runtime: BillingRuntime,\n closeId: string,\n message: string,\n): Promise<void> {\n try {\n const close = await runtime.closes.get(closeId);\n if (!close) return;\n close.attempts = (Number(close.attempts) || 0) + 1;\n close.lastError = message.slice(0, 2000);\n await close.save();\n } catch {\n // The failure is reported in the result either way.\n }\n}\n\nasync function createClose(\n runtime: BillingRuntime,\n period: BillingPeriod,\n group: CandidateGroup,\n account: BillingAccount,\n now: Date,\n): Promise<BillingPeriodClose> {\n try {\n return await runtime.closes.create({\n id: group.closeId,\n sellerTenantId: runtime.sellerTenantId,\n kind: runtime.kind,\n payerTenantId: group.payerTenantId,\n currency: group.currency,\n periodStart: period.periodStart,\n periodEnd: period.periodEnd,\n billingAccountId: String(account.id),\n status: 'collecting',\n issuedAt: now,\n _insertOnly: true,\n });\n } catch (error) {\n const concurrent = await runtime.closes.get(group.closeId);\n if (concurrent) return concurrent;\n throw error;\n }\n}\n\nasync function advance(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n candidates: Candidate[],\n account: BillingAccount,\n): Promise<PeriodCloseOutcome> {\n if (close.status === 'collecting') {\n const collected = await collectAndInvoice(\n runtime,\n close,\n candidates,\n account,\n );\n if (collected === 'empty') {\n close.status = 'completed';\n await close.save();\n return 'empty';\n }\n if (collected === 'carried_forward') return 'carried_forward';\n }\n const invoice = await runtime.getInvoice(close.invoiceId);\n if (close.status === 'invoiced') {\n await pushToProvider(runtime, close, invoice, account);\n }\n if (close.status === 'pushed') {\n await sendAndReconcile(runtime, close, invoice, account);\n }\n if (close.status === 'sent') {\n await postRevenue(runtime, invoice);\n close.status = 'completed';\n close.lastError = '';\n await close.save();\n }\n return 'completed';\n}\n\n/** Claim sources, then build the invoice from everything this close owns. */\nasync function collectAndInvoice(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n candidates: Candidate[],\n account: BillingAccount,\n): Promise<'empty' | 'carried_forward' | 'invoiced'> {\n const closeId = String(close.id);\n for (const candidate of candidates) {\n if (candidate.flat) {\n await claimFlatWindow(\n runtime,\n closeId,\n candidate,\n candidate.flat,\n account,\n );\n continue;\n }\n const id = await sourceId(candidate.sourceType, candidate.sourceId);\n const discount = candidate.discountable\n ? flatDiscount(candidate.amount, account)\n : 0;\n try {\n await runtime.sources.create({\n id,\n sellerTenantId: runtime.sellerTenantId,\n periodCloseId: closeId,\n sourceType: candidate.sourceType,\n sourceId: candidate.sourceId,\n lineKey: candidate.lineKey,\n lineDescription: candidate.lineDescription,\n amount: candidate.amount,\n discount,\n quantity: candidate.quantity,\n currency: candidate.currency,\n periodStart: candidate.periodStart,\n periodEnd: candidate.periodEnd,\n _insertOnly: true,\n });\n } catch (error) {\n // Already claimed — by this close on an earlier attempt, or by another\n // close that won the race. Only a missing row is a real failure.\n if (!(await runtime.sources.get(id))) throw error;\n }\n }\n\n const sources = await listAll(runtime.pageSize, (limit, offset) =>\n runtime.sources.list({\n where: { periodCloseId: closeId },\n orderBy: 'id ASC',\n limit,\n offset,\n }),\n );\n if (sources.length === 0) return 'empty';\n // Credits this close absorbed are consumed at their original close.\n for (const source of sources) {\n if (source.sourceType !== 'credit_carry_forward') continue;\n const carried = await runtime.closes.get(source.sourceId);\n if (carried?.status === 'carried_forward') {\n carried.status = 'completed';\n await carried.save();\n }\n }\n const lines = groupLines(sources);\n const subtotal = lines.reduce(\n (sum, line) => sum + line.amount - line.discount,\n 0,\n );\n if (subtotal < 0) {\n // A net credit is not invoiced. The claims stay with this close, so a\n // source's later eligibility cannot change the balance, and the credit\n // itself becomes a source the payer's next close bills.\n close.subtotal = subtotal;\n close.lineCount = lines.length;\n close.status = 'carried_forward';\n await close.save();\n return 'carried_forward';\n }\n\n const invoiceId = await deterministicId(['billing-invoice', closeId]);\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n let invoice = await runtime.invoices.get(invoiceId);\n if (!invoice) {\n const issueDate = close.issuedAt;\n const dueDate = new Date(\n issueDate.getTime() + Number(account.paymentTermsDays) * 86_400_000,\n );\n try {\n invoice = await runtime.invoices.create({\n id: invoiceId,\n tenantId: runtime.sellerTenantId,\n customerId: account.customerId,\n invoiceNumber: invoiceNumber(runtime, close, closeId),\n reference: `billing-period:${close.periodStart.toISOString()}/${close.periodEnd.toISOString()}`,\n issueDate,\n dueDate,\n currency: close.currency,\n subtotal: 0,\n taxAmount: 0,\n totalAmount: 0,\n _insertOnly: true,\n });\n } catch (error) {\n invoice = await runtime.invoices.get(invoiceId);\n if (!invoice) throw error;\n }\n }\n if (invoice.status !== InvoiceStatus.DRAFT) {\n throw new Error(`Invoice ${invoice.invoiceNumber} is no longer a draft.`);\n }\n for (const [index, line] of lines.entries()) {\n await runtime.lineItems.create({\n id: await deterministicId(['billing-invoice-line', closeId, line.key]),\n tenantId: runtime.sellerTenantId,\n invoiceId,\n description: line.description,\n quantity: 1,\n unitPrice: line.amount,\n discount: line.discount,\n taxRate: 0.0,\n amount: line.amount - line.discount,\n sourceType: 'billing_period_close',\n sourceId: closeId,\n periodStart: line.periodStart ?? close.periodStart,\n periodEnd: line.periodEnd ?? close.periodEnd,\n sortOrder: index,\n });\n }\n await invoice.save();\n });\n\n close.invoiceId = invoiceId;\n close.subtotal = subtotal;\n close.lineCount = lines.length;\n close.status = 'invoiced';\n await close.save();\n return 'invoiced';\n}\n\ninterface InvoiceLineGroup {\n key: string;\n description: string;\n amount: number;\n discount: number;\n periodStart: Date | null;\n periodEnd: Date | null;\n}\n\nfunction groupLines(sources: BillingLineSource[]): InvoiceLineGroup[] {\n const lines = new Map<string, InvoiceLineGroup & { quantity: number }>();\n for (const source of sources) {\n // Each numbered flat claim (#3116) is its own line with its own service\n // period, keyed by the claim alone so a resumed close rebuilds the same\n // line ids whatever other claims it gained; pre-#3116 claims (number 0)\n // keep their original key.\n const key =\n source.sourceType === 'subscription_period' &&\n Number(source.chainSequence) > 0\n ? `${source.lineKey}|${source.periodStart?.toISOString() ?? ''}`\n : source.lineKey;\n let line = lines.get(key);\n if (!line) {\n line = {\n key,\n description: source.lineDescription,\n amount: 0,\n discount: 0,\n quantity: 0,\n periodStart: source.periodStart,\n periodEnd: source.periodEnd,\n };\n lines.set(key, line);\n }\n line.amount += Number(source.amount);\n line.discount += Number(source.discount);\n line.quantity += Number(source.quantity) || 0;\n }\n return [...lines.values()]\n .sort((a, b) => a.key.localeCompare(b.key))\n .map((line) => ({\n key: line.key,\n // Usage lines describe the summed quantity, not the first event's.\n description:\n line.key.startsWith('usage|') || line.key.startsWith('retail|')\n ? line.description.replace(\n / — [\\d.e+-]+ units$/,\n ` — ${Number(line.quantity.toFixed(6))} units`,\n )\n : line.description,\n amount: line.amount,\n discount: line.discount,\n periodStart: line.periodStart,\n periodEnd: line.periodEnd,\n }));\n}\n\nfunction invoiceNumber(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n closeId: string,\n): string {\n const month = close.periodStart.toISOString().slice(0, 7).replace('-', '');\n return `${runtime.invoiceNumberPrefix}-${month}-${closeId.slice(0, 8).toUpperCase()}`;\n}\n\nasync function pushToProvider(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n invoice: Invoice,\n account: BillingAccount,\n): Promise<void> {\n const synced = await runtime.ensureProviderCustomer(account);\n const lineItems = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n runtime.lineItems.findByInvoice(String(invoice.id)),\n );\n const lines: BillingProviderInvoiceLine[] = [];\n const taxCode =\n synced.automaticTax && runtime.taxCodes.invoice\n ? { taxCode: runtime.taxCodes.invoice }\n : {};\n for (const item of [...lineItems].sort((a, b) => a.sortOrder - b.sortOrder)) {\n const gross = item.quantity * item.unitPrice;\n lines.push({\n description: item.description,\n amount: gross,\n periodStart: item.periodStart ?? undefined,\n periodEnd: item.periodEnd ?? undefined,\n ...taxCode,\n });\n if (item.discount > 0) {\n lines.push({\n description: `Discount: ${item.description}`,\n amount: -item.discount,\n periodStart: item.periodStart ?? undefined,\n periodEnd: item.periodEnd ?? undefined,\n ...taxCode,\n });\n }\n }\n // A payer with its default card on file is charged automatically (#3139),\n // decided once and recorded before the first push (#3190): a retry after a\n // push whose response was lost must ask for, and report, the same method\n // even if a card was saved since.\n let collectionMethod = invoice.collectionMethod;\n if (!collectionMethod) {\n collectionMethod =\n runtime.autoChargeInvoices &&\n (await defaultCardFor(runtime, account, synced.providerCustomerId))\n ? 'charge_automatically'\n : 'send_invoice';\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n invoice.collectionMethod = collectionMethod;\n await invoice.save();\n });\n }\n const { providerInvoiceId } = await runtime.provider.pushInvoice({\n invoiceId: String(invoice.id),\n invoiceNumber: invoice.invoiceNumber,\n providerCustomerId: synced.providerCustomerId,\n currency: close.currency,\n issueDate: invoice.issueDate,\n dueDate: invoice.dueDate,\n lines,\n subtotal: invoice.subtotal,\n idempotencyKey: `smrt-billing-close:${close.id}`,\n automaticTax: synced.automaticTax,\n collectionMethod,\n memo: invoice.customerNotes || undefined,\n });\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n invoice.externalId = providerInvoiceId;\n invoice.customerExternalId = synced.providerCustomerId;\n invoice.externalProvider = runtime.provider.name;\n invoice.syncedAt = new Date();\n await invoice.save();\n });\n close.providerInvoiceId = providerInvoiceId;\n close.status = 'pushed';\n await close.save();\n}\n\nasync function sendAndReconcile(\n runtime: BillingRuntime,\n close: BillingPeriodClose,\n invoice: Invoice,\n account: BillingAccount,\n): Promise<void> {\n const providerInvoiceId = close.providerInvoiceId;\n let state = await runtime.provider.getInvoice(providerInvoiceId);\n if (state.status === 'draft') {\n // Re-sync the tax location: a send refused for a missing or invalid\n // address succeeds once the account is corrected.\n await runtime.ensureProviderCustomer(account);\n await runtime.provider.sendInvoice(providerInvoiceId);\n state = await runtime.provider.getInvoice(providerInvoiceId);\n }\n if (state.status === 'draft' || state.status === 'void') {\n throw new Error(\n `Provider invoice ${providerInvoiceId} is ${state.status}; expected it to be sent.`,\n );\n }\n if (\n state.currency !== close.currency ||\n state.subtotal !== invoice.subtotal ||\n state.total !== state.subtotal + state.taxAmount\n ) {\n throw new Error(\n `Provider invoice ${providerInvoiceId} does not match invoice ${invoice.invoiceNumber}: ` +\n `provider ${state.subtotal}+${state.taxAmount}=${state.total} ${state.currency}, ` +\n `local subtotal ${invoice.subtotal} ${close.currency}.`,\n );\n }\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n invoice.providerTaxAmount = state.taxAmount;\n if (invoice.status === InvoiceStatus.DRAFT) invoice.markSent();\n await invoice.save();\n });\n if (invoice.totalAmount !== state.total) {\n throw new Error(\n `Invoice ${invoice.invoiceNumber} total ${invoice.totalAmount} does not match provider total ${state.total}.`,\n );\n }\n close.status = 'sent';\n await close.save();\n}\n\n/**\n * Recognize revenue once. A journal posted by an attempt that died before\n * linking it is adopted instead of posting a second one.\n */\nasync function postRevenue(\n runtime: BillingRuntime,\n invoice: Invoice,\n): Promise<void> {\n if (invoice.totalAmount <= 0 || invoice.arJournalId) return;\n await withTenant({ tenantId: runtime.sellerTenantId }, async () => {\n const posted = await runtime.journals.list({\n where: {\n sourceModule: 'smrt-commerce',\n sourceRef: String(invoice.id),\n status: 'posted',\n },\n limit: 1,\n });\n if (posted[0]?.id) {\n invoice.arJournalId = String(posted[0].id);\n await invoice.save();\n return;\n }\n await invoice.recognizeRevenue({\n arAccountId: runtime.ledger.arAccountId,\n revenueAccountId: runtime.ledger.revenueAccountId,\n taxAccountId: runtime.ledger.taxAccountId,\n });\n });\n}\n","/**\n * InvoiceLineItem model - individual items on an invoice\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { minorToMajorUnits } from '../billing/units.js';\nimport {\n calculateInvoiceMinorLine,\n type InvoiceCalculationContext,\n type InvoiceLineDraft,\n resolveInvoiceLineDraft,\n} from '../svelte/invoices/calculations.js';\nimport type {\n AccountingLineItemInput,\n InvoiceLineEditorState,\n InvoiceLineItemOptions,\n} from '../types/index.js';\n\n/**\n * InvoiceLineItem represents a single line item on an invoice.\n *\n * Line items contain details of what is being billed, including\n * quantity, pricing, and optional source tracking (e.g., from ad campaigns).\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * invoiceId: invoice.id,\n * description: 'Display Advertising - Summer Campaign',\n * quantity: 50000, // impressions\n * unitPrice: 1, // 1 cent per impression (minor units)\n * taxRate: 0.05,\n * sourceType: 'campaign',\n * sourceId: 'campaign-uuid',\n * periodStart: new Date('2025-06-01'),\n * periodEnd: new Date('2025-06-30')\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class InvoiceLineItem extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global invoice line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent invoice\n */\n @foreignKey('Invoice')\n invoiceId: string = '';\n\n /**\n * Item description\n */\n description: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /** Nonnegative decimal quantity (hours, weight, units), up to six fractional digits. */\n quantity: number = 1.0;\n\n /** Raw editor modes retained only while they resolve to the authoritative model fields. */\n invoiceEditorStateJson: string = '';\n\n /** Guarded editor metadata view; malformed stored JSON is treated as absent. */\n get invoiceEditorState(): InvoiceLineEditorState | null {\n try {\n const value: unknown = JSON.parse(this.invoiceEditorStateJson);\n if (\n !value ||\n typeof value !== 'object' ||\n !('draft' in value) ||\n !('context' in value) ||\n !value.draft ||\n typeof value.draft !== 'object' ||\n !value.context ||\n typeof value.context !== 'object'\n )\n return null;\n return value as InvoiceLineEditorState;\n } catch {\n return null;\n }\n }\n\n /** Store only this model's editor state, preserving unrelated metadata. */\n set invoiceEditorState(value: InvoiceLineEditorState | null) {\n this.invoiceEditorStateJson = value ? JSON.stringify(value) : '';\n }\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis).\n *\n * Money is exact, so it is stored as minor units and never as a float —\n * `$19.99` is `1999`. An integer literal is what maps this to an INTEGER\n * column (#2361).\n */\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2361).\n */\n discount: number = 0;\n\n /**\n * Tax rate as a decimal fraction (e.g., 0.05 for 5%).\n *\n * A genuine rate, not money: it is inherently fractional, so the `0.0`\n * initializer is load-bearing and maps it to a DECIMAL column. INTEGER would\n * truncate every rate to 0 (#2361).\n */\n taxRate: number = 0.0;\n\n /**\n * Calculated line amount, in integer minor units (#2361).\n */\n amount: number = 0;\n\n // ============================================================================\n // Source Tracking\n // ============================================================================\n\n /**\n * Type of source ('campaign' | 'contract' | 'manual' | etc.)\n */\n sourceType: string = '';\n\n /**\n * ID of the source (e.g., campaign ID, contract ID)\n */\n sourceId: string = '';\n\n /**\n * Service period start (for time-based billing)\n */\n periodStart: Date | null = null;\n\n /**\n * Service period end (for time-based billing)\n */\n periodEnd: Date | null = null;\n\n // ============================================================================\n // Accounting\n // ============================================================================\n\n /**\n * Revenue account ID (cross-package ref to smrt-ledgers)\n * Used for revenue recognition to specific accounts\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Account')\n revenueAccountId: string = '';\n\n /**\n * Sort order within the invoice\n */\n sortOrder: number = 0;\n\n constructor(options: InvoiceLineItemOptions = {}) {\n super(options);\n if (options.invoiceEditorStateJson !== undefined)\n this.invoiceEditorStateJson = options.invoiceEditorStateJson;\n if (options.invoiceEditorState !== undefined)\n this.invoiceEditorState = structuredClone(options.invoiceEditorState);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.invoiceId !== undefined) this.invoiceId = options.invoiceId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.sourceType !== undefined) this.sourceType = options.sourceType;\n if (options.sourceId !== undefined) this.sourceId = options.sourceId;\n if (options.periodStart !== undefined)\n this.periodStart = options.periodStart;\n if (options.periodEnd !== undefined) this.periodEnd = options.periodEnd;\n if (options.revenueAccountId !== undefined)\n this.revenueAccountId = options.revenueAccountId;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n /** Calculate exact safe integer-minor totals, rounding gross and tax ties toward positive infinity. */\n calculateAmount(): number {\n return calculateInvoiceMinorLine(this).totalMinor;\n }\n\n /** Rounded quantity times price, less the resolved flat minor-unit discount. */\n getSubtotal(): number {\n return calculateInvoiceMinorLine(this).subtotalMinor;\n }\n\n /** Tax on the rounded discounted subtotal, in safe integer minor units. */\n getTaxAmount(): number {\n return calculateInvoiceMinorLine(this).taxMinor;\n }\n\n /** Apply a server-validated draft without saving; authorization and transaction stay caller-owned. */\n applyEditorDraft(\n draft: InvoiceLineDraft,\n context: InvoiceCalculationContext,\n ): void {\n const resolved = resolveInvoiceLineDraft(draft, context);\n Object.assign(this, resolved);\n this.invoiceEditorState = structuredClone({ draft, context });\n }\n\n /** Recover original editing modes only when current authority and resolved model fields agree. */\n getEditorDraft(\n authorizedContext: InvoiceCalculationContext,\n ): InvoiceLineDraft | null {\n const state = this.invoiceEditorState;\n if (\n !state ||\n !authorizedContext ||\n state.context?.currency !== authorizedContext.currency ||\n state.context?.inheritedTaxRate !== authorizedContext.inheritedTaxRate\n )\n return null;\n try {\n const resolved = resolveInvoiceLineDraft(state.draft, authorizedContext);\n if (\n resolved.description !== this.description ||\n resolved.sku !== this.sku ||\n resolved.quantity !== this.quantity ||\n resolved.unitPrice !== this.unitPrice ||\n resolved.discount !== this.discount ||\n resolved.taxRate !== this.taxRate\n )\n return null;\n return structuredClone(state.draft);\n } catch {\n return null;\n }\n }\n\n /** Validate resolved fields and recompute amount before persistence; editor metadata never determines money. */\n override async save(): Promise<this> {\n this.amount = this.calculateAmount();\n const state = this.invoiceEditorState;\n if (\n this.invoiceEditorStateJson &&\n (!state || !this.getEditorDraft(state.context))\n )\n this.invoiceEditorState = null;\n return (await super.save()) as this;\n }\n\n /**\n * Check if line item has source tracking\n */\n hasSource(): boolean {\n return !!this.sourceType && !!this.sourceId;\n }\n\n /**\n * Check if line item has a service period\n */\n hasPeriod(): boolean {\n return !!this.periodStart && !!this.periodEnd;\n }\n\n /**\n * Convert to line item format for SDK accounting provider.\n *\n * `@happyvertical/accounting` takes currency **major** units. Pass the\n * invoice currency to convert (as {@link Invoice.toAccountingInput} does);\n * without it, money stays in this package's integer minor units.\n */\n toAccountingLineItem(currency?: string): AccountingLineItemInput {\n const money = (value: number) =>\n currency ? minorToMajorUnits(value, currency) : value;\n return {\n description: this.description,\n sku: this.sku || undefined,\n quantity: this.quantity,\n unitPrice: money(this.unitPrice),\n discount: this.discount ? money(this.discount) : undefined,\n taxRate: this.taxRate,\n amount: money(this.amount),\n periodStart: this.periodStart || undefined,\n periodEnd: this.periodEnd || undefined,\n };\n }\n}\n\nexport default InvoiceLineItem;\n","/**\n * InvoiceLineItemCollection - Collection manager for InvoiceLineItem objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { InvoiceLineItem } from '../models/InvoiceLineItem.js';\nimport type { AccountingLineItemInput } from '../types/index.js';\n\nexport class InvoiceLineItemCollection extends SmrtCollection<InvoiceLineItem> {\n static readonly _itemClass = InvoiceLineItem;\n\n /**\n * Find line items by invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Array of line items, sorted by sortOrder\n */\n async findByInvoice(invoiceId: string): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { invoiceId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n /**\n * Find line items by source\n *\n * @param sourceType - Source type (e.g., 'campaign', 'contract')\n * @param sourceId - Source ID\n * @returns Array of line items\n */\n async findBySource(\n sourceType: string,\n sourceId: string,\n ): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { sourceType, sourceId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find line items by source type only\n *\n * @param sourceType - Source type (e.g., 'campaign')\n * @returns Array of line items\n */\n async findBySourceType(sourceType: string): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { sourceType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Calculate total amount for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Total amount\n */\n async getTotalForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.amount, 0);\n }\n\n /**\n * Calculate subtotal (before tax) for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Subtotal\n */\n async getSubtotalForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.getSubtotal(), 0);\n }\n\n /**\n * Calculate tax amount for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Tax amount\n */\n async getTaxForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.getTaxAmount(), 0);\n }\n\n /**\n * Get the next sort order for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Next sort order number\n */\n async getNextSortOrder(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n if (lineItems.length === 0) return 0;\n\n const maxSortOrder = Math.max(...lineItems.map((item) => item.sortOrder));\n return maxSortOrder + 1;\n }\n\n /**\n * Recalculate all line item amounts for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Updated line items\n */\n async recalculateAmounts(invoiceId: string): Promise<InvoiceLineItem[]> {\n const lineItems = await this.findByInvoice(invoiceId);\n\n for (const item of lineItems) {\n item.amount = item.calculateAmount();\n }\n\n await Promise.all(lineItems.map((item) => item.save()));\n\n return lineItems;\n }\n\n /**\n * Convert line items to accounting format for SDK sync\n *\n * @param invoiceId - Invoice ID\n * @returns Array of line items in accounting format, money in integer\n * minor units (`Invoice.toAccountingInput()` converts to major units)\n */\n async toAccountingLineItems(\n invoiceId: string,\n ): Promise<AccountingLineItemInput[]> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.map((item) => item.toAccountingLineItem());\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all invoice line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of invoice line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<InvoiceLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global invoice line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global invoice line items\n */\n async findGlobal(): Promise<InvoiceLineItem[]> {\n return queryGlobal<InvoiceLineItem>(this);\n }\n\n /**\n * Find invoice line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global invoice line items\n */\n async findWithGlobals(tenantId: string): Promise<InvoiceLineItem[]> {\n return queryWithGlobals<InvoiceLineItem>(\n this,\n tenantId,\n 'InvoiceLineItem.findWithGlobals',\n );\n }\n}\n","/**\n * Provider event processing (#3060): verified webhook deliveries queued in\n * smrt-jobs' durable delivery inbox are applied here.\n *\n * `observe()` re-reads the invoice or subscription from the provider, so an\n * event is applied against current provider state rather than the snapshot it\n * carried; out-of-order and duplicate deliveries therefore converge. The\n * application writes, the inbox completion, and the host standing hook share\n * one transaction.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport {\n type ForgeDelivery,\n type ForgeObservation,\n type ForgeProjectionContext,\n ForgeProjectionRuntime,\n type ForgeProjector,\n} from '@happyvertical/smrt-jobs';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport {\n SpendingPolicyEvaluator,\n type SubscriptionStatus,\n TenantSubscriptionCollection,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { InvoiceCollection } from '../collections/InvoiceCollection.js';\nimport { PaymentAllocationCollection } from '../collections/PaymentAllocationCollection.js';\nimport { PaymentCollection } from '../collections/PaymentCollection.js';\nimport {\n type BillingAccount,\n BillingAccountCollection,\n BillingLineSourceCollection,\n BillingPaymentAttemptCollection,\n BillingPeriodCloseCollection,\n type BillingStanding,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { InvoiceStatus, PaymentMethod, PaymentStatus } from '../types/index.js';\nimport {\n CARD_SETUP_PURPOSE,\n COLLECT_ADDRESS_METADATA,\n recordSavedCard,\n SAVE_CARD_METADATA,\n type SavedCard,\n savedCardFromCheckout,\n} from './cards.js';\nimport { CREDIT_PURCHASE_PURPOSE, creditMetadata } from './credits.js';\nimport {\n type AttemptTarget,\n applyPaymentAttempt,\n attemptTarget,\n decideAttempt,\n} from './payment-attempts.js';\nimport type {\n BillingPaymentAttemptState,\n BillingProvider,\n BillingProviderCheckoutState,\n BillingProviderEvent,\n BillingProviderInvoiceState,\n BillingProviderSubscriptionState,\n} from './provider.js';\nimport type { BillingRuntime } from './runtime.js';\nimport { applyAutoTopUpOutcome, autoTopUpPaymentId } from './top-up.js';\nimport { deterministicId, normalizeCurrency, tenantKey } from './units.js';\n\ntype InvoiceEvent = Extract<BillingProviderEvent, { kind: 'invoice' }>;\ntype SubscriptionEvent = Extract<\n BillingProviderEvent,\n { kind: 'subscription' }\n>;\ntype CheckoutEvent = Extract<\n BillingProviderEvent,\n { kind: 'checkout_completed' }\n>;\ntype PaymentEvent = Extract<BillingProviderEvent, { kind: 'payment' }>;\ntype AttemptEvent = Extract<BillingProviderEvent, { kind: 'payment_attempt' }>;\n\ntype ObservedEvent =\n | { event: InvoiceEvent; invoice: BillingProviderInvoiceState }\n | {\n event: SubscriptionEvent;\n subscription: BillingProviderSubscriptionState;\n }\n | {\n event: CheckoutEvent;\n /** The session re-read from the provider, when it can be. */\n checkout: BillingProviderCheckoutState | null;\n /** A card the session saved, already the provider default. */\n card: SavedCard | null;\n }\n | { event: PaymentEvent; paymentId: string }\n | {\n event: AttemptEvent;\n providerName: string;\n attempt: BillingPaymentAttemptState;\n target: AttemptTarget;\n };\n\nconst PROJECTION = 'billing-provider-events';\n\n/** Apply up to `limit` queued events for a runtime; returns how many ran. */\nexport async function processBillingEvents(\n runtime: BillingRuntime,\n limit: number,\n): Promise<number> {\n if (!Number.isSafeInteger(limit) || limit <= 0) {\n throw new Error('limit must be a positive integer.');\n }\n const processor = new ForgeProjectionRuntime({\n db: runtime.db,\n workerId: `billing-${crypto.randomUUID()}`,\n providers: runtime.eventProviders,\n leaseMs: 60_000,\n });\n const projector = createBillingEventProjector(runtime);\n let processed = 0;\n while (processed < limit) {\n const delivery = await processor.processNext(projector);\n if (!delivery) break;\n processed += 1;\n }\n return processed;\n}\n\nexport function createBillingEventProjector(\n runtime: BillingRuntime,\n): ForgeProjector<ObservedEvent> {\n return {\n async observe(\n delivery: ForgeDelivery,\n ): Promise<ForgeObservation<ObservedEvent> | null> {\n // The claim is filtered to this seller's namespace; anything else is\n // released for retry rather than acknowledged as if it were applied.\n const provider = runtime.providerForNamespace(delivery.provider);\n if (\n !provider ||\n tenantKey(delivery.tenantId) !== runtime.sellerTenantId\n ) {\n throw new Error(\n `Billing delivery ${delivery.deliveryId} belongs to another seller.`,\n );\n }\n const event = readEvent(delivery);\n let value: ObservedEvent;\n if (event.kind === 'invoice') {\n value = {\n event,\n invoice: await provider.getInvoice(event.providerInvoiceId),\n };\n } else if (event.kind === 'subscription') {\n value = {\n event,\n subscription: await provider.getSubscription(\n event.providerSubscriptionId,\n ),\n };\n } else if (event.kind === 'checkout_completed') {\n if (provider !== runtime.provider) {\n // Checkouts and saved-card charges belong to the issuing provider;\n // a payment rail reports its checkouts as payment attempts.\n throw new Error(\n `Billing delivery ${delivery.deliveryId}: ${provider.name} does not report checkouts.`,\n );\n }\n value = await observeCheckout(runtime, event);\n } else if (event.kind === 'payment') {\n if (provider !== runtime.provider) {\n // Saved-card charges belong to the issuing provider.\n throw new Error(\n `Billing delivery ${delivery.deliveryId}: ${provider.name} does not charge saved cards.`,\n );\n }\n // Only this seller's auto top-up attempts; another runtime's charge\n // on a shared provider account is not ours to apply.\n const paymentId = autoTopUpPaymentId(event.chargeKey);\n if (!paymentId || !(await isOwnTopUp(runtime, paymentId))) {\n return null;\n }\n value = { event, paymentId };\n } else if (event.kind === 'payment_attempt') {\n const observed = await observeAttempt(runtime, provider, event);\n if (!observed) return null;\n value = observed;\n } else {\n return null;\n }\n // Each event is its own subject: provider state is re-read above, so\n // ordering is resolved by the state, not by a version counter.\n return {\n projection: PROJECTION,\n subjectKey: event.eventId,\n version: 1,\n value,\n };\n },\n\n async project(\n observation: ForgeObservation<ObservedEvent>,\n context: ForgeProjectionContext,\n ): Promise<void> {\n const value = observation.value;\n if ('invoice' in value) {\n await applyInvoiceEvent(\n runtime,\n context.db,\n value.event,\n value.invoice,\n );\n } else if ('subscription' in value) {\n await applySubscriptionState(runtime, context.db, value.subscription);\n } else if ('attempt' in value) {\n await applyAttempt(runtime, context.db, value);\n } else if ('paymentId' in value) {\n await applyAutoTopUpOutcome(runtime, context.db, value.paymentId, {\n status: value.event.status,\n providerPaymentId: value.event.providerPaymentId,\n providerCustomerId: value.event.providerCustomerId,\n amount: value.event.amount,\n currency: value.event.currency,\n subtotal: value.event.subtotal,\n tax: value.event.tax,\n failureCode: value.event.failureCode,\n });\n } else {\n if (value.card) await recordSavedCard(runtime, context.db, value.card);\n await applyCheckout(runtime, context.db, value.event, value.checkout);\n }\n },\n };\n}\n\n/**\n * Re-read a completed checkout (Stripe's event amounts are not ISO minor\n * units) and, when it saved a card for one of this seller's accounts, make it\n * the provider customer's default. That provider write is idempotent, so a\n * retried event repeats it harmlessly.\n */\nasync function observeCheckout(\n runtime: BillingRuntime,\n event: CheckoutEvent,\n): Promise<Extract<ObservedEvent, { checkout: unknown }>> {\n const getCheckout = runtime.provider.getCheckout;\n const checkout = getCheckout\n ? await getCheckout.call(runtime.provider, event.sessionId)\n : null;\n if (checkout && checkout.sessionId !== event.sessionId) {\n throw new Error(\n `Checkout ${event.sessionId} re-read as ${checkout.sessionId}.`,\n );\n }\n const savesCard =\n (event.mode === 'setup' &&\n event.metadata.smrt_purpose === CARD_SETUP_PURPOSE) ||\n event.metadata[SAVE_CARD_METADATA] === '1';\n const collectsAddress = event.metadata[COLLECT_ADDRESS_METADATA] === '1';\n let card: SavedCard | null = null;\n if ((savesCard || collectsAddress) && !checkout) {\n // Never acknowledge a saved card or address the provider cannot report.\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot re-read checkout ${event.sessionId} to apply what it saved.`,\n );\n }\n if (checkout && (savesCard || collectsAddress)) {\n card = await savedCardFromCheckout(\n runtime,\n event.metadata,\n checkout,\n savesCard,\n );\n if (card?.paymentMethodId) {\n const setDefault = runtime.provider.setDefaultPaymentMethod;\n if (!setDefault) {\n throw new Error(\n `Billing provider ${runtime.provider.name} cannot set a default payment method.`,\n );\n }\n await setDefault.call(\n runtime.provider,\n card.providerCustomerId,\n card.paymentMethodId,\n );\n }\n }\n return { event, checkout, card };\n}\n\nasync function isOwnTopUp(\n runtime: BillingRuntime,\n paymentId: string,\n): Promise<boolean> {\n const payments = await PaymentCollection.create({ db: runtime.db });\n const payment = await withSystemContext(() => payments.get(paymentId));\n return Boolean(\n payment &&\n tenantKey(payment.tenantId) === runtime.sellerTenantId &&\n payment.externalProvider === runtime.provider.name,\n );\n}\n\nfunction readEvent(delivery: ForgeDelivery): BillingProviderEvent {\n const event = (delivery.payload as { event?: BillingProviderEvent }).event;\n if (!event || typeof event !== 'object' || !event.kind || !event.eventId) {\n throw new Error(`Billing delivery ${delivery.deliveryId} has no event.`);\n }\n return event;\n}\n\n// ---------------------------------------------------------------------------\n// Invoices\n// ---------------------------------------------------------------------------\n\nasync function applyInvoiceEvent(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n event: InvoiceEvent,\n state: BillingProviderInvoiceState,\n): Promise<void> {\n const invoices = await InvoiceCollection.create({ db });\n const invoice = await invoices.findByExternalId(state.providerInvoiceId);\n if (!invoice?.id || invoice.externalProvider !== runtime.provider.name) {\n return; // Not an invoice this runtime issued.\n }\n const closes = await BillingPeriodCloseCollection.create({ db });\n const [close] = await closes.list({\n where: { invoiceId: invoice.id, sellerTenantId: runtime.sellerTenantId },\n limit: 1,\n });\n if (!close?.id) return;\n const accounts = await BillingAccountCollection.create({ db });\n const account = await accounts.get(close.billingAccountId);\n if (!account) {\n throw new Error(`Billing account ${close.billingAccountId} is missing.`);\n }\n\n let standing: BillingStanding | null = null;\n if (state.status === 'paid') {\n // A skipped event (the rail records this payment itself) leaves the\n // standing to the rail's settlement.\n if (await settlePaidInvoice(runtime, db, invoice, state)) {\n standing = 'current';\n }\n } else if (state.status === 'void') {\n if (\n invoice.status !== InvoiceStatus.PAID &&\n invoice.status !== InvoiceStatus.CANCELLED &&\n invoice.status !== InvoiceStatus.WRITTEN_OFF\n ) {\n invoice.cancel();\n await invoice.save();\n }\n } else if (state.status === 'uncollectible') {\n // Written off at the provider (#3139): written off here too, so it no\n // longer counts as overdue against the payer. It can still be paid (the\n // `paid` branch records the payment and reinstates the payer). Only the\n // first observation changes standing: a late event about an invoice\n // already written off must not undo a later reinstatement. A DRAFT is an\n // invoice whose close parked after the send reached the provider; the\n // close still completes from WRITTEN_OFF once its error is resolved.\n if (\n invoice.status === InvoiceStatus.DRAFT ||\n invoice.status === InvoiceStatus.SENT ||\n invoice.status === InvoiceStatus.VIEWED ||\n invoice.status === InvoiceStatus.PARTIAL ||\n invoice.status === InvoiceStatus.OVERDUE\n ) {\n invoice.status = InvoiceStatus.WRITTEN_OFF;\n await invoice.save();\n standing = 'uncollectible';\n }\n } else if (state.status === 'open') {\n if (event.type === 'payment_failed' || event.type === 'overdue') {\n standing = 'past_due';\n if (\n event.type === 'overdue' &&\n (invoice.status === InvoiceStatus.SENT ||\n invoice.status === InvoiceStatus.VIEWED)\n ) {\n invoice.status = InvoiceStatus.OVERDUE;\n await invoice.save();\n }\n } else if (event.type === 'uncollectible') {\n // A provider that reports written-off invoices as open.\n standing = 'uncollectible';\n }\n }\n if (\n standing &&\n standing !== 'current' &&\n runtime.hasPaymentRails &&\n (await pauseForConfirmingAttempts(\n runtime,\n db,\n String(invoice.id),\n standing,\n ))\n ) {\n // A rail payment for this invoice is confirming (#3138): dunning pauses.\n // If the payment ends without settling, it re-applies this standing.\n standing = null;\n }\n if (standing) {\n await applyStanding(\n runtime,\n db,\n account,\n invoice,\n String(close.id),\n standing,\n );\n }\n}\n\nasync function settlePaidInvoice(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoice: Invoice,\n state: BillingProviderInvoiceState,\n): Promise<boolean> {\n if (invoice.status === InvoiceStatus.PAID) return true;\n if (\n state.paidOutOfBand &&\n runtime.hasPaymentRails &&\n (await railClosedOutOfBand(runtime, db, String(invoice.id)))\n ) {\n // This rail closed the invoice at the issuer (#3138) and records the\n // payment itself in its own event; the issuer collected nothing.\n return false;\n }\n if (invoice.status === InvoiceStatus.DRAFT) {\n // Period close has not recorded the send yet; retry after it has.\n throw new Error(\n `Invoice ${invoice.invoiceNumber} is still a draft; the paid event will be retried.`,\n );\n }\n if (\n state.currency !== normalizeCurrency(invoice.currency) ||\n state.total !== invoice.totalAmount ||\n state.amountPaid !== state.total\n ) {\n throw new Error(\n `Provider invoice ${state.providerInvoiceId} paid ${state.amountPaid} of ${state.total} ${state.currency}; ` +\n `local invoice ${invoice.invoiceNumber} totals ${invoice.totalAmount} ${invoice.currency}.`,\n );\n }\n const allocations = await PaymentAllocationCollection.create({ db });\n if (state.amountPaid > 0) {\n const payments = await PaymentCollection.create({ db });\n const paymentId = await deterministicId([\n 'billing-invoice-payment',\n runtime.provider.name,\n state.providerInvoiceId,\n ]);\n const payment =\n (await payments.get(paymentId)) ??\n (await payments.create({\n id: paymentId,\n tenantId: runtime.sellerTenantId,\n customerId: invoice.customerId,\n amount: state.amountPaid,\n currency: invoice.currency,\n // Closed out of band by someone else (wire, cheque): not a card.\n method: state.paidOutOfBand\n ? PaymentMethod.OTHER\n : PaymentMethod.CREDIT_CARD,\n reference: invoice.invoiceNumber,\n externalId: state.providerInvoiceId,\n externalProvider: runtime.provider.name,\n _insertOnly: true,\n }));\n if (payment.status !== PaymentStatus.COMPLETED) {\n await payment.recordPayment({\n ledgerId: '',\n cashAccountId: runtime.ledger.cashAccountId,\n receivablesAccountId: runtime.ledger.arAccountId,\n });\n }\n const allocationId = await deterministicId([\n 'billing-invoice-allocation',\n paymentId,\n String(invoice.id),\n ]);\n if (!(await allocations.get(allocationId))) {\n await allocations.create({\n id: allocationId,\n tenantId: runtime.sellerTenantId,\n paymentId,\n invoiceId: String(invoice.id),\n amount: state.amountPaid,\n allocatedBy: 'billing-period-close',\n _insertOnly: true,\n });\n }\n }\n // A written-off invoice paid later keeps its terminal status; the payment\n // and allocation above record the recovery.\n if (invoice.status === InvoiceStatus.WRITTEN_OFF) return true;\n invoice.updatePaymentStatus(\n await allocations.getTotalAllocatedToInvoice(String(invoice.id)),\n );\n await invoice.save();\n return true;\n}\n\nconst DELINQUENT_TARGET: Record<\n BillingStanding,\n (status: SubscriptionStatus) => SubscriptionStatus | null\n> = {\n current: (status) =>\n status === 'past_due' || status === 'unpaid' ? 'active' : null,\n past_due: (status) => (status === 'active' ? 'past_due' : null),\n uncollectible: (status) =>\n status === 'active' || status === 'past_due' ? 'unpaid' : null,\n};\n\nasync function applyStanding(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n account: BillingAccount,\n invoice: Invoice,\n closeId: string,\n standing: BillingStanding,\n): Promise<void> {\n if (standing === 'current') {\n // Another unpaid, overdue invoice keeps the payer past due.\n const invoices = await InvoiceCollection.create({ db });\n const overdue = await invoices.list({\n where: { customerId: invoice.customerId, status: InvoiceStatus.OVERDUE },\n });\n if (overdue.some((row) => row.id !== invoice.id)) standing = 'past_due';\n }\n\n // Flat-plan subscriptions billed on this invoice follow the payer's standing.\n const sources = await BillingLineSourceCollection.create({ db });\n const rows = await sources.list({\n where: { periodCloseId: closeId, sourceType: 'subscription_period' },\n });\n const subscriptionIds = [\n ...new Set(rows.map((row) => row.sourceId.split(':')[0])),\n ].filter(Boolean);\n if (subscriptionIds.length > 0) {\n await withSystemContext(async () => {\n const subscriptions = await TenantSubscriptionCollection.create({ db });\n for (const subscription of await subscriptions.list({\n where: { id: subscriptionIds },\n })) {\n const target = DELINQUENT_TARGET[standing](subscription.status);\n if (target) {\n subscription.status = target;\n await subscription.save();\n }\n }\n });\n }\n\n const previous = account.standing;\n if (previous === standing) return;\n account.standing = standing;\n await account.save();\n await runtime.onPayerStanding?.({\n sellerTenantId: runtime.sellerTenantId,\n payerTenantId: account.payerTenantId,\n billingAccountId: String(account.id),\n invoiceId: String(invoice.id),\n previous,\n standing,\n db,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Provider-managed subscriptions\n// ---------------------------------------------------------------------------\n\nasync function applySubscriptionState(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n state: BillingProviderSubscriptionState,\n): Promise<void> {\n await withSystemContext(async () => {\n const subscriptions = await TenantSubscriptionCollection.create({ db });\n const rows = await subscriptions.list({\n where: {\n stripeSubscriptionId: state.providerSubscriptionId,\n externalProvider: runtime.provider.name,\n },\n });\n for (const subscription of rows) {\n subscription.status = state.status;\n subscription.cancelAtPeriodEnd = state.cancelAtPeriodEnd;\n if (state.currentPeriodStart)\n subscription.currentPeriodStart = state.currentPeriodStart;\n if (state.currentPeriodEnd)\n subscription.currentPeriodEnd = state.currentPeriodEnd;\n subscription.canceledAt = state.canceledAt ?? null;\n subscription.trialEndsAt = state.trialEndsAt ?? null;\n await subscription.save();\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Prepaid credit purchases\n// ---------------------------------------------------------------------------\n\nasync function applyCheckout(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n event: CheckoutEvent,\n checkout: BillingProviderCheckoutState | null,\n): Promise<void> {\n const metadata = creditMetadata(event.metadata);\n if (!metadata || metadata.purpose !== CREDIT_PURCHASE_PURPOSE) return;\n if (metadata.sellerTenantId !== runtime.sellerTenantId) return;\n // The re-read session is authoritative; the event's own amounts are used\n // only for a provider that cannot re-read (and reports minor units).\n const paid = checkout ? checkout.complete && checkout.paid : event.paid;\n if (!paid) return; // An async payment settles on a later event.\n const currency = normalizeCurrency(\n (checkout ? checkout.currency : event.currency) ?? '',\n );\n const subtotal = checkout ? checkout.amountSubtotal : event.amountSubtotal;\n const tax = (checkout ? checkout.amountTax : event.amountTax) ?? 0;\n const total = checkout ? checkout.amountTotal : event.amountTotal;\n if (\n currency !== metadata.currency ||\n subtotal !== metadata.amount ||\n !Number.isSafeInteger(tax) ||\n tax < 0 ||\n // Credit is granted only for money collected in full: the total is the\n // credit plus provider tax, with no discount.\n total !== metadata.amount + tax\n ) {\n throw new Error(\n `Checkout ${event.sessionId} collected ${total} (${subtotal} + ${tax} tax) ${currency}; ` +\n `expected ${metadata.amount} ${metadata.currency} plus tax.`,\n );\n }\n\n // The credit lands on the payer-owned (or child-owned) balance policy.\n await withSystemContext(async () => {\n const evaluator = await SpendingPolicyEvaluator.create({ db });\n await evaluator.grantCredit({\n spendingPolicyId: metadata.spendingPolicyId,\n amount: metadata.amount,\n reason: 'Prepaid credit purchase',\n source: `${runtime.provider.name}-checkout`,\n sourceId: event.sessionId,\n ...(metadata.grantedByTenantId\n ? { grantedByTenantId: metadata.grantedByTenantId }\n : {}),\n });\n });\n\n // The cash is recorded in the seller's books against prepaid credit, and\n // any tax collected is moved to tax payable.\n const accounts = await BillingAccountCollection.create({ db });\n const account = await accounts.get(metadata.billingAccountId);\n if (\n !account ||\n tenantKey(account.sellerTenantId) !== runtime.sellerTenantId ||\n tenantKey(account.payerTenantId) !== metadata.payerTenantId\n ) {\n throw new Error(`Checkout ${event.sessionId} has no matching account.`);\n }\n const payments = await PaymentCollection.create({ db });\n const paymentId = await deterministicId([\n 'billing-credit-payment',\n runtime.provider.name,\n event.sessionId,\n ]);\n const payment =\n (await payments.get(paymentId)) ??\n (await payments.create({\n id: paymentId,\n tenantId: runtime.sellerTenantId,\n customerId: account.customerId,\n amount: total,\n currency: metadata.currency,\n method: PaymentMethod.CREDIT_CARD,\n reference: `credit:${metadata.spendingPolicyId}`,\n externalId: event.sessionId,\n externalProvider: runtime.provider.name,\n _insertOnly: true,\n }));\n if (payment.status !== PaymentStatus.COMPLETED) {\n // The payment row, its journal, and the tax reclass commit together, so\n // a replay that finds the payment completed has nothing left to post.\n await payment.recordPayment({\n ledgerId: '',\n cashAccountId: runtime.ledger.cashAccountId,\n receivablesAccountId: runtime.ledger.prepaidCreditAccountId,\n });\n if (tax > 0) {\n const journals = await JournalCollection.create({ db });\n const journal = await journals.create({\n date: new Date(),\n description: `Tax collected on prepaid credit ${event.sessionId}`,\n sourceModule: 'smrt-commerce',\n sourceRef: `${paymentId}:tax`,\n });\n await journal.save();\n await journal.addEntry({\n accountId: runtime.ledger.prepaidCreditAccountId,\n debit: tax,\n memo: `Tax on credit ${metadata.spendingPolicyId}`,\n });\n await journal.addEntry({\n accountId: runtime.ledger.taxAccountId,\n credit: tax,\n memo: `Tax on credit ${metadata.spendingPolicyId}`,\n });\n await journal.post();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Payment-rail attempts (#3138)\n// ---------------------------------------------------------------------------\n\nasync function observeAttempt(\n runtime: BillingRuntime,\n provider: BillingProvider,\n event: AttemptEvent,\n): Promise<ObservedEvent | null> {\n if (!provider.getPaymentAttempt) {\n throw new Error(`Provider ${provider.name} has no payment attempts.`);\n }\n const attempt = await provider.getPaymentAttempt(event.checkoutId);\n if (!attempt) return null; // Not a checkout this rail created.\n const target = attemptTarget(runtime, attempt.metadata);\n if (!target) return null; // Unsigned, foreign, or another seller's.\n const decision = decideAttempt(attempt, runtime.paymentPolicy, target);\n if (decision.action === 'settle' && target.purpose === 'invoice_payment') {\n // Close the issuer's invoice before recording the payment, so it stops\n // dunning. Idempotent, and retried with the event on failure.\n const invoice = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () =>\n (await InvoiceCollection.create({ db: runtime.db })).get(\n target.invoiceId,\n ),\n );\n if (\n invoice?.id &&\n invoice.status !== InvoiceStatus.PAID &&\n invoice.externalProvider === runtime.provider.name &&\n invoice.externalId\n ) {\n if (!runtime.provider.markInvoicePaidOutOfBand) {\n throw new Error(\n `${runtime.provider.name} cannot close invoice ${invoice.invoiceNumber} paid on ${provider.name}.`,\n );\n }\n // The issuer may have collected the invoice itself (a card charge\n // whose event is not applied yet): then it must not be closed out of\n // band, and the rail money becomes customer credit.\n const issuer = await runtime.provider.getInvoice(invoice.externalId);\n if (issuer.status === 'paid' && !issuer.paidOutOfBand) {\n return {\n event,\n providerName: provider.name,\n attempt,\n target: { ...target, issuerCollected: true },\n };\n }\n // Committed before the issuer is told, so the issuer's own `paid`\n // event waits for this settlement instead of recording a payment.\n const row = await runtime.recordPaymentAttemptStart({\n orderId: attempt.orderId,\n provider: provider.name,\n checkoutId: attempt.checkoutId,\n checkoutUrl: attempt.checkoutUrl ?? '',\n purpose: target.purpose,\n payerTenantId: target.payerTenantId,\n billingAccountId: target.billingAccountId,\n invoiceId: target.invoiceId,\n amount: target.amount,\n currency: target.currency,\n });\n if (row.flag === 'out_of_band_without_settlement' || row.resolvedAt) {\n // Closing again after an operator reopened the invoice: this rail\n // owns the close once more, so the old resolution no longer applies.\n await runtime.db.query(\n `UPDATE ${runtime.attempts.tableName}\n SET flag = '', resolved_at = NULL\n WHERE id = ? AND flag = 'out_of_band_without_settlement'`,\n String(row.id),\n );\n }\n if (!row.outOfBandRequestedAt) {\n // Column-scoped, so it cannot overwrite a concurrent writer's fields.\n await runtime.db.query(\n `UPDATE ${runtime.attempts.tableName}\n SET out_of_band_requested_at = ?\n WHERE id = ? AND out_of_band_requested_at IS NULL`,\n new Date().toISOString(),\n String(row.id),\n );\n }\n await runtime.provider.markInvoicePaidOutOfBand(invoice.externalId);\n }\n }\n return { event, providerName: provider.name, attempt, target };\n}\n\nasync function applyAttempt(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n value: Extract<ObservedEvent, { attempt: BillingPaymentAttemptState }>,\n): Promise<void> {\n const outcome = await applyPaymentAttempt(\n runtime,\n db,\n value.providerName,\n value.attempt,\n value.target,\n value.event.eventId.startsWith('poll:') ? 'poll' : 'webhook',\n );\n if (value.target.purpose !== 'invoice_payment') return;\n if (!outcome.settled && !outcome.endedUnsettled) return;\n const invoices = await InvoiceCollection.create({ db });\n const invoice = await withTenant({ tenantId: runtime.sellerTenantId }, () =>\n invoices.get(value.target.invoiceId),\n );\n if (!invoice?.id) return;\n const closes = await BillingPeriodCloseCollection.create({ db });\n const [close] = await closes.list({\n where: { invoiceId: invoice.id, sellerTenantId: runtime.sellerTenantId },\n limit: 1,\n });\n if (!close?.id) return;\n const accounts = await BillingAccountCollection.create({ db });\n const account = await accounts.get(close.billingAccountId);\n if (!account) return;\n if (outcome.settled && (await invoiceSettled(runtime, db, invoice))) {\n await applyStanding(\n runtime,\n db,\n account,\n invoice,\n String(close.id),\n 'current',\n );\n } else if (outcome.endedUnsettled && invoice.status !== InvoiceStatus.PAID) {\n // The dunning paused while this payment confirmed; resume it with the\n // standing the issuer asked for meanwhile, or from the invoice's state.\n const paused = outcome.attempt.pausedStanding;\n const resumed: BillingStanding | null =\n paused === 'past_due' || paused === 'uncollectible'\n ? paused\n : invoice.status === InvoiceStatus.OVERDUE\n ? 'past_due'\n : null;\n if (resumed) {\n await applyStanding(\n runtime,\n db,\n account,\n invoice,\n String(close.id),\n resumed,\n );\n }\n }\n}\n\n/**\n * Paid in full: `PAID`, or a written-off invoice (which keeps its status)\n * whose payments now cover it (#3188).\n */\nasync function invoiceSettled(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoice: Invoice,\n): Promise<boolean> {\n if (invoice.status === InvoiceStatus.PAID) return true;\n if (invoice.status !== InvoiceStatus.WRITTEN_OFF) return false;\n const allocated = await withTenant(\n { tenantId: runtime.sellerTenantId },\n async () =>\n (\n await PaymentAllocationCollection.create({ db })\n ).getTotalAllocatedToInvoice(String(invoice.id)),\n );\n return allocated >= invoice.totalAmount;\n}\n\nconst STANDING_SEVERITY: Record<string, number> = {\n '': 0,\n past_due: 1,\n uncollectible: 2,\n};\n\n/**\n * Record a paused standing on the invoice's confirming rail attempts.\n * Returns whether any attempt is confirming (and so dunning pauses).\n */\nasync function pauseForConfirmingAttempts(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoiceId: string,\n standing: BillingStanding,\n): Promise<boolean> {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n // Lock before reading, so a concurrent writer's fields are not lost.\n await db.query(\n `UPDATE ${attempts.tableName} SET updated_at = updated_at\n WHERE seller_tenant_id = ? AND invoice_id = ? AND status = 'confirming'`,\n runtime.sellerTenantId,\n invoiceId,\n );\n const rows = await attempts.list({\n where: {\n sellerTenantId: runtime.sellerTenantId,\n invoiceId,\n status: 'confirming',\n },\n });\n for (const row of rows) {\n if (\n (STANDING_SEVERITY[standing] ?? 0) >\n (STANDING_SEVERITY[row.pausedStanding] ?? 0)\n ) {\n row.pausedStanding = standing;\n await row.save();\n }\n }\n return rows.length > 0;\n}\n\n/** A rail of this runtime asked the issuer to close this invoice. */\nasync function railClosedOutOfBand(\n runtime: BillingRuntime,\n db: DatabaseInterface,\n invoiceId: string,\n): Promise<boolean> {\n const attempts = await BillingPaymentAttemptCollection.create({ db });\n const rows = await attempts.list({\n where: { sellerTenantId: runtime.sellerTenantId, invoiceId },\n });\n // An operator who reopened the invoice at the issuer resolves the flagged\n // attempt; a later out-of-band close is then someone else's and recorded.\n return rows.some(\n (row) =>\n Boolean(row.outOfBandRequestedAt) &&\n !(row.flag === 'out_of_band_without_settlement' && row.resolvedAt),\n );\n}\n","/**\n * One seller's billing wiring (#3060): its payment provider, ledger accounts,\n * billing-owner reader, and the collections period close and provider events\n * write through.\n */\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { ForgeDeliveryCollection } from '@happyvertical/smrt-jobs';\nimport { JournalCollection } from '@happyvertical/smrt-ledgers';\nimport {\n type AutoTopUpHook,\n BillingAdjustmentCollection,\n type BillingRelationshipReader,\n ClientChargeCollection,\n PriceBookCollection,\n RetailChargeCollection,\n SpendingPolicyCollection,\n SubscriptionPlanCollection,\n TenantSubscriptionCollection,\n} from '@happyvertical/smrt-subscriptions';\nimport { withSystemContext, withTenant } from '@happyvertical/smrt-tenancy';\nimport { CustomerCollection } from '../collections/CustomerCollection.js';\nimport { InvoiceCollection } from '../collections/InvoiceCollection.js';\nimport { InvoiceLineItemCollection } from '../collections/InvoiceLineItemCollection.js';\nimport { PaymentInstrumentCollection } from '../collections/PaymentInstrumentCollection.js';\nimport {\n type BillingAccount,\n BillingAccountCollection,\n type BillingCloseKind,\n BillingLineSourceCollection,\n type BillingPaymentAttempt,\n BillingPaymentAttemptCollection,\n type BillingPaymentAttemptPurpose,\n BillingPeriodCloseCollection,\n type BillingStanding,\n} from '../models/billing.js';\nimport type { Invoice } from '../models/Invoice.js';\nimport { type Address, InvoiceStatus } from '../types/index.js';\nimport {\n type BillingCardOnFile,\n type CreateCardSetupCheckoutInput,\n cardOnFile,\n createCardSetupCheckout,\n} from './cards.js';\nimport {\n type CreateCreditCheckoutInput,\n createCreditCheckout,\n} from './credits.js';\nimport {\n billingPeriodContaining,\n type ScheduledBillingPeriod,\n} from './cycles.js';\nimport { processBillingEvents } from './events.js';\nimport {\n type CreateInvoicePaymentInput,\n createInvoicePayment,\n} from './invoice-payments.js';\nimport {\n type BillingPaymentPolicy,\n type CryptoConversionInput,\n type ManualRefundInput,\n type ManualRefundResult,\n normalizePaymentPolicy,\n recordCryptoConversion,\n recordManualRefund,\n} from './payment-attempts.js';\nimport {\n type ClosePeriodInput,\n closeBillingPeriod,\n type PeriodCloseResult,\n} from './period-close.js';\nimport {\n type BillingProvider,\n type BillingProviderCheckoutSession,\n providerCapabilities,\n} from './provider.js';\nimport {\n type AutoTopUpFailureHook,\n type AutoTopUpHookOptions,\n createAutoTopUpHook,\n} from './top-up.js';\nimport { canonicalTenantId, deterministicId, tenantKey } from './units.js';\n\n/** Ledger accounts billing posts to, in the seller's books. */\nexport interface BillingLedgerAccounts {\n /** Accounts receivable (debited when an invoice is issued). */\n arAccountId: string;\n /** Revenue (credited with the invoice subtotal). */\n revenueAccountId: string;\n /** Tax payable (credited with provider-calculated tax). */\n taxAccountId: string;\n /** Cash or clearing account payments settle into. */\n cashAccountId: string;\n /** Liability credited when prepaid credit is purchased. */\n prepaidCreditAccountId: string;\n /**\n * Asset debited when a crypto rail payment settles and the seller holds\n * the crypto (#3138). Defaults to `cashAccountId`.\n */\n cryptoHoldingsAccountId?: string;\n /** Realized gain or loss when held crypto is converted (#3138). */\n fxGainLossAccountId?: string;\n /** Conversion and network fees (#3138). */\n feesAccountId?: string;\n}\n\n/** A payer's standing changed after a provider invoice event. */\nexport interface PayerStandingChange {\n sellerTenantId: string;\n payerTenantId: string;\n billingAccountId: string;\n invoiceId: string;\n previous: BillingStanding;\n standing: BillingStanding;\n /**\n * The event transaction's database handle. Host writes made through it\n * (for example suspending the tenant) commit or roll back with the event.\n */\n db: DatabaseInterface;\n}\n\n/**\n * Host hook for suspension or reinstatement. It runs inside the event's\n * transaction and may run again if the event is retried, so it must be\n * idempotent.\n */\nexport type PayerStandingHook = (\n change: PayerStandingChange,\n) => void | Promise<void>;\n\nexport interface BillingRuntimeOptions extends SmrtClassOptions {\n /** The tenant that issues invoices. */\n sellerTenantId: string;\n kind: BillingCloseKind;\n /** The issuing provider: it issues, taxes, and sends invoices. */\n provider: BillingProvider;\n /**\n * Additional payment rails (#3138), for example a crypto checkout\n * provider. A payer picks a rail per payment; each rail has its own inbox\n * namespace and webhook route. Names must be unique across all providers.\n */\n paymentProviders?: BillingProvider[];\n /** Per-seller rules for rail payments (#3138). */\n paymentPolicy?: Partial<BillingPaymentPolicy>;\n /** smrt-tenancy's `BillingRelationshipService` (read access required). */\n billingRelationships: BillingRelationshipReader;\n ledger: BillingLedgerAccounts;\n /** Invoice number prefix (default `INV`). */\n invoiceNumberPrefix?: string;\n onPayerStanding?: PayerStandingHook;\n /** How long one worker may hold a period close (default 10 minutes). */\n leaseMs?: number;\n /** Page size for charge scans (default 500). */\n pageSize?: number;\n /**\n * Bill payers whose default card with this provider is on file (saved by\n * {@link BillingRuntime.createCardSetupCheckout} or a credit purchase with\n * `savePaymentMethod`) with automatically charged invoices (#3139): the\n * provider charges the card after the invoice is sent, and the `paid` or\n * `payment_failed` event settles it. Default false (every invoice is sent\n * for payment). Activate service on the `paid` event (a `current`\n * standing), not on send: collection happens on the provider's schedule.\n */\n autoChargeInvoices?: boolean;\n /**\n * Product tax codes for provider-calculated tax (Stripe Tax `txcd_...`,\n * #3194): `credit` classifies prepaid credit (checkout purchases and\n * automatic top-ups), `invoice` the lines of billing-period invoices.\n * Omitted: the provider account's default product tax code.\n */\n taxCodes?: BillingTaxCodes;\n /**\n * Called when an automatic top-up charge ends without collecting (a\n * decline, or the issuer requiring the payer to authenticate), from the\n * hook or the provider's `payment` event (#3139).\n */\n onAutoTopUpFailed?: AutoTopUpFailureHook;\n}\n\n/** Product tax codes (#3194). */\nexport interface BillingTaxCodes {\n credit?: string;\n invoice?: string;\n}\n\nexport interface UpsertBillingAccountInput {\n payerTenantId: string;\n name: string;\n email?: string;\n /** The payer's tax location; required when `automaticTax` is on. */\n billingAddress?: Address;\n taxExempt?: boolean;\n taxId?: string;\n automaticTax?: boolean;\n flatDiscountBasisPoints?: number;\n paymentTermsDays?: number;\n /** Link an existing provider customer (for example from checkout). */\n providerCustomerId?: string;\n /**\n * Billing-cycle anchor (#3116): periods run monthly from this instant\n * (anchor at signup/activation for no partial first period). `null` returns\n * the payer to UTC calendar months; omitted leaves it unchanged. Changing it\n * never bills time twice.\n */\n billingAnchorAt?: Date | null;\n /** Prorate flat plans to the time they were active in a period (#3116). */\n prorateFlatPlans?: boolean;\n}\n\n/** A synced account: its provider customer id is always present. */\nexport interface SyncedBillingAccount {\n account: BillingAccount;\n providerCustomerId: string;\n automaticTax: boolean;\n /** The customer has a billing address country (its tax location). */\n hasTaxLocation: boolean;\n}\n\nexport interface EnsureProviderCustomerOptions {\n /**\n * Refuse a taxed account without a tax location (default true). Card setup\n * turns it off because the address is collected at checkout.\n */\n requireTaxLocation?: boolean;\n}\n\nconst DEFAULT_LEASE_MS = 10 * 60 * 1000;\n\n/**\n * Billing for one seller. Construct once per process with\n * {@link BillingRuntime.create}; register it with `registerBillingRuntime()`\n * to drive it from smrt-jobs.\n *\n * Period close and event processing are privileged system operations: they\n * read every payer's charges and write in a system context. Call them only\n * from trusted host code (a job, an operator action), never from a\n * tenant-facing route.\n */\nexport class BillingRuntime {\n readonly sellerTenantId: string;\n readonly kind: BillingCloseKind;\n readonly provider: BillingProvider;\n readonly billingRelationships: BillingRelationshipReader;\n readonly ledger: BillingLedgerAccounts;\n readonly invoiceNumberPrefix: string;\n readonly onPayerStanding?: PayerStandingHook;\n readonly leaseMs: number;\n readonly pageSize: number;\n readonly autoChargeInvoices: boolean;\n readonly taxCodes: Readonly<BillingTaxCodes>;\n readonly onAutoTopUpFailed?: AutoTopUpFailureHook;\n /** The inbox provider namespace for the issuing provider's events. */\n readonly eventProvider: string;\n /** Per-seller rules for rail payments (#3138). */\n readonly paymentPolicy: BillingPaymentPolicy;\n private readonly providersByName = new Map<string, BillingProvider>();\n readonly attempts: BillingPaymentAttemptCollection;\n\n private constructor(\n options: BillingRuntimeOptions,\n readonly db: DatabaseInterface,\n readonly accounts: BillingAccountCollection,\n readonly closes: BillingPeriodCloseCollection,\n readonly sources: BillingLineSourceCollection,\n readonly customers: CustomerCollection,\n readonly invoices: InvoiceCollection,\n readonly lineItems: InvoiceLineItemCollection,\n readonly journals: JournalCollection,\n readonly charges: ClientChargeCollection,\n readonly adjustments: BillingAdjustmentCollection,\n readonly retailCharges: RetailChargeCollection,\n readonly books: PriceBookCollection,\n readonly subscriptions: TenantSubscriptionCollection,\n readonly plans: SubscriptionPlanCollection,\n readonly policies: SpendingPolicyCollection,\n readonly instruments: PaymentInstrumentCollection,\n attempts: BillingPaymentAttemptCollection,\n ) {\n this.attempts = attempts;\n this.sellerTenantId = canonicalTenantId(\n options.sellerTenantId,\n 'sellerTenantId',\n );\n if (options.kind !== 'provider' && options.kind !== 'reseller') {\n throw new Error('Billing runtime kind must be provider or reseller.');\n }\n this.kind = options.kind;\n this.provider = options.provider;\n this.billingRelationships = options.billingRelationships;\n this.ledger = options.ledger;\n this.invoiceNumberPrefix = options.invoiceNumberPrefix || 'INV';\n this.onPayerStanding = options.onPayerStanding;\n this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;\n this.pageSize = options.pageSize ?? 500;\n this.autoChargeInvoices = options.autoChargeInvoices ?? false;\n this.taxCodes = Object.freeze({ ...options.taxCodes });\n for (const [key, code] of Object.entries(this.taxCodes)) {\n if (code !== undefined && (typeof code !== 'string' || !code.trim())) {\n throw new Error(`taxCodes.${key} must be a non-empty string.`);\n }\n }\n this.onAutoTopUpFailed = options.onAutoTopUpFailed;\n // One inbox namespace per seller: the delivery claim is cross-tenant, so\n // a runtime must never be able to claim another seller's events.\n this.eventProvider = `${options.provider.name}-billing:${this.sellerTenantId}`;\n if (!providerCapabilities(options.provider).issuesInvoices) {\n throw new Error(\n `Provider ${options.provider.name} does not issue invoices; pass it in paymentProviders.`,\n );\n }\n for (const candidate of [\n options.provider,\n ...(options.paymentProviders ?? []),\n ]) {\n if (!candidate?.name || this.providersByName.has(candidate.name)) {\n throw new Error(\n `Billing provider names must be unique and non-empty (${candidate?.name ?? 'missing'}).`,\n );\n }\n this.providersByName.set(candidate.name, candidate);\n }\n this.paymentPolicy = normalizePaymentPolicy(options.paymentPolicy);\n if (!Number.isFinite(this.leaseMs) || this.leaseMs <= 0) {\n throw new Error('leaseMs must be a positive number.');\n }\n if (!Number.isSafeInteger(this.pageSize) || this.pageSize <= 0) {\n throw new Error('pageSize must be a positive integer.');\n }\n const optionalLedger = new Set([\n 'cryptoHoldingsAccountId',\n 'fxGainLossAccountId',\n 'feesAccountId',\n ]);\n for (const [key, value] of Object.entries(options.ledger ?? {})) {\n if (!value && !(optionalLedger.has(key) && value === undefined)) {\n throw new Error(`Ledger account ${key} is required.`);\n }\n }\n }\n\n static async create(options: BillingRuntimeOptions): Promise<BillingRuntime> {\n const {\n sellerTenantId: _seller,\n kind: _kind,\n provider: _provider,\n billingRelationships: _relationships,\n ledger: _ledger,\n invoiceNumberPrefix: _prefix,\n onPayerStanding: _hook,\n paymentProviders: _rails,\n paymentPolicy: _paymentPolicy,\n leaseMs: _leaseMs,\n pageSize: _pageSize,\n autoChargeInvoices: _autoCharge,\n taxCodes: _taxCodes,\n onAutoTopUpFailed: _topUpFailed,\n ...classOptions\n } = options;\n const accounts = await BillingAccountCollection.create(classOptions);\n const shared = { ...classOptions, db: accounts.db };\n return new BillingRuntime(\n options,\n accounts.db,\n accounts,\n await BillingPeriodCloseCollection.create(shared),\n await BillingLineSourceCollection.create(shared),\n await CustomerCollection.create(shared),\n await InvoiceCollection.create(shared),\n await InvoiceLineItemCollection.create(shared),\n await JournalCollection.create(shared),\n await ClientChargeCollection.create(shared),\n await BillingAdjustmentCollection.create(shared),\n await RetailChargeCollection.create(shared),\n await PriceBookCollection.create(shared),\n await TenantSubscriptionCollection.create(shared),\n await SubscriptionPlanCollection.create(shared),\n await SpendingPolicyCollection.create(shared),\n await PaymentInstrumentCollection.create(shared),\n await BillingPaymentAttemptCollection.create(shared),\n );\n }\n\n // -------------------------------------------------------------------------\n // Providers and rails (#3138)\n // -------------------------------------------------------------------------\n\n /** A provider by name; the issuing provider when `name` is omitted. */\n providerFor(name?: string): BillingProvider {\n if (!name) return this.provider;\n const found = this.providersByName.get(name);\n if (!found) {\n throw new Error(`No billing provider named ${name} on this runtime.`);\n }\n return found;\n }\n\n /**\n * Whether any payment rail is configured (#3138). A runtime without one\n * never touches payment attempts, so it needs no attempt table.\n */\n get hasPaymentRails(): boolean {\n return this.providers.some(\n (provider) => providerCapabilities(provider).paymentAttempts,\n );\n }\n\n /** Every provider of this runtime: the issuing one first. */\n get providers(): BillingProvider[] {\n return [...this.providersByName.values()];\n }\n\n /** A provider's inbox namespace (per provider, per seller). */\n eventProviderFor(name?: string): string {\n return `${this.providerFor(name).name}-billing:${this.sellerTenantId}`;\n }\n\n /** Every inbox namespace this runtime claims events from. */\n get eventProviders(): string[] {\n return this.providers.map((provider) =>\n this.eventProviderFor(provider.name),\n );\n }\n\n /** The provider whose inbox namespace this is, or null. */\n providerForNamespace(namespace: string): BillingProvider | null {\n return (\n this.providers.find(\n (provider) => this.eventProviderFor(provider.name) === namespace,\n ) ?? null\n );\n }\n\n // -------------------------------------------------------------------------\n // Accounts\n // -------------------------------------------------------------------------\n\n async getAccount(payerTenantId: string): Promise<BillingAccount | null> {\n return this.accounts.get(await this.accountId(payerTenantId));\n }\n\n private accountId(payerTenantId: string): Promise<string> {\n return deterministicId([\n 'billing-account',\n this.sellerTenantId,\n canonicalTenantId(payerTenantId, 'payerTenantId'),\n ]);\n }\n\n /**\n * Create or update a payer's account and its commerce `Customer` (in the\n * seller's tenant). The customer's billing address is the payer's tax\n * location for provider-calculated tax.\n */\n async upsertAccount(\n input: UpsertBillingAccountInput,\n ): Promise<BillingAccount> {\n const payerTenantId = canonicalTenantId(\n input.payerTenantId,\n 'payerTenantId',\n );\n if (\n input.billingAnchorAt !== undefined &&\n input.billingAnchorAt !== null &&\n !(\n input.billingAnchorAt instanceof Date &&\n Number.isFinite(input.billingAnchorAt.getTime())\n )\n ) {\n throw new Error('billingAnchorAt must be a valid date or null.');\n }\n const id = await this.accountId(payerTenantId);\n const existing = await this.accounts.get(id);\n const customerId = await withTenant(\n { tenantId: this.sellerTenantId },\n async () => {\n const customer = existing?.customerId\n ? await this.customers.get(existing.customerId)\n : null;\n const values = {\n tenantId: this.sellerTenantId,\n ...(input.billingAddress !== undefined\n ? { defaultBillingAddress: input.billingAddress }\n : {}),\n ...(input.taxExempt !== undefined\n ? { taxExempt: input.taxExempt }\n : {}),\n ...(input.taxId !== undefined ? { taxId: input.taxId } : {}),\n };\n if (customer) {\n Object.assign(customer, values);\n await customer.save();\n return String(customer.id);\n }\n const created = await this.customers.create({\n id: await deterministicId(['billing-customer', id]),\n ...values,\n });\n return String(created.id);\n },\n );\n const account =\n existing ??\n (await this.accounts.create({\n id,\n sellerTenantId: this.sellerTenantId,\n payerTenantId,\n customerId,\n name: input.name,\n }));\n account.customerId = customerId;\n account.name = input.name;\n if (input.email !== undefined) account.email = input.email;\n if (input.automaticTax !== undefined)\n account.automaticTax = input.automaticTax;\n if (input.flatDiscountBasisPoints !== undefined)\n account.flatDiscountBasisPoints = input.flatDiscountBasisPoints;\n if (input.paymentTermsDays !== undefined)\n account.paymentTermsDays = input.paymentTermsDays;\n if (input.billingAnchorAt !== undefined)\n account.billingAnchorAt = input.billingAnchorAt;\n if (input.prorateFlatPlans !== undefined)\n account.prorateFlatPlans = input.prorateFlatPlans;\n if (input.providerCustomerId) {\n account.provider = this.provider.name;\n account.providerCustomerId = input.providerCustomerId;\n }\n await account.save();\n return account;\n }\n\n /**\n * Create or update the provider customer, including its tax location.\n * Creation is idempotent per account at the provider (sdk#1268): an\n * attempt that dies between creation and this save finds the same\n * customer on retry.\n */\n async ensureProviderCustomer(\n account: BillingAccount,\n options: EnsureProviderCustomerOptions = {},\n ): Promise<SyncedBillingAccount> {\n const customer = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.customers.get(account.customerId),\n );\n if (!customer) {\n throw new Error(`Billing account ${account.id} has no customer.`);\n }\n const address = customer.defaultBillingAddress ?? {};\n if (\n options.requireTaxLocation !== false &&\n account.automaticTax &&\n !customer.taxExempt &&\n !address.country\n ) {\n throw new Error(\n `Billing account ${account.id} has no tax location; set a billing address country.`,\n );\n }\n const providerCustomerId =\n account.provider === this.provider.name ? account.providerCustomerId : '';\n const synced = await this.provider.syncCustomer({\n accountId: String(account.id),\n providerCustomerId: providerCustomerId || undefined,\n name: account.name,\n email: account.email || undefined,\n billingAddress: address,\n taxExempt: customer.taxExempt,\n });\n if (\n account.provider !== this.provider.name ||\n account.providerCustomerId !== synced.providerCustomerId\n ) {\n account.provider = this.provider.name;\n account.providerCustomerId = synced.providerCustomerId;\n await account.save();\n }\n return {\n account,\n providerCustomerId: synced.providerCustomerId,\n automaticTax: account.automaticTax && !customer.taxExempt,\n hasTaxLocation: Boolean(address.country),\n };\n }\n\n /** Whether the account's customer has a tax location (address country). */\n async hasTaxLocation(account: BillingAccount): Promise<boolean> {\n const customer = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.customers.get(account.customerId),\n );\n return Boolean(customer?.defaultBillingAddress?.country);\n }\n\n /** Whether provider-calculated tax applies to this account's charges. */\n async accountIsTaxed(account: BillingAccount): Promise<boolean> {\n if (!account.automaticTax) return false;\n const customer = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.customers.get(account.customerId),\n );\n return !customer?.taxExempt;\n }\n\n // -------------------------------------------------------------------------\n // Period close\n // -------------------------------------------------------------------------\n\n /**\n * Close billing periods. Without a period, each payer's last ended period\n * on its own schedule (the previous UTC calendar month unless its account\n * has a `billingAnchorAt`), so running it daily is safe. See\n * {@link ClosePeriodInput}.\n */\n closePeriod(input: ClosePeriodInput = {}): Promise<PeriodCloseResult> {\n return closeBillingPeriod(this, input);\n }\n\n /**\n * The payer's billing period containing `at` (default now) on its account's\n * schedule — for example to show the next invoice date. A payer without an\n * account is on calendar months.\n */\n async billingPeriodFor(\n payerTenantId: string,\n at: Date = new Date(),\n ): Promise<ScheduledBillingPeriod> {\n const account = await this.getAccount(payerTenantId);\n return billingPeriodContaining(account?.billingAnchorAt ?? null, at);\n }\n\n async getInvoice(invoiceId: string): Promise<Invoice> {\n const invoice = await withTenant({ tenantId: this.sellerTenantId }, () =>\n this.invoices.get(invoiceId),\n );\n if (!invoice) throw new Error(`Invoice ${invoiceId} was not found.`);\n return invoice;\n }\n\n /** Take the close's lease; returns the token, or null if another holds it. */\n async acquireCloseLease(\n closeId: string,\n now = new Date(),\n ): Promise<string | null> {\n const token = crypto.randomUUID();\n const nowIso = now.toISOString();\n const expires = new Date(now.getTime() + this.leaseMs).toISOString();\n const result = await this.db.query(\n `UPDATE _smrt_billing_period_closes\n SET lease_token = ?, lease_expires_at = ?, updated_at = ?\n WHERE id = ?\n AND (lease_token IS NULL OR lease_expires_at IS NULL\n OR lease_expires_at < ?)\n RETURNING id`,\n token,\n expires,\n nowIso,\n closeId,\n nowIso,\n );\n return result.rows.length === 1 ? token : null;\n }\n\n async releaseCloseLease(closeId: string, token: string): Promise<void> {\n await this.db.query(\n `UPDATE _smrt_billing_period_closes\n SET lease_token = NULL, lease_expires_at = NULL\n WHERE id = ? AND lease_token = ?`,\n closeId,\n token,\n );\n }\n\n // -------------------------------------------------------------------------\n // Provider events\n // -------------------------------------------------------------------------\n\n /**\n * Verify and durably enqueue a provider webhook. Returns `accepted: false`\n * for a duplicate delivery or an event this package does not act on.\n * Respond 2xx whenever this resolves; a verification failure throws\n * `BillingWebhookVerificationError`. An ignored event's `type` names what\n * was ignored: alert on one ending in `:unverified_credit_purchase` (a\n * paid checkout that claims to be a credit purchase but failed\n * verification, for example after a webhook-secret rotation).\n */\n async acceptWebhook(\n payload: string,\n signature: string,\n options: {\n /** The rail whose webhook this is (default the issuing provider). */\n provider?: string;\n /** Request headers, for rails that sign with their own header. */\n headers?: Headers | Record<string, string | undefined>;\n } = {},\n ): Promise<{\n accepted: boolean;\n eventId: string;\n kind: string;\n type?: string;\n }> {\n const provider = this.providerFor(options.provider);\n const event = await provider.verifyWebhook(\n payload,\n signature,\n options.headers,\n );\n if (event.kind === 'ignored') {\n return {\n accepted: false,\n eventId: event.eventId,\n kind: event.kind,\n type: event.type,\n };\n }\n return withTenant({ tenantId: this.sellerTenantId }, async () => {\n const inbox = await ForgeDeliveryCollection.create({ db: this.db });\n const { accepted } = await inbox.accept({\n provider: this.eventProviderFor(provider.name),\n deliveryId: event.eventId,\n eventName: event.kind,\n payload: { event },\n });\n return { accepted, eventId: event.eventId, kind: event.kind };\n });\n }\n\n /** Apply up to `limit` queued provider events; returns how many ran. */\n processEvents(limit = 25): Promise<number> {\n return processBillingEvents(this, limit);\n }\n\n // -------------------------------------------------------------------------\n // Prepaid credit\n // -------------------------------------------------------------------------\n\n /**\n * Start a provider checkout that credits a prepaid balance policy when\n * paid. The caller pays: the policy's tenant for its own balance, or the\n * parent that set a delegated balance.\n */\n createCreditCheckout(\n input: CreateCreditCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n return createCreditCheckout(this, input);\n }\n\n // -------------------------------------------------------------------------\n // Card on file and automatic top-ups (#3139)\n // -------------------------------------------------------------------------\n\n /**\n * Start a provider checkout that saves the payer's card without charging\n * it. When the checkout completes, event processing makes the card the\n * provider customer's default, records it as the payer's default\n * `PaymentInstrument`, and adopts the billing address collected at\n * checkout as its tax location. Callable by the payer or in a system\n * context. Throws when the provider cannot save payment methods.\n */\n createCardSetupCheckout(\n input: CreateCardSetupCheckoutInput,\n ): Promise<BillingProviderCheckoutSession> {\n return createCardSetupCheckout(this, input);\n }\n\n /**\n * Whether the payer has a card on file (#3187): the default card that\n * `autoChargeInvoices` bills and automatic top-ups charge, decided by the\n * same check they use. Read-only; callable by the payer or in a system\n * context.\n */\n cardOnFile(payerTenantId: string): Promise<BillingCardOnFile> {\n return cardOnFile(this, payerTenantId);\n }\n\n /**\n * The `autoTopUp` hook for smrt-subscriptions' `SpendingPolicyEvaluator`:\n * charges the payer's saved card off-session and credits the balance once\n * the charge succeeds. Without a provider that can charge a saved card (or\n * without a card) it never tops up. Enable `payment_intent.*` events on\n * the provider webhook: charges that settle later are credited from them.\n */\n autoTopUpHook(options?: AutoTopUpHookOptions): AutoTopUpHook {\n return createAutoTopUpHook(this, options);\n }\n\n /**\n * Write off a sent, unpaid invoice at the provider (#3139). The provider's\n * `uncollectible` event then moves the payer to `uncollectible` standing.\n * Call only from trusted operator code. Throws when the provider cannot.\n */\n async markInvoiceUncollectible(invoiceId: string): Promise<void> {\n const markUncollectible = this.provider.markInvoiceUncollectible;\n if (!markUncollectible) {\n throw new Error(\n `Billing provider ${this.provider.name} cannot write off invoices.`,\n );\n }\n const invoice = await this.getInvoice(invoiceId);\n if (\n invoice.externalProvider !== this.provider.name ||\n !invoice.externalId ||\n (invoice.status !== InvoiceStatus.SENT &&\n invoice.status !== InvoiceStatus.VIEWED &&\n invoice.status !== InvoiceStatus.PARTIAL &&\n invoice.status !== InvoiceStatus.OVERDUE)\n ) {\n throw new Error(\n `Invoice ${invoice.invoiceNumber} is not a sent, unpaid ${this.provider.name} invoice.`,\n );\n }\n await markUncollectible.call(this.provider, invoice.externalId);\n }\n\n // -------------------------------------------------------------------------\n // Rail payments (#3138)\n // -------------------------------------------------------------------------\n\n /**\n * Pay an issued invoice on a payment rail: opens a rail checkout for the\n * amount due. Settlement records the payment and closes the issuing\n * provider's invoice as paid out of band. Callable by the payer or from a\n * system context.\n */\n createInvoicePayment(\n input: CreateInvoicePaymentInput,\n ): Promise<BillingProviderCheckoutSession> {\n return createInvoicePayment(this, input);\n }\n\n /** Record a rail checkout just opened (idempotent). */\n async recordPaymentAttemptStart(input: {\n /** The rail order id (the checkout idempotency key), when known. */\n orderId?: string;\n provider: string;\n checkoutId: string;\n checkoutUrl: string;\n purpose: BillingPaymentAttemptPurpose;\n payerTenantId: string;\n billingAccountId: string;\n invoiceId?: string;\n spendingPolicyId?: string;\n amount: number;\n currency: string;\n }): Promise<BillingPaymentAttempt> {\n const id = await this.paymentAttemptId(input.provider, input.checkoutId);\n const existing = await this.attempts.get(id);\n if (existing) return existing;\n try {\n return await this.attempts.create({\n id,\n sellerTenantId: this.sellerTenantId,\n payerTenantId: input.payerTenantId,\n billingAccountId: input.billingAccountId,\n purpose: input.purpose,\n invoiceId: input.invoiceId ?? '',\n spendingPolicyId: input.spendingPolicyId ?? '',\n provider: input.provider,\n orderId: input.orderId ?? '',\n checkoutId: input.checkoutId,\n checkoutUrl: input.checkoutUrl,\n amount: input.amount,\n currency: input.currency,\n status: 'open',\n exception: 'none',\n timeline: [\n {\n at: new Date().toISOString(),\n status: 'open',\n exception: 'none',\n source: 'created',\n },\n ],\n _insertOnly: true,\n });\n } catch (error) {\n const concurrent = await this.attempts.get(id);\n if (concurrent) return concurrent;\n throw error;\n }\n }\n\n /** The deterministic id of a rail checkout's attempt row. */\n paymentAttemptId(provider: string, checkoutId: string): Promise<string> {\n return deterministicId(['billing-payment-attempt', provider, checkoutId]);\n }\n\n /**\n * This seller's rail payment attempts, newest first — for \"payment\n * confirming\" displays and operator queues. Every filter is applied in the\n * query, before `limit` (default 100) and `offset`, so paging through\n * `{ unresolved: true }` lists every open exception however old (#3186).\n */\n async listPaymentAttempts(\n filter: {\n payerTenantId?: string;\n invoiceId?: string;\n status?: string;\n /** Only attempts with (`true`) or without (`false`) a flag. */\n flagged?: boolean;\n /**\n * Only open exceptions: flagged attempts no operator has resolved yet.\n * Combine with `flagged: true` or leave `flagged` unset.\n */\n unresolved?: true;\n limit?: number;\n /** Rows to skip, for paging (default 0). */\n offset?: number;\n } = {},\n ): Promise<BillingPaymentAttempt[]> {\n const where: Record<string, unknown> = {\n sellerTenantId: this.sellerTenantId,\n };\n if (filter.payerTenantId) {\n where.payerTenantId = canonicalTenantId(\n filter.payerTenantId,\n 'payerTenantId',\n );\n }\n if (filter.invoiceId) where.invoiceId = filter.invoiceId;\n if (filter.status) where.status = filter.status;\n if (filter.unresolved && filter.flagged === false) {\n throw new Error('An unresolved attempt is always flagged.');\n }\n if (filter.flagged === true || filter.unresolved) where['flag !='] = '';\n else if (filter.flagged === false) where.flag = '';\n if (filter.unresolved) where.resolvedAt = null;\n return this.attempts.list({\n where,\n // `id` breaks created_at ties, so pages neither repeat nor skip rows.\n orderBy: ['created_at DESC', 'id DESC'],\n limit: filter.limit ?? 100,\n offset: filter.offset ?? 0,\n });\n }\n\n /**\n * Queue a re-read of open and confirming attempts (the polling fallback\n * for missed webhooks). Returns how many were queued; apply them with\n * `processEvents()`.\n *\n * Confirming attempts (money seen) go first, then open ones, each oldest\n * first, so a burst of newer or abandoned checkouts cannot starve a paid\n * attempt whose settlement webhook was missed (#3183). Rows leave the set\n * once the gateway reports a terminal state.\n */\n async refreshPaymentAttempts(limit = 100): Promise<number> {\n const rows: BillingPaymentAttempt[] = [];\n for (const status of ['confirming', 'open']) {\n const remaining = limit - rows.length;\n if (remaining <= 0) break;\n rows.push(\n ...(await this.attempts.list({\n where: { sellerTenantId: this.sellerTenantId, status },\n orderBy: 'created_at ASC',\n limit: remaining,\n })),\n );\n }\n let queued = 0;\n for (const row of rows) {\n if (await this.refreshPaymentAttempt(row.provider, row.checkoutId)) {\n queued += 1;\n }\n }\n return queued;\n }\n\n /** Queue a re-read of one rail checkout; false if one is already queued. */\n async refreshPaymentAttempt(\n provider: string,\n checkoutId: string,\n ): Promise<boolean> {\n const rail = this.providerFor(provider);\n if (!providerCapabilities(rail).paymentAttempts) {\n throw new Error(`Provider ${rail.name} has no payment attempts.`);\n }\n // One poll per checkout per minute: a burst of refreshes queues once.\n const bucket = Math.floor(Date.now() / 60_000);\n return withTenant({ tenantId: this.sellerTenantId }, async () => {\n const inbox = await ForgeDeliveryCollection.create({ db: this.db });\n const { accepted } = await inbox.accept({\n provider: this.eventProviderFor(rail.name),\n deliveryId: `poll:${checkoutId}:${bucket}`,\n eventName: 'payment_attempt',\n payload: {\n event: {\n kind: 'payment_attempt',\n eventId: `poll:${checkoutId}:${bucket}`,\n checkoutId,\n },\n },\n });\n return accepted;\n });\n }\n\n /** Close an attempt's flag with an operator note (the flag is kept). */\n async resolvePaymentAttempt(\n attemptId: string,\n resolution: string,\n ): Promise<BillingPaymentAttempt> {\n const attempt = await this.attempts.get(attemptId);\n if (!attempt || tenantKey(attempt.sellerTenantId) !== this.sellerTenantId) {\n throw new Error(`Payment attempt ${attemptId} was not found.`);\n }\n if (!resolution.trim()) throw new Error('A resolution note is required.');\n // Column-scoped: never overwrites what an event or refund wrote.\n await this.db.query(\n `UPDATE ${this.attempts.tableName}\n SET resolution = ?, resolved_at = ?\n WHERE id = ?`,\n resolution.trim(),\n new Date().toISOString(),\n String(attempt.id),\n );\n return (await this.attempts.get(String(attempt.id))) ?? attempt;\n }\n\n /**\n * Record a refund an operator made outside billing (no provider call):\n * posts a reversing journal and, for a credit purchase, removes the\n * credit. Idempotent by `reference`.\n */\n recordManualRefund(input: ManualRefundInput): Promise<ManualRefundResult> {\n return recordManualRefund(this, input);\n }\n\n /**\n * Record converting held crypto to fiat: cash and fees against the\n * holdings' carrying value, with the difference to FX gain/loss.\n * Idempotent by `reference`.\n */\n recordCryptoConversion(\n input: CryptoConversionInput,\n ): Promise<{ journalId: string }> {\n return recordCryptoConversion(this, input);\n }\n\n /** Run in a system context (cross-tenant reads and payer-owned writes). */\n system<T>(operation: () => Promise<T>): Promise<T> {\n return withSystemContext(operation);\n }\n}\n","/**\n * Vendor model - links to Profile with vendor-specific data\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { type VendorOptions, VendorStatus } from '../types/index.js';\n\n/**\n * Vendor represents the vendor/supplier role for a Profile.\n *\n * One Profile can have both Customer and Vendor records - e.g., a company\n * you both buy from and sell to.\n *\n * @example\n * ```typescript\n * const vendor = await vendors.create({\n * profileId: 'profile-uuid',\n * leadTimeDays: 14,\n * minimumOrderAmount: 500,\n * paymentTerms: 'Net 60'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Vendor extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global vendors\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Reference to smrt-profiles Profile\n * Plain string for cross-package reference\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n profileId: string = '';\n\n /**\n * Typical lead time in days for orders\n */\n leadTimeDays: number = 0;\n\n /**\n * Minimum order amount required, in **integer minor units** (cents,\n * satoshis). Compared against `Contract.totalAmount`, which is also minor\n * units (#2401).\n */\n minimumOrderAmount: number = 0;\n\n /**\n * Payment terms for this vendor (e.g., \"Net 60\")\n */\n paymentTerms: string = '';\n\n /**\n * Default currency for transactions with this vendor\n */\n currency: string = 'USD';\n\n /**\n * Default contact email for orders\n */\n defaultContactEmail: string = '';\n\n /**\n * Default contact phone for orders\n */\n defaultContactPhone: string = '';\n\n /**\n * Vendor status\n */\n status: VendorStatus = VendorStatus.ACTIVE;\n\n /**\n * Internal notes about this vendor\n */\n notes: string = '';\n\n /**\n * Per-currency payout destinations for this vendor.\n *\n * Stored as a JSON column. Keys are payout-rail-qualified currency codes —\n * the code must encode both the asset and the rail (e.g. `USDC-base`,\n * `USDC-solana`, `BTC`, `USD-stripe`, `USD-wire`). Values are whatever the\n * receiving rail needs as a destination (EVM address, BTC address, Stripe\n * Connect account id, IBAN, etc.).\n *\n * MVP shape: a flat map on the vendor row. A vendor with no entry for a\n * given currency cannot be paid in that currency — filtering is the\n * consumer's responsibility (the upstream `PaymentIntent` builder filters\n * `paymentOptions` at quote time against this map).\n *\n * Use {@link getPayoutAddress} for a `Map.get`-style lookup that returns\n * `undefined` for missing entries; direct property access is also fine.\n *\n * Sensitive (#1540): payout destinations are excluded from generated API/MCP\n * responses and rejected as a `where` filter key.\n */\n @field({ sensitive: true })\n payoutAddresses: Record<string, string> = {};\n\n constructor(options: VendorOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.leadTimeDays !== undefined)\n this.leadTimeDays = options.leadTimeDays;\n if (options.minimumOrderAmount !== undefined)\n this.minimumOrderAmount = options.minimumOrderAmount;\n if (options.paymentTerms !== undefined)\n this.paymentTerms = options.paymentTerms;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.defaultContactEmail !== undefined)\n this.defaultContactEmail = options.defaultContactEmail;\n if (options.defaultContactPhone !== undefined)\n this.defaultContactPhone = options.defaultContactPhone;\n if (options.status !== undefined) this.status = options.status;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.payoutAddresses !== undefined) {\n this.payoutAddresses = Vendor.normalizePayoutAddresses(\n options.payoutAddresses,\n );\n }\n }\n\n /**\n * The framework's {@link SmrtObject.initialize} re-applies `options` keys on\n * top of constructor-set values so option data always wins over field\n * initializers. That means our constructor's normalization runs *before*\n * `initializePropertiesFromOptions` overwrites `payoutAddresses` with the\n * raw cloned value. Re-normalize once super has finished so consumers\n * who pass a string, a partially-typed map, or `null` end up with a\n * clean `Record<string, string>` before any save / read happens.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.payoutAddresses = Vendor.normalizePayoutAddresses(\n this.payoutAddresses as unknown,\n );\n return this;\n }\n\n /**\n * Save with a final normalization pass so direct field assignments\n * (`vendor.payoutAddresses = somethingFunny`) can't smuggle non-string\n * values into the persisted row.\n */\n override async save(): Promise<this> {\n this.payoutAddresses = Vendor.normalizePayoutAddresses(\n this.payoutAddresses as unknown,\n );\n return super.save() as Promise<this>;\n }\n\n /**\n * Check if vendor is active\n */\n isActive(): boolean {\n return this.status === VendorStatus.ACTIVE;\n }\n\n /**\n * Check if order meets minimum requirements\n */\n meetsMinimumOrder(amount: number): boolean {\n return amount >= this.minimumOrderAmount;\n }\n\n /**\n * Get the profile ID for external lookup.\n *\n * To get the actual Profile object, use the ProfileCollection from smrt-profiles:\n * ```typescript\n * const profiles = await ProfileCollection.create(options);\n * const profile = await profiles.get({ id: vendor.profileId });\n * ```\n *\n * @returns The profile ID or empty string if not set\n */\n getProfileId(): string {\n return this.profileId;\n }\n\n /**\n * Look up the payout destination for a single currency. Returns\n * `undefined` if the vendor has no entry for that currency — caller's\n * job to decide whether that means \"skip the option\" or \"raise a\n * configuration error\". The lookup is case-sensitive; the contract is\n * that the currency code passed in matches exactly what was stored\n * (`USDC-base`, not `usdc-base`).\n */\n getPayoutAddress(currency: string): string | undefined {\n const map = this.payoutAddresses;\n if (!map || typeof map !== 'object') return undefined;\n const value = (map as Record<string, unknown>)[currency];\n return typeof value === 'string' ? value : undefined;\n }\n\n /**\n * Set the payout destination for a single currency. Mutates the live\n * map and returns `this` for chaining. Use {@link clearPayoutAddress}\n * to remove a single entry.\n */\n setPayoutAddress(currency: string, destination: string): this {\n this.payoutAddresses = {\n ...(this.payoutAddresses ?? {}),\n [currency]: destination,\n };\n return this;\n }\n\n /**\n * Remove a single currency entry from the payout map. No-op if the\n * currency was already absent.\n */\n clearPayoutAddress(currency: string): this {\n if (!this.payoutAddresses || !(currency in this.payoutAddresses)) {\n return this;\n }\n const next = { ...this.payoutAddresses };\n delete next[currency];\n this.payoutAddresses = next;\n return this;\n }\n\n /**\n * Normalize an input value (object, pre-serialized JSON string, or\n * `null`/`undefined`) into a plain `Record<string, string>`. Used by\n * the constructor so consumers can pass either shape into\n * `VendorCollection.create({ payoutAddresses: ... })`.\n *\n * Non-string values inside an input object are silently dropped — a\n * `Record<string, string>` invariant is what consumers downstream\n * (`PaymentIntent` builders, payout SDK adapters) rely on.\n */\n private static normalizePayoutAddresses(\n value: unknown,\n ): Record<string, string> {\n if (value == null) return {};\n let parsed: unknown = value;\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value);\n } catch {\n return {};\n }\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return {};\n }\n const out: Record<string, string> = {};\n for (const [key, v] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof v === 'string') out[key] = v;\n }\n return out;\n }\n}\n\nexport default Vendor;\n","/**\n * Contract model with STI for different contract types\n * @packageDocumentation\n */\n\nimport {\n foreignKey,\n type Meta,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport {\n type ContractOptions,\n ContractStatus,\n ContractType,\n type LicenseSaleOptions,\n} from '../types/index.js';\nimport { Customer } from './Customer.js';\nimport { Vendor } from './Vendor.js';\n\n/**\n * Legal status transitions for a Contract (and all its STI subtypes), keyed by\n * the prior persisted status. A status re-saved unchanged (no-op) and a\n * brand-new row are always permitted; this map governs *changes* only.\n *\n * Flow: DRAFT → SENT → (ACCEPTED | DECLINED); ACCEPTED → (COMPLETED |\n * CANCELLED). An estimate/order may also be cancelled directly from DRAFT/SENT.\n * DECLINED / COMPLETED / CANCELLED are terminal. This guards against raw\n * mass-assignment forcing, e.g., a DECLINED contract back to ACCEPTED or a\n * COMPLETED one back to DRAFT. See S5 audit #1390.\n */\nconst CONTRACT_STATUS_TRANSITIONS: Record<ContractStatus, ContractStatus[]> = {\n [ContractStatus.DRAFT]: [\n ContractStatus.SENT,\n ContractStatus.ACCEPTED,\n ContractStatus.DECLINED,\n ContractStatus.CANCELLED,\n ],\n [ContractStatus.SENT]: [\n ContractStatus.ACCEPTED,\n ContractStatus.DECLINED,\n ContractStatus.CANCELLED,\n ],\n [ContractStatus.ACCEPTED]: [\n ContractStatus.COMPLETED,\n ContractStatus.CANCELLED,\n ],\n [ContractStatus.DECLINED]: [],\n [ContractStatus.COMPLETED]: [],\n [ContractStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each Contract instance was loaded with,\n * for the save-time transition guard. WeakMap keeps it out of the schema and\n * GCs with the instance — same pattern as the other commerce models.\n */\nconst loadedContractStatus = new WeakMap<Contract, ContractStatus>();\n\n/**\n * Contract is the base class for all commercial agreements.\n *\n * Uses Single Table Inheritance (STI) to support different contract types:\n * - Estimate: Quote/proposal for customer\n * - Order: Customer purchase order\n * - Lease: Rental/lease agreement\n * - Agreement: Service/maintenance agreement\n * - PurchaseOrder: Order to vendor/supplier\n *\n * @example\n * ```typescript\n * // Create a customer order\n * const order = await contracts.create({\n * _meta_type: 'Order',\n * customerId: customer.id,\n * totalAmount: 150000, // $1,500.00 in cents\n * status: ContractStatus.DRAFT\n * });\n *\n * // Create a purchase order to vendor\n * const po = await contracts.create({\n * _meta_type: 'PurchaseOrder',\n * vendorId: vendor.id,\n * totalAmount: 500000, // $5,000.00 in cents\n * reference: 'PO-2024-001'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Contract extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global contracts\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Contract type discriminator (STI)\n */\n contractType: ContractType = ContractType.ORDER;\n\n /**\n * Current status of the contract\n */\n status: ContractStatus = ContractStatus.DRAFT;\n\n /**\n * Customer ID (for sales contracts: Estimate, Order, Agreement)\n */\n @foreignKey(Customer)\n customerId: string | null = null;\n\n /**\n * Vendor ID (for purchase contracts: PurchaseOrder)\n */\n @foreignKey(Vendor)\n vendorId: string | null = null;\n\n /**\n * Subtotal before tax, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so it is stored\n * as minor units and never as a float — `$19.99` is `1999`, not `19.99`\n * (#2401).\n */\n subtotal: number = 0;\n\n /**\n * Tax amount, in integer minor units (#2401).\n */\n taxAmount: number = 0;\n\n /**\n * Total amount including tax, in integer minor units (#2401).\n */\n totalAmount: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n currency: string = 'USD';\n\n /**\n * Date contract was issued\n */\n issueDate: Date = new Date();\n\n /**\n * Payment due date\n */\n dueDate: Date | null = null;\n\n /**\n * Expiry date (for estimates/quotes)\n */\n expiryDate: Date | null = null;\n\n /**\n * External reference number (PO number, quote number, etc.)\n */\n reference: string = '';\n\n /**\n * Internal notes\n */\n notes: string = '';\n\n /**\n * Terms and conditions\n */\n terms: string = '';\n\n /**\n * Sales channel that owns this contract.\n *\n * Plain string so consumers can model their own channels without an enum\n * change (e.g. `dtc-web`, `wholesale-b2b`, `pos-store-1`, `marketplace-faire`).\n * Empty string means \"unattributed / legacy\".\n */\n channelId: string = '';\n\n /**\n * STI discriminator field\n */\n static readonly _stiField = 'contractType';\n\n constructor(options: ContractOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractType !== undefined)\n this.contractType = options.contractType;\n if (options.status !== undefined) this.status = options.status;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.vendorId !== undefined) this.vendorId = options.vendorId;\n if (options.subtotal !== undefined) this.subtotal = options.subtotal;\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.totalAmount !== undefined)\n this.totalAmount = options.totalAmount;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.issueDate !== undefined) this.issueDate = options.issueDate;\n if (options.dueDate !== undefined) this.dueDate = options.dueDate;\n if (options.expiryDate !== undefined) this.expiryDate = options.expiryDate;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.terms !== undefined) this.terms = options.terms;\n if (options.channelId !== undefined) this.channelId = options.channelId;\n }\n\n /**\n * Check if contract is in draft state\n */\n isDraft(): boolean {\n return this.status === ContractStatus.DRAFT;\n }\n\n /**\n * Check if contract is accepted\n */\n isAccepted(): boolean {\n return this.status === ContractStatus.ACCEPTED;\n }\n\n /**\n * Check if contract is completed\n */\n isCompleted(): boolean {\n return this.status === ContractStatus.COMPLETED;\n }\n\n /**\n * Check if contract is expired (for estimates)\n */\n isExpired(): boolean {\n if (!this.expiryDate) return false;\n return new Date() > this.expiryDate;\n }\n\n /**\n * Check if contract is overdue\n */\n isOverdue(): boolean {\n if (!this.dueDate) return false;\n if (this.status === ContractStatus.COMPLETED) return false;\n return new Date() > this.dueDate;\n }\n\n /**\n * Calculate and update totals from line items\n */\n async recalculateTotals(): Promise<void> {\n // This would typically query ContractLineItems and sum them\n // For now, just ensure totalAmount = subtotal + taxAmount\n this.totalAmount = this.subtotal + this.taxAmount;\n }\n\n /**\n * Capture the status the row was loaded with so the save-time transition\n * guard can reject illegal status flips made via raw field assignment\n * (mass-assignment on the generated update route, a stale caller, etc.).\n * Only persisted rows carry a prior status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedContractStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Validate the amounts and the status transition before persisting, then\n * save. Inherited by every STI subtype (Estimate/Order/Lease/.../LicenseSale),\n * so a forged `status` on any of them is caught here. LicenseSale layers its\n * own rights-immutability guard on top via `super.save()`. See S5 audit\n * #1390.\n *\n * `subtotal` / `taxAmount` / `totalAmount` are mass-assignable on the\n * generated create/update routes, so the minor-units check runs on every\n * write surface (#2401).\n */\n override async save(): Promise<this> {\n assertIntegerMinorUnits('Contract', this.reference || this.id || '<new>', [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['totalAmount', this.totalAmount],\n ]);\n const prior = await this.resolvePriorStatus();\n this.assertContractStatusTransition(prior);\n const result = (await super.save()) as this;\n loadedContractStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Reject an illegal status flip done via raw assignment. No-op transitions\n * and brand-new rows are always allowed.\n */\n protected assertContractStatusTransition(\n prior: ContractStatus | undefined,\n ): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = CONTRACT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Contract ${this.reference || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'.`,\n );\n }\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status (S5 audit #1390 round 5, codex\n * MEDIUM#2). The WeakMap is only populated when {@link initialize} loaded the\n * row from the DB; it is empty for an instance built via\n * `collection.create({ id: <existing>, _skipLoad: true })` — the upsert path\n * that lets a caller write onto an existing row without hydrating it. Trusting\n * an empty WeakMap there would treat the write as a brand-new row and skip the\n * transition guard entirely (a poisonable prior-state). Mirrors the\n * authoritative-prior-load applied to Payment/PaymentIntent/Invoice/Payout.\n *\n * So when this instance carries an `id`, read the persisted row straight from\n * the database and treat its `status` as the prior — a create-onto-existing is\n * an update. Only when no row exists in the DB (truly new) do we fall back to\n * the WeakMap (which is also empty then), i.e. `undefined` = genuinely new.\n * The raw row's `status` column is single-word (no snake_case transform).\n */\n protected async resolvePriorStatus(): Promise<ContractStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as ContractStatus;\n }\n } catch {\n // DB not ready / table absent — fall through to the in-memory record.\n }\n }\n return loadedContractStatus.get(this);\n }\n}\n\n// Every STI child below MUST repeat the `@TenantScoped` decoration. The\n// tenancy registry keys off the concrete className passed by the\n// collection, not the inheritance chain — `OrderCollection.list()` sends\n// `'Order'` to the interceptor, which then misses the `'Contract'`\n// registration and skips tenant auto-filter/auto-populate. Without an\n// explicit `@TenantScoped` on each subclass, saves end up with\n// `tenantId: null` (the row becomes global / invisible to tenant-scoped\n// queries) and lists return rows across tenants.\n\n/**\n * Estimate - Quote or proposal for a customer\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Estimate extends Contract {\n override contractType = ContractType.ESTIMATE;\n}\n\n/**\n * Order - Customer purchase order\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Order extends Contract {\n override contractType = ContractType.ORDER;\n}\n\n/**\n * Lease - Rental or lease agreement\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Lease extends Contract {\n override contractType = ContractType.LEASE;\n}\n\n/**\n * Agreement - Service or maintenance agreement\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Agreement extends Contract {\n override contractType = ContractType.AGREEMENT;\n}\n\n/**\n * PurchaseOrder - Order sent to vendor/supplier\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class PurchaseOrder extends Contract {\n override contractType = ContractType.PURCHASE_ORDER;\n}\n\n/**\n * WholesaleOrder - B2B order placed by a wholesale customer.\n *\n * Behaves like an Order but is conventionally created against a customer with\n * `customerType: 'wholesale'`, uses NET-30/60 payment terms, and is delivered\n * via the wholesale-portal channel rather than retail checkout.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class WholesaleOrder extends Contract {\n override contractType = ContractType.WHOLESALE_ORDER;\n}\n\n/**\n * ProductionOrder - instruction to commission goods from an internal or\n * external factory.\n *\n * The manufacturing equivalent of a {@link PurchaseOrder} — but instead of\n * buying finished inventory, you're paying to *make* it. A ProductionOrder\n * consumes raw materials per a Bill of Materials (see\n * `@happyvertical/smrt-manufacturing`) and produces finished SKU stock when\n * posted (see `@happyvertical/smrt-inventory`).\n *\n * Carries two STI-meta fields that `ProductionService.consumeMaterials` /\n * `runProduction` read from the loaded row to resolve which BOM to walk:\n *\n * - `productId` — plain string ref to the upstream `Product` (or any\n * STI subtype, e.g. apparel `Style`). Used to look up the *active*\n * BOM via `BomService.findActiveForProduct` when no explicit\n * revision was pinned at the time the order was placed.\n * - `bomId` — optional pin to a specific BOM revision. When set, the\n * manufacturing service consumes against that exact revision even\n * if a newer BOM has become active in the meantime (keeps a\n * posted run from silently switching recipes mid-flight).\n *\n * Both fields are `Meta<string>` so the scanner routes them into the\n * shared `_meta_data` JSON column on the Contract STI table instead of\n * widening the base schema with manufacturing-specific columns.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class ProductionOrder extends Contract {\n override contractType = ContractType.PRODUCTION_ORDER;\n\n /**\n * Plain string reference to the upstream product the order is asked\n * to make. Cross-package id; never `@foreignKey()`. Required by\n * `ProductionService.consumeMaterials` unless `bomId` is supplied.\n */\n productId: Meta<string> = '';\n\n /**\n * Optional explicit BOM revision the order locked to at posting\n * time. When set, manufacturing uses this exact row even if the\n * product's active BOM has since changed. When empty, falls back\n * to the active BOM for `productId`.\n */\n bomId: Meta<string> = '';\n}\n\n/**\n * Cart - transient order-in-progress for a shopper.\n *\n * Persisted as a Contract so it can hold the same line items, totals, and\n * customer reference as a real Order, then convert in place (the application\n * promotes the row from `_meta_type: Cart` to `_meta_type: Order` at checkout\n * rather than copying data between tables).\n *\n * A Cart starts in {@link ContractStatus.DRAFT} via the base `Contract.status`\n * default — the subclass doesn't need its own override. Application code\n * is responsible for promoting the row to `ContractStatus.PENDING` /\n * `ACCEPTED` at checkout time; the framework doesn't enforce a state\n * machine on the cart → order transition.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class Cart extends Contract {\n override contractType = ContractType.CART;\n}\n\n/**\n * Snapshot of the rights granted by a {@link LicenseSale}. The fields are\n * intentionally typed (not a generic JSON blob) so they survive schema\n * migrations and downstream consumers (PDF templating, hash-registry\n * publishers) can rely on the shape without re-parsing.\n *\n * Each field is opaque text — the values that make sense vary by\n * industry. A stock-media marketplace might use:\n *\n * - `medium`: `web,print,social`\n * - `distributionScope`: `worldwide` / `territorial`\n * - `exclusivity`: `exclusive` / `non-exclusive`\n * - `duration`: `perpetual` / `12-months` / `event-only`\n * - `territory`: ISO-3166 list or `worldwide`\n *\n * Booleans (`sublicensing`, `derivatives`) are typed as such; everything\n * else is a string so the model doesn't dictate vocabulary.\n */\nexport interface LicenseRightsSnapshot {\n medium: string;\n distributionScope: string;\n exclusivity: string;\n duration: string;\n territory: string;\n sublicensing: boolean;\n derivatives: boolean;\n}\n\n/**\n * Module-scoped WeakMap of \"rights snapshot the row was loaded with\".\n *\n * Used by {@link LicenseSale}'s immutability guard. We can't store the\n * captured snapshot as an instance field because the AST scanner would\n * pick it up as a persisted column; we can't store it as a `Meta<T>`\n * because then it'd round-trip with the row and the comparison would\n * always tautologically succeed. A WeakMap keyed by the instance is\n * the cleanest scope: no schema interaction, garbage-collected with\n * the instance.\n */\nconst issuedRightsSnapshot = new WeakMap<LicenseSale, string>();\n\n/**\n * LicenseSale — industry-neutral licensing primitive.\n *\n * Sits alongside the other Contract STI subtypes (Estimate, Order,\n * Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder,\n * Cart) and represents a single sale of rights for a fee. A\n * stock-media marketplace selling a one-time license, a music-\n * licensing platform issuing a sync license, a code-asset marketplace\n * granting a redistribution right — all use the same primitive.\n *\n * Immutability invariant: once a `LicenseSale` is saved with\n * {@link ContractStatus.ACCEPTED}, its rights snapshot becomes\n * frozen. Re-saving with mutated rights throws. The only legal\n * transition out of ACCEPTED is `revoke()` (which moves status to\n * CANCELLED without changing the rights). If a different snapshot\n * is genuinely needed, the operator's contract is to issue a *new*\n * LicenseSale row and let the old one stand as historical record.\n *\n * All subtype-specific fields are declared as `Meta<T>` so the\n * scanner routes them into the shared `_meta_data` JSON column on\n * the `contracts` STI table — no migration of the base schema needed\n * to land this subtype.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt()\nexport class LicenseSale extends Contract {\n override contractType = ContractType.LICENSE_SALE;\n\n /**\n * Plain string reference to the upstream `Sku` (from\n * `@happyvertical/smrt-products`) that this license was sold against.\n * Cross-package id; never `@foreignKey()`.\n */\n skuId: Meta<string> = '';\n\n /**\n * Plain string reference to the `Payment` that satisfied the\n * purchase. Cross-model reference matches the rest of the\n * package's convention.\n */\n paymentId: Meta<string> = '';\n\n /**\n * The licensee's email address — the primary identity carried\n * forward into PDF generation and any downstream hash-registry\n * publication. Required; without it, downstream rights cannot be\n * attributed.\n */\n licenseeEmail: Meta<string> = '';\n\n /**\n * Optional legal-entity name of the licensee for purchases that\n * warrant it (typically high-tier B2B licenses). Empty string for\n * individual-buyer licenses where the email *is* the legal identity.\n */\n licenseeLegalEntity: Meta<string> = '';\n\n /**\n * Optional ISO-3166 jurisdiction code of the licensee, captured for\n * tax / disclosure / governing-law purposes. Empty when not\n * required.\n */\n licenseeJurisdiction: Meta<string> = '';\n\n // -------- Rights snapshot (immutable once issued) --------\n\n /** Permitted media — `web,print,social`, `broadcast`, etc. */\n rightsMedium: Meta<string> = '';\n\n /** Distribution scope — `worldwide`, `territorial`, `private`, etc. */\n rightsDistributionScope: Meta<string> = '';\n\n /** Exclusivity — `exclusive` / `non-exclusive` / `co-exclusive`. */\n rightsExclusivity: Meta<string> = '';\n\n /** Duration — `perpetual` / `12-months` / `event-only`, etc. */\n rightsDuration: Meta<string> = '';\n\n /** Territory — ISO-3166 list, `worldwide`, region name, etc. */\n rightsTerritory: Meta<string> = '';\n\n /** Whether the licensee may sublicense the rights. */\n rightsSublicensing: Meta<boolean> = false;\n\n /** Whether the licensee may create derivative works. */\n rightsDerivatives: Meta<boolean> = false;\n\n // -------- Artefacts --------\n\n /** URL of the signed PDF (typically a cloud-storage public URL). */\n pdfUrl: Meta<string> = '';\n\n /**\n * SHA-256 of the signed PDF bytes (hex string, no `0x` prefix).\n * Lets downstream consumers verify the PDF the licensee holds is\n * the one this row was issued for, without re-fetching.\n */\n pdfHash: Meta<string> = '';\n\n /**\n * Optional reference to an on-chain hash-registry entry — set when\n * the LicenseSale's `pdfHash` has been anchored on a public chain\n * for tamper-evidence. Format is whatever the hash-registry\n * service emits (e.g. `chain:tx-hash:log-index`). Empty for\n * deployments that don't publish hashes on-chain.\n */\n onChainHashRegistryRef: Meta<string> = '';\n\n constructor(options: LicenseSaleOptions = {}) {\n super(options);\n if (options.skuId !== undefined) this.skuId = options.skuId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.licenseeEmail !== undefined)\n this.licenseeEmail = options.licenseeEmail;\n if (options.licenseeLegalEntity !== undefined)\n this.licenseeLegalEntity = options.licenseeLegalEntity;\n if (options.licenseeJurisdiction !== undefined)\n this.licenseeJurisdiction = options.licenseeJurisdiction;\n if (options.rightsMedium !== undefined)\n this.rightsMedium = options.rightsMedium;\n if (options.rightsDistributionScope !== undefined)\n this.rightsDistributionScope = options.rightsDistributionScope;\n if (options.rightsExclusivity !== undefined)\n this.rightsExclusivity = options.rightsExclusivity;\n if (options.rightsDuration !== undefined)\n this.rightsDuration = options.rightsDuration;\n if (options.rightsTerritory !== undefined)\n this.rightsTerritory = options.rightsTerritory;\n if (options.rightsSublicensing !== undefined)\n this.rightsSublicensing = options.rightsSublicensing;\n if (options.rightsDerivatives !== undefined)\n this.rightsDerivatives = options.rightsDerivatives;\n if (options.pdfUrl !== undefined) this.pdfUrl = options.pdfUrl;\n if (options.pdfHash !== undefined) this.pdfHash = options.pdfHash;\n if (options.onChainHashRegistryRef !== undefined)\n this.onChainHashRegistryRef = options.onChainHashRegistryRef;\n }\n\n /**\n * Once `super.initialize()` has applied the framework's option-\n * override pass, capture the rights snapshot if (and only if) the\n * row arrived already issued. That's how the immutability guard\n * tells \"freshly drafting a license\" (snapshot still mutable) apart\n * from \"loaded an already-issued row\" (snapshot frozen).\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (this.status === ContractStatus.ACCEPTED && (await this.isSaved())) {\n issuedRightsSnapshot.set(this, this.serializeRightsSnapshot());\n }\n return this;\n }\n\n /**\n * Return the current rights snapshot as a typed object. Read-only\n * for callers — mutation should go through assignment to the\n * individual `rights*` fields, gated by the issued-immutability\n * guard.\n */\n getRightsSnapshot(): LicenseRightsSnapshot {\n return {\n medium: this.rightsMedium,\n distributionScope: this.rightsDistributionScope,\n exclusivity: this.rightsExclusivity,\n duration: this.rightsDuration,\n territory: this.rightsTerritory,\n sublicensing: this.rightsSublicensing,\n derivatives: this.rightsDerivatives,\n };\n }\n\n /**\n * Revoke an issued license — moves status to `CANCELLED` without\n * touching the rights snapshot (which stays frozen). Throws if\n * called on a non-issued license; a draft license should be\n * deleted, not revoked.\n */\n revoke(): void {\n if (this.status !== ContractStatus.ACCEPTED) {\n throw new Error(\n `LicenseSale ${this.id ?? '<new>'}: cannot revoke from status '${this.status}' — only ACCEPTED licenses can be revoked`,\n );\n }\n this.status = ContractStatus.CANCELLED;\n }\n\n /**\n * Save with the immutability check: if the row was loaded with\n * `ACCEPTED` status, the rights snapshot at save time must match\n * the snapshot at load time. Anything else means an out-of-band\n * mutation crept in and we surface it instead of letting the row\n * drift silently.\n *\n * Newly-drafted licenses (where the row was constructed without\n * `ACCEPTED` status) capture their snapshot at the moment the\n * caller transitions to `ACCEPTED` and saves — `validateImmutable`\n * picks that up post-save on the next call.\n */\n override async save(): Promise<this> {\n this.validateImmutable();\n const result = (await super.save()) as this;\n // If this save transitioned the row to ACCEPTED, freeze the\n // snapshot for future saves.\n if (\n this.status === ContractStatus.ACCEPTED &&\n !issuedRightsSnapshot.has(this)\n ) {\n issuedRightsSnapshot.set(this, this.serializeRightsSnapshot());\n }\n return result;\n }\n\n /**\n * Compare the current rights against the captured snapshot\n * (populated either at load time by `initialize` or at first\n * issuance by `save`). Throws on mismatch.\n */\n private validateImmutable(): void {\n const captured = issuedRightsSnapshot.get(this);\n if (!captured) return;\n const current = this.serializeRightsSnapshot();\n if (captured !== current) {\n throw new Error(\n `LicenseSale ${this.id ?? '<new>'}: rights snapshot is immutable once issued (status=ACCEPTED). ` +\n 'Use revoke() to cancel the existing license and issue a new one with the updated rights.',\n );\n }\n }\n\n private serializeRightsSnapshot(): string {\n // Stable key ordering so a re-serialization after a no-op\n // assignment doesn't trip the equality check.\n return JSON.stringify({\n medium: this.rightsMedium,\n distributionScope: this.rightsDistributionScope,\n exclusivity: this.rightsExclusivity,\n duration: this.rightsDuration,\n territory: this.rightsTerritory,\n sublicensing: this.rightsSublicensing,\n derivatives: this.rightsDerivatives,\n });\n }\n}\n\nexport default Contract;\n","/**\n * ContractCollection - Collection manager for Contract objects (STI)\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Contract } from '../models/Contract.js';\nimport { ContractStatus, ContractType } from '../types/index.js';\n\nexport class ContractCollection extends SmrtCollection<Contract> {\n static readonly _itemClass = Contract;\n\n /**\n * Find contracts by customer\n *\n * @param customerId - Customer ID\n * @returns Array of contracts\n */\n async findByCustomer(customerId: string): Promise<Contract[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find contracts by vendor\n *\n * @param vendorId - Vendor ID\n * @returns Array of contracts\n */\n async findByVendor(vendorId: string): Promise<Contract[]> {\n return await this.list({\n where: { vendorId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find contracts by type (STI)\n *\n * @param contractType - Contract type\n * @returns Array of contracts\n */\n async findByType(contractType: ContractType): Promise<Contract[]> {\n return await this.list({\n where: { contractType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find contracts by status\n *\n * @param status - Contract status\n * @returns Array of contracts\n */\n async findByStatus(status: ContractStatus): Promise<Contract[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all draft contracts\n *\n * @returns Array of draft contracts\n */\n async findDrafts(): Promise<Contract[]> {\n return await this.findByStatus(ContractStatus.DRAFT);\n }\n\n /**\n * Find all accepted contracts\n *\n * @returns Array of accepted contracts\n */\n async findAccepted(): Promise<Contract[]> {\n return await this.findByStatus(ContractStatus.ACCEPTED);\n }\n\n /**\n * Find overdue contracts\n *\n * @returns Array of overdue contracts\n */\n async findOverdue(): Promise<Contract[]> {\n const now = new Date().toISOString();\n // Get all contracts past due date that aren't completed or cancelled\n const all = await this.list({\n where: {\n 'dueDate <': now,\n },\n orderBy: 'dueDate ASC',\n });\n return all.filter(\n (c) =>\n c.status !== ContractStatus.COMPLETED &&\n c.status !== ContractStatus.CANCELLED,\n );\n }\n\n /**\n * Find expired estimates/quotes\n *\n * @returns Array of expired estimates\n */\n async findExpired(): Promise<Contract[]> {\n const now = new Date().toISOString();\n // Get estimates past expiry date that aren't accepted, declined, or cancelled\n const estimates = await this.list({\n where: {\n contractType: ContractType.ESTIMATE,\n 'expiryDate <': now,\n },\n orderBy: 'expiryDate ASC',\n });\n return estimates.filter(\n (c) =>\n c.status !== ContractStatus.ACCEPTED &&\n c.status !== ContractStatus.DECLINED &&\n c.status !== ContractStatus.CANCELLED,\n );\n }\n\n /**\n * Find contracts in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of contracts\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Contract[]> {\n return await this.list({\n where: {\n 'issueDate >=': startDate.toISOString(),\n 'issueDate <=': endDate.toISOString(),\n },\n orderBy: 'issueDate DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contracts belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contracts for the tenant\n */\n async findByTenant(tenantId: string): Promise<Contract[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contracts (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contracts\n */\n async findGlobal(): Promise<Contract[]> {\n return queryGlobal<Contract>(this);\n }\n\n /**\n * Find contracts for a tenant including global contracts.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contracts\n */\n async findWithGlobals(tenantId: string): Promise<Contract[]> {\n return queryWithGlobals<Contract>(\n this,\n tenantId,\n 'Contract.findWithGlobals',\n );\n }\n}\n","/**\n * ContractLineItem model - individual items on a contract\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ContractLineItemOptions } from '../types/index.js';\n\n/**\n * ContractLineItem represents a single line item on a contract.\n *\n * Line items contain the details of what is being sold/purchased,\n * including quantity, pricing, and optional subscription details.\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * contractId: order.id,\n * description: 'Widget Pro',\n * quantity: 10,\n * unitPrice: 4999, // $49.99 in cents\n * taxRate: 0.08\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ContractLineItem extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract\n */\n @foreignKey('Contract')\n contractId: string = '';\n\n /**\n * Item description\n */\n description: string = '';\n\n /**\n * Quantity ordered. Decimal on purpose — contracts price fractional\n * quantities (hours, weight, bandwidth), as do invoice line items.\n */\n quantity: number = 1.0;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis).\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so `$49.99` is\n * `4999` (#2401).\n */\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2401).\n */\n discount: number = 0;\n\n /**\n * Tax rate as decimal (e.g., 0.08 for 8%). A rate is inherently fractional,\n * so it stays DECIMAL — INTEGER would truncate every meaningful value.\n */\n taxRate: number = 0.0;\n\n /**\n * Calculated line amount (qty * price - discount + tax), in integer minor\n * units. Derived by {@link ContractLineItem.calculateAmount}, which rounds\n * the fractional tax to a whole minor unit (#2401).\n */\n amount: number = 0;\n\n /**\n * Optional product reference (cross-package, plain string)\n */\n @crossPackageRef('@happyvertical/smrt-products:Product')\n productId: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /**\n * Start date (for lease/subscription items)\n */\n startDate: Date | null = null;\n\n /**\n * End date (for lease/subscription items)\n */\n endDate: Date | null = null;\n\n /**\n * Billing period (for subscriptions: 'monthly', 'yearly', etc.)\n */\n billingPeriod: string = '';\n\n /**\n * Sort order within the contract\n */\n sortOrder: number = 0;\n\n constructor(options: ContractLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.startDate !== undefined) this.startDate = options.startDate;\n if (options.endDate !== undefined) this.endDate = options.endDate;\n if (options.billingPeriod !== undefined)\n this.billingPeriod = options.billingPeriod;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n /**\n * Calculate the line amount, in integer minor units.\n *\n * `quantity` is decimal and `taxRate` is a fraction, so the product is not\n * generally a whole number of minor units. Rounding happens here — at the\n * one boundary where a rate meets money — so that every value that reaches a\n * money column is already exact and every downstream comparison can be a\n * plain `===` instead of an epsilon tolerance (#2401).\n */\n calculateAmount(): number {\n const subtotal = this.quantity * this.unitPrice - this.discount;\n const tax = subtotal * this.taxRate;\n return Math.round(subtotal + tax);\n }\n\n /**\n * Check if this is a subscription/recurring item\n */\n isRecurring(): boolean {\n return !!this.billingPeriod && !!this.startDate;\n }\n\n /**\n * Check if subscription is active\n */\n isSubscriptionActive(): boolean {\n if (!this.isRecurring()) return false;\n if (!this.startDate) return false;\n\n const now = new Date();\n if (now < this.startDate) return false;\n if (this.endDate && now > this.endDate) return false;\n\n return true;\n }\n}\n\nexport default ContractLineItem;\n","/**\n * ContractLineItemCollection - Collection manager for ContractLineItem objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { ContractLineItem } from '../models/ContractLineItem.js';\n\nexport class ContractLineItemCollection extends SmrtCollection<ContractLineItem> {\n static readonly _itemClass = ContractLineItem;\n\n /**\n * Find contract line items by their parent contract.\n *\n * @param contractId - Contract ID\n * @returns Array of contract line items, sorted by sortOrder\n */\n async findByContract(contractId: string): Promise<ContractLineItem[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contract line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contract line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<ContractLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contract line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contract line items\n */\n async findGlobal(): Promise<ContractLineItem[]> {\n return queryGlobal<ContractLineItem>(this);\n }\n\n /**\n * Find contract line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contract line items\n */\n async findWithGlobals(tenantId: string): Promise<ContractLineItem[]> {\n return queryWithGlobals<ContractLineItem>(\n this,\n tenantId,\n 'ContractLineItem.findWithGlobals',\n );\n }\n}\n","/**\n * Fulfillment model - tracks delivery of contract items\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type Address,\n type FulfillmentOptions,\n FulfillmentStatus,\n FulfillmentType,\n} from '../types/index.js';\n\n/**\n * Legal status transitions for a Fulfillment, keyed by the prior persisted\n * status. A status re-saved unchanged (no-op) and a brand-new row are always\n * permitted; this map governs *changes* only.\n *\n * Forward order: PENDING → PROCESSING → SHIPPED → DELIVERED, but progress may\n * SKIP intermediate states — not every order has a PROCESSING step, so\n * PENDING → SHIPPED and PENDING/PROCESSING → DELIVERED are deliberately legal.\n * The map only forbids moving BACKWARD (e.g. SHIPPED → PENDING) and leaving a\n * terminal state. CANCELLED is reachable from any non-terminal state.\n * DELIVERED and CANCELLED are terminal. This guards against raw mass-assignment\n * (the open `api:{create,update}` surface, or a stale caller) forcing, e.g., a\n * DELIVERED shipment back to PENDING or a CANCELLED one back into SHIPPED.\n * Mirrors the Contract/Payment/Payout guards added in S5 audit #1390 —\n * Fulfillment was the one money/state model missed.\n */\nconst FULFILLMENT_STATUS_TRANSITIONS: Record<\n FulfillmentStatus,\n FulfillmentStatus[]\n> = {\n [FulfillmentStatus.PENDING]: [\n FulfillmentStatus.PROCESSING,\n FulfillmentStatus.SHIPPED,\n FulfillmentStatus.DELIVERED,\n FulfillmentStatus.CANCELLED,\n ],\n [FulfillmentStatus.PROCESSING]: [\n FulfillmentStatus.SHIPPED,\n FulfillmentStatus.DELIVERED,\n FulfillmentStatus.CANCELLED,\n ],\n [FulfillmentStatus.SHIPPED]: [\n FulfillmentStatus.DELIVERED,\n FulfillmentStatus.CANCELLED,\n ],\n // Terminal states: no outbound transitions.\n [FulfillmentStatus.DELIVERED]: [],\n [FulfillmentStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each Fulfillment instance was loaded with,\n * for the save-time transition guard. A WeakMap keeps it out of the schema and\n * GCs with the instance — same pattern as the other commerce models.\n */\nconst loadedFulfillmentStatus = new WeakMap<Fulfillment, FulfillmentStatus>();\n\n/**\n * Fulfillment tracks the delivery or completion of contract items.\n *\n * A contract can have multiple fulfillments (e.g., partial shipments).\n * Each fulfillment tracks what was delivered and when.\n *\n * @example\n * ```typescript\n * const fulfillment = await fulfillments.create({\n * contractId: order.id,\n * fulfillmentType: FulfillmentType.SHIPMENT,\n * trackingNumber: 'UPS-123456789',\n * carrier: 'UPS',\n * shippingAddress: {\n * street1: '123 Main St',\n * city: 'Anytown',\n * state: 'CA',\n * postalCode: '90210',\n * country: 'US'\n * }\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class Fulfillment extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global fulfillments\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract being fulfilled\n */\n @foreignKey('Contract')\n contractId: string = '';\n\n /**\n * Type of fulfillment\n */\n fulfillmentType: FulfillmentType = FulfillmentType.SHIPMENT;\n\n /**\n * Current fulfillment status\n */\n status: FulfillmentStatus = FulfillmentStatus.PENDING;\n\n /**\n * Tracking number from carrier\n */\n trackingNumber: string = '';\n\n /**\n * Shipping carrier name\n */\n carrier: string = '';\n\n /**\n * Shipping destination address\n */\n shippingAddress: Address = {};\n\n /**\n * When the shipment was sent\n */\n shippedAt: Date | null = null;\n\n /**\n * When the shipment was delivered\n */\n deliveredAt: Date | null = null;\n\n /**\n * Estimated delivery date\n */\n estimatedDelivery: Date | null = null;\n\n /**\n * Notes about this fulfillment\n */\n notes: string = '';\n\n constructor(options: FulfillmentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.fulfillmentType !== undefined)\n this.fulfillmentType = options.fulfillmentType;\n if (options.status !== undefined) this.status = options.status;\n if (options.trackingNumber !== undefined)\n this.trackingNumber = options.trackingNumber;\n if (options.carrier !== undefined) this.carrier = options.carrier;\n if (options.shippingAddress !== undefined)\n this.shippingAddress = options.shippingAddress;\n if (options.shippedAt !== undefined) this.shippedAt = options.shippedAt;\n if (options.deliveredAt !== undefined)\n this.deliveredAt = options.deliveredAt;\n if (options.estimatedDelivery !== undefined)\n this.estimatedDelivery = options.estimatedDelivery;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Capture the status the row was loaded with so the save-time transition\n * guard can reject illegal status flips made via raw field assignment\n * (mass-assignment on the generated update route, a stale caller, etc.).\n * Only persisted rows carry a prior status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n loadedFulfillmentStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Validate the status transition before persisting, then save. Blocks a\n * forged `status` written via raw mass-assignment on the open\n * `api:{create,update}` surface. See S5 audit #1390 follow-up.\n */\n override async save(): Promise<this> {\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n const result = (await super.save()) as this;\n loadedFulfillmentStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Reject an illegal status flip done via raw assignment. No-op transitions\n * and brand-new rows are always allowed.\n */\n private assertStatusTransition(prior: FulfillmentStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = FULFILLMENT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Fulfillment ${this.trackingNumber || this.id}: illegal status ` +\n `transition '${prior}' → '${this.status}'. Use the guarded helpers ` +\n '(markShipped / markDelivered / cancel).',\n );\n }\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status. The WeakMap is only populated when\n * {@link initialize} loaded the row; it is empty for an instance built via\n * `collection.create({ id: <existing>, _skipLoad: true })` (the upsert path\n * that writes onto an existing row without hydrating it). Trusting an empty\n * WeakMap there would treat the write as a brand-new row and skip the guard.\n * So when this instance carries an `id`, read the persisted row straight from\n * the DB and treat its `status` as the prior. `undefined` = genuinely new.\n * Mirrors the authoritative-prior-load on Contract/Payment/Invoice/Payout.\n */\n private async resolvePriorStatus(): Promise<FulfillmentStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as FulfillmentStatus;\n }\n } catch {\n // DB not ready / table absent — fall through to the in-memory record.\n }\n }\n return loadedFulfillmentStatus.get(this);\n }\n\n /**\n * Check if fulfillment is complete\n */\n isDelivered(): boolean {\n return this.status === FulfillmentStatus.DELIVERED;\n }\n\n /**\n * Check if fulfillment is in transit\n */\n isInTransit(): boolean {\n return this.status === FulfillmentStatus.SHIPPED;\n }\n\n /**\n * Check if fulfillment is pending\n */\n isPending(): boolean {\n return this.status === FulfillmentStatus.PENDING;\n }\n\n /**\n * Assert the current in-memory status may legally transition to `next`,\n * surfacing the illegal-transition error at the mutation call site rather\n * than deferring it to save(). A no-op (already in `next`) is allowed.\n */\n private assertCanTransitionTo(next: FulfillmentStatus): void {\n if (this.status === next) return;\n const allowed = FULFILLMENT_STATUS_TRANSITIONS[this.status] ?? [];\n if (!allowed.includes(next)) {\n throw new Error(\n `Fulfillment ${this.trackingNumber || this.id || '<new>'}: cannot move ` +\n `from '${this.status}' to '${next}'.`,\n );\n }\n }\n\n /**\n * Mark as shipped. Rejected from a terminal state (DELIVERED / CANCELLED).\n */\n markShipped(trackingNumber?: string, carrier?: string): void {\n this.assertCanTransitionTo(FulfillmentStatus.SHIPPED);\n this.status = FulfillmentStatus.SHIPPED;\n this.shippedAt = new Date();\n if (trackingNumber) this.trackingNumber = trackingNumber;\n if (carrier) this.carrier = carrier;\n }\n\n /**\n * Mark as delivered. Rejected from CANCELLED (a cancelled shipment can't be\n * delivered).\n */\n markDelivered(): void {\n this.assertCanTransitionTo(FulfillmentStatus.DELIVERED);\n this.status = FulfillmentStatus.DELIVERED;\n this.deliveredAt = new Date();\n }\n\n /**\n * Cancel fulfillment. Rejected once DELIVERED (a delivered shipment can't be\n * cancelled — model a return/refund elsewhere instead).\n */\n cancel(): void {\n this.assertCanTransitionTo(FulfillmentStatus.CANCELLED);\n this.status = FulfillmentStatus.CANCELLED;\n }\n}\n\nexport default Fulfillment;\n","/**\n * FulfillmentCollection - Collection manager for Fulfillment objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Fulfillment } from '../models/Fulfillment.js';\nimport { FulfillmentStatus, type FulfillmentType } from '../types/index.js';\n\nexport class FulfillmentCollection extends SmrtCollection<Fulfillment> {\n static readonly _itemClass = Fulfillment;\n\n /**\n * Find fulfillments by contract\n *\n * @param contractId - Contract ID\n * @returns Array of fulfillments\n */\n async findByContract(contractId: string): Promise<Fulfillment[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find fulfillments by status\n *\n * @param status - Fulfillment status\n * @returns Array of fulfillments\n */\n async findByStatus(status: FulfillmentStatus): Promise<Fulfillment[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find fulfillments by type\n *\n * @param fulfillmentType - Fulfillment type\n * @returns Array of fulfillments\n */\n async findByType(fulfillmentType: FulfillmentType): Promise<Fulfillment[]> {\n return await this.list({\n where: { fulfillmentType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all pending fulfillments\n *\n * @returns Array of pending fulfillments\n */\n async findPending(): Promise<Fulfillment[]> {\n return await this.findByStatus(FulfillmentStatus.PENDING);\n }\n\n /**\n * Find all in-transit shipments\n *\n * @returns Array of shipped fulfillments\n */\n async findInTransit(): Promise<Fulfillment[]> {\n return await this.findByStatus(FulfillmentStatus.SHIPPED);\n }\n\n /**\n * Find by tracking number\n *\n * @param trackingNumber - Tracking number\n * @returns Fulfillment or null\n */\n async findByTracking(trackingNumber: string): Promise<Fulfillment | null> {\n const results = await this.list({\n where: { trackingNumber },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find fulfillments shipped in date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of fulfillments\n */\n async findShippedInRange(\n startDate: Date,\n endDate: Date,\n ): Promise<Fulfillment[]> {\n return await this.list({\n where: {\n 'shippedAt >=': startDate.toISOString(),\n 'shippedAt <=': endDate.toISOString(),\n },\n orderBy: 'shippedAt DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all fulfillments belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of fulfillments for the tenant\n */\n async findByTenant(tenantId: string): Promise<Fulfillment[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global fulfillments (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global fulfillments\n */\n async findGlobal(): Promise<Fulfillment[]> {\n return queryGlobal<Fulfillment>(this);\n }\n\n /**\n * Find fulfillments for a tenant including global fulfillments.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global fulfillments\n */\n async findWithGlobals(tenantId: string): Promise<Fulfillment[]> {\n return queryWithGlobals<Fulfillment>(\n this,\n tenantId,\n 'Fulfillment.findWithGlobals',\n );\n }\n}\n","/**\n * FulfillmentLineItem model - tracks which contract items were fulfilled\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { FulfillmentLineItemOptions } from '../types/index.js';\n\n/**\n * Sub-unit rounding tolerance for the over-fulfillment cap, matching the rest\n * of the package's EPSILON convention. Quantities are decimal, so a tiny\n * float-summation overshoot must not trip the guard.\n */\nconst FULFILLMENT_QUANTITY_EPSILON = 0.01;\n\n/**\n * FulfillmentLineItem tracks which contract line items are included\n * in a specific fulfillment and how much was fulfilled.\n *\n * This allows partial fulfillment - e.g., shipping 5 of 10 items ordered.\n *\n * @example\n * ```typescript\n * const fulfillmentItem = await fulfillmentItems.create({\n * fulfillmentId: fulfillment.id,\n * contractLineItemId: lineItem.id,\n * quantityFulfilled: 5 // of 10 ordered\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class FulfillmentLineItem extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global fulfillment line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent fulfillment\n */\n @foreignKey('Fulfillment')\n fulfillmentId: string = '';\n\n /**\n * Contract line item being fulfilled\n */\n @foreignKey('ContractLineItem')\n contractLineItemId: string = '';\n\n /**\n * Quantity fulfilled in this fulfillment\n */\n quantityFulfilled: number = 1.0;\n\n /**\n * Notes about this line item\n */\n notes: string = '';\n\n constructor(options: FulfillmentLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.fulfillmentId !== undefined)\n this.fulfillmentId = options.fulfillmentId;\n if (options.contractLineItemId !== undefined)\n this.contractLineItemId = options.contractLineItemId;\n if (options.quantityFulfilled !== undefined)\n this.quantityFulfilled = options.quantityFulfilled;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Save-time over-fulfillment guard (S5 audit #1390 follow-up, round 2):\n * - `quantityFulfilled` must be a finite, positive number,\n * - the referenced `ContractLineItem` must resolve **within the caller's\n * tenant scope** AND belong to the same contract as the parent\n * Fulfillment, and\n * - the sum of all FulfillmentLineItems against that ContractLineItem\n * (this row included) must not exceed the ordered `ContractLineItem.quantity`.\n *\n * Without this, a caller could ship 50 of 10 ordered — the direct parallel\n * to the PaymentAllocation over-allocation hole #1390 closed.\n *\n * Tenant isolation (round 2): the ContractLineItem is loaded through\n * `ContractLineItemCollection`, so it goes through the tenancy\n * auto-filtering interceptors. A cross-tenant `contractLineItemId` therefore\n * fails closed — it resolves to `null` and trips the existing \"does not\n * exist\" error rather than leaking a foreign-tenant ordered quantity. The\n * raw `this.db.get('contract_line_items', …)` it replaced bypassed those\n * filters. We additionally load the parent Fulfillment (also tenant-scoped)\n * and reject when the line item belongs to a *different* contract than the\n * Fulfillment is fulfilling, so a valid-but-unrelated line id can't be\n * used to fulfill against the wrong order.\n */\n override async save(): Promise<this> {\n if (\n !Number.isFinite(this.quantityFulfilled) ||\n this.quantityFulfilled <= 0\n ) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: quantityFulfilled must be a ` +\n `positive number (got ${this.quantityFulfilled}).`,\n );\n }\n\n if (this.contractLineItemId) {\n const { ContractLineItemCollection } = await import(\n '../collections/ContractLineItemCollection.js'\n );\n const lineItems = await ContractLineItemCollection.create(this.options);\n const orderedLineItem = await lineItems.get(this.contractLineItemId);\n if (!orderedLineItem) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: referenced ContractLineItem ` +\n `'${this.contractLineItemId}' does not exist — refusing to fulfill ` +\n 'against a missing line item (the over-fulfillment cap cannot be ' +\n 'enforced otherwise).',\n );\n }\n\n // The line item must belong to the same contract the parent Fulfillment\n // is fulfilling. Otherwise a caller could point a Fulfillment for\n // contract A at a line item from contract B (over-fulfilling B's order\n // via A's shipment, and falsifying both contracts' fulfilled totals).\n if (this.fulfillmentId) {\n const { FulfillmentCollection } = await import(\n '../collections/FulfillmentCollection.js'\n );\n const fulfillments = await FulfillmentCollection.create(this.options);\n const fulfillment = await fulfillments.get(this.fulfillmentId);\n if (\n fulfillment &&\n orderedLineItem.contractId !== fulfillment.contractId\n ) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: ContractLineItem ` +\n `'${this.contractLineItemId}' belongs to contract ` +\n `'${orderedLineItem.contractId}', but its Fulfillment ` +\n `'${this.fulfillmentId}' fulfills contract ` +\n `'${fulfillment.contractId}' — refusing to fulfill a line item ` +\n 'from a different contract.',\n );\n }\n }\n\n const ordered = Number(orderedLineItem.quantity);\n\n const { FulfillmentLineItemCollection } = await import(\n '../collections/FulfillmentLineItemCollection.js'\n );\n const siblings = await FulfillmentLineItemCollection.create(this.options);\n const existing = await siblings.findByContractLineItem(\n this.contractLineItemId,\n );\n const otherFulfilled = existing.reduce(\n (sum: number, item: FulfillmentLineItem) =>\n item.id === this.id ? sum : sum + item.quantityFulfilled,\n 0,\n );\n\n if (\n Number.isFinite(ordered) &&\n otherFulfilled + this.quantityFulfilled - ordered >\n FULFILLMENT_QUANTITY_EPSILON\n ) {\n throw new Error(\n `FulfillmentLineItem ${this.id ?? '<new>'}: fulfilling ` +\n `${this.quantityFulfilled} would over-fulfill contract line ` +\n `'${this.contractLineItemId}' — already fulfilled ${otherFulfilled} ` +\n `of ${ordered} ordered.`,\n );\n }\n }\n\n return super.save() as Promise<this>;\n }\n}\n\nexport default FulfillmentLineItem;\n","/**\n * FulfillmentLineItemCollection - Collection manager for FulfillmentLineItem objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { FulfillmentLineItem } from '../models/FulfillmentLineItem.js';\n\nexport class FulfillmentLineItemCollection extends SmrtCollection<FulfillmentLineItem> {\n static readonly _itemClass = FulfillmentLineItem;\n\n /**\n * Find fulfillment line items by their parent fulfillment.\n *\n * @param fulfillmentId - Fulfillment ID\n * @returns Array of fulfillment line items\n */\n async findByFulfillment(\n fulfillmentId: string,\n ): Promise<FulfillmentLineItem[]> {\n return await this.list({\n where: { fulfillmentId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find every fulfillment line item that fulfills a given contract line item,\n * across all fulfillments. Used by the over-fulfillment cap to sum how much\n * of an ordered line has already been fulfilled.\n *\n * @param contractLineItemId - Contract line item ID\n * @returns Array of fulfillment line items targeting that contract line\n */\n async findByContractLineItem(\n contractLineItemId: string,\n ): Promise<FulfillmentLineItem[]> {\n return await this.list({\n where: { contractLineItemId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Sum the quantity already fulfilled for a contract line item across all\n * fulfillments.\n *\n * @param contractLineItemId - Contract line item ID\n * @returns Total quantity fulfilled\n */\n async getTotalFulfilledForContractLine(\n contractLineItemId: string,\n ): Promise<number> {\n const items = await this.findByContractLineItem(contractLineItemId);\n return items.reduce((sum, item) => sum + item.quantityFulfilled, 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all fulfillment line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of fulfillment line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<FulfillmentLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global fulfillment line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global fulfillment line items\n */\n async findGlobal(): Promise<FulfillmentLineItem[]> {\n return queryGlobal<FulfillmentLineItem>(this);\n }\n\n /**\n * Find fulfillment line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global fulfillment line items\n */\n async findWithGlobals(tenantId: string): Promise<FulfillmentLineItem[]> {\n return queryWithGlobals<FulfillmentLineItem>(\n this,\n tenantId,\n 'FulfillmentLineItem.findWithGlobals',\n );\n }\n}\n","/**\n * PaymentIntent — short-lived pre-payment commitment with multi-option\n * semantics.\n *\n * A PaymentIntent is a one-shot quote that locks a USD price for a fixed\n * window and lists one or more `PaymentOption`s describing different\n * payment rails (USDC-on-Base, BTC, Stripe, etc.) that can each satisfy\n * the intent. The first option that receives an incoming payment wins —\n * the intent transitions to {@link PaymentIntentStatus.PAID} and the\n * other options are implicitly retired. Later inbound funds to a\n * retired option must be flagged for refund by the consumer's\n * payment-routing layer.\n *\n * Distinct from `Estimate` (a long-form contract proposal STI subtype\n * of `Contract`): a `PaymentIntent` is short-lived (minutes, not days),\n * carries no line items, and exists to coordinate the moment-of-payment.\n *\n * Industry-neutral: any application that offers more than one route to\n * pay for the same thing — marketplaces, billing systems with multi-\n * provider checkout, agent-driven (x402) flows — benefits.\n *\n * @packageDocumentation\n */\n\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type PaymentIntentOptions,\n PaymentIntentStatus,\n type PaymentOption,\n PaymentStatus,\n} from '../types/index.js';\n\n/*\n * There is deliberately no `PAYMENT_INTENT_EPSILON` here any more (#2401).\n *\n * `PaymentOption.nativeAmount` and `Payment.nativeAmount` / `Payment.amount`\n * are all integer minor units, so reconciling the winning option against the\n * payment that arrived is an exact equality. A tolerance would let a payment\n * that is a cent short satisfy an option it does not actually cover.\n */\n\n/**\n * Legal status transitions for a PaymentIntent, keyed by the prior persisted\n * status. Mirrors the state machine documented on {@link PaymentIntentStatus}\n * and enforced by the dedicated helpers, so a raw `status` mass-assignment on\n * the generated update route can't skip steps (e.g. `awaiting_payment →\n * issued`) or revive a terminal state.\n *\n * `awaiting_payment → paid → (issued | retired)`, with `expired` / `cancelled`\n * as alternate terminal exits from `awaiting_payment`. `issued` may still be\n * `retired` (a reversal after rights were issued).\n */\nconst PAYMENT_INTENT_STATUS_TRANSITIONS: Record<\n PaymentIntentStatus,\n PaymentIntentStatus[]\n> = {\n [PaymentIntentStatus.AWAITING_PAYMENT]: [\n PaymentIntentStatus.PAID,\n PaymentIntentStatus.EXPIRED,\n PaymentIntentStatus.CANCELLED,\n ],\n [PaymentIntentStatus.PAID]: [\n PaymentIntentStatus.ISSUED,\n PaymentIntentStatus.RETIRED,\n ],\n [PaymentIntentStatus.ISSUED]: [PaymentIntentStatus.RETIRED],\n // Terminal states.\n [PaymentIntentStatus.RETIRED]: [],\n [PaymentIntentStatus.EXPIRED]: [],\n [PaymentIntentStatus.CANCELLED]: [],\n};\n\n/**\n * Module-scoped record of the status each PaymentIntent was loaded with, so\n * the save-time guard can detect a status being forced to PAID via raw\n * mass-assignment (bypassing the verified transition path). Same WeakMap\n * pattern as the rest of the package.\n */\nconst loadedIntentStatus = new WeakMap<PaymentIntent, PaymentIntentStatus>();\n\n/**\n * Default price-lock window — 15 minutes is a reasonable middle ground:\n * long enough for a buyer to swap wallets / approve a Stripe payment,\n * short enough that volatile-currency drift stays bounded.\n */\nconst DEFAULT_PRICE_LOCK_WINDOW_MS = 15 * 60 * 1000;\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Natural-key idempotency: a consumer that retries `create` with the\n // same (offeringRef, licenseeEmail, idempotencyKey) within the\n // retention window will hit the existing row via upsert rather than\n // creating a duplicate. Tenant id is part of the key so the same\n // idempotency key never collides across tenants.\n conflictColumns: [\n 'tenant_id',\n 'offering_ref',\n 'licensee_email',\n 'idempotency_key',\n ],\n // ROOT FIX (S5 audit #1390 round 4): the generated create/update surface may\n // only set the quote-definition fields. The PAID-state backing fields\n // (`status`, `paymentId`, `paidOptionBackendId`) are EXCLUDED — they are set\n // exclusively by the verified `verifyAndMarkPaid()` / `markIssued()` path, so\n // a caller can never forge a PAID intent (or repoint one) through a generated\n // route. `paymentOptions` IS writable (a quote may be re-priced while\n // AWAITING_PAYMENT) but is frozen by `save()` once the intent is PAID/ISSUED\n // (codex HIGH#2). Timestamps + priceLockExpiresAt are derived and excluded.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'skuId',\n 'offeringRef',\n 'licenseeEmail',\n 'customerId',\n 'paymentOptions',\n 'usdPriceLocked',\n 'priceLockWindowMs',\n 'idempotencyKey',\n 'notes',\n ],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class PaymentIntent extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable so the same model\n * can serve both per-tenant SaaS deployments and single-tenant /\n * global setups.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Plain string reference to the upstream `Sku` (from\n * `@happyvertical/smrt-products`) being purchased. Cross-package\n * reference — plain string, not `@foreignKey()`, to avoid the\n * circular dependency the framework warns against.\n *\n * Required for the marketplace flow; other consumers can leave it\n * empty if they don't model a catalog.\n */\n skuId: string = '';\n\n /**\n * Caller-supplied scope for the idempotency-key natural key. The\n * marketplace sets this to a `Sku` id, but the field is intentionally\n * abstract so other consumers (subscription tiers, donation tiers,\n * tipping flows) can scope idempotency by whatever granularity makes\n * sense.\n *\n * Empty string is permitted but disables the natural-key dedup —\n * every retry then creates a fresh row.\n */\n offeringRef: string = '';\n\n /**\n * The buyer's email address. The licensee on any downstream\n * `LicenseSale` (or similar rights-issuance row) is identified by\n * this email — high-tier purchases also create a `Customer` record\n * and set {@link customerId}, but most purchases get away with email\n * alone.\n */\n licenseeEmail: string = '';\n\n /**\n * Optional link to a `Customer` row for purchases that warrant a\n * full customer record (high-tier licensing, recurring buyers,\n * etc.). Cross-model reference is kept as a plain string to match\n * the rest of the package's cross-package convention.\n */\n customerId: string = '';\n\n /**\n * The payment rails the buyer can choose between to satisfy this\n * intent. Stored as a JSON column. See {@link PaymentOption} for\n * the per-option shape. Empty array means the intent is invalid /\n * mis-built; callers should reject save in that case.\n */\n paymentOptions: PaymentOption[] = [];\n\n /**\n * USD price locked at quote time, in **integer minor units** (US cents).\n * Independent of any specific option's native-currency amount;\n * `usdPriceLocked` is the canonical \"what this costs in USD\"\n * number used downstream for reporting, tax, and drift accounting (#2401).\n */\n usdPriceLocked: number = 0;\n\n /**\n * Length of the price-lock window in milliseconds. Defaults to 15\n * minutes; consumers can override per-intent. Stored so a later\n * audit can see the original window even after {@link priceLockExpiresAt}\n * has passed.\n */\n priceLockWindowMs: number = DEFAULT_PRICE_LOCK_WINDOW_MS;\n\n /**\n * Wall-clock time at which the price lock expires. Set by the\n * constructor (or by `expire()` if you want to short-circuit a\n * specific intent). Once `Date.now()` passes this value and the\n * status is still `AWAITING_PAYMENT`, callers should treat the\n * intent as expired and refuse to record a payment against it.\n */\n priceLockExpiresAt: Date | null = null;\n\n /**\n * Current status — see {@link PaymentIntentStatus} for the state\n * machine. Mutate via the dedicated `markPaid` / `markIssued` /\n * `expire` / `cancel` / `retire` helpers rather than assigning\n * directly, so the helpers' invariant checks run.\n */\n status: PaymentIntentStatus = PaymentIntentStatus.AWAITING_PAYMENT;\n\n /**\n * Caller-supplied idempotency key. Combined with `tenantId`,\n * `offeringRef`, and `licenseeEmail`, this forms the natural key\n * registered in `conflictColumns` — a retried `create` with the\n * same tuple upserts the existing row instead of creating a\n * duplicate.\n *\n * Empty string disables natural-key dedup (every retry creates a\n * fresh row).\n */\n idempotencyKey: string = '';\n\n /**\n * `backendId` of the option that satisfied the intent. Set by\n * {@link markPaid}; remains empty for non-paid intents. Used by\n * {@link isOptionRetired} to flag inbound funds on the other\n * options as \"needs refund\".\n */\n paidOptionBackendId: string = '';\n\n /**\n * Plain string reference to the `Payment` row that satisfied the\n * intent. Cross-model reference matches the rest of the\n * package's convention.\n */\n paymentId: string = '';\n\n /**\n * When the intent transitioned to `PAID`.\n */\n paidAt: Date | null = null;\n\n /**\n * When the intent transitioned to `ISSUED`.\n */\n issuedAt: Date | null = null;\n\n /**\n * When the intent transitioned to `EXPIRED`.\n */\n expiredAt: Date | null = null;\n\n /**\n * When the intent transitioned to `CANCELLED`.\n */\n cancelledAt: Date | null = null;\n\n /**\n * When the intent transitioned to `RETIRED`.\n */\n retiredAt: Date | null = null;\n\n /**\n * Optional human-readable notes (e.g., support tickets, refund\n * memos, cancellation reasons). Append-only by convention.\n */\n notes: string = '';\n\n constructor(options: PaymentIntentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.skuId !== undefined) this.skuId = options.skuId;\n if (options.offeringRef !== undefined)\n this.offeringRef = options.offeringRef;\n if (options.licenseeEmail !== undefined)\n this.licenseeEmail = options.licenseeEmail;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.paymentOptions !== undefined)\n this.paymentOptions = PaymentIntent.normalizePaymentOptions(\n options.paymentOptions,\n );\n if (options.usdPriceLocked !== undefined)\n this.usdPriceLocked = options.usdPriceLocked;\n if (options.priceLockWindowMs !== undefined)\n this.priceLockWindowMs = options.priceLockWindowMs;\n if (options.priceLockExpiresAt !== undefined) {\n this.priceLockExpiresAt = PaymentIntent.coerceDate(\n options.priceLockExpiresAt,\n );\n }\n if (options.status !== undefined) this.status = options.status;\n if (options.idempotencyKey !== undefined)\n this.idempotencyKey = options.idempotencyKey;\n if (options.paidOptionBackendId !== undefined)\n this.paidOptionBackendId = options.paidOptionBackendId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.paidAt !== undefined)\n this.paidAt = PaymentIntent.coerceDate(options.paidAt);\n if (options.issuedAt !== undefined)\n this.issuedAt = PaymentIntent.coerceDate(options.issuedAt);\n if (options.expiredAt !== undefined)\n this.expiredAt = PaymentIntent.coerceDate(options.expiredAt);\n if (options.cancelledAt !== undefined)\n this.cancelledAt = PaymentIntent.coerceDate(options.cancelledAt);\n if (options.retiredAt !== undefined)\n this.retiredAt = PaymentIntent.coerceDate(options.retiredAt);\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Re-normalize `paymentOptions` after the framework's\n * `initializePropertiesFromOptions` pass has overwritten the\n * constructor-set values with the raw cloned input, and stamp a\n * default `priceLockExpiresAt` so newly-created intents have a\n * concrete expiry without callers needing to compute it.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.paymentOptions = PaymentIntent.normalizePaymentOptions(\n this.paymentOptions as unknown,\n );\n this.priceLockExpiresAt = PaymentIntent.coerceDate(this.priceLockExpiresAt);\n this.paidAt = PaymentIntent.coerceDate(this.paidAt);\n this.issuedAt = PaymentIntent.coerceDate(this.issuedAt);\n this.expiredAt = PaymentIntent.coerceDate(this.expiredAt);\n this.cancelledAt = PaymentIntent.coerceDate(this.cancelledAt);\n this.retiredAt = PaymentIntent.coerceDate(this.retiredAt);\n if (!this.priceLockExpiresAt) {\n // Anchor the price-lock window to \"now\" if no explicit expiry\n // was supplied. Using the configured window so consumers that\n // override `priceLockWindowMs` get the right anchor.\n this.priceLockExpiresAt = new Date(\n Date.now() + (this.priceLockWindowMs || DEFAULT_PRICE_LOCK_WINDOW_MS),\n );\n }\n if (await this.isSaved()) {\n loadedIntentStatus.set(this, this.status);\n }\n return this;\n }\n\n /**\n * Save-time state-machine guard (S5 audit #1390 round 2).\n *\n * `status`, `paymentId`, and `paidOptionBackendId` are all mass-assignable on\n * the generated update route, and the sync `markPaid` helper trusts its\n * arguments. Two distinct attacks follow:\n *\n * 1. **Forge a PAID intent** — set `status: 'paid'` (or call bare `markPaid`)\n * against a bogus / pending / non-matching Payment. This falsifies\n * downstream \"this was paid for\" rights issuance.\n * 2. **Repoint an already-PAID intent** — leave `status: 'paid'` but swap\n * `paymentId` / `paidOptionBackendId` to a bogus Payment after the fact.\n * Round 1 only verified the *transition into* PAID, so an already-PAID\n * row could be silently repointed with no re-verify. Likewise an intent\n * forced straight to `ISSUED` (the terminal happy-path that gates rights\n * issuance) was never required to have been backed by a real Payment.\n *\n * This guard therefore:\n * - validates the status transition is legal (no `awaiting_payment → issued`\n * skips, no reviving terminal states);\n * - re-verifies the backing Payment on **every** save where the persisted\n * status is PAID or ISSUED — not just the transition edge — so a repoint\n * of the paid backing fields is caught.\n *\n * Re-verifying an unchanged PAID/ISSUED row is cheap (one Payment load) and\n * closes the repoint hole; freezing the backing fields would instead block\n * legitimate corrections, so we re-verify.\n */\n override async save(): Promise<this> {\n const priorRow = await this.loadPersistedRow();\n const prior = priorRow\n ? (priorRow.status as PaymentIntentStatus)\n : loadedIntentStatus.get(this);\n this.assertStatusTransition(prior);\n\n // Freeze the backing fields once the intent is already PAID/ISSUED in the\n // database (codex HIGH#2). Round 3 re-verified the backing Payment but did\n // so against the *mutable* in-memory `paymentOptions`, so a caller could\n // swap `paymentId` to a different completed Payment AND edit the matching\n // option's `nativeAmount` so the reconciliation still passed — silently\n // repointing a settled intent at unrelated funds. A PAID/ISSUED intent's\n // economic identity (paymentId + paidOptionBackendId + usdPriceLocked +\n // paymentOptions) is settled and must not change; corrections go through\n // `retire()` + a fresh intent.\n if (\n priorRow &&\n (priorRow.status === PaymentIntentStatus.PAID ||\n priorRow.status === PaymentIntentStatus.ISSUED)\n ) {\n this.assertBackingFieldsUnchanged(priorRow);\n }\n\n const persistingPaidOrIssued =\n this.status === PaymentIntentStatus.PAID ||\n this.status === PaymentIntentStatus.ISSUED;\n\n if (persistingPaidOrIssued) {\n await this.assertBackedByCompletedPayment();\n }\n\n const result = (await super.save()) as this;\n loadedIntentStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Load the authoritative persisted row for this intent (S5 audit #1390 round\n * 4). Returns `undefined` when the intent has no `id` or no row exists yet\n * (truly new). Reading the DB directly — rather than trusting the\n * {@link loadedIntentStatus} WeakMap, which is only populated when\n * {@link initialize} hydrated the row — defeats the poisonable-prior-state\n * vector where `create({ id: <existing>, _skipLoad: true })` produces an\n * un-hydrated instance whose WeakMap entry is missing. A create-onto-existing\n * is thus correctly treated as an update.\n */\n private async loadPersistedRow(): Promise<\n Record<string, unknown> | undefined\n > {\n if (!this.id) return undefined;\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n return row ?? undefined;\n } catch {\n // DB not ready / table absent — treat as new (in-memory fallback in save).\n return undefined;\n }\n }\n\n /**\n * Reject any change to the settled backing fields of an already-PAID/ISSUED\n * intent (codex HIGH#2). `paymentOptions` is compared structurally because\n * the winning option's `nativeAmount` is exactly what the reconciliation in\n * {@link assertBackedByCompletedPayment} binds against — letting it drift\n * would let a repointed `paymentId` match a doctored option.\n */\n private assertBackingFieldsUnchanged(\n priorRow: Record<string, unknown>,\n ): void {\n const frozen: Array<[string, unknown, unknown]> = [\n ['paymentId', priorRow.payment_id ?? '', this.paymentId],\n [\n 'paidOptionBackendId',\n priorRow.paid_option_backend_id ?? '',\n this.paidOptionBackendId,\n ],\n [\n 'usdPriceLocked',\n Number(priorRow.usd_price_locked ?? 0),\n Number(this.usdPriceLocked ?? 0),\n ],\n ];\n for (const [field, prior, next] of frozen) {\n if (prior !== next) {\n throw new Error(\n `PaymentIntent ${this.id}: '${field}' is frozen once the intent is ` +\n `PAID/ISSUED (was '${prior}', got '${next}'). A settled intent's ` +\n 'backing fields cannot be repointed — retire() it and issue a new intent.',\n );\n }\n }\n\n const priorOptions = PaymentIntent.normalizePaymentOptions(\n priorRow.payment_options as unknown,\n );\n const nextOptions = PaymentIntent.normalizePaymentOptions(\n this.paymentOptions as unknown,\n );\n if (JSON.stringify(priorOptions) !== JSON.stringify(nextOptions)) {\n throw new Error(\n `PaymentIntent ${this.id}: 'paymentOptions' is frozen once the intent ` +\n 'is PAID/ISSUED — the winning option backs the verified Payment ' +\n 'reconciliation and cannot change. retire() it and issue a new intent.',\n );\n }\n }\n\n /**\n * Reject an illegal status flip done via raw field assignment. Compares the\n * about-to-be-written status against the status the row was loaded with.\n * No-op re-saves (status unchanged) and brand-new rows are allowed (the\n * backing-Payment verification still runs separately for PAID/ISSUED).\n */\n private assertStatusTransition(prior: PaymentIntentStatus | undefined): void {\n if (prior === undefined) return; // new row\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYMENT_INTENT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `PaymentIntent ${this.id}: illegal status transition '${prior}' → ` +\n `'${this.status}'. Use the guarded transition helpers ` +\n '(verifyAndMarkPaid / markIssued / expire / cancel / retire).',\n );\n }\n }\n\n /**\n * Verify the intent's `paidOptionBackendId` / `paymentId` reference a real,\n * COMPLETED, amount-matching Payment. Throws otherwise. Shared by\n * {@link verifyAndMarkPaid} (pre-transition) and the save-time guard\n * (catch-all for raw mass-assignment).\n */\n private async assertBackedByCompletedPayment(): Promise<void> {\n if (!this.paidOptionBackendId) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot persist PAID status without a paidOptionBackendId`,\n );\n }\n if (!this.paymentId) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot persist PAID status without a paymentId`,\n );\n }\n const option = this.getOption(this.paidOptionBackendId);\n if (!option) {\n throw new Error(\n `PaymentIntent ${this.id}: paidOptionBackendId '${this.paidOptionBackendId}' is not one of the listed options`,\n );\n }\n\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: this.paymentId });\n if (!payment) {\n throw new Error(\n `PaymentIntent ${this.id}: referenced Payment '${this.paymentId}' does not exist`,\n );\n }\n if (payment.status !== PaymentStatus.COMPLETED) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${this.paymentId}' is not COMPLETED ` +\n `(status='${payment.status}'); cannot persist PAID intent against it`,\n );\n }\n this.reconcilePaymentWithOption(payment, option, this.paymentId);\n }\n\n /**\n * Reconcile a COMPLETED Payment against the winning {@link PaymentOption}\n * (S5 audit #1390). Beyond the amount check, the Payment must have arrived on\n * the SAME rail and currency the option quoted — otherwise an unrelated\n * completed payment that merely shares a numeric amount (e.g. a USD 199\n * payment) could satisfy a different option (e.g. a `base-usdc` 199 option),\n * marking the intent paid on the wrong rail with no matching funds. Compares\n * the Payment's `backendId` (rail) and native currency (falling back to its\n * settlement currency) against the option's `backendId` / `currency`, then\n * the native amount.\n *\n * Shared by {@link verifyAndMarkPaid} (pre-transition) and\n * {@link assertBackedByCompletedPayment} (save-time catch-all) so both gates\n * enforce the identical invariant.\n */\n private reconcilePaymentWithOption(\n payment: {\n backendId?: string;\n nativeCurrency?: string;\n currency?: string;\n nativeAmount?: number;\n amount?: number;\n },\n option: PaymentOption,\n paymentIdLabel: string,\n ): void {\n // Only enforce the rail check when the Payment actually recorded a backend\n // (PaymentBackend-routed flows). Manually-recorded payments may carry no\n // backendId; the amount + currency checks still apply.\n if (payment.backendId && payment.backendId !== option.backendId) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${paymentIdLabel}' arrived on rail ` +\n `'${payment.backendId}' but option '${option.backendId}' expects rail ` +\n `'${option.backendId}' — cannot mark paid on a mismatched rail`,\n );\n }\n // The option's `currency` is the native-rail-qualified code (e.g.\n // `USDC-base`), which lines up with the Payment's `nativeCurrency`. Fall\n // back to the settlement `currency` when no native currency was recorded.\n const paymentCurrency = payment.nativeCurrency || payment.currency || '';\n if (paymentCurrency && paymentCurrency !== option.currency) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${paymentIdLabel}' currency ` +\n `'${paymentCurrency}' does not match option '${option.backendId}' ` +\n `currency '${option.currency}'`,\n );\n }\n const paymentNative =\n typeof payment.nativeAmount === 'number' && payment.nativeAmount > 0\n ? payment.nativeAmount\n : payment.amount;\n if (\n typeof paymentNative !== 'number' ||\n paymentNative !== option.nativeAmount\n ) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${paymentIdLabel}' amount ${paymentNative} ` +\n `does not match option '${option.backendId}' nativeAmount ${option.nativeAmount}`,\n );\n }\n }\n\n // -------- Status helpers --------\n\n isAwaitingPayment(): boolean {\n return this.status === PaymentIntentStatus.AWAITING_PAYMENT;\n }\n\n isPaid(): boolean {\n return this.status === PaymentIntentStatus.PAID;\n }\n\n isIssued(): boolean {\n return this.status === PaymentIntentStatus.ISSUED;\n }\n\n isCancelled(): boolean {\n return this.status === PaymentIntentStatus.CANCELLED;\n }\n\n isRetired(): boolean {\n return this.status === PaymentIntentStatus.RETIRED;\n }\n\n /**\n * Terminal-state predicate: any status other than `AWAITING_PAYMENT`\n * is terminal (the intent will not accept further state changes\n * except the explicit `markIssued` after `PAID`).\n */\n isTerminal(): boolean {\n return this.status !== PaymentIntentStatus.AWAITING_PAYMENT;\n }\n\n /**\n * `Date.now()`-based predicate: has the price lock window passed?\n * Independent of `status` so callers can detect a stale-but-not-\n * yet-marked-EXPIRED intent (and call `expire()` to advance it).\n */\n isExpired(): boolean {\n if (this.status === PaymentIntentStatus.EXPIRED) return true;\n if (!this.priceLockExpiresAt) return false;\n return Date.now() > this.priceLockExpiresAt.getTime();\n }\n\n // -------- State transitions --------\n\n /**\n * Transition the intent to `PAID`, naming which option satisfied it\n * and which `Payment` row carries the funds. Implicitly retires the\n * other options — callers can check {@link isOptionRetired} to flag\n * later inbound funds for refund.\n *\n * Throws when called on a non-`AWAITING_PAYMENT` intent so a stale\n * caller can't accidentally overwrite a winning option with a\n * losing one.\n */\n markPaid(args: { backendId: string; paymentId: string }): void {\n if (this.status !== PaymentIntentStatus.AWAITING_PAYMENT) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot transition to PAID from status '${this.status}'`,\n );\n }\n // Refuse stale quotes. A backend can report a payment after the price-lock\n // window has passed but before a cleanup job has run `expire()` — the\n // intent is still AWAITING_PAYMENT, yet honoring it would accept funds at\n // an expired USD price. `isExpired()` is status-independent (it checks\n // `priceLockExpiresAt`), so guard on it here; callers should `expire()`\n // and re-quote rather than record against a lapsed lock.\n if (this.isExpired()) {\n throw new Error(\n `PaymentIntent ${this.id}: price lock expired at ` +\n `${this.priceLockExpiresAt?.toISOString() ?? 'unknown'}; cannot record a ` +\n `payment against a stale quote (call expire() and re-quote)`,\n );\n }\n if (!args.backendId) {\n throw new Error(\n `PaymentIntent ${this.id}: markPaid requires a backendId`,\n );\n }\n const option = this.paymentOptions.find(\n (o) => o.backendId === args.backendId,\n );\n if (!option) {\n throw new Error(\n `PaymentIntent ${this.id}: backendId '${args.backendId}' is not one of the listed options`,\n );\n }\n this.status = PaymentIntentStatus.PAID;\n this.paidOptionBackendId = args.backendId;\n this.paymentId = args.paymentId ?? '';\n this.paidAt = new Date();\n }\n\n /**\n * Verify-then-transition variant of {@link markPaid} (S5 audit #1390).\n *\n * `markPaid` trusts the caller-supplied `paymentId` / `backendId` without\n * checking the referenced `Payment` actually exists, is `COMPLETED`, or\n * carries the amount the winning option quoted. That lets a caller mark an\n * intent PAID against a non-existent or still-pending payment. This method\n * loads the `Payment` row and enforces:\n *\n * - the Payment exists,\n * - it is `COMPLETED` (not pending / failed / cancelled / refunded),\n * - it arrived on the same rail (`backendId`) and currency the option\n * quoted (so an unrelated completed payment that merely shares a numeric\n * amount can't satisfy a different option),\n * - and its amount reconciles with the winning option's `nativeAmount`\n * (within sub-cent tolerance), falling back to the option vs. the\n * Payment's settlement `amount` when no native rail is recorded.\n *\n * Only after those pass does it delegate to the sync `markPaid` for the\n * status-machine invariants.\n *\n * @param args.backendId backendId of the option that was satisfied\n * @param args.paymentId id of the Payment row carrying the funds (required)\n */\n async verifyAndMarkPaid(args: {\n backendId: string;\n paymentId: string;\n }): Promise<void> {\n if (!args.paymentId) {\n throw new Error(\n `PaymentIntent ${this.id}: verifyAndMarkPaid requires a paymentId`,\n );\n }\n const option = this.paymentOptions.find(\n (o) => o.backendId === args.backendId,\n );\n if (!option) {\n throw new Error(\n `PaymentIntent ${this.id}: backendId '${args.backendId}' is not one of the listed options`,\n );\n }\n\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: args.paymentId });\n if (!payment) {\n throw new Error(\n `PaymentIntent ${this.id}: referenced Payment '${args.paymentId}' does not exist`,\n );\n }\n if (payment.status !== PaymentStatus.COMPLETED) {\n throw new Error(\n `PaymentIntent ${this.id}: Payment '${args.paymentId}' is not COMPLETED ` +\n `(status='${payment.status}'); cannot mark intent PAID against it`,\n );\n }\n\n // Reconcile the payment against the winning option — same rail, currency,\n // and amount (shared with the save-time guard so both gates agree).\n this.reconcilePaymentWithOption(payment, option, args.paymentId);\n\n this.markPaid({ backendId: args.backendId, paymentId: args.paymentId });\n }\n\n /**\n * Transition a `PAID` intent to `ISSUED` once the downstream rights\n * / contract / fulfillment have been created. Idempotent — calling\n * twice is a no-op so callers don't have to guard against retries.\n */\n markIssued(): void {\n if (this.status === PaymentIntentStatus.ISSUED) return;\n if (this.status !== PaymentIntentStatus.PAID) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot transition to ISSUED from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.ISSUED;\n this.issuedAt = new Date();\n }\n\n /**\n * Move an `AWAITING_PAYMENT` intent to `EXPIRED`. Safe to call on\n * an already-expired intent (no-op). Throws on terminal non-\n * expired states so a confused caller can't undo a paid / issued\n * intent.\n */\n expire(): void {\n if (this.status === PaymentIntentStatus.EXPIRED) return;\n if (this.status !== PaymentIntentStatus.AWAITING_PAYMENT) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot expire from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.EXPIRED;\n this.expiredAt = new Date();\n }\n\n /**\n * Cancel an open intent. Only valid from `AWAITING_PAYMENT` —\n * a paid / issued intent is no longer the buyer's to cancel; that\n * path is a refund, modelled separately on `Payment`.\n */\n cancel(reason?: string): void {\n if (this.status !== PaymentIntentStatus.AWAITING_PAYMENT) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot cancel from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.CANCELLED;\n this.cancelledAt = new Date();\n if (reason) {\n this.notes = this.notes\n ? `${this.notes}\\nCancelled: ${reason}`\n : `Cancelled: ${reason}`;\n }\n }\n\n /**\n * Mark a paid intent as retired — used when a previously-recorded\n * payment was reversed (chargeback, refund) and the intent should\n * not be considered \"satisfied\" anymore. Distinct from `cancel()`\n * which only applies to never-paid intents.\n */\n retire(reason?: string): void {\n if (\n this.status !== PaymentIntentStatus.PAID &&\n this.status !== PaymentIntentStatus.ISSUED\n ) {\n throw new Error(\n `PaymentIntent ${this.id}: cannot retire from status '${this.status}'`,\n );\n }\n this.status = PaymentIntentStatus.RETIRED;\n this.retiredAt = new Date();\n if (reason) {\n this.notes = this.notes\n ? `${this.notes}\\nRetired: ${reason}`\n : `Retired: ${reason}`;\n }\n }\n\n // -------- Option helpers --------\n\n /**\n * Return the option whose `backendId` matches, or `undefined` if\n * no such option is on this intent. Useful for routing inbound\n * payments to the right option's `nativeAmount` / `payTo` for\n * verification.\n */\n getOption(backendId: string): PaymentOption | undefined {\n return this.paymentOptions.find((o) => o.backendId === backendId);\n }\n\n /**\n * `true` once the intent is paid and the given option was NOT the\n * winning one. Consumers use this to flag inbound funds to retired\n * options for refund. Returns `false` for the winning option, for\n * unpaid intents, and for an unknown `backendId`.\n */\n isOptionRetired(backendId: string): boolean {\n if (!this.isPaid() && !this.isIssued() && !this.isRetired()) return false;\n if (!this.paidOptionBackendId) return false;\n if (!this.getOption(backendId)) return false;\n return backendId !== this.paidOptionBackendId;\n }\n\n /**\n * The options that were NOT selected at payment time. Returns an\n * empty array for unpaid intents so callers can iterate without\n * special-casing.\n */\n getRetiredOptions(): PaymentOption[] {\n if (!this.paidOptionBackendId) return [];\n return this.paymentOptions.filter(\n (o) => o.backendId !== this.paidOptionBackendId,\n );\n }\n\n // -------- Normalization helpers --------\n\n /**\n * Defensive coercion for `paymentOptions` input. Accepts either an\n * already-parsed array or a JSON string (e.g. from a row whose\n * column was hand-edited or migrated from a different schema).\n * Drops entries that don't carry the required `backendId` /\n * `currency` / `payTo` / `nativeAmount` quartet so downstream\n * routing code can rely on the invariant.\n *\n * `nativeAmount` must be a whole number of minor units (#2401): the\n * reconciliation in {@link reconcilePaymentWithOption} compares it to\n * `Payment.nativeAmount` with `!==`, so a fractional major-unit option could\n * never match the payment that arrives and would silently strand the intent.\n */\n private static normalizePaymentOptions(value: unknown): PaymentOption[] {\n if (value == null) return [];\n let parsed: unknown = value;\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value);\n } catch {\n return [];\n }\n }\n if (!Array.isArray(parsed)) return [];\n const out: PaymentOption[] = [];\n for (const raw of parsed) {\n if (!raw || typeof raw !== 'object') continue;\n const o = raw as Record<string, unknown>;\n const backendId = typeof o.backendId === 'string' ? o.backendId : '';\n const currency = typeof o.currency === 'string' ? o.currency : '';\n const payTo = typeof o.payTo === 'string' ? o.payTo : '';\n const nativeAmount =\n typeof o.nativeAmount === 'number' ? o.nativeAmount : Number.NaN;\n if (\n !backendId ||\n !currency ||\n !payTo ||\n !Number.isInteger(nativeAmount)\n ) {\n continue;\n }\n const option: PaymentOption = {\n backendId,\n currency,\n payTo,\n nativeAmount,\n };\n if (typeof o.chain === 'string' && o.chain) option.chain = o.chain;\n if (typeof o.memo === 'string' && o.memo) option.memo = o.memo;\n if (typeof o.x402Capable === 'boolean') {\n option.x402Capable = o.x402Capable;\n }\n if (typeof o.expiresAt === 'string' && o.expiresAt) {\n option.expiresAt = o.expiresAt;\n }\n out.push(option);\n }\n return out;\n }\n\n /**\n * Coerce a Date-ish input (Date / number / ISO string / null /\n * undefined) into a `Date | null`. The framework hands us strings\n * when hydrating from SQLite, numbers when round-tripping through\n * JSON, and Date instances from the application.\n */\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default PaymentIntent;\n","/**\n * PaymentIntentCollection — collection manager for {@link PaymentIntent}.\n *\n * Adds helpers for the common query shapes (find by idempotency key,\n * find still-open intents, find by licensee email, find expired) and a\n * `getOrCreateByIdempotencyKey` helper that callers use to implement\n * safe at-least-once retries from the marketplace's PaymentIntent\n * issuance handler.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { PaymentIntent } from '../models/PaymentIntent.js';\nimport { PaymentIntentStatus, type PaymentOption } from '../types/index.js';\n\nexport interface PaymentIntentIdempotencyArgs {\n /** Caller-supplied scope (Sku id / tier id / etc.) */\n offeringRef: string;\n /** Buyer email — second factor of the natural key */\n licenseeEmail: string;\n /** Caller-supplied idempotency key */\n idempotencyKey: string;\n /** Optional tenant scope, when called outside an active tenant context */\n tenantId?: string | null;\n}\n\nexport interface PaymentIntentSeed\n extends Partial<Omit<PaymentIntentIdempotencyArgs, 'tenantId'>> {\n tenantId?: string | null;\n skuId?: string;\n customerId?: string;\n paymentOptions: PaymentOption[];\n usdPriceLocked: number;\n priceLockWindowMs?: number;\n}\n\nexport class PaymentIntentCollection extends SmrtCollection<PaymentIntent> {\n static readonly _itemClass = PaymentIntent;\n\n /**\n * Look up an existing intent by its natural key. Returns `null` when\n * any of the three components are empty — empty natural-key inputs\n * disable dedup by design, so a `null` return tells the caller to\n * fall through to a fresh `create`.\n */\n async findByIdempotencyKey(\n args: PaymentIntentIdempotencyArgs,\n ): Promise<PaymentIntent | null> {\n if (!args.offeringRef || !args.licenseeEmail || !args.idempotencyKey) {\n return null;\n }\n const where: Record<string, unknown> = {\n offeringRef: args.offeringRef,\n licenseeEmail: args.licenseeEmail,\n idempotencyKey: args.idempotencyKey,\n };\n if (args.tenantId !== undefined) {\n where.tenantId = args.tenantId;\n }\n const results = await this.list({ where, limit: 1 });\n return results[0] ?? null;\n }\n\n /**\n * Idempotency-aware create. If an intent already exists with the\n * same `(tenantId, offeringRef, licenseeEmail, idempotencyKey)`\n * tuple, returns the existing row. Otherwise creates a new one\n * with the supplied seed.\n *\n * Returns a `{ intent, created }` pair so callers can branch on\n * whether they're handling a fresh quote or a replay (e.g. to\n * skip a duplicate \"intent created\" webhook emission).\n */\n async getOrCreateByIdempotencyKey(\n seed: PaymentIntentSeed,\n ): Promise<{ intent: PaymentIntent; created: boolean }> {\n const existing = await this.findByIdempotencyKey({\n offeringRef: seed.offeringRef ?? '',\n licenseeEmail: seed.licenseeEmail ?? '',\n idempotencyKey: seed.idempotencyKey ?? '',\n tenantId: seed.tenantId,\n });\n if (existing) {\n return { intent: existing, created: false };\n }\n return { intent: await this.create({ ...seed }), created: true };\n }\n\n /**\n * Open intents — `AWAITING_PAYMENT` and not yet past the price-\n * lock window. The price-lock check is `Date.now()`-based, so a\n * stale-but-not-yet-marked-EXPIRED row drops out of this list even\n * before a cleanup job advances its status.\n */\n async findOpen(): Promise<PaymentIntent[]> {\n const open = await this.list({\n where: { status: PaymentIntentStatus.AWAITING_PAYMENT },\n orderBy: 'created_at DESC',\n });\n const now = Date.now();\n return open.filter((p) => {\n if (!p.priceLockExpiresAt) return true;\n return p.priceLockExpiresAt.getTime() > now;\n });\n }\n\n /**\n * `AWAITING_PAYMENT` intents whose price-lock window has already\n * passed. The expected use is a periodic cleanup job that loads\n * this list, calls `intent.expire(); await intent.save()` on each,\n * and emits whatever downstream signal the application needs.\n */\n async findStale(): Promise<PaymentIntent[]> {\n const open = await this.list({\n where: { status: PaymentIntentStatus.AWAITING_PAYMENT },\n });\n const now = Date.now();\n return open.filter((p) => {\n if (!p.priceLockExpiresAt) return false;\n return p.priceLockExpiresAt.getTime() <= now;\n });\n }\n\n async findByLicenseeEmail(email: string): Promise<PaymentIntent[]> {\n return this.list({\n where: { licenseeEmail: email },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByOfferingRef(offeringRef: string): Promise<PaymentIntent[]> {\n return this.list({\n where: { offeringRef },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByStatus(status: PaymentIntentStatus): Promise<PaymentIntent[]> {\n return this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n async findByTenant(tenantId: string): Promise<PaymentIntent[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payment intents (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n */\n async findGlobal(): Promise<PaymentIntent[]> {\n return queryGlobal<PaymentIntent>(this);\n }\n\n /**\n * Find payment intents for a tenant including global intents.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n */\n async findWithGlobals(tenantId: string): Promise<PaymentIntent[]> {\n return queryWithGlobals<PaymentIntent>(\n this,\n tenantId,\n 'PaymentIntent.findWithGlobals',\n );\n }\n}\n","/**\n * Payout — operator-to-supplier outgoing remittance.\n *\n * A Payout sits on the opposite side of a {@link Payment}: it tracks\n * funds *leaving* the operator's wallet and arriving at a vendor's\n * payout address. The two are kept as distinct models (rather than\n * overloading `Payment` with a `direction` field) because the status\n * machine, chain semantics, and audit story differ.\n *\n * Industry-neutral: any operator that ingests one payment and remits\n * funds to a third party — marketplaces, payment processors with\n * Connect-style fan-out, royalty distributors, escrow services —\n * benefits.\n *\n * MVP scope: no currency conversion. The payout's amounts and currency\n * mirror the originating Payment's native currency, and the vendor\n * needs a matching `Vendor.payoutAddresses` entry to receive funds.\n * Filtering at quote time (`PaymentIntent.paymentOptions`) is the\n * consumer's job; the model just enforces the natural-key invariant\n * and the status machine.\n *\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertIntegerMinorUnits } from '../money.js';\nimport { type PayoutOptions, PayoutStatus } from '../types/index.js';\nimport { Vendor } from './Vendor.js';\n\n/*\n * There is deliberately no `PAYOUT_EPSILON` here any more (#2401).\n *\n * The gross/fee/net triple and the source `Payment.amount` it is capped\n * against are all integer minor units, so both the invariant and the cap are\n * exact. A payout that remits one cent more than its funding payment brought\n * in is over-remittance, not rounding, and the old `0.01` tolerance silently\n * allowed it.\n */\n\n/**\n * Legal status transitions for a Payout, keyed by the prior persisted status.\n * Mirrors the state machine enforced by `markSent` / `markConfirmed` /\n * `markFailed` / `resetFromFailed`, so a raw `status` mass-assignment on the\n * generated update route can't skip steps (e.g. `pending → confirmed`) or\n * revive a terminal-ish state outside the dedicated reset path.\n *\n * `pending → sent → confirmed`, with `failed` reachable from `pending` / `sent`\n * and `failed → pending` allowed only via `resetFromFailed()`.\n */\nconst PAYOUT_STATUS_TRANSITIONS: Record<PayoutStatus, PayoutStatus[]> = {\n [PayoutStatus.PENDING]: [PayoutStatus.SENT, PayoutStatus.FAILED],\n [PayoutStatus.SENT]: [PayoutStatus.CONFIRMED, PayoutStatus.FAILED],\n [PayoutStatus.CONFIRMED]: [],\n // FAILED is resettable to PENDING via resetFromFailed().\n [PayoutStatus.FAILED]: [PayoutStatus.PENDING],\n};\n\n/**\n * Module-scoped record of the status each Payout instance was loaded with, so\n * the save-time transition guard can compare the prior persisted status\n * against the one being written. WeakMap keeps it out of the schema and GCs\n * with the instance — same pattern as the other commerce models.\n */\nconst loadedPayoutStatus = new WeakMap<Payout, PayoutStatus>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): a Payout has NO safe generated write.\n // Its status drives an outgoing remittance and its amount triple\n // (grossAmount / operatorFee / supplierNet, all integer minor units) is the\n // integrity core — none may\n // be set by a caller. The only legitimate way to mint a payout is the\n // verified `PayoutCollection.createFromPayment()` domain path (which derives\n // the amounts from the source Payment and starts PENDING), and the only legal\n // status moves are the guarded `markSent` / `markConfirmed` / `markFailed` /\n // `resetFromFailed` helpers. So the generated surface is read-only — this\n // closes the \"forged CONFIRMED Payout via api.create\" vector (codex HIGH#4)\n // at the surface, not just in the save() guard.\n //\n // CLI parity (round 6, codex ROOT INSIGHT): the CLI is an independently\n // configured write surface. `cli: true` would still generate create/update/\n // delete commands that set status / the gross-fee-net triple, re-opening the\n // exact vector closed on api/mcp. The CLI is locked to the same read-only\n // surface — there is no safe generated payout write on ANY surface.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class Payout extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable so global / single-\n * tenant deployments work too.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * The source {@link Payment} that funded this payout. Plain string\n * reference (FK convention: `@foreignKey('ModelName')` would be\n * fine within the package, but `Payment` lives in this same\n * package — staying with a string keeps the dependency graph\n * clean and matches how `PaymentIntent` references `Payment`).\n */\n paymentId: string = '';\n\n /**\n * The destination {@link Vendor}. Foreign-key constrained because\n * Vendor lives in this same package and a hard reference catches\n * dangling payouts at insert time.\n */\n @foreignKey(Vendor)\n vendorId: string = '';\n\n /**\n * Total funds moved, in the originating payment's native currency, as\n * **integer minor units** (cents, satoshis) — the same convention as\n * `Payment.amount` / `Payment.nativeAmount` (#2401).\n */\n grossAmount: number = 0;\n\n /**\n * Operator's take from `grossAmount`, in integer minor units. Must equal\n * `grossAmount - supplierNet` **exactly** at save time — the model enforces\n * the invariant via `validateAmounts()`.\n */\n operatorFee: number = 0;\n\n /**\n * Net funds the supplier receives, in integer minor units. Must equal\n * `grossAmount - operatorFee` exactly at save time.\n *\n * Converts with the other two: the invariant is a subtraction over all\n * three, so a decimal member would reintroduce the fuzz the tolerance used\n * to hide (#2401).\n */\n supplierNet: number = 0;\n\n /**\n * Native currency the funds are denominated in — payout-rail-\n * qualified, matching `Payment.nativeCurrency` and\n * `Vendor.payoutAddresses` keys (`USDC-base`, `BTC`, `USD-stripe`,\n * etc.).\n */\n currency: string = '';\n\n /**\n * The `PaymentBackend` adapter id used to send the payout. For a\n * marketplace this typically matches the source `Payment.backendId`\n * (currency-match settlement, no conversion); other consumers can\n * use a different backend if they bridge currencies elsewhere.\n */\n backendId: string = '';\n\n /**\n * Outgoing chain transaction hash, gateway settlement id, or\n * (for not-yet-broadcast BTC payouts) a PSBT identifier. Set when\n * the payout transitions to `SENT`. Empty until then.\n */\n backendTxRef: string = '';\n\n /**\n * Current status — see {@link PayoutStatus} for the state machine.\n * Mutate via `markSent` / `markConfirmed` / `markFailed` /\n * `resetFromFailed` rather than direct assignment so the\n * transition invariants run.\n */\n status: PayoutStatus = PayoutStatus.PENDING;\n\n /**\n * When the payout transitioned to `SENT`.\n */\n sentAt: Date | null = null;\n\n /**\n * When the payout transitioned to `CONFIRMED`.\n */\n confirmedAt: Date | null = null;\n\n /**\n * When the payout transitioned to `FAILED`.\n */\n failedAt: Date | null = null;\n\n /**\n * Caller-supplied human-readable failure reason. Empty for non-\n * failed payouts. Cleared by `resetFromFailed()`.\n */\n failureReason: string = '';\n\n /**\n * Append-only operator notes — chargeback memos, manual-retry\n * justifications, etc.\n */\n notes: string = '';\n\n constructor(options: PayoutOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.paymentId !== undefined) this.paymentId = options.paymentId;\n if (options.vendorId !== undefined) this.vendorId = options.vendorId;\n if (options.grossAmount !== undefined)\n this.grossAmount = options.grossAmount;\n if (options.operatorFee !== undefined)\n this.operatorFee = options.operatorFee;\n if (options.supplierNet !== undefined)\n this.supplierNet = options.supplierNet;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.backendId !== undefined) this.backendId = options.backendId;\n if (options.backendTxRef !== undefined)\n this.backendTxRef = options.backendTxRef;\n if (options.status !== undefined) this.status = options.status;\n if (options.sentAt !== undefined)\n this.sentAt = Payout.coerceDate(options.sentAt);\n if (options.confirmedAt !== undefined)\n this.confirmedAt = Payout.coerceDate(options.confirmedAt);\n if (options.failedAt !== undefined)\n this.failedAt = Payout.coerceDate(options.failedAt);\n if (options.failureReason !== undefined)\n this.failureReason = options.failureReason;\n if (options.notes !== undefined) this.notes = options.notes;\n }\n\n /**\n * Re-coerce timestamp fields after the framework reapplies raw option\n * values during initialization.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.sentAt = Payout.coerceDate(this.sentAt);\n this.confirmedAt = Payout.coerceDate(this.confirmedAt);\n this.failedAt = Payout.coerceDate(this.failedAt);\n if (await this.isSaved()) {\n loadedPayoutStatus.set(this, this.status);\n }\n return this;\n }\n\n // -------- Status predicates --------\n\n isPending(): boolean {\n return this.status === PayoutStatus.PENDING;\n }\n\n isSent(): boolean {\n return this.status === PayoutStatus.SENT;\n }\n\n isConfirmed(): boolean {\n return this.status === PayoutStatus.CONFIRMED;\n }\n\n isFailed(): boolean {\n return this.status === PayoutStatus.FAILED;\n }\n\n /**\n * Transition `PENDING → SENT`. Requires `backendTxRef` — without a\n * way to identify the outgoing transaction the payout cannot be\n * tracked further. Throws on any other source status so a confused\n * caller can't accidentally roll back a confirmed payout.\n */\n markSent(backendTxRef: string): void {\n if (this.status !== PayoutStatus.PENDING) {\n throw new Error(\n `Payout ${this.id}: cannot transition to SENT from status '${this.status}'`,\n );\n }\n if (!backendTxRef) {\n throw new Error(`Payout ${this.id}: markSent requires a backendTxRef`);\n }\n this.backendTxRef = backendTxRef;\n this.status = PayoutStatus.SENT;\n this.sentAt = new Date();\n }\n\n /**\n * Transition `SENT → CONFIRMED`. Throws if called from any other\n * status — confirmations on a pending payout would mean a chain\n * confirmation arrived before the application even recorded the\n * outgoing tx, which is a code bug worth surfacing rather than\n * silently fixing.\n */\n markConfirmed(): void {\n if (this.status !== PayoutStatus.SENT) {\n throw new Error(\n `Payout ${this.id}: cannot transition to CONFIRMED from status '${this.status}'`,\n );\n }\n this.status = PayoutStatus.CONFIRMED;\n this.confirmedAt = new Date();\n }\n\n /**\n * Move the payout to `FAILED`. Valid from `PENDING` (couldn't even\n * broadcast) or `SENT` (broadcast but chain rejected). Confirmed\n * payouts cannot fail — that path is a refund, modelled separately.\n */\n markFailed(reason: string): void {\n if (\n this.status !== PayoutStatus.PENDING &&\n this.status !== PayoutStatus.SENT\n ) {\n throw new Error(\n `Payout ${this.id}: cannot transition to FAILED from status '${this.status}'`,\n );\n }\n this.status = PayoutStatus.FAILED;\n this.failedAt = new Date();\n this.failureReason = reason ?? '';\n }\n\n /**\n * Operator-driven reset: move a `FAILED` payout back to `PENDING`\n * after fixing whatever broke. Clears the failure metadata and the\n * old `backendTxRef` (the next attempt will have a new one).\n * Distinct from `markFailed` reflexivity — the issue spec says\n * \"failed is terminal but allows manual reset via explicit method\".\n */\n resetFromFailed(): void {\n if (this.status !== PayoutStatus.FAILED) {\n throw new Error(\n `Payout ${this.id}: cannot reset from status '${this.status}' — only FAILED is resettable`,\n );\n }\n this.status = PayoutStatus.PENDING;\n this.sentAt = null;\n this.confirmedAt = null;\n this.failedAt = null;\n this.failureReason = '';\n // Drop the old tx ref so the next markSent attempt gets a fresh one.\n this.backendTxRef = '';\n }\n\n /**\n * Throws if the gross/fee/net invariant doesn't hold.\n *\n * Exact: all three are integer minor units, so `supplierNet` must equal\n * `grossAmount - operatorFee` on the nose (#2401).\n */\n validateAmounts(): void {\n const amounts = [\n ['grossAmount', this.grossAmount],\n ['operatorFee', this.operatorFee],\n ['supplierNet', this.supplierNet],\n ] as const;\n // Whole minor units first — the exact invariant below depends on it.\n assertIntegerMinorUnits('Payout', this.id ?? '<new>', amounts);\n // Non-negativity guard (S5 audit #1390). A negative operatorFee would let\n // an operator manufacture a supplierNet larger than the gross it took in\n // (paying out more than it received); negative gross/net are nonsensical.\n for (const [field, value] of amounts) {\n if (value < 0) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: ${field} must be a non-negative number (got ${value}).`,\n );\n }\n }\n const expectedNet = this.grossAmount - this.operatorFee;\n if (this.supplierNet !== expectedNet) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: amount invariant violated — ` +\n `gross=${this.grossAmount} fee=${this.operatorFee} net=${this.supplierNet} ` +\n `(expected net=${expectedNet})`,\n );\n }\n }\n\n /**\n * Save-time state-machine + settlement guard (S5 audit #1390 round 2).\n *\n * `status`, `backendTxRef`, and the amount triple are all mass-assignable on\n * the generated create/update routes. The amount invariant alone (round 1)\n * still let a caller raw-flip a persisted payout to `CONFIRMED` / `SENT`\n * (skipping the chain steps) or remit a `grossAmount` larger than the source\n * Payment actually brought in — money that never arrived.\n *\n * This guard adds:\n * - **Transitions on an existing row must be legal** per\n * {@link PAYOUT_STATUS_TRANSITIONS} (no `pending → confirmed` skip on a\n * raw update, no reviving a CONFIRMED payout). Brand-new rows may be\n * created in any status (collection/import/query fixtures), but advancing\n * a persisted row is constrained to the legal edges the helpers enforce.\n * - **SENT requires a backendTxRef** (matches `markSent`'s invariant) so a\n * sent payout is always traceable, regardless of how the status was set.\n * - **grossAmount is capped by the source Payment** when that Payment\n * resolves: a payout can never remit more than the funding Payment\n * settled. A `paymentId` that doesn't resolve (synthetic id, Payment-less\n * deployment) skips the cap — the source Payment is a plain-string,\n * cross-model reference by design and may legitimately be a manually\n * recorded (non-COMPLETED) row, so the cap is best-effort rather than a\n * hard existence requirement.\n *\n * The amount-invariant check (`validateAmounts`) still runs first as the\n * arithmetic floor.\n */\n override async save(): Promise<this> {\n this.validateAmounts();\n\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n\n // SENT *and* CONFIRMED both require a backendTxRef (S5 audit #1390 round 4):\n // a confirmed payout that carries no outgoing-transaction reference is\n // untraceable and indistinguishable from a forged confirmation. Round 3\n // only required it for SENT, leaving a raw CONFIRMED-with-no-txref hole.\n if (\n (this.status === PayoutStatus.SENT ||\n this.status === PayoutStatus.CONFIRMED) &&\n !this.backendTxRef\n ) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: a ${this.status} payout requires a ` +\n 'backendTxRef (use markSent()).',\n );\n }\n\n // CONFIRMED is only ever reachable from SENT (codex HIGH#4 / HIGH#3): an\n // existing row's transition is checked by assertStatusTransition, but a\n // brand-new row (no prior) would otherwise be allowed to start CONFIRMED.\n // A confirmation means the chain/gateway acknowledged a previously-sent tx,\n // so it can never be the genesis state.\n if (this.status === PayoutStatus.CONFIRMED && prior !== PayoutStatus.SENT) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: CONFIRMED is only reachable from SENT ` +\n '(use markConfirmed() after markSent()).',\n );\n }\n\n await this.assertCappedBySourcePayment();\n\n const result = (await super.save()) as this;\n loadedPayoutStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status from the database (S5 audit #1390\n * round 4). The {@link loadedPayoutStatus} WeakMap is only populated when\n * {@link initialize} hydrated the row, so a `create({ id: <existing>,\n * _skipLoad: true })` upsert produces an instance with a missing WeakMap\n * entry — trusting it would treat the write as a brand-new row and skip the\n * transition + CONFIRMED-genesis guards. Reading the persisted row directly\n * makes a create-onto-existing behave as an update. `undefined` means no row\n * exists (genuinely new).\n */\n private async resolvePriorStatus(): Promise<PayoutStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as PayoutStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedPayoutStatus.get(this);\n }\n\n /**\n * Reject an illegal status flip on an existing row. Brand-new payouts (no\n * prior) may start in any status — collection imports and query fixtures\n * legitimately seed SENT / CONFIRMED rows — but advancing a *persisted* row\n * is constrained to the legal edges the helpers enforce, so a raw update\n * can't skip `pending → confirmed` or revive a terminal CONFIRMED.\n */\n private assertStatusTransition(prior: PayoutStatus | undefined): void {\n if (prior === undefined) return; // new row\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYOUT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Payout ${this.id}: illegal status transition '${prior}' → ` +\n `'${this.status}'. Use the guarded helpers (markSent / markConfirmed ` +\n '/ markFailed / resetFromFailed).',\n );\n }\n }\n\n /**\n * Cap `grossAmount` by the source {@link Payment}'s settled funds. A payout\n * that remits more than the Payment that funded it brought in is money the\n * operator never received.\n *\n * HARD requirement (S5 audit #1390 round 4, codex HIGH#3): the source Payment\n * MUST resolve. Round 3 made the cap best-effort — a `paymentId` that didn't\n * resolve silently skipped the cap, so a caller could remit an arbitrary\n * `grossAmount` simply by pointing at a non-existent (or empty) payment.\n * A payout's whole purpose is to remit funds that *arrived* via a Payment;\n * without a resolvable source there is no funded amount to cap against, so we\n * reject rather than skip. `PayoutCollection.createFromPayment()` always wires\n * a real `paymentId`, so this never blocks the supported creation path.\n */\n private async assertCappedBySourcePayment(): Promise<void> {\n if (!this.paymentId) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: a paymentId is required — a payout must ` +\n 'reference the source Payment that funded it (use createFromPayment()).',\n );\n }\n\n const { PaymentCollection } = await import(\n '../collections/PaymentCollection.js'\n );\n const payments = await PaymentCollection.create(this.options);\n const payment = await payments.get({ id: this.paymentId });\n if (!payment) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: referenced source Payment ` +\n `'${this.paymentId}' does not exist — refusing to remit against a ` +\n 'missing payment (the gross-amount cap cannot be enforced otherwise).',\n );\n }\n\n // Cap against the payment's funds. Prefer the native-rail figure when\n // present (volatile-currency rails), otherwise the settlement amount —\n // mirrors the PaymentIntent reconciliation convention.\n const settled =\n typeof payment.nativeAmount === 'number' && payment.nativeAmount > 0\n ? payment.nativeAmount\n : payment.amount;\n if (this.grossAmount > settled) {\n throw new Error(\n `Payout ${this.id ?? '<new>'}: grossAmount ${this.grossAmount} exceeds the ` +\n `source Payment '${this.paymentId}' funded amount ${settled}.`,\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default Payout;\n","/**\n * PayoutCollection — collection manager for {@link Payout} rows.\n *\n * Adds helpers for the common operator-side query shapes: payouts for\n * a specific vendor, payouts derived from a specific source payment,\n * status-bucketed lookups for the operator dashboard, and a\n * `createFromPayment` convenience that wires up the source-payment\n * link in one call.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport type { Payment } from '../models/Payment.js';\nimport { Payout } from '../models/Payout.js';\nimport { PayoutStatus } from '../types/index.js';\n\nexport interface CreatePayoutFromPaymentArgs {\n /** Source Payment row */\n payment: Payment;\n /** Destination vendor id */\n vendorId: string;\n /** Operator fee in the payment's native currency */\n operatorFee: number;\n /** Backend that will send the payout — defaults to the payment's */\n backendId?: string;\n /** Optional initial notes */\n notes?: string;\n}\n\nexport class PayoutCollection extends SmrtCollection<Payout> {\n static readonly _itemClass = Payout;\n\n /**\n * Build a `PENDING` payout linked to a source payment. The gross\n * defaults to the payment's `nativeAmount` (falling back to\n * `amount` if no backend rail was recorded), the currency defaults\n * to the payment's `nativeCurrency` (falling back to `currency`),\n * and `supplierNet` is computed as `gross - operatorFee`.\n *\n * The returned instance is initialized but not persisted. The caller\n * is still responsible for `await payout.save()` — keeping create /\n * save as separate steps lets callers stitch the payout into an\n * application-level transaction.\n */\n async createFromPayment(args: CreatePayoutFromPaymentArgs): Promise<Payout> {\n const { payment, vendorId, operatorFee } = args;\n const grossAmount = payment.nativeAmount || payment.amount || 0;\n const currency = payment.nativeCurrency || payment.currency || '';\n const backendId = args.backendId ?? payment.backendId ?? '';\n const payout = new Payout({\n ai: this.options.ai,\n db: this.db,\n _skipLoad: true,\n // Inherit the source payment's tenant. Payout is @TenantScoped, so when\n // this runs outside an active tenant context (e.g. a background payout\n // job iterating Payment rows), the interceptor's auto-populate can't\n // stamp a tenant — without carrying it explicitly the remittance would\n // save as global/null and drop out of tenant-filtered payout queries.\n tenantId: payment.tenantId,\n paymentId: payment.id ?? '',\n vendorId,\n grossAmount,\n operatorFee,\n supplierNet: grossAmount - operatorFee,\n currency,\n backendId,\n status: PayoutStatus.PENDING,\n notes: args.notes ?? '',\n });\n await payout.initialize();\n return payout;\n }\n\n async findByPayment(paymentId: string): Promise<Payout[]> {\n return this.list({\n where: { paymentId },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByVendor(vendorId: string): Promise<Payout[]> {\n return this.list({\n where: { vendorId },\n orderBy: 'created_at DESC',\n });\n }\n\n async findByStatus(status: PayoutStatus): Promise<Payout[]> {\n return this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n async findPending(): Promise<Payout[]> {\n return this.findByStatus(PayoutStatus.PENDING);\n }\n\n async findFailed(): Promise<Payout[]> {\n return this.findByStatus(PayoutStatus.FAILED);\n }\n\n async findByBackendTxRef(backendTxRef: string): Promise<Payout | null> {\n if (!backendTxRef) return null;\n const results = await this.list({\n where: { backendTxRef },\n limit: 1,\n });\n return results[0] ?? null;\n }\n\n /**\n * Total funds delivered to a vendor in confirmed payouts. Useful for\n * a vendor-balance widget on the operator dashboard.\n */\n async getConfirmedTotalForVendor(vendorId: string): Promise<number> {\n const confirmed = await this.list({\n where: { vendorId, status: PayoutStatus.CONFIRMED },\n });\n return confirmed.reduce((sum, p) => sum + p.supplierNet, 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n async findByTenant(tenantId: string): Promise<Payout[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global payouts (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n */\n async findGlobal(): Promise<Payout[]> {\n return queryGlobal<Payout>(this);\n }\n\n /**\n * Find payouts for a tenant including global payouts.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n */\n async findWithGlobals(tenantId: string): Promise<Payout[]> {\n return queryWithGlobals<Payout>(this, tenantId, 'Payout.findWithGlobals');\n }\n}\n","/**\n * VendorCollection - Collection manager for Vendor objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Vendor } from '../models/Vendor.js';\nimport { VendorStatus } from '../types/index.js';\n\nexport class VendorCollection extends SmrtCollection<Vendor> {\n static readonly _itemClass = Vendor;\n\n /**\n * Find vendors by profile ID\n *\n * @param profileId - Profile ID from smrt-profiles\n * @returns Array of vendors linked to this profile\n */\n async findByProfile(profileId: string): Promise<Vendor[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all active vendors\n *\n * @returns Array of active vendors\n */\n async findActive(): Promise<Vendor[]> {\n return await this.list({\n where: { status: VendorStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find vendors by status\n *\n * @param status - Vendor status\n * @returns Array of vendors\n */\n async findByStatus(status: VendorStatus): Promise<Vendor[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get or create a vendor for a profile\n *\n * @param profileId - Profile ID\n * @param defaults - Default values if creating\n * @returns Vendor\n */\n async getOrCreateForProfile(\n profileId: string,\n defaults: Partial<{\n leadTimeDays: number;\n minimumOrderAmount: number;\n paymentTerms: string;\n }> = {},\n ): Promise<Vendor> {\n const existing = await this.findByProfile(profileId);\n if (existing.length > 0) {\n return existing[0];\n }\n\n const vendor = await this.create({\n profileId,\n leadTimeDays: defaults.leadTimeDays ?? 0,\n minimumOrderAmount: defaults.minimumOrderAmount ?? 0,\n paymentTerms: defaults.paymentTerms ?? '',\n status: VendorStatus.ACTIVE,\n });\n await vendor.save();\n return vendor;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all vendors belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of vendors for the tenant\n */\n async findByTenant(tenantId: string): Promise<Vendor[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global vendors (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global vendors\n */\n async findGlobal(): Promise<Vendor[]> {\n return queryGlobal<Vendor>(this);\n }\n\n /**\n * Find vendors for a tenant including global vendors.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global vendors\n */\n async findWithGlobals(tenantId: string): Promise<Vendor[]> {\n return queryWithGlobals<Vendor>(this, tenantId, 'Vendor.findWithGlobals');\n }\n}\n","/** Explicit invoice quantity widening and additive editor-state storage migration. */\nimport {\n type DatabaseInterface,\n planSqliteTableRebuilds,\n sqliteRebuildPlaceholderSql,\n} from '@happyvertical/smrt-core/migrations';\nimport { calculateInvoiceMinorLine } from '../svelte/invoices/calculations.js';\n\n/** Supported SQL engines; explicit selection avoids guessing from connection URLs. */\nexport type InvoiceEditorStorageEngine = 'sqlite' | 'postgres' | 'duckdb';\n/** Read-only quantity migration findings. */\nexport interface InvoiceEditorStoragePreflight {\n /** Requested engine. */\n engine: InvoiceEditorStorageEngine;\n /** Existing column type, or null when absent. */\n declaredType: string | null;\n /** All existing quantities satisfy numeric persistence and precision constraints. */\n ok: boolean;\n /** Whether the dedicated TEXT editor-state column already exists. */\n editorStateColumnPresent: boolean;\n /** Rows examined. */\n inspectedRows: number;\n /** Invalid row identifiers; no data is modified while findings remain. */\n invalidRowIds: string[];\n}\n/** Completed quantity migration report. */\nexport interface InvoiceEditorStorageMigrationResult {\n /** Preflight used to guard this migration. */\n preflight: InvoiceEditorStoragePreflight;\n /** True if quantity was widened or the editor-state column was added. */\n changed: boolean;\n}\ntype QueryExecutor = Pick<DatabaseInterface, 'query'>;\n\nfunction scaledQuantity(raw: unknown): bigint | null {\n if (raw === null || raw === undefined) return null;\n const match = /^(\\d+)(?:\\.(\\d*))?(?:e([+-]?\\d+))?$/i.exec(String(raw));\n if (!match) return null;\n const exponent = Number(match[3] ?? 0) - (match[2]?.length ?? 0) + 6;\n if (!Number.isSafeInteger(exponent) || Math.abs(exponent) > 100) return null;\n const coefficient = BigInt(match[1] + (match[2] ?? ''));\n const divisor = exponent < 0 ? 10n ** BigInt(-exponent) : 1n;\n if (coefficient % divisor !== 0n) return null;\n return exponent < 0\n ? coefficient / divisor\n : coefficient * 10n ** BigInt(exponent);\n}\n\nfunction exactQuantity(raw: unknown): boolean {\n const scaled = scaledQuantity(raw);\n if (scaled === null) return false;\n const value = Number(raw);\n try {\n calculateInvoiceMinorLine({\n quantity: value,\n unitPrice: 0,\n discount: 0,\n taxRate: 0,\n });\n // Compare exact source text with canonical numeric text, never toFixed's\n // rounded binary expansion. DECIMAL values must survive Number hydration.\n return scaledQuantity(value.toString()) === scaled;\n } catch {\n return false;\n }\n}\n\n/** Inspect every existing quantity without rounding, rescaling, or modifying it. */\nexport async function preflightInvoiceEditorStorage(\n db: QueryExecutor,\n engine: InvoiceEditorStorageEngine,\n): Promise<InvoiceEditorStoragePreflight> {\n if (!['sqlite', 'postgres', 'duckdb'].includes(engine))\n throw new Error('Unsupported invoice quantity engine.');\n const columns =\n engine === 'sqlite'\n ? await db.query('PRAGMA table_info(\"invoice_line_items\")')\n : await db.query(\n \"SELECT column_name AS name, data_type AS type FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'invoice_line_items'\",\n );\n const column = (columns.rows as { name: string; type: string }[]).find(\n (row) => row.name === 'quantity',\n );\n const result: InvoiceEditorStoragePreflight = {\n engine,\n declaredType: column?.type ?? null,\n ok: true,\n editorStateColumnPresent: (columns.rows as { name: string }[]).some(\n (row) => row.name === 'invoice_editor_state_json',\n ),\n inspectedRows: 0,\n invalidRowIds: [],\n };\n if (!column) {\n if (columns.rows.length)\n throw new Error('Invoice line table has no quantity column.');\n return result;\n }\n if (\n !/^(?:tinyint|smallint|integer|int|bigint|int[248]|hugeint|real|float|double(?: precision)?|decimal(?:\\(.*\\))?|numeric(?:\\(.*\\))?)$/i.test(\n column.type,\n )\n )\n throw new Error(`Unsupported invoice quantity column type: ${column.type}`);\n // Preserve exact integer/DECIMAL text, but read binary floats as numbers: SQLite's\n // CAST-to-text prints only 15 significant digits and can hide stored precision.\n const expression =\n engine === 'sqlite'\n ? \"CASE WHEN typeof(quantity) = 'real' THEN quantity ELSE CAST(quantity AS VARCHAR) END\"\n : /^(?:real|float|double(?: precision)?)$/i.test(column.type)\n ? 'quantity'\n : 'CAST(quantity AS VARCHAR)';\n const rows = await db.query(\n `SELECT id, ${expression} AS quantity FROM \"invoice_line_items\"`,\n );\n for (const row of rows.rows as { id: unknown; quantity: unknown }[]) {\n result.inspectedRows++;\n if (!exactQuantity(row.quantity)) result.invalidRowIds.push(String(row.id));\n }\n result.ok = result.invalidRowIds.length === 0;\n return result;\n}\n\n/**\n * Widen existing integer quantity columns without rescaling any values.\n * Run during a writer maintenance window using a root transaction-capable handle.\n * PostgreSQL locks writers while checking and altering; DuckDB detects conflicting transactions.\n * SQLite uses the core live-DDL rebuild planner and refuses unsafe incoming-FK rebuilds.\n * Repeating the migration is harmless and never marks an absent table as migrated.\n */\nexport async function migrateInvoiceEditorStorage(\n db: DatabaseInterface,\n engine: InvoiceEditorStorageEngine,\n): Promise<InvoiceEditorStorageMigrationResult> {\n if (!db.transaction)\n throw new Error('Invoice quantity migration requires transaction support.');\n let result: InvoiceEditorStorageMigrationResult | undefined;\n await db.transaction(async (tx) => {\n // Acquire the PG lock only if the table exists, before reading quantities.\n if (engine === 'postgres') {\n const table = await tx.query(\n \"SELECT to_regclass('invoice_line_items') AS name\",\n );\n if (table.rows[0]?.name)\n await tx.query(\n 'LOCK TABLE \"invoice_line_items\" IN ACCESS EXCLUSIVE MODE',\n );\n }\n const preflight = await preflightInvoiceEditorStorage(tx, engine);\n if (!preflight.ok)\n throw new RangeError(\n `Invoice quantity migration refused invalid rows: ${preflight.invalidRowIds.join(', ')}`,\n );\n const integer =\n preflight.declaredType !== null &&\n /^(?:tinyint|smallint|integer|int|bigint|int[248]|hugeint)$/i.test(\n preflight.declaredType,\n );\n result = { preflight, changed: false };\n if (integer && engine === 'sqlite') {\n const changes = await planSqliteTableRebuilds({\n db: tx as DatabaseInterface,\n tableName: 'invoice_line_items',\n changes: [\n {\n type: 'type_upgrade',\n table: 'invoice_line_items',\n name: 'quantity',\n column: { type: 'REAL' },\n sql: sqliteRebuildPlaceholderSql('quantity'),\n },\n ],\n mapType: () => 'REAL',\n });\n const statements = changes[0]?.sqlStatements;\n if (!statements?.length)\n throw new Error(\n `Invoice quantity rebuild requires explicit maintenance: ${changes[0]?.sql ?? 'no safe plan'}`,\n );\n for (const statement of statements) await tx.query(statement);\n result.changed = true;\n }\n if (integer && engine !== 'sqlite') {\n const type = engine === 'postgres' ? 'DOUBLE PRECISION' : 'DOUBLE';\n await tx.query(\n `ALTER TABLE \"invoice_line_items\" ALTER COLUMN \"quantity\" TYPE ${type} USING CAST(\"quantity\" AS ${type})`,\n );\n result.changed = true;\n }\n if (\n preflight.declaredType !== null &&\n !preflight.editorStateColumnPresent\n ) {\n await tx.query(\n \"ALTER TABLE invoice_line_items ADD COLUMN invoice_editor_state_json TEXT DEFAULT ''\",\n );\n result.changed = true;\n }\n });\n if (!result)\n throw new Error('Invoice quantity migration transaction did not execute.');\n return result;\n}\n","/**\n * Commerce money columns: floating-point major units → integer minor units.\n *\n * Ships as an explicit, opt-in migration rather than a schema-differ upgrade\n * because REAL→INTEGER is not information-preserving: an automatic\n * `ALTER … TYPE integer` would truncate a stored `19.99` to `19` and destroy 99\n * cents on every row. The values have to be rescaled by the currency's\n * minor-unit factor *during* the type change (#2401).\n *\n * Run it from one deploy process, after the writers are upgraded:\n *\n * ```ts\n * import { getDatabase } from '@happyvertical/sql';\n * import {\n * preflightCommerceMoneyMinorUnits,\n * migrateCommerceMoneyToMinorUnits,\n * } from '@happyvertical/smrt-commerce';\n *\n * const db = await getDatabase({ type: 'postgres', url: process.env.DATABASE_URL! });\n *\n * // 1. Read-only. Prints rows that would be rounded or exceed JavaScript's\n * // safe-integer range when hydrated.\n * console.log((await preflightCommerceMoneyMinorUnits(db)).summary);\n *\n * // 2. Convert. Idempotent — a second run is a no-op.\n * await migrateCommerceMoneyToMinorUnits(db);\n * ```\n *\n * Engine notes:\n *\n * - **PostgreSQL / DuckDB** — the column type changes in place with\n * `USING round(col * 100)`.\n * - **SQLite** — values are rescaled, but the *declared* type cannot be altered\n * in place. SQLite's declared type is only an affinity, so the column already\n * behaves as integer minor units; bringing the declaration into line needs\n * the table-rebuild path (#2370). The result reports those columns in\n * `declaredTypeChangePending`.\n *\n * Fresh PostgreSQL and DuckDB INTEGER columns materialize as BIGINT. The\n * preflight lists values that would exceed JavaScript's safe-integer range\n * after rescaling, because SMRT rejects rather than rounds those on hydration.\n * Existing PostgreSQL `int4` columns require the explicit widening in #2424.\n *\n * ## Two things this migration deliberately does NOT convert\n *\n * Both hold amounts in a **native asset's** minor unit rather than the\n * deployment's currency, so there is no single scale that is correct for every\n * row and a blanket ×100 would silently destroy data:\n *\n * - **`payments.native_amount`** — satoshis on a BTC rail (×1e8), cents on a\n * fiat or stablecoin rail (×1e2), in the same column, discriminated only by\n * `native_currency`. See {@link COMMERCE_NATIVE_UNIT_COLUMNS} for the\n * two-step conversion.\n * - **`payment_intents.payment_options[].nativeAmount`** — the same problem\n * inside a JSON column, per option. Because an intent's price lock defaults\n * to 15 minutes, the supported answer is to **let open intents expire and be\n * re-quoted**: an unconverted option simply fails to reconcile against the\n * payment that arrives (the check is an exact `!==`), which is a safe, loud\n * failure rather than a silently mis-scaled quote.\n */\n\nimport {\n type DatabaseInterface,\n type MinorUnitsPreflightResult,\n type MinorUnitsRescaleResult,\n type MoneyColumnTarget,\n preflightMinorUnitsRescale,\n rescaleMoneyColumnsToMinorUnits,\n} from '@happyvertical/smrt-core/migrations';\n\n/** Marker recorded in `_smrt_backfills` once the column rescale has run. */\nexport const COMMERCE_MONEY_MINOR_UNITS_BACKFILL =\n '@happyvertical/smrt-commerce:money-minor-units:v1';\n\n/**\n * Every commerce money column denominated in the deployment's own currency,\n * i.e. everything a single scale converts correctly.\n *\n * `invoices`, `invoice_line_items` and `payment_allocations` are absent on\n * purpose: those were already INTEGER (#2361), so the preflight reports them as\n * converted and there is nothing to rescale. Rate columns (`tax_rate`) are\n * absent for the opposite reason — they are genuinely fractional and must stay\n * DECIMAL. `payments.native_amount` is absent because its scale is per-asset;\n * see {@link COMMERCE_NATIVE_UNIT_COLUMNS}.\n */\nexport const COMMERCE_MONEY_COLUMNS: MoneyColumnTarget[] = [\n { table: 'contracts', columns: ['subtotal', 'tax_amount', 'total_amount'] },\n {\n table: 'contract_line_items',\n columns: ['unit_price', 'discount', 'amount'],\n },\n {\n table: 'payments',\n columns: ['amount', 'usd_at_quote', 'usd_at_confirmation'],\n },\n {\n table: 'payouts',\n columns: ['gross_amount', 'operator_fee', 'supplier_net'],\n },\n { table: 'vendors', columns: ['minimum_order_amount'] },\n { table: 'customers', columns: ['credit_limit'] },\n { table: 'payment_intents', columns: ['usd_price_locked'] },\n];\n\n/**\n * Columns whose minor-unit scale is a property of the **row's** asset, not of\n * the deployment — so no single `scale` converts them and they are excluded\n * from {@link migrateCommerceMoneyToMinorUnits}.\n *\n * `payments.native_amount` holds \"the amount the backend actually moved, in the\n * native currency's own minor units\": satoshis for `BTC` (×1e8), cents for\n * `USDC-base` or `USD-stripe` (×1e2). A blanket ×100 would store 1 satoshi-ish\n * nonsense for a 0.00713 BTC payment whose correct value is 713 000 sats, and\n * a round amount like 0.01 BTC would even pass the preflight's integrality\n * check while being wrong by six orders of magnitude.\n *\n * Convert it in two deliberate steps, from one deploy process, while the column\n * is still floating-point:\n *\n * ```ts\n * // 1. Normalise each row to its own asset's minor units, in the REAL column.\n * for (const [currency, scale] of Object.entries({ BTC: 1e8, 'USDC-base': 100 })) {\n * await db.query(\n * 'UPDATE payments SET native_amount = round(native_amount * ?) WHERE native_currency = ?',\n * scale,\n * currency,\n * );\n * }\n *\n * // 2. Change the column type with no further scaling.\n * await rescaleMoneyColumnsToMinorUnits(db, COMMERCE_NATIVE_UNIT_COLUMNS, {\n * scale: 1,\n * backfillName: '<your-app>:payments-native-amount-minor-units:v1',\n * });\n * ```\n *\n * Step 1 is deliberately yours to write: only the deployment knows which\n * `native_currency` values it has used and what each one's exponent is.\n */\nexport const COMMERCE_NATIVE_UNIT_COLUMNS: MoneyColumnTarget[] = [\n { table: 'payments', columns: ['native_amount'] },\n];\n\n/**\n * Report what {@link migrateCommerceMoneyToMinorUnits} would do, without\n * writing anything.\n *\n * @param db - Root database handle.\n * @param options - `scale` (default 100) and `engineHint`.\n * @returns Per-column state plus the rows that would lose information.\n */\nexport function preflightCommerceMoneyMinorUnits(\n db: DatabaseInterface,\n options: { scale?: number; engineHint?: string } = {},\n): Promise<MinorUnitsPreflightResult> {\n return preflightMinorUnitsRescale(db, COMMERCE_MONEY_COLUMNS, options);\n}\n\n/**\n * Convert every single-scale commerce money column from floating-point major\n * units to integer minor units.\n *\n * Idempotent: guarded by a `_smrt_backfills` marker, so re-running can never\n * multiply a table by the scale twice.\n *\n * Does **not** touch `payments.native_amount` or the `paymentOptions` JSON —\n * see this module's header and {@link COMMERCE_NATIVE_UNIT_COLUMNS}.\n *\n * @param db - Root database handle.\n * @param options - `scale` (default 100 — cents), `engineHint`, and `force` to\n * convert despite rows the preflight flagged as lossy.\n * @throws `MinorUnitsPreflightError` when a row would be rounded or exceed the\n * JavaScript safe-integer range and `force` was not set. Print\n * {@link preflightCommerceMoneyMinorUnits}'s summary, fix the data, retry.\n */\nexport function migrateCommerceMoneyToMinorUnits(\n db: DatabaseInterface,\n options: { scale?: number; engineHint?: string; force?: boolean } = {},\n): Promise<MinorUnitsRescaleResult> {\n return rescaleMoneyColumnsToMinorUnits(db, COMMERCE_MONEY_COLUMNS, {\n ...options,\n backfillName: COMMERCE_MONEY_MINOR_UNITS_BACKFILL,\n packageName: '@happyvertical/smrt-commerce',\n });\n}\n"],"mappings":"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"}