@dineway-ai/plugin-payment 0.2.6 → 0.2.8

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.
@@ -0,0 +1,168 @@
1
+ import type { Kysely } from "kysely";
2
+
3
+ import { schemaExists } from "./adoption.js";
4
+
5
+ const tables = [
6
+ "payment_subscription_plans",
7
+ "payment_subscription_prices",
8
+ "payment_subscription_customers",
9
+ "payment_member_subscriptions",
10
+ "payment_subscription_periods",
11
+ "payment_subscription_events",
12
+ "payment_subscription_attempts",
13
+ "payment_subscription_locks",
14
+ "payment_subscription_portals",
15
+ ] as const;
16
+
17
+ export async function up(db: Kysely<unknown>): Promise<void> {
18
+ if (
19
+ await schemaExists(db, {
20
+ payment_subscription_plans: ["scope", "id", "name", "enabled", "entitlements", "version"],
21
+ payment_subscription_prices: [
22
+ "scope",
23
+ "id",
24
+ "plan_code",
25
+ "product_id",
26
+ "currency",
27
+ "amount",
28
+ "interval",
29
+ "enabled",
30
+ ],
31
+ payment_subscription_customers: ["scope", "id", "customer_id"],
32
+ payment_member_subscriptions: [
33
+ "scope",
34
+ "id",
35
+ "user_id",
36
+ "customer_id",
37
+ "plan_code",
38
+ "price_id",
39
+ "status",
40
+ "period_end",
41
+ "cancel_at_period_end",
42
+ "verified_at",
43
+ "last_error",
44
+ ],
45
+ payment_subscription_periods: [
46
+ "scope",
47
+ "id",
48
+ "subscription_id",
49
+ "invoice_id",
50
+ "plan_code",
51
+ "starts_at",
52
+ "ends_at",
53
+ ],
54
+ payment_subscription_events: ["scope", "id", "received_at"],
55
+ payment_subscription_attempts: [
56
+ "scope",
57
+ "id",
58
+ "fingerprint",
59
+ "request_id",
60
+ "session_id",
61
+ "created_at",
62
+ ],
63
+ payment_subscription_locks: ["scope", "id", "token", "expires_at"],
64
+ payment_subscription_portals: ["scope", "id", "configuration_id"],
65
+ })
66
+ )
67
+ return;
68
+ const base = (table: (typeof tables)[number]) =>
69
+ db.schema
70
+ .createTable(table)
71
+ .addColumn("scope", "text", (col) => col.notNull())
72
+ .addColumn("id", "text", (col) => col.notNull())
73
+ .addPrimaryKeyConstraint("pk_" + table, ["scope", "id"]);
74
+ await base("payment_subscription_plans")
75
+ .addColumn("name", "text", (col) => col.notNull())
76
+ .addColumn("enabled", "integer", (col) => col.notNull().defaultTo(0))
77
+ .addColumn("entitlements", "text", (col) => col.notNull())
78
+ .addColumn("version", "integer", (col) => col.notNull().defaultTo(1))
79
+ .execute();
80
+ await base("payment_subscription_prices")
81
+ .addColumn("plan_code", "text", (col) => col.notNull())
82
+ .addColumn("product_id", "text", (col) => col.notNull())
83
+ .addColumn("currency", "text", (col) => col.notNull())
84
+ .addColumn("amount", "integer", (col) => col.notNull())
85
+ .addColumn("interval", "text", (col) => col.notNull())
86
+ .addColumn("enabled", "integer", (col) => col.notNull().defaultTo(1))
87
+ .addForeignKeyConstraint(
88
+ "fk_payment_price_plan",
89
+ ["scope", "plan_code"],
90
+ "payment_subscription_plans",
91
+ ["scope", "id"],
92
+ )
93
+ .execute();
94
+ await base("payment_subscription_customers")
95
+ .addColumn("customer_id", "text", (col) => col.notNull())
96
+ .addUniqueConstraint("uq_payment_subscription_customer", ["scope", "customer_id"])
97
+ .execute();
98
+ await base("payment_member_subscriptions")
99
+ .addColumn("user_id", "text", (col) => col.notNull())
100
+ .addColumn("customer_id", "text", (col) => col.notNull())
101
+ .addColumn("plan_code", "text", (col) => col.notNull())
102
+ .addColumn("price_id", "text", (col) => col.notNull())
103
+ .addColumn("status", "text", (col) => col.notNull())
104
+ .addColumn("period_end", "integer", (col) => col.notNull())
105
+ .addColumn("cancel_at_period_end", "integer", (col) => col.notNull())
106
+ .addColumn("verified_at", "integer", (col) => col.notNull())
107
+ .addColumn("last_error", "text")
108
+ .addForeignKeyConstraint(
109
+ "fk_payment_subscription_user",
110
+ ["scope", "user_id"],
111
+ "payment_subscription_customers",
112
+ ["scope", "id"],
113
+ )
114
+ .addForeignKeyConstraint(
115
+ "fk_payment_subscription_price",
116
+ ["scope", "price_id"],
117
+ "payment_subscription_prices",
118
+ ["scope", "id"],
119
+ )
120
+ .execute();
121
+ await base("payment_subscription_periods")
122
+ .addColumn("subscription_id", "text", (col) => col.notNull())
123
+ .addColumn("invoice_id", "text", (col) => col.notNull())
124
+ .addColumn("plan_code", "text", (col) => col.notNull())
125
+ .addColumn("starts_at", "integer", (col) => col.notNull())
126
+ .addColumn("ends_at", "integer", (col) => col.notNull())
127
+ .addForeignKeyConstraint(
128
+ "fk_payment_period_subscription",
129
+ ["scope", "subscription_id"],
130
+ "payment_member_subscriptions",
131
+ ["scope", "id"],
132
+ )
133
+ .execute();
134
+ await base("payment_subscription_events")
135
+ .addColumn("received_at", "integer", (col) => col.notNull())
136
+ .execute();
137
+ await base("payment_subscription_attempts")
138
+ .addColumn("fingerprint", "text", (col) => col.notNull())
139
+ .addColumn("request_id", "text", (col) => col.notNull())
140
+ .addColumn("session_id", "text")
141
+ .addColumn("created_at", "integer", (col) => col.notNull())
142
+ .execute();
143
+ await base("payment_subscription_locks")
144
+ .addColumn("token", "text", (col) => col.notNull())
145
+ .addColumn("expires_at", "integer", (col) => col.notNull())
146
+ .execute();
147
+ for (const [table, column] of [
148
+ // Composite indexes keep reads within the configured account and application.
149
+ ["payment_subscription_prices", "plan_code"],
150
+ ["payment_member_subscriptions", "user_id"],
151
+ ["payment_member_subscriptions", "price_id"],
152
+ ["payment_subscription_periods", "subscription_id"],
153
+ ["payment_subscription_periods", "invoice_id"],
154
+ ] as const) {
155
+ await db.schema
156
+ .createIndex("idx_" + table + "_" + column)
157
+ .on(table)
158
+ .columns(["scope", column])
159
+ .execute();
160
+ }
161
+ await base("payment_subscription_portals")
162
+ .addColumn("configuration_id", "text", (col) => col.notNull())
163
+ .execute();
164
+ }
165
+
166
+ export async function down(db: Kysely<unknown>): Promise<void> {
167
+ for (const table of tables.toReversed()) await db.schema.dropTable(table).execute();
168
+ }
@@ -0,0 +1,193 @@
1
+ import { sql, type Kysely } from "kysely";
2
+
3
+ import { up as subscriptionSchema, down as removeSubscriptionSchema } from "./002_subscriptions.js";
4
+ import { schemaExists } from "./adoption.js";
5
+
6
+ export async function up(db: Kysely<unknown>): Promise<void> {
7
+ if (
8
+ await schemaExists(
9
+ db,
10
+ {
11
+ payment_subscription_purchases: [
12
+ "scope",
13
+ "id",
14
+ "user_id",
15
+ "group_key",
16
+ "contract",
17
+ "fingerprint",
18
+ "created_at",
19
+ "expires_at",
20
+ "session_id",
21
+ "subscription_id",
22
+ "closed",
23
+ "cancel_mode",
24
+ "last_attempt_at",
25
+ "last_error",
26
+ ],
27
+ payment_subscription_changes: [
28
+ "scope",
29
+ "id",
30
+ "subscription_id",
31
+ "revision",
32
+ "snapshot",
33
+ "created_at",
34
+ "acknowledged_at",
35
+ ],
36
+ },
37
+ {
38
+ payment_subscription_customers: ["subject"],
39
+ payment_member_subscriptions: ["purchase_reference", "revision", "sync_attempted_at"],
40
+ payment_subscription_periods: ["eligible", "evidence"],
41
+ payment_subscription_events: [
42
+ "digest",
43
+ "event_type",
44
+ "object_type",
45
+ "object_id",
46
+ "subscription_id",
47
+ "last_error",
48
+ "payload",
49
+ "processed_at",
50
+ "last_attempt_at",
51
+ ],
52
+ },
53
+ )
54
+ )
55
+ return;
56
+ await db.schema
57
+ .alterTable("payment_subscription_customers")
58
+ .addColumn("subject", "text")
59
+ .execute();
60
+ await sql`CREATE TABLE payment_subscription_purchases (
61
+ scope TEXT NOT NULL, id TEXT NOT NULL, user_id TEXT NOT NULL,
62
+ group_key TEXT NOT NULL, contract TEXT NOT NULL, fingerprint TEXT NOT NULL,
63
+ created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL,
64
+ session_id TEXT, subscription_id TEXT, closed INTEGER NOT NULL DEFAULT 0,
65
+ cancel_mode TEXT, last_attempt_at INTEGER, last_error TEXT,
66
+ PRIMARY KEY (scope, id),
67
+ FOREIGN KEY (scope, user_id) REFERENCES payment_subscription_customers(scope, id)
68
+ )`.execute(db);
69
+ await sql`CREATE UNIQUE INDEX idx_payment_purchase_open_group ON payment_subscription_purchases(scope, user_id, group_key) WHERE closed = 0`.execute(
70
+ db,
71
+ );
72
+ await sql`CREATE UNIQUE INDEX idx_payment_purchase_session ON payment_subscription_purchases(scope, session_id) WHERE session_id IS NOT NULL`.execute(
73
+ db,
74
+ );
75
+ await sql`CREATE UNIQUE INDEX idx_payment_purchase_subscription ON payment_subscription_purchases(scope, subscription_id) WHERE subscription_id IS NOT NULL`.execute(
76
+ db,
77
+ );
78
+ await sql`CREATE INDEX idx_payment_purchase_recovery ON payment_subscription_purchases(scope, last_attempt_at, id)`.execute(
79
+ db,
80
+ );
81
+
82
+ // Dynamic prices are verified against their immutable purchase contract, not the plan catalogue.
83
+ await sql`CREATE TABLE _payment_periods_121 AS SELECT * FROM payment_subscription_periods`.execute(
84
+ db,
85
+ );
86
+ await db.schema.dropTable("payment_subscription_periods").execute();
87
+ await sql`CREATE TABLE _payment_subscriptions_121 (
88
+ scope TEXT NOT NULL, id TEXT NOT NULL, user_id TEXT NOT NULL, customer_id TEXT NOT NULL,
89
+ plan_code TEXT NOT NULL, price_id TEXT NOT NULL, status TEXT NOT NULL,
90
+ period_end INTEGER NOT NULL, cancel_at_period_end INTEGER NOT NULL,
91
+ verified_at INTEGER NOT NULL, last_error TEXT,
92
+ purchase_reference TEXT, revision INTEGER NOT NULL DEFAULT 0, sync_attempted_at INTEGER NOT NULL DEFAULT 0,
93
+ PRIMARY KEY (scope, id),
94
+ FOREIGN KEY (scope, user_id) REFERENCES payment_subscription_customers(scope, id)
95
+ )`.execute(db);
96
+ await sql`INSERT INTO _payment_subscriptions_121 (scope,id,user_id,customer_id,plan_code,price_id,status,period_end,cancel_at_period_end,verified_at,last_error)
97
+ SELECT scope,id,user_id,customer_id,plan_code,price_id,status,period_end,cancel_at_period_end,verified_at,last_error FROM payment_member_subscriptions`.execute(
98
+ db,
99
+ );
100
+ await db.schema.dropTable("payment_member_subscriptions").execute();
101
+ await db.schema
102
+ .alterTable("_payment_subscriptions_121")
103
+ .renameTo("payment_member_subscriptions")
104
+ .execute();
105
+ for (const column of [
106
+ "user_id",
107
+ "price_id",
108
+ "purchase_reference",
109
+ "verified_at",
110
+ "sync_attempted_at",
111
+ ]) {
112
+ await db.schema
113
+ .createIndex("idx_payment_subscription_" + column)
114
+ .on("payment_member_subscriptions")
115
+ .columns(["scope", column])
116
+ .execute();
117
+ }
118
+ await sql`CREATE TABLE payment_subscription_periods (
119
+ scope TEXT NOT NULL, id TEXT NOT NULL, subscription_id TEXT NOT NULL,
120
+ invoice_id TEXT NOT NULL, plan_code TEXT NOT NULL, starts_at INTEGER NOT NULL, ends_at INTEGER NOT NULL,
121
+ eligible INTEGER NOT NULL DEFAULT 0, evidence TEXT NOT NULL DEFAULT '{}',
122
+ PRIMARY KEY (scope,id), FOREIGN KEY (scope,subscription_id) REFERENCES payment_member_subscriptions(scope,id)
123
+ )`.execute(db);
124
+ await sql`INSERT INTO payment_subscription_periods (scope,id,subscription_id,invoice_id,plan_code,starts_at,ends_at)
125
+ SELECT scope,id,subscription_id,invoice_id,plan_code,starts_at,ends_at FROM _payment_periods_121`.execute(
126
+ db,
127
+ );
128
+ await db.schema.dropTable("_payment_periods_121").execute();
129
+ for (const column of ["subscription_id", "invoice_id"]) {
130
+ await db.schema
131
+ .createIndex("idx_payment_period_" + column)
132
+ .on("payment_subscription_periods")
133
+ .columns(["scope", column])
134
+ .execute();
135
+ }
136
+ for (const column of [
137
+ "digest",
138
+ "event_type",
139
+ "object_type",
140
+ "object_id",
141
+ "subscription_id",
142
+ "last_error",
143
+ "payload",
144
+ ]) {
145
+ await db.schema.alterTable("payment_subscription_events").addColumn(column, "text").execute();
146
+ }
147
+ for (const column of ["processed_at", "last_attempt_at"]) {
148
+ await db.schema
149
+ .alterTable("payment_subscription_events")
150
+ .addColumn(column, "integer")
151
+ .execute();
152
+ }
153
+ await sql`UPDATE payment_subscription_events SET processed_at = received_at`.execute(db);
154
+ await sql`CREATE INDEX idx_payment_event_pending ON payment_subscription_events(scope, processed_at, last_attempt_at)`.execute(
155
+ db,
156
+ );
157
+ await sql`CREATE TABLE payment_subscription_changes (
158
+ scope TEXT NOT NULL, id TEXT NOT NULL, subscription_id TEXT NOT NULL,
159
+ revision INTEGER NOT NULL, snapshot TEXT NOT NULL, created_at INTEGER NOT NULL, acknowledged_at INTEGER,
160
+ PRIMARY KEY (scope,id), FOREIGN KEY (scope,subscription_id) REFERENCES payment_member_subscriptions(scope,id)
161
+ )`.execute(db);
162
+ await sql`CREATE INDEX idx_payment_change_pending ON payment_subscription_changes(scope, acknowledged_at, created_at, id)`.execute(
163
+ db,
164
+ );
165
+ await sql`CREATE INDEX idx_payment_change_subscription ON payment_subscription_changes(scope, subscription_id)`.execute(
166
+ db,
167
+ );
168
+ }
169
+
170
+ export async function down(db: Kysely<unknown>): Promise<void> {
171
+ for (const table of [
172
+ "payment_subscription_changes",
173
+ "payment_subscription_purchases",
174
+ "payment_subscription_plans",
175
+ "payment_subscription_prices",
176
+ "payment_subscription_customers",
177
+ "payment_member_subscriptions",
178
+ "payment_subscription_periods",
179
+ "payment_subscription_events",
180
+ "payment_subscription_attempts",
181
+ "payment_subscription_locks",
182
+ "payment_subscription_portals",
183
+ ]) {
184
+ if ((await sql`SELECT 1 FROM ${sql.table(table)} LIMIT 1`.execute(db)).rows.length)
185
+ throw new Error(
186
+ "Restore a database snapshot to roll back populated payment tables; automatic rollback would discard payment evidence.",
187
+ );
188
+ }
189
+ await db.schema.dropTable("payment_subscription_changes").execute();
190
+ await db.schema.dropTable("payment_subscription_purchases").execute();
191
+ await removeSubscriptionSchema(db);
192
+ await subscriptionSchema(db);
193
+ }
@@ -0,0 +1,27 @@
1
+ import type { Kysely } from "kysely";
2
+
3
+ /** Adopt complete schemas installed by core; reject partial schemas without modifying their data. */
4
+ export async function schemaExists(
5
+ db: Kysely<unknown>,
6
+ owned: Record<string, readonly string[]>,
7
+ additions: Record<string, readonly string[]> = {},
8
+ ): Promise<boolean> {
9
+ const tables = await db.introspection.getTables();
10
+ const names = new Map(
11
+ tables.map((table) => [table.name, new Set(table.columns.map((column) => column.name))]),
12
+ );
13
+ const checks: boolean[] = [];
14
+ for (const [name, columns] of Object.entries(owned)) {
15
+ checks.push(names.has(name), ...columns.map((column) => names.get(name)?.has(column) ?? false));
16
+ }
17
+ for (const [name, columns] of Object.entries(additions)) {
18
+ if (!names.has(name)) throw new Error(`Payment migration requires table ${name}`);
19
+ checks.push(...columns.map((column) => names.get(name)!.has(column)));
20
+ }
21
+ if (checks.every(Boolean)) return true;
22
+ if (checks.some(Boolean))
23
+ throw new Error(
24
+ "Payment schema is incomplete; restore or repair it before retrying migrations",
25
+ );
26
+ return false;
27
+ }
@@ -0,0 +1,137 @@
1
+ import type { Database } from "dineway";
2
+ import type { Generated } from "kysely";
3
+
4
+ export interface PaymentSubscriptionKey {
5
+ scope: string;
6
+ id: string;
7
+ }
8
+ export type PaymentSubscriptionTables = {
9
+ payment_subscription_plans: PaymentSubscriptionPlanTable;
10
+ payment_subscription_prices: PaymentSubscriptionPriceTable;
11
+ payment_subscription_customers: PaymentSubscriptionCustomerTable;
12
+ payment_member_subscriptions: PaymentMemberSubscriptionTable;
13
+ payment_subscription_periods: PaymentSubscriptionPeriodTable;
14
+ payment_subscription_events: PaymentSubscriptionEventTable;
15
+ payment_subscription_attempts: PaymentSubscriptionAttemptTable;
16
+ payment_subscription_locks: PaymentSubscriptionLockTable;
17
+ payment_subscription_portals: PaymentSubscriptionPortalTable;
18
+ payment_subscription_purchases: PaymentSubscriptionPurchaseTable;
19
+ payment_subscription_changes: PaymentSubscriptionChangeTable;
20
+ };
21
+ export interface PaymentSubscriptionPlanTable extends PaymentSubscriptionKey {
22
+ name: string;
23
+ enabled: number;
24
+ entitlements: string;
25
+ version: number;
26
+ }
27
+ export interface PaymentSubscriptionPriceTable extends PaymentSubscriptionKey {
28
+ plan_code: string;
29
+ product_id: string;
30
+ currency: string;
31
+ amount: number;
32
+ interval: string;
33
+ enabled: number;
34
+ }
35
+ export interface PaymentSubscriptionCustomerTable extends PaymentSubscriptionKey {
36
+ customer_id: string;
37
+ subject: Generated<string | null>;
38
+ }
39
+ export interface PaymentMemberSubscriptionTable extends PaymentSubscriptionKey {
40
+ user_id: string;
41
+ customer_id: string;
42
+ plan_code: string;
43
+ price_id: string;
44
+ status: string;
45
+ period_end: number;
46
+ cancel_at_period_end: number;
47
+ verified_at: number;
48
+ last_error: string | null;
49
+ purchase_reference: Generated<string | null>;
50
+ revision: Generated<number>;
51
+ sync_attempted_at: Generated<number>;
52
+ }
53
+ export interface PaymentSubscriptionPeriodTable extends PaymentSubscriptionKey {
54
+ subscription_id: string;
55
+ invoice_id: string;
56
+ plan_code: string;
57
+ starts_at: number;
58
+ ends_at: number;
59
+ eligible: Generated<number>;
60
+ evidence: Generated<string>;
61
+ }
62
+ export interface PaymentSubscriptionEventTable extends PaymentSubscriptionKey {
63
+ received_at: number;
64
+ payload: Generated<string | null>;
65
+ digest: Generated<string | null>;
66
+ event_type: Generated<string | null>;
67
+ object_type: Generated<string | null>;
68
+ object_id: Generated<string | null>;
69
+ subscription_id: Generated<string | null>;
70
+ processed_at: Generated<number | null>;
71
+ last_attempt_at: Generated<number | null>;
72
+ last_error: Generated<string | null>;
73
+ }
74
+ export interface PaymentSubscriptionAttemptTable extends PaymentSubscriptionKey {
75
+ fingerprint: string;
76
+ request_id: string;
77
+ session_id: string | null;
78
+ created_at: number;
79
+ }
80
+ export interface PaymentSubscriptionLockTable extends PaymentSubscriptionKey {
81
+ token: string;
82
+ expires_at: number;
83
+ }
84
+ export interface PaymentSubscriptionPortalTable extends PaymentSubscriptionKey {
85
+ configuration_id: string;
86
+ }
87
+
88
+ export interface PaymentSubscriptionPurchaseTable extends PaymentSubscriptionKey {
89
+ user_id: string;
90
+ group_key: string;
91
+ contract: string;
92
+ fingerprint: string;
93
+ created_at: number;
94
+ expires_at: number;
95
+ session_id: Generated<string | null>;
96
+ subscription_id: Generated<string | null>;
97
+ closed: Generated<number>;
98
+ cancel_mode: Generated<string | null>;
99
+ last_attempt_at: Generated<number | null>;
100
+ last_error: Generated<string | null>;
101
+ }
102
+
103
+ export interface PaymentSubscriptionChangeTable extends PaymentSubscriptionKey {
104
+ subscription_id: string;
105
+ revision: number;
106
+ snapshot: string;
107
+ created_at: number;
108
+ acknowledged_at: Generated<number | null>;
109
+ }
110
+
111
+ export type PaymentStripeMode = "test" | "live";
112
+ export type PaymentStripeConfigurationStatus = "unconfigured" | "unverified" | "valid" | "invalid";
113
+ export type PaymentStripeTaxStatus = "unknown" | "active" | "pending" | "unavailable";
114
+ export type PaymentStripeConnectStatus = "not_configured" | "restricted" | "ready";
115
+
116
+ export interface PaymentStripeSettingsTable {
117
+ id: string;
118
+ default_key: Generated<"default">;
119
+ mode: Generated<PaymentStripeMode>;
120
+ publishable_key: string | null;
121
+ configuration_status: Generated<PaymentStripeConfigurationStatus>;
122
+ stripe_account_id: string | null;
123
+ charges_enabled: number | null;
124
+ details_submitted: number | null;
125
+ tax_status: Generated<PaymentStripeTaxStatus>;
126
+ validation_error_code: string | null;
127
+ last_validated_at: string | null;
128
+ connect_status: Generated<PaymentStripeConnectStatus>;
129
+ version: Generated<number>;
130
+ created_at: Generated<string>;
131
+ updated_at: Generated<string>;
132
+ }
133
+
134
+ export type PaymentTables = PaymentSubscriptionTables & {
135
+ payment_stripe_settings: PaymentStripeSettingsTable;
136
+ };
137
+ export type PaymentDatabase = Database & PaymentTables;
package/src/index.ts CHANGED
@@ -4,9 +4,17 @@ import type { PluginDescriptor, PluginHooks, ResolvedPlugin } from "dineway";
4
4
  import { version as packageVersion } from "../package.json";
5
5
  import { PaymentStripeConfigurationRepository } from "./configuration/repository.js";
6
6
  import { PAYMENT_PLUGIN_ID } from "./constants.js";
7
+ import { paymentMigrations } from "./database/index.js";
7
8
  import { createPaymentRoutes } from "./routes/index.js";
8
9
  import { createSubscriptionRoutes } from "./subscriptions/routes.js";
10
+ import { getPaymentSubscriptionService } from "./subscriptions/runtime.js";
11
+ import { SUBSCRIPTION_WEBHOOK_EVENTS } from "./subscriptions/service.js";
9
12
  import type { SubscriptionOptions } from "./subscriptions/types.js";
13
+ import {
14
+ createPaymentWebhookRoute,
15
+ PAYMENT_WEBHOOK_EXTENSION,
16
+ type PaymentWebhookConsumer,
17
+ } from "./webhooks/index.js";
10
18
 
11
19
  const PAYMENT_ADMIN_ENTRY = "@dineway-ai/plugin-payment/admin";
12
20
  const PAYMENT_ADMIN_PAGES = [
@@ -61,9 +69,24 @@ export function createPlugin(options: PaymentPluginOptions = {}): ResolvedPlugin
61
69
  : []),
62
70
  ],
63
71
  },
72
+ migrations: paymentMigrations,
73
+ nativeExtensions: options.subscriptions
74
+ ? {
75
+ [PAYMENT_WEBHOOK_EXTENSION]: {
76
+ id: "subscriptions",
77
+ eventTypes: SUBSCRIPTION_WEBHOOK_EVENTS,
78
+ consume: async ({ event, rawBody }) => {
79
+ await (
80
+ await getPaymentSubscriptionService(options.subscriptions!)
81
+ ).receiveVerifiedWebhook(event, rawBody);
82
+ },
83
+ } satisfies PaymentWebhookConsumer,
84
+ }
85
+ : {},
64
86
  hooks,
65
87
  routes: {
66
88
  ...createPaymentRoutes(),
89
+ "stripe/webhook": createPaymentWebhookRoute(),
67
90
  ...(options.subscriptions ? createSubscriptionRoutes(options.subscriptions) : {}),
68
91
  },
69
92
  });
@@ -74,3 +97,5 @@ export { PAYMENT_PLUGIN_ID } from "./constants.js";
74
97
  export * from "./stripe/index.js";
75
98
  export * from "./routes/index.js";
76
99
  export * from "./types.js";
100
+
101
+ export * from "./webhooks/index.js";
@@ -97,6 +97,7 @@ function projectEvent(event: Stripe.Event, rawBody: Uint8Array): StripeWebhookEv
97
97
 
98
98
  return {
99
99
  eventId: event.id,
100
+ ...(event.account ? { accountId: requiredSafeString(event.account, 255) } : {}),
100
101
  eventType: event.type,
101
102
  eventCreated: event.created,
102
103
  livemode: event.livemode,