@noodleseed/one 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.d.ts +8 -0
- package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.js +49 -0
- package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/in-memory.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/in-memory.js +3 -2
- package/node_modules/@noodle-borg/service/dist/billing/in-memory.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/model.d.ts +16 -3
- package/node_modules/@noodle-borg/service/dist/billing/model.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/model.js +1 -0
- package/node_modules/@noodle-borg/service/dist/billing/model.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.d.ts +25 -0
- package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.js +87 -0
- package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/billing/postgres-records.d.ts +4 -0
- package/node_modules/@noodle-borg/service/dist/billing/postgres-records.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/postgres-records.js +38 -9
- package/node_modules/@noodle-borg/service/dist/billing/postgres-records.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/postgres-schema.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/postgres-schema.js +28 -21
- package/node_modules/@noodle-borg/service/dist/billing/postgres-schema.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.d.ts +4 -0
- package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.js +319 -0
- package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/billing/subscription-model.d.ts +40 -0
- package/node_modules/@noodle-borg/service/dist/billing/subscription-model.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/billing/subscription-model.js +380 -0
- package/node_modules/@noodle-borg/service/dist/billing/subscription-model.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/billing-public.d.ts +4 -1
- package/node_modules/@noodle-borg/service/dist/billing-public.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/billing-public.js +3 -0
- package/node_modules/@noodle-borg/service/dist/billing-public.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BILLING_PUBLIC_PLAN_VERSIONS } from './paid-plan-catalog.js';
|
|
2
2
|
import { ensureBillingEnforcementActivationSchema } from './postgres-enforcement-activation-schema.js';
|
|
3
3
|
import { ensureBillingEnforcementProfileSchema } from './postgres-enforcement-profile-schema.js';
|
|
4
4
|
import { ensureAuthoritativeMeterSchema } from './postgres-meter-schema.js';
|
|
5
5
|
import { ensureBillingMeterWriterHealthSchema } from './postgres-meter-writer-health-schema.js';
|
|
6
6
|
import { planVersionRowToRecord } from './postgres-records.js';
|
|
7
|
+
import { ensureBillingSubscriptionSchema } from './postgres-subscription-schema.js';
|
|
7
8
|
/** Additive BA-1 schema. No existing organization is linked or otherwise migrated here. */
|
|
8
9
|
export async function ensureBillingSchema(pool) {
|
|
9
10
|
await pool.query(`
|
|
@@ -81,12 +82,16 @@ export async function ensureBillingSchema(pool) {
|
|
|
81
82
|
billing_account_id text NOT NULL REFERENCES billing_accounts(id) ON DELETE RESTRICT,
|
|
82
83
|
version integer NOT NULL CHECK (version > 0),
|
|
83
84
|
plan_version_id text NOT NULL REFERENCES billing_plan_versions(id) ON DELETE RESTRICT,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
schema_version integer NOT NULL DEFAULT 1,
|
|
86
|
+
source_kind text NOT NULL,
|
|
87
|
+
generation_reason text NOT NULL,
|
|
88
|
+
verification_status text NOT NULL,
|
|
87
89
|
effective_from timestamptz NOT NULL,
|
|
90
|
+
effective_until timestamptz,
|
|
88
91
|
monthly_usage_anchor timestamptz NOT NULL,
|
|
89
92
|
entitlements jsonb NOT NULL,
|
|
93
|
+
source_subscription_id text,
|
|
94
|
+
source_subscription_version integer,
|
|
90
95
|
content_checksum text NOT NULL,
|
|
91
96
|
verified_at timestamptz NOT NULL,
|
|
92
97
|
created_at timestamptz NOT NULL,
|
|
@@ -102,6 +107,7 @@ export async function ensureBillingSchema(pool) {
|
|
|
102
107
|
REFERENCES billing_entitlement_snapshots(billing_account_id, id) ON DELETE RESTRICT
|
|
103
108
|
)
|
|
104
109
|
`);
|
|
110
|
+
await ensureBillingSubscriptionSchema(pool);
|
|
105
111
|
await pool.query(`
|
|
106
112
|
CREATE TABLE IF NOT EXISTS org_billing_home_accounts (
|
|
107
113
|
org_slug text PRIMARY KEY REFERENCES orgs(slug) ON DELETE RESTRICT,
|
|
@@ -271,23 +277,24 @@ export async function ensureBillingSchema(pool) {
|
|
|
271
277
|
PRIMARY KEY (migration_id, org_slug)
|
|
272
278
|
)
|
|
273
279
|
`);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
280
|
+
for (const plan of BILLING_PUBLIC_PLAN_VERSIONS) {
|
|
281
|
+
await pool.query(`INSERT INTO billing_plan_versions
|
|
282
|
+
(id, plan_code, version, effective_from, entitlements, content_checksum, created_at)
|
|
283
|
+
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
|
|
284
|
+
ON CONFLICT (id) DO NOTHING`, [
|
|
285
|
+
plan.id,
|
|
286
|
+
plan.planCode,
|
|
287
|
+
plan.version,
|
|
288
|
+
plan.effectiveFrom,
|
|
289
|
+
JSON.stringify(plan.entitlements),
|
|
290
|
+
plan.contentChecksum,
|
|
291
|
+
plan.createdAt,
|
|
292
|
+
]);
|
|
293
|
+
const { rows } = await pool.query('SELECT * FROM billing_plan_versions WHERE id = $1', [plan.id]);
|
|
294
|
+
const persisted = rows[0] ? planVersionRowToRecord(rows[0]) : undefined;
|
|
295
|
+
if (persisted === undefined || persisted.contentChecksum !== plan.contentChecksum) {
|
|
296
|
+
throw new Error(`persisted ${plan.planCode} plan version does not match the immutable platform definition`);
|
|
297
|
+
}
|
|
291
298
|
}
|
|
292
299
|
}
|
|
293
300
|
//# sourceMappingURL=postgres-schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-schema.js","sourceRoot":"","sources":["../../src/billing/postgres-schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"postgres-schema.js","sourceRoot":"","sources":["../../src/billing/postgres-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
/** Additive, dormant paid-subscription history. This schema never advances an active snapshot. */
|
|
3
|
+
export declare function ensureBillingSubscriptionSchema(pool: Pool): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=postgres-subscription-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-subscription-schema.d.ts","sourceRoot":"","sources":["../../src/billing/postgres-subscription-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAE3C,kGAAkG;AAClG,wBAAsB,+BAA+B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB/E"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/** Additive, dormant paid-subscription history. This schema never advances an active snapshot. */
|
|
2
|
+
export async function ensureBillingSubscriptionSchema(pool) {
|
|
3
|
+
const client = await pool.connect();
|
|
4
|
+
try {
|
|
5
|
+
await client.query('BEGIN');
|
|
6
|
+
try {
|
|
7
|
+
// Service replicas may all start against the same database. Serialize this migration so the
|
|
8
|
+
// catalog checks and trigger installation below cannot race one another.
|
|
9
|
+
await client.query(`SELECT pg_advisory_xact_lock(hashtext('noodle.billing.subscription-schema.v1'))`);
|
|
10
|
+
await ensureBillingSubscriptionSchemaTx(client);
|
|
11
|
+
await client.query('COMMIT');
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
await client.query('ROLLBACK');
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
client.release();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function ensureBillingSubscriptionSchemaTx(client) {
|
|
23
|
+
await client.query(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS billing_base_subscriptions (
|
|
25
|
+
id text PRIMARY KEY,
|
|
26
|
+
billing_account_id text NOT NULL REFERENCES billing_accounts(id) ON DELETE RESTRICT,
|
|
27
|
+
created_at timestamptz NOT NULL,
|
|
28
|
+
UNIQUE (billing_account_id, id)
|
|
29
|
+
)
|
|
30
|
+
`);
|
|
31
|
+
await client.query(`
|
|
32
|
+
CREATE TABLE IF NOT EXISTS billing_base_subscription_revisions (
|
|
33
|
+
schema_version integer NOT NULL CHECK (schema_version = 1),
|
|
34
|
+
subscription_id text NOT NULL,
|
|
35
|
+
billing_account_id text NOT NULL,
|
|
36
|
+
version integer NOT NULL CHECK (version > 0),
|
|
37
|
+
plan_version_id text NOT NULL
|
|
38
|
+
CHECK (plan_version_id IN ('pro@1', 'scale@1'))
|
|
39
|
+
REFERENCES billing_plan_versions(id) ON DELETE RESTRICT,
|
|
40
|
+
state text NOT NULL CHECK (state IN ('active', 'payment_grace', 'ended')),
|
|
41
|
+
billing_interval text NOT NULL CHECK (billing_interval IN ('month', 'year')),
|
|
42
|
+
currency text NOT NULL CHECK (currency = 'usd'),
|
|
43
|
+
base_amount_minor bigint NOT NULL
|
|
44
|
+
CHECK (base_amount_minor >= 0 AND base_amount_minor <= 9007199254740991),
|
|
45
|
+
current_period_start timestamptz NOT NULL,
|
|
46
|
+
current_period_end timestamptz NOT NULL CHECK (current_period_end > current_period_start),
|
|
47
|
+
effective_from timestamptz NOT NULL,
|
|
48
|
+
effective_until timestamptz NOT NULL CHECK (effective_until > effective_from),
|
|
49
|
+
renews boolean NOT NULL,
|
|
50
|
+
content_checksum text NOT NULL,
|
|
51
|
+
verified_at timestamptz NOT NULL,
|
|
52
|
+
created_at timestamptz NOT NULL,
|
|
53
|
+
PRIMARY KEY (subscription_id, version),
|
|
54
|
+
UNIQUE (billing_account_id, subscription_id, version),
|
|
55
|
+
UNIQUE (billing_account_id, subscription_id, version, plan_version_id),
|
|
56
|
+
CONSTRAINT billing_base_subscription_revisions_snapshot_source_key
|
|
57
|
+
UNIQUE (
|
|
58
|
+
billing_account_id,
|
|
59
|
+
subscription_id,
|
|
60
|
+
version,
|
|
61
|
+
plan_version_id,
|
|
62
|
+
effective_from,
|
|
63
|
+
effective_until
|
|
64
|
+
),
|
|
65
|
+
FOREIGN KEY (billing_account_id, subscription_id)
|
|
66
|
+
REFERENCES billing_base_subscriptions(billing_account_id, id) ON DELETE RESTRICT,
|
|
67
|
+
CHECK (verified_at >= effective_from),
|
|
68
|
+
CHECK (created_at >= verified_at),
|
|
69
|
+
CHECK (state <> 'active' OR effective_until = current_period_end),
|
|
70
|
+
CONSTRAINT billing_base_subscription_revisions_payment_grace_check CHECK (
|
|
71
|
+
state <> 'payment_grace' OR
|
|
72
|
+
(
|
|
73
|
+
effective_until = current_period_end + INTERVAL '168 hours' AND
|
|
74
|
+
verified_at >= current_period_end
|
|
75
|
+
)
|
|
76
|
+
),
|
|
77
|
+
CHECK (state <> 'ended' OR (NOT renews AND verified_at >= effective_until))
|
|
78
|
+
)
|
|
79
|
+
`);
|
|
80
|
+
await client.query(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS billing_account_current_base_subscriptions (
|
|
82
|
+
billing_account_id text PRIMARY KEY REFERENCES billing_accounts(id) ON DELETE RESTRICT,
|
|
83
|
+
subscription_id text NOT NULL,
|
|
84
|
+
revision_version integer NOT NULL CHECK (revision_version > 0),
|
|
85
|
+
FOREIGN KEY (billing_account_id, subscription_id, revision_version)
|
|
86
|
+
REFERENCES billing_base_subscription_revisions
|
|
87
|
+
(billing_account_id, subscription_id, version)
|
|
88
|
+
ON DELETE RESTRICT
|
|
89
|
+
)
|
|
90
|
+
`);
|
|
91
|
+
await client.query(`
|
|
92
|
+
ALTER TABLE billing_entitlement_snapshots
|
|
93
|
+
ADD COLUMN IF NOT EXISTS schema_version integer NOT NULL DEFAULT 1,
|
|
94
|
+
ADD COLUMN IF NOT EXISTS effective_until timestamptz,
|
|
95
|
+
ADD COLUMN IF NOT EXISTS source_subscription_id text,
|
|
96
|
+
ADD COLUMN IF NOT EXISTS source_subscription_version integer
|
|
97
|
+
`);
|
|
98
|
+
await client.query(`
|
|
99
|
+
DO $billing_snapshot_anchor_consistency$
|
|
100
|
+
BEGIN
|
|
101
|
+
IF EXISTS (
|
|
102
|
+
SELECT 1
|
|
103
|
+
FROM billing_entitlement_snapshots
|
|
104
|
+
GROUP BY billing_account_id
|
|
105
|
+
HAVING MIN(monthly_usage_anchor) <> MAX(monthly_usage_anchor)
|
|
106
|
+
) THEN
|
|
107
|
+
RAISE EXCEPTION 'persisted billing account has inconsistent monthly usage anchors';
|
|
108
|
+
END IF;
|
|
109
|
+
END
|
|
110
|
+
$billing_snapshot_anchor_consistency$
|
|
111
|
+
`);
|
|
112
|
+
await client.query(`
|
|
113
|
+
CREATE OR REPLACE FUNCTION billing_enforce_entitlement_anchor_stability()
|
|
114
|
+
RETURNS trigger
|
|
115
|
+
LANGUAGE plpgsql
|
|
116
|
+
AS $billing_snapshot_anchor_guard$
|
|
117
|
+
BEGIN
|
|
118
|
+
IF TG_OP = 'UPDATE' THEN
|
|
119
|
+
IF NEW.billing_account_id IS DISTINCT FROM OLD.billing_account_id THEN
|
|
120
|
+
RAISE EXCEPTION 'billing entitlement snapshot account is immutable'
|
|
121
|
+
USING ERRCODE = '23514';
|
|
122
|
+
END IF;
|
|
123
|
+
IF NEW.monthly_usage_anchor IS DISTINCT FROM OLD.monthly_usage_anchor THEN
|
|
124
|
+
RAISE EXCEPTION 'billing account monthly usage anchor is immutable'
|
|
125
|
+
USING ERRCODE = '23514';
|
|
126
|
+
END IF;
|
|
127
|
+
RETURN NEW;
|
|
128
|
+
END IF;
|
|
129
|
+
|
|
130
|
+
PERFORM 1 FROM billing_accounts
|
|
131
|
+
WHERE id = NEW.billing_account_id
|
|
132
|
+
FOR UPDATE;
|
|
133
|
+
IF EXISTS (
|
|
134
|
+
SELECT 1 FROM billing_entitlement_snapshots
|
|
135
|
+
WHERE billing_account_id = NEW.billing_account_id
|
|
136
|
+
AND monthly_usage_anchor IS DISTINCT FROM NEW.monthly_usage_anchor
|
|
137
|
+
) THEN
|
|
138
|
+
RAISE EXCEPTION 'billing account monthly usage anchor is immutable'
|
|
139
|
+
USING ERRCODE = '23514';
|
|
140
|
+
END IF;
|
|
141
|
+
RETURN NEW;
|
|
142
|
+
END
|
|
143
|
+
$billing_snapshot_anchor_guard$
|
|
144
|
+
`);
|
|
145
|
+
await client.query(`
|
|
146
|
+
DO $billing_snapshot_anchor_trigger$
|
|
147
|
+
BEGIN
|
|
148
|
+
IF NOT EXISTS (
|
|
149
|
+
SELECT 1 FROM pg_trigger
|
|
150
|
+
WHERE tgname = 'billing_entitlement_snapshots_anchor_stability'
|
|
151
|
+
AND tgrelid = 'billing_entitlement_snapshots'::regclass
|
|
152
|
+
AND NOT tgisinternal
|
|
153
|
+
) THEN
|
|
154
|
+
BEGIN
|
|
155
|
+
CREATE TRIGGER billing_entitlement_snapshots_anchor_stability
|
|
156
|
+
BEFORE INSERT OR UPDATE ON billing_entitlement_snapshots
|
|
157
|
+
FOR EACH ROW EXECUTE FUNCTION billing_enforce_entitlement_anchor_stability();
|
|
158
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
159
|
+
NULL;
|
|
160
|
+
END;
|
|
161
|
+
END IF;
|
|
162
|
+
END
|
|
163
|
+
$billing_snapshot_anchor_trigger$
|
|
164
|
+
`);
|
|
165
|
+
await client.query(`
|
|
166
|
+
DO $billing_paid_snapshot_constraints$
|
|
167
|
+
BEGIN
|
|
168
|
+
IF NOT EXISTS (
|
|
169
|
+
SELECT 1 FROM pg_constraint
|
|
170
|
+
WHERE conname = 'billing_base_subscription_revisions_snapshot_source_key'
|
|
171
|
+
AND conrelid = 'billing_base_subscription_revisions'::regclass
|
|
172
|
+
) THEN
|
|
173
|
+
BEGIN
|
|
174
|
+
ALTER TABLE billing_base_subscription_revisions
|
|
175
|
+
ADD CONSTRAINT billing_base_subscription_revisions_snapshot_source_key
|
|
176
|
+
UNIQUE (
|
|
177
|
+
billing_account_id,
|
|
178
|
+
subscription_id,
|
|
179
|
+
version,
|
|
180
|
+
plan_version_id,
|
|
181
|
+
effective_from,
|
|
182
|
+
effective_until
|
|
183
|
+
);
|
|
184
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
185
|
+
NULL;
|
|
186
|
+
END;
|
|
187
|
+
END IF;
|
|
188
|
+
IF NOT EXISTS (
|
|
189
|
+
SELECT 1 FROM pg_constraint
|
|
190
|
+
WHERE conname = 'billing_entitlement_snapshots_provenance_v2_check'
|
|
191
|
+
AND conrelid = 'billing_entitlement_snapshots'::regclass
|
|
192
|
+
) THEN
|
|
193
|
+
BEGIN
|
|
194
|
+
ALTER TABLE billing_entitlement_snapshots
|
|
195
|
+
ADD CONSTRAINT billing_entitlement_snapshots_provenance_v2_check
|
|
196
|
+
CHECK (
|
|
197
|
+
verification_status = 'verified' AND (
|
|
198
|
+
(
|
|
199
|
+
schema_version = 1 AND
|
|
200
|
+
source_kind = 'system-free' AND
|
|
201
|
+
generation_reason = 'default-account-created' AND
|
|
202
|
+
effective_until IS NULL AND
|
|
203
|
+
source_subscription_id IS NULL AND
|
|
204
|
+
source_subscription_version IS NULL
|
|
205
|
+
) OR (
|
|
206
|
+
schema_version = 2 AND
|
|
207
|
+
source_kind = 'subscription' AND
|
|
208
|
+
generation_reason = 'subscription-reconciled' AND
|
|
209
|
+
effective_until > effective_from AND
|
|
210
|
+
source_subscription_id IS NOT NULL AND
|
|
211
|
+
source_subscription_version > 0
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
216
|
+
NULL;
|
|
217
|
+
END;
|
|
218
|
+
END IF;
|
|
219
|
+
END
|
|
220
|
+
$billing_paid_snapshot_constraints$
|
|
221
|
+
`);
|
|
222
|
+
// Replace the earlier four-column draft FK inside the same transaction. Including both effective
|
|
223
|
+
// bounds makes the snapshot an exact projection of the referenced immutable revision.
|
|
224
|
+
await client.query(`
|
|
225
|
+
DO $billing_paid_snapshot_revision_fk$
|
|
226
|
+
BEGIN
|
|
227
|
+
IF EXISTS (
|
|
228
|
+
SELECT 1 FROM pg_constraint
|
|
229
|
+
WHERE conname = 'billing_entitlement_snapshots_subscription_revision_fk'
|
|
230
|
+
AND conrelid = 'billing_entitlement_snapshots'::regclass
|
|
231
|
+
AND array_length(conkey, 1) <> 6
|
|
232
|
+
) THEN
|
|
233
|
+
ALTER TABLE billing_entitlement_snapshots
|
|
234
|
+
DROP CONSTRAINT billing_entitlement_snapshots_subscription_revision_fk;
|
|
235
|
+
END IF;
|
|
236
|
+
IF NOT EXISTS (
|
|
237
|
+
SELECT 1 FROM pg_constraint
|
|
238
|
+
WHERE conname = 'billing_entitlement_snapshots_subscription_revision_fk'
|
|
239
|
+
AND conrelid = 'billing_entitlement_snapshots'::regclass
|
|
240
|
+
) THEN
|
|
241
|
+
BEGIN
|
|
242
|
+
ALTER TABLE billing_entitlement_snapshots
|
|
243
|
+
ADD CONSTRAINT billing_entitlement_snapshots_subscription_revision_fk
|
|
244
|
+
FOREIGN KEY (
|
|
245
|
+
billing_account_id,
|
|
246
|
+
source_subscription_id,
|
|
247
|
+
source_subscription_version,
|
|
248
|
+
plan_version_id,
|
|
249
|
+
effective_from,
|
|
250
|
+
effective_until
|
|
251
|
+
) REFERENCES billing_base_subscription_revisions (
|
|
252
|
+
billing_account_id,
|
|
253
|
+
subscription_id,
|
|
254
|
+
version,
|
|
255
|
+
plan_version_id,
|
|
256
|
+
effective_from,
|
|
257
|
+
effective_until
|
|
258
|
+
) ON DELETE RESTRICT;
|
|
259
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
260
|
+
NULL;
|
|
261
|
+
END;
|
|
262
|
+
END IF;
|
|
263
|
+
END
|
|
264
|
+
$billing_paid_snapshot_revision_fk$
|
|
265
|
+
`);
|
|
266
|
+
// Install the replacement provenance guard before retiring the legacy Free-only checks. PostgreSQL
|
|
267
|
+
// exposes all of this transactional DDL atomically, so readers never observe an unguarded table.
|
|
268
|
+
await client.query(`
|
|
269
|
+
ALTER TABLE billing_entitlement_snapshots
|
|
270
|
+
DROP CONSTRAINT IF EXISTS billing_entitlement_snapshots_source_kind_check,
|
|
271
|
+
DROP CONSTRAINT IF EXISTS billing_entitlement_snapshots_generation_reason_check,
|
|
272
|
+
DROP CONSTRAINT IF EXISTS billing_entitlement_snapshots_verification_status_check
|
|
273
|
+
`);
|
|
274
|
+
await client.query(`
|
|
275
|
+
CREATE OR REPLACE FUNCTION billing_reject_subscription_history_mutation()
|
|
276
|
+
RETURNS trigger
|
|
277
|
+
LANGUAGE plpgsql
|
|
278
|
+
AS $billing_subscription_history_guard$
|
|
279
|
+
BEGIN
|
|
280
|
+
RAISE EXCEPTION 'billing subscription history is append-only' USING ERRCODE = '55000';
|
|
281
|
+
END
|
|
282
|
+
$billing_subscription_history_guard$
|
|
283
|
+
`);
|
|
284
|
+
await client.query(`
|
|
285
|
+
DO $billing_subscription_history_triggers$
|
|
286
|
+
BEGIN
|
|
287
|
+
IF NOT EXISTS (
|
|
288
|
+
SELECT 1 FROM pg_trigger
|
|
289
|
+
WHERE tgname = 'billing_base_subscriptions_append_only'
|
|
290
|
+
AND tgrelid = 'billing_base_subscriptions'::regclass
|
|
291
|
+
AND NOT tgisinternal
|
|
292
|
+
) THEN
|
|
293
|
+
BEGIN
|
|
294
|
+
CREATE TRIGGER billing_base_subscriptions_append_only
|
|
295
|
+
BEFORE UPDATE OR DELETE ON billing_base_subscriptions
|
|
296
|
+
FOR EACH ROW EXECUTE FUNCTION billing_reject_subscription_history_mutation();
|
|
297
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
298
|
+
NULL;
|
|
299
|
+
END;
|
|
300
|
+
END IF;
|
|
301
|
+
IF NOT EXISTS (
|
|
302
|
+
SELECT 1 FROM pg_trigger
|
|
303
|
+
WHERE tgname = 'billing_base_subscription_revisions_append_only'
|
|
304
|
+
AND tgrelid = 'billing_base_subscription_revisions'::regclass
|
|
305
|
+
AND NOT tgisinternal
|
|
306
|
+
) THEN
|
|
307
|
+
BEGIN
|
|
308
|
+
CREATE TRIGGER billing_base_subscription_revisions_append_only
|
|
309
|
+
BEFORE UPDATE OR DELETE ON billing_base_subscription_revisions
|
|
310
|
+
FOR EACH ROW EXECUTE FUNCTION billing_reject_subscription_history_mutation();
|
|
311
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
312
|
+
NULL;
|
|
313
|
+
END;
|
|
314
|
+
END IF;
|
|
315
|
+
END
|
|
316
|
+
$billing_subscription_history_triggers$
|
|
317
|
+
`);
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=postgres-subscription-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-subscription-schema.js","sourceRoot":"","sources":["../../src/billing/postgres-subscription-schema.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,IAAU;IAC9D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC;YACH,4FAA4F;YAC5F,yEAAyE;YACzE,MAAM,MAAM,CAAC,KAAK,CAChB,iFAAiF,CAClF,CAAC;YACF,MAAM,iCAAiC,CAAC,MAAM,CAAC,CAAC;YAChD,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,iCAAiC,CAAC,MAAkB;IACjE,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;GAOlB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDlB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;GAUlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;GAMlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;GAalB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgClB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;GAmBlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDlB,CAAC,CAAC;IAEH,iGAAiG;IACjG,sFAAsF;IACtF,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyClB,CAAC,CAAC;IAEH,mGAAmG;IACnG,iGAAiG;IACjG,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;GAKlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;GASlB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiClB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type EntitlementSnapshotRecord, type PaidEntitlementSnapshotRecord } from './model.js';
|
|
2
|
+
export type BillingBaseSubscriptionState = 'active' | 'payment_grace' | 'ended';
|
|
3
|
+
export type BillingInterval = 'month' | 'year';
|
|
4
|
+
export interface BillingBaseSubscriptionRecord {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly billingAccountId: string;
|
|
7
|
+
readonly createdAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BillingBaseSubscriptionRevisionRecord {
|
|
10
|
+
readonly schemaVersion: 1;
|
|
11
|
+
readonly subscriptionId: string;
|
|
12
|
+
readonly billingAccountId: string;
|
|
13
|
+
readonly version: number;
|
|
14
|
+
readonly planVersionId: string;
|
|
15
|
+
readonly state: BillingBaseSubscriptionState;
|
|
16
|
+
readonly billingInterval: BillingInterval;
|
|
17
|
+
readonly currency: 'usd';
|
|
18
|
+
readonly baseAmountMinor: number;
|
|
19
|
+
readonly currentPeriodStart: string;
|
|
20
|
+
readonly currentPeriodEnd: string;
|
|
21
|
+
readonly effectiveFrom: string;
|
|
22
|
+
readonly effectiveUntil: string;
|
|
23
|
+
readonly renews: boolean;
|
|
24
|
+
readonly contentChecksum: string;
|
|
25
|
+
readonly verifiedAt: string;
|
|
26
|
+
readonly createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
export type BillingBaseSubscriptionRevisionInput = Omit<BillingBaseSubscriptionRevisionRecord, 'schemaVersion' | 'contentChecksum'>;
|
|
29
|
+
export declare function createBillingBaseSubscriptionRevision(input: BillingBaseSubscriptionRevisionInput): BillingBaseSubscriptionRevisionRecord;
|
|
30
|
+
export declare function parseBillingBaseSubscriptionRevision(value: unknown): BillingBaseSubscriptionRevisionRecord;
|
|
31
|
+
export interface CreatePaidEntitlementSnapshotInput {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly version: number;
|
|
34
|
+
readonly sourceSubscription: BillingBaseSubscriptionRevisionRecord;
|
|
35
|
+
readonly previousSnapshot: EntitlementSnapshotRecord;
|
|
36
|
+
}
|
|
37
|
+
export declare function createPaidEntitlementSnapshot(input: CreatePaidEntitlementSnapshotInput): PaidEntitlementSnapshotRecord;
|
|
38
|
+
export declare function parsePaidEntitlementSnapshot(value: unknown): PaidEntitlementSnapshotRecord;
|
|
39
|
+
export declare function validateBillingSubscriptionId(value: string): string;
|
|
40
|
+
//# sourceMappingURL=subscription-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-model.d.ts","sourceRoot":"","sources":["../../src/billing/subscription-model.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAEnC,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,4BAA4B,GAAG,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC;AAChF,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAI/C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qCAAqC;IACpD,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,4BAA4B,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,oCAAoC,GAAG,IAAI,CACrD,qCAAqC,EACrC,eAAe,GAAG,iBAAiB,CACpC,CAAC;AAOF,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,oCAAoC,GAC1C,qCAAqC,CAMvC;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,qCAAqC,CAyBvC;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,qCAAqC,CAAC;IACnE,QAAQ,CAAC,gBAAgB,EAAE,yBAAyB,CAAC;CACtD;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,kCAAkC,GACxC,6BAA6B,CAsC/B;AAmDD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,6BAA6B,CAmC1F;AAiLD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE"}
|