stripe-experiment-sync 0.0.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.
Files changed (47) hide show
  1. package/README.md +100 -0
  2. package/dist/index.cjs +2370 -0
  3. package/dist/index.d.cts +222 -0
  4. package/dist/index.d.ts +222 -0
  5. package/dist/index.js +2328 -0
  6. package/dist/migrations/0000_initial_migration.sql +1 -0
  7. package/dist/migrations/0001_products.sql +17 -0
  8. package/dist/migrations/0002_customers.sql +23 -0
  9. package/dist/migrations/0003_prices.sql +34 -0
  10. package/dist/migrations/0004_subscriptions.sql +56 -0
  11. package/dist/migrations/0005_invoices.sql +77 -0
  12. package/dist/migrations/0006_charges.sql +43 -0
  13. package/dist/migrations/0007_coupons.sql +19 -0
  14. package/dist/migrations/0008_disputes.sql +17 -0
  15. package/dist/migrations/0009_events.sql +12 -0
  16. package/dist/migrations/0010_payouts.sql +30 -0
  17. package/dist/migrations/0011_plans.sql +25 -0
  18. package/dist/migrations/0012_add_updated_at.sql +108 -0
  19. package/dist/migrations/0013_add_subscription_items.sql +12 -0
  20. package/dist/migrations/0014_migrate_subscription_items.sql +26 -0
  21. package/dist/migrations/0015_add_customer_deleted.sql +2 -0
  22. package/dist/migrations/0016_add_invoice_indexes.sql +2 -0
  23. package/dist/migrations/0017_drop_charges_unavailable_columns.sql +6 -0
  24. package/dist/migrations/0018_setup_intents.sql +17 -0
  25. package/dist/migrations/0019_payment_methods.sql +12 -0
  26. package/dist/migrations/0020_disputes_payment_intent_created_idx.sql +3 -0
  27. package/dist/migrations/0021_payment_intent.sql +42 -0
  28. package/dist/migrations/0022_adjust_plans.sql +5 -0
  29. package/dist/migrations/0023_invoice_deleted.sql +1 -0
  30. package/dist/migrations/0024_subscription_schedules.sql +29 -0
  31. package/dist/migrations/0025_tax_ids.sql +14 -0
  32. package/dist/migrations/0026_credit_notes.sql +36 -0
  33. package/dist/migrations/0027_add_marketing_features_to_products.sql +2 -0
  34. package/dist/migrations/0028_early_fraud_warning.sql +22 -0
  35. package/dist/migrations/0029_reviews.sql +28 -0
  36. package/dist/migrations/0030_refunds.sql +29 -0
  37. package/dist/migrations/0031_add_default_price.sql +2 -0
  38. package/dist/migrations/0032_update_subscription_items.sql +3 -0
  39. package/dist/migrations/0033_add_last_synced_at.sql +85 -0
  40. package/dist/migrations/0034_remove_foreign_keys.sql +13 -0
  41. package/dist/migrations/0035_checkout_sessions.sql +77 -0
  42. package/dist/migrations/0036_checkout_session_line_items.sql +24 -0
  43. package/dist/migrations/0037_add_features.sql +18 -0
  44. package/dist/migrations/0038_active_entitlement.sql +20 -0
  45. package/dist/migrations/0039_add_paused_to_subscription_status.sql +1 -0
  46. package/dist/migrations/0040_managed_webhooks.sql +28 -0
  47. package/package.json +60 -0
@@ -0,0 +1,222 @@
1
+ import { Express } from 'express';
2
+ import pg, { PoolConfig, QueryResult } from 'pg';
3
+ import pino from 'pino';
4
+ import Stripe from 'stripe';
5
+
6
+ interface StripeAutoSyncOptions {
7
+ databaseUrl: string;
8
+ stripeApiKey: string;
9
+ baseUrl: () => string;
10
+ webhookPath?: string;
11
+ schema?: string;
12
+ stripeApiVersion?: string;
13
+ autoExpandLists?: boolean;
14
+ backfillRelatedEntities?: boolean;
15
+ }
16
+ interface StripeAutoSyncInfo {
17
+ baseUrl: string;
18
+ webhookUrl: string;
19
+ webhookUuid: string;
20
+ }
21
+ /**
22
+ * Manages Stripe webhook auto-sync infrastructure:
23
+ * - Runs database migrations
24
+ * - Creates managed webhook in Stripe
25
+ * - Mounts webhook handler on Express app
26
+ */
27
+ declare class StripeAutoSync {
28
+ private options;
29
+ private webhookId;
30
+ private webhookUuid;
31
+ private stripeSync;
32
+ constructor(options: StripeAutoSyncOptions);
33
+ /**
34
+ * Starts the Stripe Sync infrastructure and mounts webhook handler:
35
+ * 1. Runs database migrations
36
+ * 2. Creates StripeSync instance
37
+ * 3. Creates managed webhook endpoint
38
+ * 4. Mounts webhook handler on provided Express app
39
+ * 5. Applies body parsing middleware (automatically skips webhook routes)
40
+ *
41
+ * @param app - Express app to mount webhook handler on
42
+ * @returns Information about the running instance
43
+ */
44
+ start(app: Express): Promise<StripeAutoSyncInfo>;
45
+ /**
46
+ * Stops all services and cleans up resources:
47
+ * 1. Deletes Stripe webhook endpoint from Stripe and database
48
+ */
49
+ stop(): Promise<void>;
50
+ /**
51
+ * Returns Express middleware for body parsing that automatically skips webhook routes.
52
+ * This middleware applies JSON and URL-encoded parsers to all routes EXCEPT the webhook path,
53
+ * which needs raw body for signature verification.
54
+ *
55
+ * @returns Express middleware function
56
+ */
57
+ private getBodyParserMiddleware;
58
+ /**
59
+ * Mounts the Stripe webhook handler on the provided Express app.
60
+ * Applies raw body parser middleware for signature verification.
61
+ * IMPORTANT: Must be called BEFORE app.use(express.json()) to ensure raw body parsing.
62
+ */
63
+ private mountWebhook;
64
+ }
65
+
66
+ type RevalidateEntity = 'charge' | 'credit_note' | 'customer' | 'dispute' | 'invoice' | 'payment_intent' | 'payment_method' | 'plan' | 'price' | 'product' | 'refund' | 'review' | 'radar.early_fraud_warning' | 'setup_intent' | 'subscription' | 'subscription_schedule' | 'tax_id' | 'entitlements';
67
+ type StripeSyncConfig = {
68
+ /** @deprecated Use `poolConfig` with a connection string instead. */
69
+ databaseUrl?: string;
70
+ /** Database schema name. */
71
+ schema?: string;
72
+ /** Stripe secret key used to authenticate requests to the Stripe API. Defaults to empty string */
73
+ stripeSecretKey: string;
74
+ /** Stripe API version for the webhooks, defaults to 2020-08-27 */
75
+ stripeApiVersion?: string;
76
+ /**
77
+ * Stripe limits related lists like invoice items in an invoice to 10 by default.
78
+ * By enabling this, sync-engine automatically fetches the remaining elements before saving
79
+ * */
80
+ autoExpandLists?: boolean;
81
+ /**
82
+ * If true, the sync engine will backfill related entities, i.e. when a invoice webhook comes in, it ensures that the customer is present and synced.
83
+ * This ensures foreign key integrity, but comes at the cost of additional queries to the database (and added latency for Stripe calls if the entity is actually missing).
84
+ */
85
+ backfillRelatedEntities?: boolean;
86
+ /**
87
+ * If true, the webhook data is not used and instead the webhook is just a trigger to fetch the entity from Stripe again. This ensures that a race condition with failed webhooks can never accidentally overwrite the data with an older state.
88
+ *
89
+ * Default: false
90
+ */
91
+ revalidateObjectsViaStripeApi?: Array<RevalidateEntity>;
92
+ /** @deprecated Use `poolConfig` instead. */
93
+ maxPostgresConnections?: number;
94
+ poolConfig: PoolConfig;
95
+ logger?: pino.Logger;
96
+ };
97
+ type SyncObject = 'all' | 'customer' | 'customer_with_entitlements' | 'invoice' | 'price' | 'product' | 'subscription' | 'subscription_schedules' | 'setup_intent' | 'payment_method' | 'dispute' | 'charge' | 'payment_intent' | 'plan' | 'tax_id' | 'credit_note' | 'early_fraud_warning' | 'refund' | 'checkout_sessions';
98
+ interface Sync {
99
+ synced: number;
100
+ }
101
+ interface SyncBackfill {
102
+ products?: Sync;
103
+ prices?: Sync;
104
+ plans?: Sync;
105
+ customers?: Sync;
106
+ subscriptions?: Sync;
107
+ subscriptionSchedules?: Sync;
108
+ invoices?: Sync;
109
+ setupIntents?: Sync;
110
+ paymentIntents?: Sync;
111
+ paymentMethods?: Sync;
112
+ disputes?: Sync;
113
+ charges?: Sync;
114
+ taxIds?: Sync;
115
+ creditNotes?: Sync;
116
+ earlyFraudWarnings?: Sync;
117
+ refunds?: Sync;
118
+ checkoutSessions?: Sync;
119
+ }
120
+ interface SyncBackfillParams {
121
+ created?: {
122
+ /**
123
+ * Minimum value to filter by (exclusive)
124
+ */
125
+ gt?: number;
126
+ /**
127
+ * Minimum value to filter by (inclusive)
128
+ */
129
+ gte?: number;
130
+ /**
131
+ * Maximum value to filter by (exclusive)
132
+ */
133
+ lt?: number;
134
+ /**
135
+ * Maximum value to filter by (inclusive)
136
+ */
137
+ lte?: number;
138
+ };
139
+ object?: SyncObject;
140
+ backfillRelatedEntities?: boolean;
141
+ }
142
+ interface SyncEntitlementsParams {
143
+ object: 'entitlements';
144
+ customerId: string;
145
+ pagination?: Pick<Stripe.PaginationParams, 'starting_after' | 'ending_before'>;
146
+ }
147
+ interface SyncFeaturesParams {
148
+ object: 'features';
149
+ pagination?: Pick<Stripe.PaginationParams, 'starting_after' | 'ending_before'>;
150
+ }
151
+
152
+ interface EntitySchema {
153
+ readonly properties: string[];
154
+ }
155
+
156
+ type PostgresConfig = {
157
+ schema: string;
158
+ poolConfig: PoolConfig;
159
+ };
160
+ declare class PostgresClient {
161
+ private config;
162
+ pool: pg.Pool;
163
+ constructor(config: PostgresConfig);
164
+ delete(table: string, id: string): Promise<boolean>;
165
+ query(text: string, params?: string[]): Promise<QueryResult>;
166
+ upsertMany<T extends {
167
+ [Key: string]: any;
168
+ }>(entries: T[], table: string, tableSchema: EntitySchema): Promise<T[]>;
169
+ upsertManyWithTimestampProtection<T extends {
170
+ [Key: string]: any;
171
+ }>(entries: T[], table: string, tableSchema: EntitySchema, syncTimestamp?: string): Promise<T[]>;
172
+ findMissingEntries(table: string, ids: string[]): Promise<string[]>;
173
+ /**
174
+ * Returns an (yesql formatted) upsert function based on the key/vals of an object.
175
+ * eg,
176
+ * insert into customers ("id", "name")
177
+ * values (:id, :name)
178
+ * on conflict (id)
179
+ * do update set (
180
+ * "id" = :id,
181
+ * "name" = :name
182
+ * )
183
+ */
184
+ private constructUpsertSql;
185
+ /**
186
+ * Returns an (yesql formatted) upsert function with timestamp protection.
187
+ *
188
+ * The WHERE clause in ON CONFLICT DO UPDATE only applies to the conflicting row
189
+ * (the row being updated), not to all rows in the table. PostgreSQL ensures that
190
+ * the condition is evaluated only for the specific row that conflicts with the INSERT.
191
+ *
192
+ *
193
+ * eg:
194
+ * INSERT INTO "stripe"."charges" (
195
+ * "id", "amount", "created", "last_synced_at"
196
+ * )
197
+ * VALUES (
198
+ * :id, :amount, :created, :last_synced_at
199
+ * )
200
+ * ON CONFLICT (id) DO UPDATE SET
201
+ * "amount" = EXCLUDED."amount",
202
+ * "created" = EXCLUDED."created",
203
+ * last_synced_at = :last_synced_at
204
+ * WHERE "charges"."last_synced_at" IS NULL
205
+ * OR "charges"."last_synced_at" < :last_synced_at;
206
+ */
207
+ private constructUpsertWithTimestampProtectionSql;
208
+ /**
209
+ * For array object field like invoice.custom_fields
210
+ * ex: [{"name":"Project name","value":"Test Project"}]
211
+ *
212
+ * we need to stringify it first cos passing array object directly will end up with
213
+ * {
214
+ * invalid input syntax for type json
215
+ * detail: 'Expected ":", but found "}".',
216
+ * where: 'JSON data, line 1: ...\\":\\"Project name\\",\\"value\\":\\"Test Project\\"}"}',
217
+ * }
218
+ */
219
+ private cleanseArrayField;
220
+ }
221
+
222
+ export { PostgresClient, type RevalidateEntity, StripeAutoSync, type StripeAutoSyncInfo, type StripeAutoSyncOptions, type StripeSyncConfig, type Sync, type SyncBackfill, type SyncBackfillParams, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject };
@@ -0,0 +1,222 @@
1
+ import { Express } from 'express';
2
+ import pg, { PoolConfig, QueryResult } from 'pg';
3
+ import pino from 'pino';
4
+ import Stripe from 'stripe';
5
+
6
+ interface StripeAutoSyncOptions {
7
+ databaseUrl: string;
8
+ stripeApiKey: string;
9
+ baseUrl: () => string;
10
+ webhookPath?: string;
11
+ schema?: string;
12
+ stripeApiVersion?: string;
13
+ autoExpandLists?: boolean;
14
+ backfillRelatedEntities?: boolean;
15
+ }
16
+ interface StripeAutoSyncInfo {
17
+ baseUrl: string;
18
+ webhookUrl: string;
19
+ webhookUuid: string;
20
+ }
21
+ /**
22
+ * Manages Stripe webhook auto-sync infrastructure:
23
+ * - Runs database migrations
24
+ * - Creates managed webhook in Stripe
25
+ * - Mounts webhook handler on Express app
26
+ */
27
+ declare class StripeAutoSync {
28
+ private options;
29
+ private webhookId;
30
+ private webhookUuid;
31
+ private stripeSync;
32
+ constructor(options: StripeAutoSyncOptions);
33
+ /**
34
+ * Starts the Stripe Sync infrastructure and mounts webhook handler:
35
+ * 1. Runs database migrations
36
+ * 2. Creates StripeSync instance
37
+ * 3. Creates managed webhook endpoint
38
+ * 4. Mounts webhook handler on provided Express app
39
+ * 5. Applies body parsing middleware (automatically skips webhook routes)
40
+ *
41
+ * @param app - Express app to mount webhook handler on
42
+ * @returns Information about the running instance
43
+ */
44
+ start(app: Express): Promise<StripeAutoSyncInfo>;
45
+ /**
46
+ * Stops all services and cleans up resources:
47
+ * 1. Deletes Stripe webhook endpoint from Stripe and database
48
+ */
49
+ stop(): Promise<void>;
50
+ /**
51
+ * Returns Express middleware for body parsing that automatically skips webhook routes.
52
+ * This middleware applies JSON and URL-encoded parsers to all routes EXCEPT the webhook path,
53
+ * which needs raw body for signature verification.
54
+ *
55
+ * @returns Express middleware function
56
+ */
57
+ private getBodyParserMiddleware;
58
+ /**
59
+ * Mounts the Stripe webhook handler on the provided Express app.
60
+ * Applies raw body parser middleware for signature verification.
61
+ * IMPORTANT: Must be called BEFORE app.use(express.json()) to ensure raw body parsing.
62
+ */
63
+ private mountWebhook;
64
+ }
65
+
66
+ type RevalidateEntity = 'charge' | 'credit_note' | 'customer' | 'dispute' | 'invoice' | 'payment_intent' | 'payment_method' | 'plan' | 'price' | 'product' | 'refund' | 'review' | 'radar.early_fraud_warning' | 'setup_intent' | 'subscription' | 'subscription_schedule' | 'tax_id' | 'entitlements';
67
+ type StripeSyncConfig = {
68
+ /** @deprecated Use `poolConfig` with a connection string instead. */
69
+ databaseUrl?: string;
70
+ /** Database schema name. */
71
+ schema?: string;
72
+ /** Stripe secret key used to authenticate requests to the Stripe API. Defaults to empty string */
73
+ stripeSecretKey: string;
74
+ /** Stripe API version for the webhooks, defaults to 2020-08-27 */
75
+ stripeApiVersion?: string;
76
+ /**
77
+ * Stripe limits related lists like invoice items in an invoice to 10 by default.
78
+ * By enabling this, sync-engine automatically fetches the remaining elements before saving
79
+ * */
80
+ autoExpandLists?: boolean;
81
+ /**
82
+ * If true, the sync engine will backfill related entities, i.e. when a invoice webhook comes in, it ensures that the customer is present and synced.
83
+ * This ensures foreign key integrity, but comes at the cost of additional queries to the database (and added latency for Stripe calls if the entity is actually missing).
84
+ */
85
+ backfillRelatedEntities?: boolean;
86
+ /**
87
+ * If true, the webhook data is not used and instead the webhook is just a trigger to fetch the entity from Stripe again. This ensures that a race condition with failed webhooks can never accidentally overwrite the data with an older state.
88
+ *
89
+ * Default: false
90
+ */
91
+ revalidateObjectsViaStripeApi?: Array<RevalidateEntity>;
92
+ /** @deprecated Use `poolConfig` instead. */
93
+ maxPostgresConnections?: number;
94
+ poolConfig: PoolConfig;
95
+ logger?: pino.Logger;
96
+ };
97
+ type SyncObject = 'all' | 'customer' | 'customer_with_entitlements' | 'invoice' | 'price' | 'product' | 'subscription' | 'subscription_schedules' | 'setup_intent' | 'payment_method' | 'dispute' | 'charge' | 'payment_intent' | 'plan' | 'tax_id' | 'credit_note' | 'early_fraud_warning' | 'refund' | 'checkout_sessions';
98
+ interface Sync {
99
+ synced: number;
100
+ }
101
+ interface SyncBackfill {
102
+ products?: Sync;
103
+ prices?: Sync;
104
+ plans?: Sync;
105
+ customers?: Sync;
106
+ subscriptions?: Sync;
107
+ subscriptionSchedules?: Sync;
108
+ invoices?: Sync;
109
+ setupIntents?: Sync;
110
+ paymentIntents?: Sync;
111
+ paymentMethods?: Sync;
112
+ disputes?: Sync;
113
+ charges?: Sync;
114
+ taxIds?: Sync;
115
+ creditNotes?: Sync;
116
+ earlyFraudWarnings?: Sync;
117
+ refunds?: Sync;
118
+ checkoutSessions?: Sync;
119
+ }
120
+ interface SyncBackfillParams {
121
+ created?: {
122
+ /**
123
+ * Minimum value to filter by (exclusive)
124
+ */
125
+ gt?: number;
126
+ /**
127
+ * Minimum value to filter by (inclusive)
128
+ */
129
+ gte?: number;
130
+ /**
131
+ * Maximum value to filter by (exclusive)
132
+ */
133
+ lt?: number;
134
+ /**
135
+ * Maximum value to filter by (inclusive)
136
+ */
137
+ lte?: number;
138
+ };
139
+ object?: SyncObject;
140
+ backfillRelatedEntities?: boolean;
141
+ }
142
+ interface SyncEntitlementsParams {
143
+ object: 'entitlements';
144
+ customerId: string;
145
+ pagination?: Pick<Stripe.PaginationParams, 'starting_after' | 'ending_before'>;
146
+ }
147
+ interface SyncFeaturesParams {
148
+ object: 'features';
149
+ pagination?: Pick<Stripe.PaginationParams, 'starting_after' | 'ending_before'>;
150
+ }
151
+
152
+ interface EntitySchema {
153
+ readonly properties: string[];
154
+ }
155
+
156
+ type PostgresConfig = {
157
+ schema: string;
158
+ poolConfig: PoolConfig;
159
+ };
160
+ declare class PostgresClient {
161
+ private config;
162
+ pool: pg.Pool;
163
+ constructor(config: PostgresConfig);
164
+ delete(table: string, id: string): Promise<boolean>;
165
+ query(text: string, params?: string[]): Promise<QueryResult>;
166
+ upsertMany<T extends {
167
+ [Key: string]: any;
168
+ }>(entries: T[], table: string, tableSchema: EntitySchema): Promise<T[]>;
169
+ upsertManyWithTimestampProtection<T extends {
170
+ [Key: string]: any;
171
+ }>(entries: T[], table: string, tableSchema: EntitySchema, syncTimestamp?: string): Promise<T[]>;
172
+ findMissingEntries(table: string, ids: string[]): Promise<string[]>;
173
+ /**
174
+ * Returns an (yesql formatted) upsert function based on the key/vals of an object.
175
+ * eg,
176
+ * insert into customers ("id", "name")
177
+ * values (:id, :name)
178
+ * on conflict (id)
179
+ * do update set (
180
+ * "id" = :id,
181
+ * "name" = :name
182
+ * )
183
+ */
184
+ private constructUpsertSql;
185
+ /**
186
+ * Returns an (yesql formatted) upsert function with timestamp protection.
187
+ *
188
+ * The WHERE clause in ON CONFLICT DO UPDATE only applies to the conflicting row
189
+ * (the row being updated), not to all rows in the table. PostgreSQL ensures that
190
+ * the condition is evaluated only for the specific row that conflicts with the INSERT.
191
+ *
192
+ *
193
+ * eg:
194
+ * INSERT INTO "stripe"."charges" (
195
+ * "id", "amount", "created", "last_synced_at"
196
+ * )
197
+ * VALUES (
198
+ * :id, :amount, :created, :last_synced_at
199
+ * )
200
+ * ON CONFLICT (id) DO UPDATE SET
201
+ * "amount" = EXCLUDED."amount",
202
+ * "created" = EXCLUDED."created",
203
+ * last_synced_at = :last_synced_at
204
+ * WHERE "charges"."last_synced_at" IS NULL
205
+ * OR "charges"."last_synced_at" < :last_synced_at;
206
+ */
207
+ private constructUpsertWithTimestampProtectionSql;
208
+ /**
209
+ * For array object field like invoice.custom_fields
210
+ * ex: [{"name":"Project name","value":"Test Project"}]
211
+ *
212
+ * we need to stringify it first cos passing array object directly will end up with
213
+ * {
214
+ * invalid input syntax for type json
215
+ * detail: 'Expected ":", but found "}".',
216
+ * where: 'JSON data, line 1: ...\\":\\"Project name\\",\\"value\\":\\"Test Project\\"}"}',
217
+ * }
218
+ */
219
+ private cleanseArrayField;
220
+ }
221
+
222
+ export { PostgresClient, type RevalidateEntity, StripeAutoSync, type StripeAutoSyncInfo, type StripeAutoSyncOptions, type StripeSyncConfig, type Sync, type SyncBackfill, type SyncBackfillParams, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject };