@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.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/adapters/drizzle/index.d.ts +28 -0
- package/dist/adapters/drizzle/index.d.ts.map +1 -0
- package/dist/adapters/drizzle/index.js +75 -0
- package/dist/adapters/drizzle/index.js.map +1 -0
- package/dist/adapters/drizzle/store.d.ts +37 -0
- package/dist/adapters/drizzle/store.d.ts.map +1 -0
- package/dist/adapters/drizzle/store.js +889 -0
- package/dist/adapters/drizzle/store.js.map +1 -0
- package/dist/catalog/index.d.ts +5 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +4 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalog/public-view.d.ts +7 -0
- package/dist/catalog/public-view.d.ts.map +1 -0
- package/dist/catalog/public-view.js +22 -0
- package/dist/catalog/public-view.js.map +1 -0
- package/dist/catalog/revisions.d.ts +8 -0
- package/dist/catalog/revisions.d.ts.map +1 -0
- package/dist/catalog/revisions.js +12 -0
- package/dist/catalog/revisions.js.map +1 -0
- package/dist/catalog/types.d.ts +28 -0
- package/dist/catalog/types.d.ts.map +1 -0
- package/dist/catalog/types.js +2 -0
- package/dist/catalog/types.js.map +1 -0
- package/dist/catalog/validate.d.ts +16 -0
- package/dist/catalog/validate.d.ts.map +1 -0
- package/dist/catalog/validate.js +66 -0
- package/dist/catalog/validate.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +35 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/generate.d.ts +15 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +68 -0
- package/dist/cli/generate.js.map +1 -0
- package/dist/config/define.d.ts +3 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/define.js +5 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/validate.d.ts +32 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +227 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/core/checkout-attempt.d.ts +29 -0
- package/dist/core/checkout-attempt.d.ts.map +1 -0
- package/dist/core/checkout-attempt.js +10 -0
- package/dist/core/checkout-attempt.js.map +1 -0
- package/dist/core/clock.d.ts +10 -0
- package/dist/core/clock.d.ts.map +1 -0
- package/dist/core/clock.js +26 -0
- package/dist/core/clock.js.map +1 -0
- package/dist/core/customer.d.ts +13 -0
- package/dist/core/customer.d.ts.map +1 -0
- package/dist/core/customer.js +2 -0
- package/dist/core/customer.js.map +1 -0
- package/dist/core/errors.d.ts +28 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +43 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/ids.d.ts +15 -0
- package/dist/core/ids.d.ts.map +1 -0
- package/dist/core/ids.js +29 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/money.d.ts +8 -0
- package/dist/core/money.d.ts.map +1 -0
- package/dist/core/money.js +23 -0
- package/dist/core/money.js.map +1 -0
- package/dist/core/plan-change-attempt.d.ts +30 -0
- package/dist/core/plan-change-attempt.d.ts.map +1 -0
- package/dist/core/plan-change-attempt.js +9 -0
- package/dist/core/plan-change-attempt.js.map +1 -0
- package/dist/core/projection-diff.d.ts +32 -0
- package/dist/core/projection-diff.d.ts.map +1 -0
- package/dist/core/projection-diff.js +32 -0
- package/dist/core/projection-diff.js.map +1 -0
- package/dist/core/reconciliation-job.d.ts +21 -0
- package/dist/core/reconciliation-job.d.ts.map +1 -0
- package/dist/core/reconciliation-job.js +26 -0
- package/dist/core/reconciliation-job.js.map +1 -0
- package/dist/core/subscription.d.ts +62 -0
- package/dist/core/subscription.d.ts.map +1 -0
- package/dist/core/subscription.js +14 -0
- package/dist/core/subscription.js.map +1 -0
- package/dist/core/transitions.d.ts +25 -0
- package/dist/core/transitions.d.ts.map +1 -0
- package/dist/core/transitions.js +161 -0
- package/dist/core/transitions.js.map +1 -0
- package/dist/core/webhook-inbox.d.ts +23 -0
- package/dist/core/webhook-inbox.d.ts.map +1 -0
- package/dist/core/webhook-inbox.js +9 -0
- package/dist/core/webhook-inbox.js.map +1 -0
- package/dist/maintenance/batches.d.ts +3 -0
- package/dist/maintenance/batches.d.ts.map +1 -0
- package/dist/maintenance/batches.js +3 -0
- package/dist/maintenance/batches.js.map +1 -0
- package/dist/maintenance/claims.d.ts +2 -0
- package/dist/maintenance/claims.d.ts.map +1 -0
- package/dist/maintenance/claims.js +2 -0
- package/dist/maintenance/claims.js.map +1 -0
- package/dist/maintenance/health.d.ts +13 -0
- package/dist/maintenance/health.d.ts.map +1 -0
- package/dist/maintenance/health.js +19 -0
- package/dist/maintenance/health.js.map +1 -0
- package/dist/maintenance/retry.d.ts +9 -0
- package/dist/maintenance/retry.d.ts.map +1 -0
- package/dist/maintenance/retry.js +19 -0
- package/dist/maintenance/retry.js.map +1 -0
- package/dist/operations/index.d.ts +28 -0
- package/dist/operations/index.d.ts.map +1 -0
- package/dist/operations/index.js +106 -0
- package/dist/operations/index.js.map +1 -0
- package/dist/persistence/memory.d.ts +104 -0
- package/dist/persistence/memory.d.ts.map +1 -0
- package/dist/persistence/memory.js +657 -0
- package/dist/persistence/memory.js.map +1 -0
- package/dist/persistence/store.d.ts +126 -0
- package/dist/persistence/store.d.ts.map +1 -0
- package/dist/persistence/store.js +2 -0
- package/dist/persistence/store.js.map +1 -0
- package/dist/ports/audit.d.ts +20 -0
- package/dist/ports/audit.d.ts.map +1 -0
- package/dist/ports/audit.js +9 -0
- package/dist/ports/audit.js.map +1 -0
- package/dist/ports/authorization.d.ts +28 -0
- package/dist/ports/authorization.d.ts.map +1 -0
- package/dist/ports/authorization.js +33 -0
- package/dist/ports/authorization.js.map +1 -0
- package/dist/ports/lifecycle.d.ts +23 -0
- package/dist/ports/lifecycle.d.ts.map +1 -0
- package/dist/ports/lifecycle.js +2 -0
- package/dist/ports/lifecycle.js.map +1 -0
- package/dist/ports/sensitive-values.d.ts +23 -0
- package/dist/ports/sensitive-values.d.ts.map +1 -0
- package/dist/ports/sensitive-values.js +19 -0
- package/dist/ports/sensitive-values.js.map +1 -0
- package/dist/ports/telemetry.d.ts +16 -0
- package/dist/ports/telemetry.d.ts.map +1 -0
- package/dist/ports/telemetry.js +48 -0
- package/dist/ports/telemetry.js.map +1 -0
- package/dist/providers/contract.d.ts +98 -0
- package/dist/providers/contract.d.ts.map +1 -0
- package/dist/providers/contract.js +2 -0
- package/dist/providers/contract.js.map +1 -0
- package/dist/providers/dodo/index.d.ts +25 -0
- package/dist/providers/dodo/index.d.ts.map +1 -0
- package/dist/providers/dodo/index.js +296 -0
- package/dist/providers/dodo/index.js.map +1 -0
- package/dist/providers/dodo/normalize.d.ts +7 -0
- package/dist/providers/dodo/normalize.d.ts.map +1 -0
- package/dist/providers/dodo/normalize.js +77 -0
- package/dist/providers/dodo/normalize.js.map +1 -0
- package/dist/providers/fake/index.d.ts +59 -0
- package/dist/providers/fake/index.d.ts.map +1 -0
- package/dist/providers/fake/index.js +312 -0
- package/dist/providers/fake/index.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/registry.d.ts +10 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +32 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/schema/canonical.d.ts +7 -0
- package/dist/schema/canonical.d.ts.map +1 -0
- package/dist/schema/canonical.js +35 -0
- package/dist/schema/canonical.js.map +1 -0
- package/dist/schema/generator.d.ts +3 -0
- package/dist/schema/generator.d.ts.map +1 -0
- package/dist/schema/generator.js +496 -0
- package/dist/schema/generator.js.map +1 -0
- package/dist/schema/pglite-sql.d.ts +3 -0
- package/dist/schema/pglite-sql.d.ts.map +1 -0
- package/dist/schema/pglite-sql.js +226 -0
- package/dist/schema/pglite-sql.js.map +1 -0
- package/dist/testing/clocks.d.ts +2 -0
- package/dist/testing/clocks.d.ts.map +1 -0
- package/dist/testing/clocks.js +2 -0
- package/dist/testing/clocks.js.map +1 -0
- package/dist/testing/consumer-conformance.d.ts +12 -0
- package/dist/testing/consumer-conformance.d.ts.map +1 -0
- package/dist/testing/consumer-conformance.js +17 -0
- package/dist/testing/consumer-conformance.js.map +1 -0
- package/dist/testing/fixtures.d.ts +9 -0
- package/dist/testing/fixtures.d.ts.map +1 -0
- package/dist/testing/fixtures.js +69 -0
- package/dist/testing/fixtures.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/provider-contract.d.ts +14 -0
- package/dist/testing/provider-contract.d.ts.map +1 -0
- package/dist/testing/provider-contract.js +238 -0
- package/dist/testing/provider-contract.js.map +1 -0
- package/dist/testing/state-machine.d.ts +2 -0
- package/dist/testing/state-machine.d.ts.map +1 -0
- package/dist/testing/state-machine.js +2 -0
- package/dist/testing/state-machine.js.map +1 -0
- package/dist/testing/workflow-harness.d.ts +224 -0
- package/dist/testing/workflow-harness.d.ts.map +1 -0
- package/dist/testing/workflow-harness.js +65 -0
- package/dist/testing/workflow-harness.js.map +1 -0
- package/dist/workflows/engine.d.ts +157 -0
- package/dist/workflows/engine.d.ts.map +1 -0
- package/dist/workflows/engine.js +2015 -0
- package/dist/workflows/engine.js.map +1 -0
- package/dist/workflows/index.d.ts +10 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +6 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/lifecycle.d.ts +5 -0
- package/dist/workflows/lifecycle.d.ts.map +1 -0
- package/dist/workflows/lifecycle.js +7 -0
- package/dist/workflows/lifecycle.js.map +1 -0
- package/docs/architecture.md +1188 -0
- package/docs/decisions/0001-extraction-discrepancies.md +29 -0
- package/docs/runbooks.md +140 -0
- 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":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACH,yBAAyB,GAM5B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACH,yBAAyB,GAM5B,CAAC;AA6CF,MAAM,UAAU,qBAAqB,CACjC,EAAe,EACf,OAAmC;IAEnC,MAAM,KAAK,GAAG,yBAAyB,CACnC,EAAwB,EACxB,OAAO,CACV,CAAC;IACF,MAAM,OAAO,GAA0B;QACnC,GAAG,KAAK;QACR,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,EAAE;QACF,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,gBAAgB;YAChB,OAAO,KAAK,CAAC,gBAAgB,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,eAAe,CACjB,EAA0D;YAE1D,OAAO,KAAK,CAAC,eAAe,CACxB,EAAE,CAAC,MAAM,GAAG,CAAC;gBACT,CAAC,CAAC,CAAC,EAAa,EAAE,EAAE,CACb,EAAsC,CACnC,EAAiB,CACpB;gBACP,CAAC,CAAE,EAAuB,CACjC,CAAC;QACN,CAAC;QACD,KAAK,CAAC,qBAAqB,CAAC,MAAM;YAC9B,OAAQ,EAAiB;iBACpB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,uBAAgC,CAAC;iBACvD,MAAM,CAAC,MAAM,CAAC;iBACd,SAAS,EAAE,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,kBAAkB,CAAC,MAAM;YAC3B,OAAQ,EAAiB;iBACpB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,oBAA6B,CAAC;iBACpD,MAAM,CAAC,MAAM,CAAC;iBACd,SAAS,EAAE,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,sBAAsB,CAAC,MAAM;YAC/B,OAAQ,EAAiB;iBACpB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,wBAAiC,CAAC;iBACxD,MAAM,CAAC,MAAM,CAAC;iBACd,SAAS,EAAE,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,4BAA4B,CAAC,MAAM;YACrC,OAAQ,EAAiB;iBACpB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,oBAA6B,CAAC;iBACpD,MAAM,CAAC,MAAM,CAAC;iBACd,SAAS,EAAE,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,sBAAsB,CAAC,EAAU;YACnC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,uBAE5B,CAAC;YACF,MAAM,IAAI,GAAG,MAAO,EAAiB;iBAChC,MAAM,EAAE;iBACR,IAAI,CAAC,KAAK,CAAC;iBACX,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAW,EAAE,EAAE,CAAC,CAAC;iBAChC,KAAK,CAAC,CAAC,CAAC,CAAC;YACd,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAC3B,CAAC;QACD,KAAK,CAAC,eAAe,CAAC,YAAoB,EAAE,eAAuB;YAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,oBAG5B,CAAC;YACF,MAAM,IAAI,GAAG,MAAO,EAAiB;iBAChC,MAAM,EAAE;iBACR,IAAI,CAAC,KAAK,CAAC;iBACX,KAAK,CACF,GAAG,CACC,EAAE,CAAC,KAAK,CAAC,QAAiB,EAAE,YAAY,CAAC,EACzC,EAAE,CAAC,KAAK,CAAC,eAAwB,EAAE,eAAe,CAAC,CACtD,CACJ;iBACA,KAAK,CAAC,CAAC,CAAC,CAAC;YACd,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAC3B,CAAC;QACD,KAAK,CAAC,yBAAyB,CAAC,YAAY,EAAE,sBAAsB;YAChE,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,oBAG5B,CAAC;YACF,MAAM,IAAI,GAAG,MAAO,EAAiB;iBAChC,MAAM,EAAE;iBACR,IAAI,CAAC,KAAK,CAAC;iBACX,KAAK,CACF,GAAG,CACC,EAAE,CAAC,KAAK,CAAC,QAAiB,EAAE,YAAY,CAAC,EACzC,EAAE,CACE,KAAK,CAAC,sBAA+B,EACrC,sBAAsB,CACzB,CACJ,CACJ;iBACA,KAAK,CAAC,CAAC,CAAC,CAAC;YACd,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAC3B,CAAC;KACJ,CAAC;IACF,KAAK,GAAG,CAAC;IACT,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC"}
|
|
@@ -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"}
|