@codelitdev/billing 0.1.0-alpha.1

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 (230) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/dist/adapters/drizzle/index.d.ts +28 -0
  4. package/dist/adapters/drizzle/index.d.ts.map +1 -0
  5. package/dist/adapters/drizzle/index.js +75 -0
  6. package/dist/adapters/drizzle/index.js.map +1 -0
  7. package/dist/adapters/drizzle/store.d.ts +37 -0
  8. package/dist/adapters/drizzle/store.d.ts.map +1 -0
  9. package/dist/adapters/drizzle/store.js +889 -0
  10. package/dist/adapters/drizzle/store.js.map +1 -0
  11. package/dist/catalog/index.d.ts +5 -0
  12. package/dist/catalog/index.d.ts.map +1 -0
  13. package/dist/catalog/index.js +4 -0
  14. package/dist/catalog/index.js.map +1 -0
  15. package/dist/catalog/public-view.d.ts +7 -0
  16. package/dist/catalog/public-view.d.ts.map +1 -0
  17. package/dist/catalog/public-view.js +22 -0
  18. package/dist/catalog/public-view.js.map +1 -0
  19. package/dist/catalog/revisions.d.ts +8 -0
  20. package/dist/catalog/revisions.d.ts.map +1 -0
  21. package/dist/catalog/revisions.js +12 -0
  22. package/dist/catalog/revisions.js.map +1 -0
  23. package/dist/catalog/types.d.ts +28 -0
  24. package/dist/catalog/types.d.ts.map +1 -0
  25. package/dist/catalog/types.js +2 -0
  26. package/dist/catalog/types.js.map +1 -0
  27. package/dist/catalog/validate.d.ts +16 -0
  28. package/dist/catalog/validate.d.ts.map +1 -0
  29. package/dist/catalog/validate.js +66 -0
  30. package/dist/catalog/validate.js.map +1 -0
  31. package/dist/cli/bin.d.ts +3 -0
  32. package/dist/cli/bin.d.ts.map +1 -0
  33. package/dist/cli/bin.js +35 -0
  34. package/dist/cli/bin.js.map +1 -0
  35. package/dist/cli/generate.d.ts +15 -0
  36. package/dist/cli/generate.d.ts.map +1 -0
  37. package/dist/cli/generate.js +68 -0
  38. package/dist/cli/generate.js.map +1 -0
  39. package/dist/config/define.d.ts +3 -0
  40. package/dist/config/define.d.ts.map +1 -0
  41. package/dist/config/define.js +5 -0
  42. package/dist/config/define.js.map +1 -0
  43. package/dist/config/index.d.ts +3 -0
  44. package/dist/config/index.d.ts.map +1 -0
  45. package/dist/config/index.js +3 -0
  46. package/dist/config/index.js.map +1 -0
  47. package/dist/config/validate.d.ts +32 -0
  48. package/dist/config/validate.d.ts.map +1 -0
  49. package/dist/config/validate.js +227 -0
  50. package/dist/config/validate.js.map +1 -0
  51. package/dist/core/checkout-attempt.d.ts +29 -0
  52. package/dist/core/checkout-attempt.d.ts.map +1 -0
  53. package/dist/core/checkout-attempt.js +10 -0
  54. package/dist/core/checkout-attempt.js.map +1 -0
  55. package/dist/core/clock.d.ts +10 -0
  56. package/dist/core/clock.d.ts.map +1 -0
  57. package/dist/core/clock.js +26 -0
  58. package/dist/core/clock.js.map +1 -0
  59. package/dist/core/customer.d.ts +13 -0
  60. package/dist/core/customer.d.ts.map +1 -0
  61. package/dist/core/customer.js +2 -0
  62. package/dist/core/customer.js.map +1 -0
  63. package/dist/core/errors.d.ts +28 -0
  64. package/dist/core/errors.d.ts.map +1 -0
  65. package/dist/core/errors.js +43 -0
  66. package/dist/core/errors.js.map +1 -0
  67. package/dist/core/ids.d.ts +15 -0
  68. package/dist/core/ids.d.ts.map +1 -0
  69. package/dist/core/ids.js +29 -0
  70. package/dist/core/ids.js.map +1 -0
  71. package/dist/core/index.d.ts +14 -0
  72. package/dist/core/index.d.ts.map +1 -0
  73. package/dist/core/index.js +14 -0
  74. package/dist/core/index.js.map +1 -0
  75. package/dist/core/money.d.ts +8 -0
  76. package/dist/core/money.d.ts.map +1 -0
  77. package/dist/core/money.js +23 -0
  78. package/dist/core/money.js.map +1 -0
  79. package/dist/core/plan-change-attempt.d.ts +30 -0
  80. package/dist/core/plan-change-attempt.d.ts.map +1 -0
  81. package/dist/core/plan-change-attempt.js +9 -0
  82. package/dist/core/plan-change-attempt.js.map +1 -0
  83. package/dist/core/projection-diff.d.ts +32 -0
  84. package/dist/core/projection-diff.d.ts.map +1 -0
  85. package/dist/core/projection-diff.js +32 -0
  86. package/dist/core/projection-diff.js.map +1 -0
  87. package/dist/core/reconciliation-job.d.ts +21 -0
  88. package/dist/core/reconciliation-job.d.ts.map +1 -0
  89. package/dist/core/reconciliation-job.js +26 -0
  90. package/dist/core/reconciliation-job.js.map +1 -0
  91. package/dist/core/subscription.d.ts +62 -0
  92. package/dist/core/subscription.d.ts.map +1 -0
  93. package/dist/core/subscription.js +14 -0
  94. package/dist/core/subscription.js.map +1 -0
  95. package/dist/core/transitions.d.ts +25 -0
  96. package/dist/core/transitions.d.ts.map +1 -0
  97. package/dist/core/transitions.js +161 -0
  98. package/dist/core/transitions.js.map +1 -0
  99. package/dist/core/webhook-inbox.d.ts +23 -0
  100. package/dist/core/webhook-inbox.d.ts.map +1 -0
  101. package/dist/core/webhook-inbox.js +9 -0
  102. package/dist/core/webhook-inbox.js.map +1 -0
  103. package/dist/maintenance/batches.d.ts +3 -0
  104. package/dist/maintenance/batches.d.ts.map +1 -0
  105. package/dist/maintenance/batches.js +3 -0
  106. package/dist/maintenance/batches.js.map +1 -0
  107. package/dist/maintenance/claims.d.ts +2 -0
  108. package/dist/maintenance/claims.d.ts.map +1 -0
  109. package/dist/maintenance/claims.js +2 -0
  110. package/dist/maintenance/claims.js.map +1 -0
  111. package/dist/maintenance/health.d.ts +13 -0
  112. package/dist/maintenance/health.d.ts.map +1 -0
  113. package/dist/maintenance/health.js +19 -0
  114. package/dist/maintenance/health.js.map +1 -0
  115. package/dist/maintenance/retry.d.ts +9 -0
  116. package/dist/maintenance/retry.d.ts.map +1 -0
  117. package/dist/maintenance/retry.js +19 -0
  118. package/dist/maintenance/retry.js.map +1 -0
  119. package/dist/operations/index.d.ts +28 -0
  120. package/dist/operations/index.d.ts.map +1 -0
  121. package/dist/operations/index.js +106 -0
  122. package/dist/operations/index.js.map +1 -0
  123. package/dist/persistence/memory.d.ts +104 -0
  124. package/dist/persistence/memory.d.ts.map +1 -0
  125. package/dist/persistence/memory.js +657 -0
  126. package/dist/persistence/memory.js.map +1 -0
  127. package/dist/persistence/store.d.ts +126 -0
  128. package/dist/persistence/store.d.ts.map +1 -0
  129. package/dist/persistence/store.js +2 -0
  130. package/dist/persistence/store.js.map +1 -0
  131. package/dist/ports/audit.d.ts +20 -0
  132. package/dist/ports/audit.d.ts.map +1 -0
  133. package/dist/ports/audit.js +9 -0
  134. package/dist/ports/audit.js.map +1 -0
  135. package/dist/ports/authorization.d.ts +28 -0
  136. package/dist/ports/authorization.d.ts.map +1 -0
  137. package/dist/ports/authorization.js +33 -0
  138. package/dist/ports/authorization.js.map +1 -0
  139. package/dist/ports/lifecycle.d.ts +23 -0
  140. package/dist/ports/lifecycle.d.ts.map +1 -0
  141. package/dist/ports/lifecycle.js +2 -0
  142. package/dist/ports/lifecycle.js.map +1 -0
  143. package/dist/ports/sensitive-values.d.ts +23 -0
  144. package/dist/ports/sensitive-values.d.ts.map +1 -0
  145. package/dist/ports/sensitive-values.js +19 -0
  146. package/dist/ports/sensitive-values.js.map +1 -0
  147. package/dist/ports/telemetry.d.ts +16 -0
  148. package/dist/ports/telemetry.d.ts.map +1 -0
  149. package/dist/ports/telemetry.js +48 -0
  150. package/dist/ports/telemetry.js.map +1 -0
  151. package/dist/providers/contract.d.ts +98 -0
  152. package/dist/providers/contract.d.ts.map +1 -0
  153. package/dist/providers/contract.js +2 -0
  154. package/dist/providers/contract.js.map +1 -0
  155. package/dist/providers/dodo/index.d.ts +25 -0
  156. package/dist/providers/dodo/index.d.ts.map +1 -0
  157. package/dist/providers/dodo/index.js +296 -0
  158. package/dist/providers/dodo/index.js.map +1 -0
  159. package/dist/providers/dodo/normalize.d.ts +7 -0
  160. package/dist/providers/dodo/normalize.d.ts.map +1 -0
  161. package/dist/providers/dodo/normalize.js +77 -0
  162. package/dist/providers/dodo/normalize.js.map +1 -0
  163. package/dist/providers/fake/index.d.ts +59 -0
  164. package/dist/providers/fake/index.d.ts.map +1 -0
  165. package/dist/providers/fake/index.js +312 -0
  166. package/dist/providers/fake/index.js.map +1 -0
  167. package/dist/providers/index.d.ts +4 -0
  168. package/dist/providers/index.d.ts.map +1 -0
  169. package/dist/providers/index.js +3 -0
  170. package/dist/providers/index.js.map +1 -0
  171. package/dist/providers/registry.d.ts +10 -0
  172. package/dist/providers/registry.d.ts.map +1 -0
  173. package/dist/providers/registry.js +32 -0
  174. package/dist/providers/registry.js.map +1 -0
  175. package/dist/schema/canonical.d.ts +7 -0
  176. package/dist/schema/canonical.d.ts.map +1 -0
  177. package/dist/schema/canonical.js +35 -0
  178. package/dist/schema/canonical.js.map +1 -0
  179. package/dist/schema/generator.d.ts +3 -0
  180. package/dist/schema/generator.d.ts.map +1 -0
  181. package/dist/schema/generator.js +496 -0
  182. package/dist/schema/generator.js.map +1 -0
  183. package/dist/schema/pglite-sql.d.ts +3 -0
  184. package/dist/schema/pglite-sql.d.ts.map +1 -0
  185. package/dist/schema/pglite-sql.js +226 -0
  186. package/dist/schema/pglite-sql.js.map +1 -0
  187. package/dist/testing/clocks.d.ts +2 -0
  188. package/dist/testing/clocks.d.ts.map +1 -0
  189. package/dist/testing/clocks.js +2 -0
  190. package/dist/testing/clocks.js.map +1 -0
  191. package/dist/testing/consumer-conformance.d.ts +12 -0
  192. package/dist/testing/consumer-conformance.d.ts.map +1 -0
  193. package/dist/testing/consumer-conformance.js +17 -0
  194. package/dist/testing/consumer-conformance.js.map +1 -0
  195. package/dist/testing/fixtures.d.ts +9 -0
  196. package/dist/testing/fixtures.d.ts.map +1 -0
  197. package/dist/testing/fixtures.js +69 -0
  198. package/dist/testing/fixtures.js.map +1 -0
  199. package/dist/testing/index.d.ts +7 -0
  200. package/dist/testing/index.d.ts.map +1 -0
  201. package/dist/testing/index.js +7 -0
  202. package/dist/testing/index.js.map +1 -0
  203. package/dist/testing/provider-contract.d.ts +14 -0
  204. package/dist/testing/provider-contract.d.ts.map +1 -0
  205. package/dist/testing/provider-contract.js +238 -0
  206. package/dist/testing/provider-contract.js.map +1 -0
  207. package/dist/testing/state-machine.d.ts +2 -0
  208. package/dist/testing/state-machine.d.ts.map +1 -0
  209. package/dist/testing/state-machine.js +2 -0
  210. package/dist/testing/state-machine.js.map +1 -0
  211. package/dist/testing/workflow-harness.d.ts +224 -0
  212. package/dist/testing/workflow-harness.d.ts.map +1 -0
  213. package/dist/testing/workflow-harness.js +65 -0
  214. package/dist/testing/workflow-harness.js.map +1 -0
  215. package/dist/workflows/engine.d.ts +157 -0
  216. package/dist/workflows/engine.d.ts.map +1 -0
  217. package/dist/workflows/engine.js +2015 -0
  218. package/dist/workflows/engine.js.map +1 -0
  219. package/dist/workflows/index.d.ts +10 -0
  220. package/dist/workflows/index.d.ts.map +1 -0
  221. package/dist/workflows/index.js +6 -0
  222. package/dist/workflows/index.js.map +1 -0
  223. package/dist/workflows/lifecycle.d.ts +5 -0
  224. package/dist/workflows/lifecycle.d.ts.map +1 -0
  225. package/dist/workflows/lifecycle.js +7 -0
  226. package/dist/workflows/lifecycle.js.map +1 -0
  227. package/docs/architecture.md +1188 -0
  228. package/docs/decisions/0001-extraction-discrepancies.md +29 -0
  229. package/docs/runbooks.md +140 -0
  230. package/package.json +110 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CodeLit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,339 @@
1
+ # @codelitdev/billing
2
+
3
+ Provider-neutral billing engine for CodeLit products. It runs **inside** your API process against **your** PostgreSQL database. It is not a hosted billing service.
4
+
5
+ Version **0.1.0-alpha.1**. Workflows, action grants, operator services, and lifecycle hooks are **experimental**. There is no v1 cutover until a second consumer (CourseLit) proves the same contracts.
6
+
7
+ ## Purpose
8
+
9
+ CodeLit products sell their own SaaS (organizations, schools, later other aggregates) through a payment provider. Without a shared engine, each product tends to grow its own catalog, checkout, webhooks, and reconciliation — and those copies diverge on the parts that are easy to get wrong.
10
+
11
+ This package standardizes those mechanics:
12
+
13
+ - Immutable, versioned price catalogs verified against the provider
14
+ - Checkout, customer portal, cancellation, and plan change
15
+ - Webhook verification, durable inbox, retrieve-current-then-project
16
+ - Subscription transitions, material projection diffs, and reconciliation leases
17
+ - Provider-safe public catalog / commercial-state read models
18
+ - Fake + Dodo adapters behind one contract
19
+
20
+ Your product still owns:
21
+
22
+ - Who is billed (organization, school, …)
23
+ - Plan names, entitlements, quotas, trials, grace, availability
24
+ - Auth, CSRF, recent-auth tokens, HTTP routes, UI
25
+ - Schema migrations, backfills, encryption keys, audit storage, alerts
26
+
27
+ The package never runs DDL on install, import, or process startup.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pnpm add @codelitdev/billing@0.1.0-alpha.1
33
+ # or: pnpm add @codelitdev/billing@alpha
34
+ # Dodo is an optional peer of the Dodo adapter
35
+ pnpm add dodopayments
36
+ ```
37
+
38
+ Public imports (do not import `src/`):
39
+
40
+ | Subpath | Use |
41
+ | --- | --- |
42
+ | `@codelitdev/billing/config` | `defineBillingConfig` |
43
+ | `@codelitdev/billing/drizzle` | `createDrizzleBillingStore` |
44
+ | `@codelitdev/billing/workflows` | `createBilling` (experimental) |
45
+ | `@codelitdev/billing/operations` | Operator inspect / retry / cancel |
46
+ | `@codelitdev/billing/providers` | Registry, fake, contract types |
47
+ | `@codelitdev/billing/providers/dodo` | `createDodoBillingProvider` |
48
+ | `@codelitdev/billing/core` | Statuses, money, errors, clock |
49
+ | `@codelitdev/billing/catalog` | Offer validation / public catalog |
50
+ | `@codelitdev/billing/testing` | Provider contract + workflow harness |
51
+
52
+ CLI:
53
+
54
+ ```bash
55
+ pnpm exec codelit-billing generate --config billing.config.ts
56
+ pnpm exec codelit-billing generate --check --config billing.config.ts
57
+ ```
58
+
59
+ ## How it fits together
60
+
61
+ ```
62
+ Your API / UI
63
+ auth, grants, plan policy, entitlements
64
+ │
65
+ ▼
66
+ createBilling ──► catalog + state machines
67
+ ──► provider adapter (Dodo / fake)
68
+ ──► Drizzle store ──► your Postgres
69
+ ──► your audit + afterProjection hooks
70
+ ```
71
+
72
+ Hexagonal rules that matter in practice:
73
+
74
+ - Core never reads `process.env` or wall time. You inject a `Clock` and provider options.
75
+ - No database transaction is held across a provider HTTP call. Durable checkout / webhook / job rows bridge that gap.
76
+ - A verified webhook is a wake-up. Entitlement changes only after `retrieveSubscription` and a material snapshot diff.
77
+ - Cloud composition requires an audit hook and a return-URL allowlist.
78
+
79
+ ## 1. Describe schema (`billing.config.ts`)
80
+
81
+ Same idea as Better Auth: the package owns the canonical model; you check in generated TypeScript and own SQL via drizzle-kit.
82
+
83
+ ```ts
84
+ import { defineBillingConfig } from "@codelitdev/billing/config";
85
+
86
+ export default defineBillingConfig({
87
+ dialect: "postgresql",
88
+ adapter: "drizzle",
89
+ output: "./src/db/schema/billing.generated.ts",
90
+ billableEntity: {
91
+ modelName: "organization",
92
+ tableImport: "./organizations",
93
+ tableExport: "organizations",
94
+ idColumn: "id",
95
+ idType: "uuid",
96
+ onDelete: "restrict",
97
+ },
98
+ payer: {
99
+ modelName: "user",
100
+ tableImport: "./auth",
101
+ tableExport: "user",
102
+ idColumn: "id",
103
+ idType: "text",
104
+ onDelete: "restrict",
105
+ },
106
+ planIds: ["pro", "business"],
107
+ requiredOfferKeys: [
108
+ "pro_month",
109
+ "pro_year",
110
+ "business_month",
111
+ "business_year",
112
+ ],
113
+ additionalFields: {
114
+ checkoutAttempts: {
115
+ pendingTeamName: { type: "text", nullable: true },
116
+ },
117
+ },
118
+ });
119
+ ```
120
+
121
+ `planIds` are **paid** plans persisted on canonical rows. A product Free plan or cardless trial stays in your tables — do not put `"free"` here.
122
+
123
+ Then:
124
+
125
+ ```bash
126
+ pnpm exec codelit-billing generate --config apps/api/billing.config.ts
127
+ pnpm exec drizzle-kit generate
128
+ pnpm exec drizzle-kit migrate
129
+ ```
130
+
131
+ CI should run `generate --check` so committed `billing.generated.ts` cannot drift. Hand-edit SQL for backfills (for example seeding a Free `billing_plan_states` row); never edit the generated TypeScript.
132
+
133
+ Examples: `examples/reference-product/`, `examples/consumers/sendlit/`.
134
+
135
+ ## 2. Compose the engine
136
+
137
+ Parse your own env. Pass typed Dodo options. Wire Drizzle, grants, encryption, and product effects.
138
+
139
+ ```ts
140
+ import { systemClock } from "@codelitdev/billing/core";
141
+ import { createDrizzleBillingStore } from "@codelitdev/billing/drizzle";
142
+ import { createBilling } from "@codelitdev/billing/workflows";
143
+ import { createDodoBillingProvider } from "@codelitdev/billing/providers/dodo";
144
+ import { db } from "./db/client";
145
+ import * as billingSchema from "./db/billing.generated";
146
+
147
+ const clock = systemClock;
148
+ const store = createDrizzleBillingStore(db, {
149
+ schema: billingSchema,
150
+ clock,
151
+ planStateDefaults: { plan: "free", rampStage: 0, rampCleanStageDays: 0 },
152
+ checkoutApplicationFields: {
153
+ toColumns: (fields) => ({
154
+ pendingTeamName:
155
+ typeof fields.pendingTeamName === "string"
156
+ ? fields.pendingTeamName
157
+ : null,
158
+ }),
159
+ fromRow: (row) => ({ pendingTeamName: row.pendingTeamName ?? null }),
160
+ },
161
+ });
162
+
163
+ export const billing = createBilling({
164
+ database: store,
165
+ providers: [
166
+ createDodoBillingProvider({
167
+ apiKey: process.env.DODO_PAYMENTS_API_KEY!,
168
+ environment: "test_mode", // or "live_mode"
169
+ webhookSecrets: [
170
+ { version: "current", secret: process.env.DODO_PAYMENTS_WEBHOOK_KEY_CURRENT! },
171
+ ],
172
+ clock,
173
+ }),
174
+ ],
175
+ clock,
176
+ authorization: yourGrantPort, // consume-once BillingAuthorizationPort
177
+ sensitiveValues: yourEncryptPort,
178
+ hooks: {
179
+ audit: yourAuditHook,
180
+ lifecycle: {
181
+ afterProjection: (input) =>
182
+ applyProductEffects(input, store.getTransaction() ?? db),
183
+ },
184
+ },
185
+ mode: "cloud", // "oss" forbids providers, offers, and catalog revision
186
+ checkoutProvider: "dodo",
187
+ requestedRevision: 1,
188
+ requiredOfferKeys: ["pro_month", "pro_year", "business_month", "business_year"],
189
+ offers: yourOffers, // must match requiredOfferKeys exactly
190
+ returnUrlValidator: (url) =>
191
+ new URL(url).origin === new URL(process.env.WEB_CLIENT!).origin,
192
+ });
193
+ ```
194
+
195
+ `afterProjection` runs **inside** the engine transaction. Use `store.getTransaction() ?? db` and do **not** open a nested `db.transaction()` on the root pool client — that deadlocks on `FOR UPDATE` rows. If you were passed the engine client, write on it; only nest when you received the root `db`.
196
+
197
+ OSS mode: `mode: "oss"`, empty providers, `checkoutProvider: ""`, `requestedRevision: null`, no offers. The engine rejects checkout; your product unlocks paid capabilities itself.
198
+
199
+ ## 3. Catalog at startup
200
+
201
+ Cloud startup validates config shape and **records** the requested revision. It must not block ordinary API readiness on Dodo.
202
+
203
+ ```ts
204
+ await billing.recordRequestedCatalog();
205
+ // later, from a worker / sweep — not on the hot health path
206
+ await billing.verifyRequestedCatalog();
207
+ ```
208
+
209
+ Checkout is available only when that requested revision is the verified **active** revision. A pending or invalid request sets `checkoutAvailable: false` on the public catalog; the last verified revision remains the commercial projection. Operators abandon a bad request with `abandonRequestedCatalog` after reverting config.
210
+
211
+ Immediately before creating a provider session, checkout re-fetches the selected product. A mismatch freezes new checkout; existing subscribers keep the last verified mapping.
212
+
213
+ ## 4. Human billing actions
214
+
215
+ Your app authenticates the user, proves recent auth / CSRF however you like, then issues a **single-use** `BillingActionGrant`. The engine consumes the grant **and** checks the persisted payer. A fresh grant cannot switch the payer on an existing customer or subscription.
216
+
217
+ ```ts
218
+ const { checkoutUrl } = await billing.startCheckout({
219
+ grant,
220
+ entity: { kind: "organization", id: organizationId },
221
+ payer: { id: userId, email, name },
222
+ offerKey: "pro_month",
223
+ catalogRevision: displayedRevision, // the revision the UI showed
224
+ returnUrl,
225
+ trialDays: 14, // 0 if ineligible; the package does not decide eligibility
226
+ applicationFields: { pendingTeamName: "Acme Team" },
227
+ });
228
+ ```
229
+
230
+ Other grant actions: `startPortal`, `startPlanChange`, `cancel`. Submit the catalog revision the user saw; a stale revision fails with `catalog_changed` and may include the current public catalog.
231
+
232
+ Redirect is not payment. Entitlement changes from webhook projection or reconciliation.
233
+
234
+ ## 5. Webhooks and workers
235
+
236
+ Preserve the **raw** body. Return 2xx after the event is durably inserted (duplicates included). Project asynchronously.
237
+
238
+ ```ts
239
+ const ingested = await billing.ingestWebhook({
240
+ provider: "dodo",
241
+ raw: { body, headers },
242
+ });
243
+ res.status(ingested.duplicate ? 200 : 202).json({ accepted: true });
244
+ void billing.runWebhookInboxBatch({ workerId: `billing-${process.pid}` });
245
+ ```
246
+
247
+ The package does not start timers. Your scheduler should call, with unique `workerId`s:
248
+
249
+ | Unit | Job |
250
+ | --- | --- |
251
+ | `runWebhookInboxBatch` | Drain verified events (retrieve + project) |
252
+ | `runReconciliationBatch` | Stuck customers/checkouts/changes/subscriptions |
253
+ | `runDeadlineBatch` | Expire open checkouts; drop elapsed paid-through entitlement |
254
+ | `purgeExpiredSensitiveValues` | Drop old encrypted checkout URLs / replay payloads |
255
+ | `verifyRequestedCatalog` | Finish a pending catalog revision |
256
+
257
+ Multi-instance safety is database leases, not a process-local mutex. Never hold an app transaction across a provider call.
258
+
259
+ ## 6. Read models
260
+
261
+ Ordinary routes must not call the provider.
262
+
263
+ ```ts
264
+ const catalog = await billing.publicCatalog();
265
+ // { revision, checkoutAvailable, offers: [{ key, plan, interval, amountMinor, currency, displayTrialDays }] }
266
+ // no provider product IDs
267
+
268
+ const commercial = await billing.commercialState(organizationId);
269
+ // activePaidPlan, subscriptionStatus, periods, pendingCheckout / pendingPlanChange, projectionVersion
270
+ ```
271
+
272
+ Compose those with your trial, Free plan, quotas, and `canManageBilling`. `projectionVersion` is a cache key; equivalent webhook replays update freshness only and do not increment it.
273
+
274
+ Before close / delete / owner demotion, call `getBillableEntityBillingBlockers` / `getPayerBillingResponsibilities` in the same transaction as the mutation. The package reports blockers; you decide the user-facing error.
275
+
276
+ Workflow failures are `BillingWorkflowError` with stable codes (`catalog_changed`, `payer_mismatch`, `checkout_pending`, …). Map them to HTTP in the app. Do not persist raw SDK messages.
277
+
278
+ ## 7. Operator CLI
279
+
280
+ Product CLIs call `createOperations`. Every mutation needs `OperatorContext` (`actorId`, bounded `reason`, optional ticket) and hits the audit hook.
281
+
282
+ ```ts
283
+ import { createOperations } from "@codelitdev/billing/operations";
284
+
285
+ const ops = createOperations({
286
+ billing,
287
+ clock,
288
+ requestedRevision: 1,
289
+ checkoutProvider: "dodo",
290
+ sensitiveValues,
291
+ });
292
+
293
+ await ops.health();
294
+ await ops.retryWebhook(ctx, providerEventId, "preserve_attempts");
295
+ await ops.reconcileSubscription(ctx, subscriptionId);
296
+ await ops.requestCancellation(ctx, subscriptionId);
297
+ const payload = await ops.decryptReplay(ctx, ciphertext); // separately audited
298
+ ```
299
+
300
+ Do not `UPDATE billing_webhook_events SET status = 'pending'` by hand. Retry either preserves the attempt counter or starts `new_budget`.
301
+
302
+ Procedures: [`docs/runbooks.md`](docs/runbooks.md).
303
+
304
+ ## 8. Tests
305
+
306
+ ```ts
307
+ import { runBillingProviderContract, createWorkflowHarness } from "@codelitdev/billing/testing";
308
+ ```
309
+
310
+ - `runBillingProviderContract(adapter)` — every adapter, including Dodo in CI against the fake and gated sandbox tests against Dodo test mode.
311
+ - `createWorkflowHarness()` — catalog, checkout, webhook ordering, grants, reconciliation without a network.
312
+
313
+ `pnpm test` is deterministic. Live Dodo sandbox tests stay behind an explicit env gate.
314
+
315
+ ## What this package will not do
316
+
317
+ - Product routes, ts-rest, MCP, or UI
318
+ - Auth, membership, CSRF, or issuing action tokens
319
+ - Automatic SQL on install/import/startup
320
+ - A shared billing database
321
+ - Usage metering, tax, or invoices in this alpha
322
+ - Moving a live subscription from one provider to another
323
+ - SendLit reputation, domains, or “one owned Free org” policy
324
+
325
+ ## Versioning
326
+
327
+ - **0.1.0-alpha.x** — experimental workflows. Public core/schema/provider types aim to stay stable; workflow/hook contracts can still change. Install with the `alpha` tag, not `latest`.
328
+ - Schema-changing package releases bump the generated schema version. Upgrade: bump the dependency → `generate` → review TS diff → `drizzle-kit generate` → add app backfills → migrate in the product job → deploy compatible API/worker.
329
+ - Architecture and rollout: [`docs/architecture.md`](docs/architecture.md). Extraction discrepancies: [`docs/decisions/0001-extraction-discrepancies.md`](docs/decisions/0001-extraction-discrepancies.md).
330
+
331
+ ## Develop this repo
332
+
333
+ ```bash
334
+ pnpm install
335
+ pnpm test
336
+ pnpm typecheck
337
+ pnpm build
338
+ pnpm test:packed # install the tarball via public exports only
339
+ ```
@@ -0,0 +1,28 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import type { Clock } from "../../core/clock.js";
3
+ import { createDrizzleBillingStore, type CheckoutApplicationFieldsCodec, type DrizzleBillingStore, type DrizzleBillingStoreOptions, type DrizzleDb, type GeneratedBillingSchema } from "./store.js";
4
+ import type { BillingStore } from "../../persistence/store.js";
5
+ export { createDrizzleBillingStore, type CheckoutApplicationFieldsCodec, type DrizzleBillingStore, type DrizzleBillingStoreOptions, type DrizzleDb, type GeneratedBillingSchema, };
6
+ export type DrizzleLike = {
7
+ insert: (...args: never[]) => unknown;
8
+ select: (...args: never[]) => unknown;
9
+ update: (...args: never[]) => unknown;
10
+ transaction: <T>(fn: (tx: DrizzleLike) => Promise<T>) => Promise<T>;
11
+ execute?: (query: unknown) => Promise<unknown>;
12
+ };
13
+ export type DrizzleBillingAdapter = BillingStore & {
14
+ schema: GeneratedBillingSchema;
15
+ db: DrizzleLike;
16
+ clock: Clock;
17
+ withTransaction<T>(fn: (() => Promise<T>) | ((tx: DrizzleLike) => Promise<T>)): Promise<T>;
18
+ insertCheckoutAttempt(values: Record<string, unknown>): Promise<unknown>;
19
+ insertWebhookEvent(values: Record<string, unknown>): Promise<unknown>;
20
+ insertProviderCustomer(values: Record<string, unknown>): Promise<unknown>;
21
+ insertSubscriptionProjection(values: Record<string, unknown>): Promise<unknown>;
22
+ getCheckoutAttemptById(id: string): Promise<unknown>;
23
+ getWebhookEvent(provider: string, providerEventId: string): Promise<unknown>;
24
+ getSubscriptionProjection(provider: string, providerSubscriptionId: string): Promise<unknown>;
25
+ };
26
+ export declare function drizzleBillingAdapter(db: DrizzleLike, options: DrizzleBillingStoreOptions): DrizzleBillingAdapter;
27
+ export { and, eq };
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/drizzle/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAO,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACH,yBAAyB,EACzB,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,SAAS,EACd,KAAK,sBAAsB,EAC9B,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EACH,yBAAyB,EACzB,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,SAAS,EACd,KAAK,sBAAsB,GAC9B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;IACtC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;IACtC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAClD,CAAC;AAaF,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG;IAC/C,MAAM,EAAE,sBAAsB,CAAC;IAC/B,EAAE,EAAE,WAAW,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,eAAe,CAAC,CAAC,EACb,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAC3D,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E,4BAA4B,CACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,eAAe,CACX,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACxB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,yBAAyB,CACrB,QAAQ,EAAE,MAAM,EAChB,sBAAsB,EAAE,MAAM,GAC/B,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEF,wBAAgB,qBAAqB,CACjC,EAAE,EAAE,WAAW,EACf,OAAO,EAAE,0BAA0B,GACpC,qBAAqB,CAoGvB;AAED,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { and, eq, sql } from "drizzle-orm";
2
+ import { createDrizzleBillingStore, } from "./store.js";
3
+ export { createDrizzleBillingStore, };
4
+ export function drizzleBillingAdapter(db, options) {
5
+ const store = createDrizzleBillingStore(db, options);
6
+ const adapter = {
7
+ ...store,
8
+ schema: options.schema,
9
+ db,
10
+ clock: options.clock,
11
+ get transactionDepth() {
12
+ return store.transactionDepth;
13
+ },
14
+ async withTransaction(fn) {
15
+ return store.withTransaction(fn.length > 0
16
+ ? (tx) => fn(tx)
17
+ : fn);
18
+ },
19
+ async insertCheckoutAttempt(values) {
20
+ return db
21
+ .insert(options.schema.billingCheckoutAttempts)
22
+ .values(values)
23
+ .returning();
24
+ },
25
+ async insertWebhookEvent(values) {
26
+ return db
27
+ .insert(options.schema.billingWebhookEvents)
28
+ .values(values)
29
+ .returning();
30
+ },
31
+ async insertProviderCustomer(values) {
32
+ return db
33
+ .insert(options.schema.billingProviderCustomers)
34
+ .values(values)
35
+ .returning();
36
+ },
37
+ async insertSubscriptionProjection(values) {
38
+ return db
39
+ .insert(options.schema.billingSubscriptions)
40
+ .values(values)
41
+ .returning();
42
+ },
43
+ async getCheckoutAttemptById(id) {
44
+ const table = options.schema.billingCheckoutAttempts;
45
+ const rows = await db
46
+ .select()
47
+ .from(table)
48
+ .where(eq(table.id, id))
49
+ .limit(1);
50
+ return rows[0] ?? null;
51
+ },
52
+ async getWebhookEvent(providerName, providerEventId) {
53
+ const table = options.schema.billingWebhookEvents;
54
+ const rows = await db
55
+ .select()
56
+ .from(table)
57
+ .where(and(eq(table.provider, providerName), eq(table.providerEventId, providerEventId)))
58
+ .limit(1);
59
+ return rows[0] ?? null;
60
+ },
61
+ async getSubscriptionProjection(providerName, providerSubscriptionId) {
62
+ const table = options.schema.billingSubscriptions;
63
+ const rows = await db
64
+ .select()
65
+ .from(table)
66
+ .where(and(eq(table.provider, providerName), eq(table.providerSubscriptionId, providerSubscriptionId)))
67
+ .limit(1);
68
+ return rows[0] ?? null;
69
+ },
70
+ };
71
+ void sql;
72
+ return adapter;
73
+ }
74
+ export { and, eq };
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/drizzle/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Clock } from "../../core/clock.js";
2
+ import type { BillingStore } from "../../persistence/store.js";
3
+ export type GeneratedBillingSchema = {
4
+ billingPriceEntries: unknown;
5
+ billingCatalogRevisions: unknown;
6
+ billingCatalogRevisionItems: unknown;
7
+ billingProviderCustomers: unknown;
8
+ billingCheckoutAttempts: unknown;
9
+ billingPlanChangeAttempts: unknown;
10
+ billingSubscriptions: unknown;
11
+ billingPlanStates: unknown;
12
+ billingWebhookEvents: unknown;
13
+ billingReconciliationJobs: unknown;
14
+ };
15
+ export type DrizzleDb = {
16
+ select: (...args: never[]) => unknown;
17
+ insert: (...args: never[]) => unknown;
18
+ update: (...args: never[]) => unknown;
19
+ transaction: <T>(fn: (tx: DrizzleDb) => Promise<T>) => Promise<T>;
20
+ };
21
+ export type DrizzleBillingStoreOptions = {
22
+ schema: GeneratedBillingSchema;
23
+ clock: Clock;
24
+ planStateDefaults?: Record<string, unknown>;
25
+ checkoutApplicationFields?: CheckoutApplicationFieldsCodec;
26
+ };
27
+ export type CheckoutApplicationFieldsCodec = {
28
+ toColumns(fields: Record<string, unknown>): Record<string, unknown>;
29
+ fromRow(row: Readonly<Record<string, unknown>>): Record<string, unknown>;
30
+ };
31
+ export type DrizzleBillingStore = BillingStore & {
32
+ withTransaction<T>(fn: (() => Promise<T>) | ((tx: DrizzleDb) => Promise<T>)): Promise<T>;
33
+ /** Open Drizzle transaction when called inside `withTransaction`. */
34
+ getTransaction(): DrizzleDb | undefined;
35
+ };
36
+ export declare function createDrizzleBillingStore(db: DrizzleDb, options: DrizzleBillingStoreOptions): DrizzleBillingStore;
37
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/adapters/drizzle/store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAQjD,OAAO,KAAK,EACR,YAAY,EAQf,MAAM,4BAA4B,CAAC;AACpC,MAAM,MAAM,sBAAsB,GAAG;IACjC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,uBAAuB,EAAE,OAAO,CAAC;IACjC,2BAA2B,EAAE,OAAO,CAAC;IACrC,wBAAwB,EAAE,OAAO,CAAC;IAClC,uBAAuB,EAAE,OAAO,CAAC;IACjC,yBAAyB,EAAE,OAAO,CAAC;IACnC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,yBAAyB,EAAE,OAAO,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACpB,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;IACtC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;IACtC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACrE,CAAC;AAoBF,MAAM,MAAM,0BAA0B,GAAG;IACrC,MAAM,EAAE,sBAAsB,CAAC;IAC/B,KAAK,EAAE,KAAK,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,yBAAyB,CAAC,EAAE,8BAA8B,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IACzC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5E,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG;IAC7C,eAAe,CAAC,CAAC,EACb,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,qEAAqE;IACrE,cAAc,IAAI,SAAS,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,wBAAgB,yBAAyB,CACrC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE,0BAA0B,GACpC,mBAAmB,CAwxCrB"}