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
package/dist/index.cjs ADDED
@@ -0,0 +1,2370 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ PostgresClient: () => PostgresClient,
34
+ StripeAutoSync: () => StripeAutoSync
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // ../../node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js
39
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
40
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
+
42
+ // src/stripeSync.ts
43
+ var import_stripe = __toESM(require("stripe"), 1);
44
+ var import_yesql2 = require("yesql");
45
+
46
+ // src/database/postgres.ts
47
+ var import_pg = __toESM(require("pg"), 1);
48
+ var import_yesql = require("yesql");
49
+ var PostgresClient = class {
50
+ constructor(config) {
51
+ this.config = config;
52
+ this.pool = new import_pg.default.Pool(config.poolConfig);
53
+ }
54
+ pool;
55
+ async delete(table, id) {
56
+ const prepared = (0, import_yesql.pg)(`
57
+ delete from "${this.config.schema}"."${table}"
58
+ where id = :id
59
+ returning id;
60
+ `)({ id });
61
+ const { rows } = await this.query(prepared.text, prepared.values);
62
+ return rows.length > 0;
63
+ }
64
+ async query(text, params) {
65
+ return this.pool.query(text, params);
66
+ }
67
+ async upsertMany(entries, table, tableSchema) {
68
+ if (!entries.length) return [];
69
+ const chunkSize = 5;
70
+ const results = [];
71
+ for (let i = 0; i < entries.length; i += chunkSize) {
72
+ const chunk = entries.slice(i, i + chunkSize);
73
+ const queries = [];
74
+ chunk.forEach((entry) => {
75
+ const cleansed = this.cleanseArrayField(entry);
76
+ const upsertSql = this.constructUpsertSql(this.config.schema, table, tableSchema);
77
+ const prepared = (0, import_yesql.pg)(upsertSql, {
78
+ useNullForMissing: true
79
+ })(cleansed);
80
+ queries.push(this.pool.query(prepared.text, prepared.values));
81
+ });
82
+ results.push(...await Promise.all(queries));
83
+ }
84
+ return results.flatMap((it) => it.rows);
85
+ }
86
+ async upsertManyWithTimestampProtection(entries, table, tableSchema, syncTimestamp) {
87
+ const timestamp = syncTimestamp || (/* @__PURE__ */ new Date()).toISOString();
88
+ if (!entries.length) return [];
89
+ const chunkSize = 5;
90
+ const results = [];
91
+ for (let i = 0; i < entries.length; i += chunkSize) {
92
+ const chunk = entries.slice(i, i + chunkSize);
93
+ const queries = [];
94
+ chunk.forEach((entry) => {
95
+ const cleansed = this.cleanseArrayField(entry);
96
+ cleansed.last_synced_at = timestamp;
97
+ const upsertSql = this.constructUpsertWithTimestampProtectionSql(
98
+ this.config.schema,
99
+ table,
100
+ tableSchema
101
+ );
102
+ const prepared = (0, import_yesql.pg)(upsertSql, {
103
+ useNullForMissing: true
104
+ })(cleansed);
105
+ queries.push(this.pool.query(prepared.text, prepared.values));
106
+ });
107
+ results.push(...await Promise.all(queries));
108
+ }
109
+ return results.flatMap((it) => it.rows);
110
+ }
111
+ async findMissingEntries(table, ids) {
112
+ if (!ids.length) return [];
113
+ const prepared = (0, import_yesql.pg)(`
114
+ select id from "${this.config.schema}"."${table}"
115
+ where id=any(:ids::text[]);
116
+ `)({ ids });
117
+ const { rows } = await this.query(prepared.text, prepared.values);
118
+ const existingIds = rows.map((it) => it.id);
119
+ const missingIds = ids.filter((it) => !existingIds.includes(it));
120
+ return missingIds;
121
+ }
122
+ /**
123
+ * Returns an (yesql formatted) upsert function based on the key/vals of an object.
124
+ * eg,
125
+ * insert into customers ("id", "name")
126
+ * values (:id, :name)
127
+ * on conflict (id)
128
+ * do update set (
129
+ * "id" = :id,
130
+ * "name" = :name
131
+ * )
132
+ */
133
+ constructUpsertSql(schema, table, tableSchema, options) {
134
+ const { conflict = "id" } = options || {};
135
+ const properties = tableSchema.properties;
136
+ return `
137
+ insert into "${schema}"."${table}" (
138
+ ${properties.map((x) => `"${x}"`).join(",")}
139
+ )
140
+ values (
141
+ ${properties.map((x) => `:${x}`).join(",")}
142
+ )
143
+ on conflict (
144
+ ${conflict}
145
+ )
146
+ do update set
147
+ ${properties.map((x) => `"${x}" = :${x}`).join(",")}
148
+ ;`;
149
+ }
150
+ /**
151
+ * Returns an (yesql formatted) upsert function with timestamp protection.
152
+ *
153
+ * The WHERE clause in ON CONFLICT DO UPDATE only applies to the conflicting row
154
+ * (the row being updated), not to all rows in the table. PostgreSQL ensures that
155
+ * the condition is evaluated only for the specific row that conflicts with the INSERT.
156
+ *
157
+ *
158
+ * eg:
159
+ * INSERT INTO "stripe"."charges" (
160
+ * "id", "amount", "created", "last_synced_at"
161
+ * )
162
+ * VALUES (
163
+ * :id, :amount, :created, :last_synced_at
164
+ * )
165
+ * ON CONFLICT (id) DO UPDATE SET
166
+ * "amount" = EXCLUDED."amount",
167
+ * "created" = EXCLUDED."created",
168
+ * last_synced_at = :last_synced_at
169
+ * WHERE "charges"."last_synced_at" IS NULL
170
+ * OR "charges"."last_synced_at" < :last_synced_at;
171
+ */
172
+ constructUpsertWithTimestampProtectionSql = (schema, table, tableSchema) => {
173
+ const conflict = "id";
174
+ const properties = tableSchema.properties;
175
+ return `
176
+ INSERT INTO "${schema}"."${table}" (
177
+ ${properties.map((x) => `"${x}"`).join(",")}, "last_synced_at"
178
+ )
179
+ VALUES (
180
+ ${properties.map((x) => `:${x}`).join(",")}, :last_synced_at
181
+ )
182
+ ON CONFLICT (${conflict}) DO UPDATE SET
183
+ ${properties.filter((x) => x !== "last_synced_at").map((x) => `"${x}" = EXCLUDED."${x}"`).join(",")},
184
+ last_synced_at = :last_synced_at
185
+ WHERE "${table}"."last_synced_at" IS NULL
186
+ OR "${table}"."last_synced_at" < :last_synced_at;`;
187
+ };
188
+ /**
189
+ * For array object field like invoice.custom_fields
190
+ * ex: [{"name":"Project name","value":"Test Project"}]
191
+ *
192
+ * we need to stringify it first cos passing array object directly will end up with
193
+ * {
194
+ * invalid input syntax for type json
195
+ * detail: 'Expected ":", but found "}".',
196
+ * where: 'JSON data, line 1: ...\\":\\"Project name\\",\\"value\\":\\"Test Project\\"}"}',
197
+ * }
198
+ */
199
+ cleanseArrayField(obj) {
200
+ const cleansed = { ...obj };
201
+ Object.keys(cleansed).map((k) => {
202
+ const data = cleansed[k];
203
+ if (Array.isArray(data)) {
204
+ cleansed[k] = JSON.stringify(data);
205
+ }
206
+ });
207
+ return cleansed;
208
+ }
209
+ };
210
+
211
+ // src/schemas/charge.ts
212
+ var chargeSchema = {
213
+ properties: [
214
+ "id",
215
+ "object",
216
+ "paid",
217
+ "order",
218
+ "amount",
219
+ "review",
220
+ "source",
221
+ "status",
222
+ "created",
223
+ "dispute",
224
+ "invoice",
225
+ "outcome",
226
+ "refunds",
227
+ "captured",
228
+ "currency",
229
+ "customer",
230
+ "livemode",
231
+ "metadata",
232
+ "refunded",
233
+ "shipping",
234
+ "application",
235
+ "description",
236
+ "destination",
237
+ "failure_code",
238
+ "on_behalf_of",
239
+ "fraud_details",
240
+ "receipt_email",
241
+ "payment_intent",
242
+ "receipt_number",
243
+ "transfer_group",
244
+ "amount_refunded",
245
+ "application_fee",
246
+ "failure_message",
247
+ "source_transfer",
248
+ "balance_transaction",
249
+ "statement_descriptor",
250
+ "payment_method_details"
251
+ ]
252
+ };
253
+
254
+ // src/schemas/checkout_sessions.ts
255
+ var checkoutSessionSchema = {
256
+ properties: [
257
+ "id",
258
+ "object",
259
+ "adaptive_pricing",
260
+ "after_expiration",
261
+ "allow_promotion_codes",
262
+ "amount_subtotal",
263
+ "amount_total",
264
+ "automatic_tax",
265
+ "billing_address_collection",
266
+ "cancel_url",
267
+ "client_reference_id",
268
+ "client_secret",
269
+ "collected_information",
270
+ "consent",
271
+ "consent_collection",
272
+ "created",
273
+ "currency",
274
+ "currency_conversion",
275
+ "custom_fields",
276
+ "custom_text",
277
+ "customer",
278
+ "customer_creation",
279
+ "customer_details",
280
+ "customer_email",
281
+ "discounts",
282
+ "expires_at",
283
+ "invoice",
284
+ "invoice_creation",
285
+ "livemode",
286
+ "locale",
287
+ "metadata",
288
+ "mode",
289
+ "optional_items",
290
+ "payment_intent",
291
+ "payment_link",
292
+ "payment_method_collection",
293
+ "payment_method_configuration_details",
294
+ "payment_method_options",
295
+ "payment_method_types",
296
+ "payment_status",
297
+ "permissions",
298
+ "phone_number_collection",
299
+ "presentment_details",
300
+ "recovered_from",
301
+ "redirect_on_completion",
302
+ "return_url",
303
+ "saved_payment_method_options",
304
+ "setup_intent",
305
+ "shipping_address_collection",
306
+ "shipping_cost",
307
+ "shipping_details",
308
+ "shipping_options",
309
+ "status",
310
+ "submit_type",
311
+ "subscription",
312
+ "success_url",
313
+ "tax_id_collection",
314
+ "total_details",
315
+ "ui_mode",
316
+ "url",
317
+ "wallet_options"
318
+ ]
319
+ };
320
+
321
+ // src/schemas/checkout_session_line_items.ts
322
+ var checkoutSessionLineItemSchema = {
323
+ properties: [
324
+ "id",
325
+ "object",
326
+ "amount_discount",
327
+ "amount_subtotal",
328
+ "amount_tax",
329
+ "amount_total",
330
+ "currency",
331
+ "description",
332
+ "price",
333
+ "quantity",
334
+ "checkout_session"
335
+ ]
336
+ };
337
+
338
+ // src/schemas/credit_note.ts
339
+ var creditNoteSchema = {
340
+ properties: [
341
+ "id",
342
+ "object",
343
+ "amount",
344
+ "amount_shipping",
345
+ "created",
346
+ "currency",
347
+ "customer",
348
+ "customer_balance_transaction",
349
+ "discount_amount",
350
+ "discount_amounts",
351
+ "invoice",
352
+ "lines",
353
+ "livemode",
354
+ "memo",
355
+ "metadata",
356
+ "number",
357
+ "out_of_band_amount",
358
+ "pdf",
359
+ "reason",
360
+ "refund",
361
+ "shipping_cost",
362
+ "status",
363
+ "subtotal",
364
+ "subtotal_excluding_tax",
365
+ "tax_amounts",
366
+ "total",
367
+ "total_excluding_tax",
368
+ "type",
369
+ "voided_at"
370
+ ]
371
+ };
372
+
373
+ // src/schemas/customer.ts
374
+ var customerSchema = {
375
+ properties: [
376
+ "id",
377
+ "object",
378
+ "address",
379
+ "description",
380
+ "email",
381
+ "metadata",
382
+ "name",
383
+ "phone",
384
+ "shipping",
385
+ "balance",
386
+ "created",
387
+ "currency",
388
+ "default_source",
389
+ "delinquent",
390
+ "discount",
391
+ "invoice_prefix",
392
+ "invoice_settings",
393
+ "livemode",
394
+ "next_invoice_sequence",
395
+ "preferred_locales",
396
+ "tax_exempt"
397
+ ]
398
+ };
399
+ var customerDeletedSchema = {
400
+ properties: ["id", "object", "deleted"]
401
+ };
402
+
403
+ // src/schemas/dispute.ts
404
+ var disputeSchema = {
405
+ properties: [
406
+ "id",
407
+ "object",
408
+ "amount",
409
+ "charge",
410
+ "created",
411
+ "currency",
412
+ "balance_transactions",
413
+ "evidence",
414
+ "evidence_details",
415
+ "is_charge_refundable",
416
+ "livemode",
417
+ "metadata",
418
+ "payment_intent",
419
+ "reason",
420
+ "status"
421
+ ]
422
+ };
423
+
424
+ // src/schemas/invoice.ts
425
+ var invoiceSchema = {
426
+ properties: [
427
+ "id",
428
+ "object",
429
+ "auto_advance",
430
+ "collection_method",
431
+ "currency",
432
+ "description",
433
+ "hosted_invoice_url",
434
+ "lines",
435
+ "metadata",
436
+ "period_end",
437
+ "period_start",
438
+ "status",
439
+ "total",
440
+ "account_country",
441
+ "account_name",
442
+ "account_tax_ids",
443
+ "amount_due",
444
+ "amount_paid",
445
+ "amount_remaining",
446
+ "application_fee_amount",
447
+ "attempt_count",
448
+ "attempted",
449
+ "billing_reason",
450
+ "created",
451
+ "custom_fields",
452
+ "customer_address",
453
+ "customer_email",
454
+ "customer_name",
455
+ "customer_phone",
456
+ "customer_shipping",
457
+ "customer_tax_exempt",
458
+ "customer_tax_ids",
459
+ "default_tax_rates",
460
+ "discount",
461
+ "discounts",
462
+ "due_date",
463
+ "ending_balance",
464
+ "footer",
465
+ "invoice_pdf",
466
+ "last_finalization_error",
467
+ "livemode",
468
+ "next_payment_attempt",
469
+ "number",
470
+ "paid",
471
+ "payment_settings",
472
+ "post_payment_credit_notes_amount",
473
+ "pre_payment_credit_notes_amount",
474
+ "receipt_number",
475
+ "starting_balance",
476
+ "statement_descriptor",
477
+ "status_transitions",
478
+ "subtotal",
479
+ "tax",
480
+ "total_discount_amounts",
481
+ "total_tax_amounts",
482
+ "transfer_data",
483
+ "webhooks_delivered_at",
484
+ "customer",
485
+ "subscription",
486
+ "payment_intent",
487
+ "default_payment_method",
488
+ "default_source",
489
+ "on_behalf_of",
490
+ "charge"
491
+ ]
492
+ };
493
+
494
+ // src/schemas/plan.ts
495
+ var planSchema = {
496
+ properties: [
497
+ "id",
498
+ "object",
499
+ "active",
500
+ "amount",
501
+ "created",
502
+ "product",
503
+ "currency",
504
+ "interval",
505
+ "livemode",
506
+ "metadata",
507
+ "nickname",
508
+ "tiers_mode",
509
+ "usage_type",
510
+ "billing_scheme",
511
+ "interval_count",
512
+ "aggregate_usage",
513
+ "transform_usage",
514
+ "trial_period_days"
515
+ ]
516
+ };
517
+
518
+ // src/schemas/price.ts
519
+ var priceSchema = {
520
+ properties: [
521
+ "id",
522
+ "object",
523
+ "active",
524
+ "currency",
525
+ "metadata",
526
+ "nickname",
527
+ "recurring",
528
+ "type",
529
+ "unit_amount",
530
+ "billing_scheme",
531
+ "created",
532
+ "livemode",
533
+ "lookup_key",
534
+ "tiers_mode",
535
+ "transform_quantity",
536
+ "unit_amount_decimal",
537
+ "product"
538
+ ]
539
+ };
540
+
541
+ // src/schemas/product.ts
542
+ var productSchema = {
543
+ properties: [
544
+ "id",
545
+ "object",
546
+ "active",
547
+ "default_price",
548
+ "description",
549
+ "metadata",
550
+ "name",
551
+ "created",
552
+ "images",
553
+ "marketing_features",
554
+ "livemode",
555
+ "package_dimensions",
556
+ "shippable",
557
+ "statement_descriptor",
558
+ "unit_label",
559
+ "updated",
560
+ "url"
561
+ ]
562
+ };
563
+
564
+ // src/schemas/payment_intent.ts
565
+ var paymentIntentSchema = {
566
+ properties: [
567
+ "id",
568
+ "object",
569
+ "amount",
570
+ "amount_capturable",
571
+ "amount_details",
572
+ "amount_received",
573
+ "application",
574
+ "application_fee_amount",
575
+ "automatic_payment_methods",
576
+ "canceled_at",
577
+ "cancellation_reason",
578
+ "capture_method",
579
+ "client_secret",
580
+ "confirmation_method",
581
+ "created",
582
+ "currency",
583
+ "customer",
584
+ "description",
585
+ "invoice",
586
+ "last_payment_error",
587
+ "livemode",
588
+ "metadata",
589
+ "next_action",
590
+ "on_behalf_of",
591
+ "payment_method",
592
+ "payment_method_options",
593
+ "payment_method_types",
594
+ "processing",
595
+ "receipt_email",
596
+ "review",
597
+ "setup_future_usage",
598
+ "shipping",
599
+ "statement_descriptor",
600
+ "statement_descriptor_suffix",
601
+ "status",
602
+ "transfer_data",
603
+ "transfer_group"
604
+ ]
605
+ };
606
+
607
+ // src/schemas/payment_methods.ts
608
+ var paymentMethodsSchema = {
609
+ properties: [
610
+ "id",
611
+ "object",
612
+ "created",
613
+ "customer",
614
+ "type",
615
+ "billing_details",
616
+ "metadata",
617
+ "card"
618
+ ]
619
+ };
620
+
621
+ // src/schemas/setup_intents.ts
622
+ var setupIntentsSchema = {
623
+ properties: [
624
+ "id",
625
+ "object",
626
+ "created",
627
+ "customer",
628
+ "description",
629
+ "payment_method",
630
+ "status",
631
+ "usage",
632
+ "cancellation_reason",
633
+ "latest_attempt",
634
+ "mandate",
635
+ "single_use_mandate",
636
+ "on_behalf_of"
637
+ ]
638
+ };
639
+
640
+ // src/schemas/tax_id.ts
641
+ var taxIdSchema = {
642
+ properties: [
643
+ "id",
644
+ "country",
645
+ "customer",
646
+ "type",
647
+ "value",
648
+ "object",
649
+ "created",
650
+ "livemode",
651
+ "owner"
652
+ ]
653
+ };
654
+
655
+ // src/schemas/subscription_item.ts
656
+ var subscriptionItemSchema = {
657
+ properties: [
658
+ "id",
659
+ "object",
660
+ "billing_thresholds",
661
+ "created",
662
+ "deleted",
663
+ "metadata",
664
+ "quantity",
665
+ "price",
666
+ "subscription",
667
+ "tax_rates",
668
+ "current_period_end",
669
+ "current_period_start"
670
+ ]
671
+ };
672
+
673
+ // src/schemas/subscription_schedules.ts
674
+ var subscriptionScheduleSchema = {
675
+ properties: [
676
+ "id",
677
+ "object",
678
+ "application",
679
+ "canceled_at",
680
+ "completed_at",
681
+ "created",
682
+ "current_phase",
683
+ "customer",
684
+ "default_settings",
685
+ "end_behavior",
686
+ "livemode",
687
+ "metadata",
688
+ "phases",
689
+ "released_at",
690
+ "released_subscription",
691
+ "status",
692
+ "subscription",
693
+ "test_clock"
694
+ ]
695
+ };
696
+
697
+ // src/schemas/subscription.ts
698
+ var subscriptionSchema = {
699
+ properties: [
700
+ "id",
701
+ "object",
702
+ "cancel_at_period_end",
703
+ "current_period_end",
704
+ "current_period_start",
705
+ "default_payment_method",
706
+ "items",
707
+ "metadata",
708
+ "pending_setup_intent",
709
+ "pending_update",
710
+ "status",
711
+ "application_fee_percent",
712
+ "billing_cycle_anchor",
713
+ "billing_thresholds",
714
+ "cancel_at",
715
+ "canceled_at",
716
+ "collection_method",
717
+ "created",
718
+ "days_until_due",
719
+ "default_source",
720
+ "default_tax_rates",
721
+ "discount",
722
+ "ended_at",
723
+ "livemode",
724
+ "next_pending_invoice_item_invoice",
725
+ "pause_collection",
726
+ "pending_invoice_item_interval",
727
+ "start_date",
728
+ "transfer_data",
729
+ "trial_end",
730
+ "trial_start",
731
+ "schedule",
732
+ "customer",
733
+ "latest_invoice",
734
+ "plan"
735
+ ]
736
+ };
737
+
738
+ // src/schemas/early_fraud_warning.ts
739
+ var earlyFraudWarningSchema = {
740
+ properties: [
741
+ "id",
742
+ "object",
743
+ "actionable",
744
+ "charge",
745
+ "created",
746
+ "fraud_type",
747
+ "livemode",
748
+ "payment_intent"
749
+ ]
750
+ };
751
+
752
+ // src/schemas/review.ts
753
+ var reviewSchema = {
754
+ properties: [
755
+ "id",
756
+ "object",
757
+ "billing_zip",
758
+ "created",
759
+ "charge",
760
+ "closed_reason",
761
+ "livemode",
762
+ "ip_address",
763
+ "ip_address_location",
764
+ "open",
765
+ "opened_reason",
766
+ "payment_intent",
767
+ "reason",
768
+ "session"
769
+ ]
770
+ };
771
+
772
+ // src/schemas/refund.ts
773
+ var refundSchema = {
774
+ properties: [
775
+ "id",
776
+ "object",
777
+ "amount",
778
+ "balance_transaction",
779
+ "charge",
780
+ "created",
781
+ "currency",
782
+ "destination_details",
783
+ "metadata",
784
+ "payment_intent",
785
+ "reason",
786
+ "receipt_number",
787
+ "source_transfer_reversal",
788
+ "status",
789
+ "transfer_reversal"
790
+ ]
791
+ };
792
+
793
+ // src/schemas/active_entitlement.ts
794
+ var activeEntitlementSchema = {
795
+ properties: ["id", "object", "feature", "lookup_key", "livemode", "customer"]
796
+ };
797
+
798
+ // src/schemas/feature.ts
799
+ var featureSchema = {
800
+ properties: ["id", "object", "livemode", "name", "lookup_key", "active", "metadata"]
801
+ };
802
+
803
+ // src/schemas/managed_webhook.ts
804
+ var managedWebhookSchema = {
805
+ properties: [
806
+ "id",
807
+ "object",
808
+ "uuid",
809
+ "url",
810
+ "enabled_events",
811
+ "description",
812
+ "enabled",
813
+ "livemode",
814
+ "metadata",
815
+ "secret",
816
+ "status",
817
+ "api_version",
818
+ "created"
819
+ ]
820
+ };
821
+
822
+ // src/stripeSync.ts
823
+ var import_node_crypto = require("crypto");
824
+
825
+ // src/database/migrate.ts
826
+ var import_pg2 = require("pg");
827
+ var import_pg_node_migrations = require("pg-node-migrations");
828
+ var import_node_fs = __toESM(require("fs"), 1);
829
+ var import_node_path = __toESM(require("path"), 1);
830
+ var import_node_url = require("url");
831
+ var __filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
832
+ var __dirname = import_node_path.default.dirname(__filename2);
833
+ async function connectAndMigrate(client, migrationsDirectory, config, logOnError = false) {
834
+ if (!import_node_fs.default.existsSync(migrationsDirectory)) {
835
+ config.logger?.info(`Migrations directory ${migrationsDirectory} not found, skipping`);
836
+ return;
837
+ }
838
+ const optionalConfig = {
839
+ schemaName: config.schema,
840
+ tableName: "migrations"
841
+ };
842
+ try {
843
+ await (0, import_pg_node_migrations.migrate)({ client }, migrationsDirectory, optionalConfig);
844
+ } catch (error) {
845
+ if (logOnError && error instanceof Error) {
846
+ config.logger?.error(error, "Migration error:");
847
+ } else {
848
+ throw error;
849
+ }
850
+ }
851
+ }
852
+ async function runMigrations(config) {
853
+ const client = new import_pg2.Client({
854
+ connectionString: config.databaseUrl,
855
+ ssl: config.ssl,
856
+ connectionTimeoutMillis: 1e4
857
+ });
858
+ try {
859
+ await client.connect();
860
+ await client.query(`CREATE SCHEMA IF NOT EXISTS ${config.schema};`);
861
+ config.logger?.info("Running migrations");
862
+ await connectAndMigrate(client, import_node_path.default.resolve(__dirname, "./migrations"), config);
863
+ } catch (err) {
864
+ config.logger?.error(err, "Error running migrations");
865
+ throw err;
866
+ } finally {
867
+ await client.end();
868
+ config.logger?.info("Finished migrations");
869
+ }
870
+ }
871
+
872
+ // src/stripeSync.ts
873
+ var import_express = __toESM(require("express"), 1);
874
+ function getUniqueIds(entries, key) {
875
+ const set = new Set(
876
+ entries.map((subscription) => subscription?.[key]?.toString()).filter((it) => Boolean(it))
877
+ );
878
+ return Array.from(set);
879
+ }
880
+ var DEFAULT_SCHEMA = "stripe";
881
+ var StripeAutoSync = class {
882
+ options;
883
+ webhookId = null;
884
+ webhookUuid = null;
885
+ stripeSync = null;
886
+ constructor(options) {
887
+ this.options = {
888
+ ...options,
889
+ // Apply defaults for undefined values
890
+ schema: options.schema || "stripe",
891
+ webhookPath: options.webhookPath || "/stripe-webhooks",
892
+ stripeApiVersion: options.stripeApiVersion || "2020-08-27",
893
+ autoExpandLists: options.autoExpandLists !== void 0 ? options.autoExpandLists : false,
894
+ backfillRelatedEntities: options.backfillRelatedEntities !== void 0 ? options.backfillRelatedEntities : true
895
+ };
896
+ }
897
+ /**
898
+ * Starts the Stripe Sync infrastructure and mounts webhook handler:
899
+ * 1. Runs database migrations
900
+ * 2. Creates StripeSync instance
901
+ * 3. Creates managed webhook endpoint
902
+ * 4. Mounts webhook handler on provided Express app
903
+ * 5. Applies body parsing middleware (automatically skips webhook routes)
904
+ *
905
+ * @param app - Express app to mount webhook handler on
906
+ * @returns Information about the running instance
907
+ */
908
+ async start(app) {
909
+ try {
910
+ await runMigrations({
911
+ databaseUrl: this.options.databaseUrl,
912
+ schema: this.options.schema
913
+ });
914
+ const poolConfig = {
915
+ max: 10,
916
+ connectionString: this.options.databaseUrl,
917
+ keepAlive: true
918
+ };
919
+ this.stripeSync = new StripeSync({
920
+ databaseUrl: this.options.databaseUrl,
921
+ schema: this.options.schema,
922
+ stripeSecretKey: this.options.stripeApiKey,
923
+ stripeApiVersion: this.options.stripeApiVersion,
924
+ autoExpandLists: this.options.autoExpandLists,
925
+ backfillRelatedEntities: this.options.backfillRelatedEntities,
926
+ poolConfig
927
+ });
928
+ const baseUrl = this.options.baseUrl();
929
+ const { webhook, uuid } = await this.stripeSync.createManagedWebhook(
930
+ `${baseUrl}${this.options.webhookPath}`,
931
+ {
932
+ enabled_events: ["*"],
933
+ // Subscribe to all events
934
+ description: "stripe-sync-cli development webhook"
935
+ }
936
+ );
937
+ this.webhookId = webhook.id;
938
+ this.webhookUuid = uuid;
939
+ this.mountWebhook(app);
940
+ app.use(this.getBodyParserMiddleware());
941
+ return {
942
+ baseUrl,
943
+ webhookUrl: webhook.url,
944
+ webhookUuid: uuid
945
+ };
946
+ } catch (error) {
947
+ if (error instanceof Error) {
948
+ console.error("Failed to start Stripe Sync:", error.message);
949
+ console.error(error.stack || "");
950
+ } else {
951
+ console.error("Failed to start Stripe Sync:", String(error));
952
+ }
953
+ await this.stop();
954
+ throw error;
955
+ }
956
+ }
957
+ /**
958
+ * Stops all services and cleans up resources:
959
+ * 1. Deletes Stripe webhook endpoint from Stripe and database
960
+ */
961
+ async stop() {
962
+ if (this.webhookId && this.stripeSync) {
963
+ try {
964
+ await this.stripeSync.deleteManagedWebhook(this.webhookId);
965
+ } catch (error) {
966
+ console.error("Could not delete webhook:", error);
967
+ }
968
+ }
969
+ }
970
+ /**
971
+ * Returns Express middleware for body parsing that automatically skips webhook routes.
972
+ * This middleware applies JSON and URL-encoded parsers to all routes EXCEPT the webhook path,
973
+ * which needs raw body for signature verification.
974
+ *
975
+ * @returns Express middleware function
976
+ */
977
+ getBodyParserMiddleware() {
978
+ const webhookPath = this.options.webhookPath;
979
+ return (req, res, next) => {
980
+ if (req.path.startsWith(webhookPath)) {
981
+ return next();
982
+ }
983
+ import_express.default.json()(req, res, (err) => {
984
+ if (err) return next(err);
985
+ import_express.default.urlencoded({ extended: false })(req, res, next);
986
+ });
987
+ };
988
+ }
989
+ /**
990
+ * Mounts the Stripe webhook handler on the provided Express app.
991
+ * Applies raw body parser middleware for signature verification.
992
+ * IMPORTANT: Must be called BEFORE app.use(express.json()) to ensure raw body parsing.
993
+ */
994
+ mountWebhook(app) {
995
+ const webhookRoute = `${this.options.webhookPath}/:uuid`;
996
+ app.use(webhookRoute, import_express.default.raw({ type: "application/json" }));
997
+ app.post(webhookRoute, async (req, res) => {
998
+ const sig = req.headers["stripe-signature"];
999
+ if (!sig || typeof sig !== "string") {
1000
+ console.error("[Webhook] Missing stripe-signature header");
1001
+ return res.status(400).send({ error: "Missing stripe-signature header" });
1002
+ }
1003
+ const { uuid } = req.params;
1004
+ const rawBody = req.body;
1005
+ if (!rawBody || !Buffer.isBuffer(rawBody)) {
1006
+ console.error("[Webhook] Body is not a Buffer!", {
1007
+ hasBody: !!rawBody,
1008
+ bodyType: typeof rawBody,
1009
+ isBuffer: Buffer.isBuffer(rawBody),
1010
+ bodyConstructor: rawBody?.constructor?.name
1011
+ });
1012
+ return res.status(400).send({ error: "Missing raw body for signature verification" });
1013
+ }
1014
+ try {
1015
+ await this.stripeSync.processWebhook(rawBody, sig, uuid);
1016
+ return res.status(200).send({ received: true });
1017
+ } catch (error) {
1018
+ console.error("[Webhook] Processing error:", error.message);
1019
+ return res.status(400).send({ error: error.message });
1020
+ }
1021
+ });
1022
+ }
1023
+ };
1024
+ var StripeSync = class {
1025
+ constructor(config) {
1026
+ this.config = config;
1027
+ this.stripe = new import_stripe.default(config.stripeSecretKey, {
1028
+ // https://github.com/stripe/stripe-node#configuration
1029
+ // @ts-ignore
1030
+ apiVersion: config.stripeApiVersion,
1031
+ appInfo: {
1032
+ name: "Stripe Postgres Sync"
1033
+ }
1034
+ });
1035
+ this.config.logger?.info(
1036
+ { autoExpandLists: config.autoExpandLists, stripeApiVersion: config.stripeApiVersion },
1037
+ "StripeSync initialized"
1038
+ );
1039
+ const poolConfig = config.poolConfig ?? {};
1040
+ if (config.databaseUrl) {
1041
+ poolConfig.connectionString = config.databaseUrl;
1042
+ }
1043
+ if (config.maxPostgresConnections) {
1044
+ poolConfig.max = config.maxPostgresConnections;
1045
+ }
1046
+ if (poolConfig.max === void 0) {
1047
+ poolConfig.max = 10;
1048
+ }
1049
+ if (poolConfig.keepAlive === void 0) {
1050
+ poolConfig.keepAlive = true;
1051
+ }
1052
+ this.postgresClient = new PostgresClient({
1053
+ schema: config.schema || DEFAULT_SCHEMA,
1054
+ poolConfig
1055
+ });
1056
+ }
1057
+ stripe;
1058
+ postgresClient;
1059
+ async processWebhook(payload, signature, uuid) {
1060
+ const result = await this.postgresClient.query(
1061
+ `SELECT secret FROM "${this.config.schema || DEFAULT_SCHEMA}"."managed_webhooks" WHERE uuid = $1`,
1062
+ [uuid]
1063
+ );
1064
+ if (result.rows.length === 0) {
1065
+ throw new Error(`No managed webhook found with UUID: ${uuid}`);
1066
+ }
1067
+ const webhookSecret = result.rows[0].secret;
1068
+ const event = await this.stripe.webhooks.constructEventAsync(
1069
+ payload,
1070
+ signature,
1071
+ webhookSecret
1072
+ );
1073
+ return this.processEvent(event);
1074
+ }
1075
+ async processEvent(event) {
1076
+ switch (event.type) {
1077
+ case "charge.captured":
1078
+ case "charge.expired":
1079
+ case "charge.failed":
1080
+ case "charge.pending":
1081
+ case "charge.refunded":
1082
+ case "charge.succeeded":
1083
+ case "charge.updated": {
1084
+ const { entity: charge, refetched } = await this.fetchOrUseWebhookData(
1085
+ event.data.object,
1086
+ (id) => this.stripe.charges.retrieve(id),
1087
+ (charge2) => charge2.status === "failed" || charge2.status === "succeeded"
1088
+ );
1089
+ this.config.logger?.info(
1090
+ `Received webhook ${event.id}: ${event.type} for charge ${charge.id}`
1091
+ );
1092
+ await this.upsertCharges([charge], false, this.getSyncTimestamp(event, refetched));
1093
+ break;
1094
+ }
1095
+ case "customer.deleted": {
1096
+ const customer = {
1097
+ id: event.data.object.id,
1098
+ object: "customer",
1099
+ deleted: true
1100
+ };
1101
+ this.config.logger?.info(
1102
+ `Received webhook ${event.id}: ${event.type} for customer ${customer.id}`
1103
+ );
1104
+ await this.upsertCustomers([customer], this.getSyncTimestamp(event, false));
1105
+ break;
1106
+ }
1107
+ case "checkout.session.async_payment_failed":
1108
+ case "checkout.session.async_payment_succeeded":
1109
+ case "checkout.session.completed":
1110
+ case "checkout.session.expired": {
1111
+ const { entity: checkoutSession, refetched } = await this.fetchOrUseWebhookData(
1112
+ event.data.object,
1113
+ (id) => this.stripe.checkout.sessions.retrieve(id)
1114
+ );
1115
+ this.config.logger?.info(
1116
+ `Received webhook ${event.id}: ${event.type} for checkout session ${checkoutSession.id}`
1117
+ );
1118
+ await this.upsertCheckoutSessions(
1119
+ [checkoutSession],
1120
+ false,
1121
+ this.getSyncTimestamp(event, refetched)
1122
+ );
1123
+ break;
1124
+ }
1125
+ case "customer.created":
1126
+ case "customer.updated": {
1127
+ const { entity: customer, refetched } = await this.fetchOrUseWebhookData(
1128
+ event.data.object,
1129
+ (id) => this.stripe.customers.retrieve(id),
1130
+ (customer2) => customer2.deleted === true
1131
+ );
1132
+ this.config.logger?.info(
1133
+ `Received webhook ${event.id}: ${event.type} for customer ${customer.id}`
1134
+ );
1135
+ await this.upsertCustomers([customer], this.getSyncTimestamp(event, refetched));
1136
+ break;
1137
+ }
1138
+ case "customer.subscription.created":
1139
+ case "customer.subscription.deleted":
1140
+ // Soft delete using `status = canceled`
1141
+ case "customer.subscription.paused":
1142
+ case "customer.subscription.pending_update_applied":
1143
+ case "customer.subscription.pending_update_expired":
1144
+ case "customer.subscription.trial_will_end":
1145
+ case "customer.subscription.resumed":
1146
+ case "customer.subscription.updated": {
1147
+ const { entity: subscription, refetched } = await this.fetchOrUseWebhookData(
1148
+ event.data.object,
1149
+ (id) => this.stripe.subscriptions.retrieve(id),
1150
+ (subscription2) => subscription2.status === "canceled" || subscription2.status === "incomplete_expired"
1151
+ );
1152
+ this.config.logger?.info(
1153
+ `Received webhook ${event.id}: ${event.type} for subscription ${subscription.id}`
1154
+ );
1155
+ await this.upsertSubscriptions(
1156
+ [subscription],
1157
+ false,
1158
+ this.getSyncTimestamp(event, refetched)
1159
+ );
1160
+ break;
1161
+ }
1162
+ case "customer.tax_id.updated":
1163
+ case "customer.tax_id.created": {
1164
+ const { entity: taxId, refetched } = await this.fetchOrUseWebhookData(
1165
+ event.data.object,
1166
+ (id) => this.stripe.taxIds.retrieve(id)
1167
+ );
1168
+ this.config.logger?.info(
1169
+ `Received webhook ${event.id}: ${event.type} for taxId ${taxId.id}`
1170
+ );
1171
+ await this.upsertTaxIds([taxId], false, this.getSyncTimestamp(event, refetched));
1172
+ break;
1173
+ }
1174
+ case "customer.tax_id.deleted": {
1175
+ const taxId = event.data.object;
1176
+ this.config.logger?.info(
1177
+ `Received webhook ${event.id}: ${event.type} for taxId ${taxId.id}`
1178
+ );
1179
+ await this.deleteTaxId(taxId.id);
1180
+ break;
1181
+ }
1182
+ case "invoice.created":
1183
+ case "invoice.deleted":
1184
+ case "invoice.finalized":
1185
+ case "invoice.finalization_failed":
1186
+ case "invoice.paid":
1187
+ case "invoice.payment_action_required":
1188
+ case "invoice.payment_failed":
1189
+ case "invoice.payment_succeeded":
1190
+ case "invoice.upcoming":
1191
+ case "invoice.sent":
1192
+ case "invoice.voided":
1193
+ case "invoice.marked_uncollectible":
1194
+ case "invoice.updated": {
1195
+ const { entity: invoice, refetched } = await this.fetchOrUseWebhookData(
1196
+ event.data.object,
1197
+ (id) => this.stripe.invoices.retrieve(id),
1198
+ (invoice2) => invoice2.status === "void"
1199
+ );
1200
+ this.config.logger?.info(
1201
+ `Received webhook ${event.id}: ${event.type} for invoice ${invoice.id}`
1202
+ );
1203
+ await this.upsertInvoices([invoice], false, this.getSyncTimestamp(event, refetched));
1204
+ break;
1205
+ }
1206
+ case "product.created":
1207
+ case "product.updated": {
1208
+ try {
1209
+ const { entity: product, refetched } = await this.fetchOrUseWebhookData(
1210
+ event.data.object,
1211
+ (id) => this.stripe.products.retrieve(id)
1212
+ );
1213
+ this.config.logger?.info(
1214
+ `Received webhook ${event.id}: ${event.type} for product ${product.id}`
1215
+ );
1216
+ await this.upsertProducts([product], this.getSyncTimestamp(event, refetched));
1217
+ } catch (err) {
1218
+ if (err instanceof import_stripe.default.errors.StripeAPIError && err.code === "resource_missing") {
1219
+ await this.deleteProduct(event.data.object.id);
1220
+ } else {
1221
+ throw err;
1222
+ }
1223
+ }
1224
+ break;
1225
+ }
1226
+ case "product.deleted": {
1227
+ const product = event.data.object;
1228
+ this.config.logger?.info(
1229
+ `Received webhook ${event.id}: ${event.type} for product ${product.id}`
1230
+ );
1231
+ await this.deleteProduct(product.id);
1232
+ break;
1233
+ }
1234
+ case "price.created":
1235
+ case "price.updated": {
1236
+ try {
1237
+ const { entity: price, refetched } = await this.fetchOrUseWebhookData(
1238
+ event.data.object,
1239
+ (id) => this.stripe.prices.retrieve(id)
1240
+ );
1241
+ this.config.logger?.info(
1242
+ `Received webhook ${event.id}: ${event.type} for price ${price.id}`
1243
+ );
1244
+ await this.upsertPrices([price], false, this.getSyncTimestamp(event, refetched));
1245
+ } catch (err) {
1246
+ if (err instanceof import_stripe.default.errors.StripeAPIError && err.code === "resource_missing") {
1247
+ await this.deletePrice(event.data.object.id);
1248
+ } else {
1249
+ throw err;
1250
+ }
1251
+ }
1252
+ break;
1253
+ }
1254
+ case "price.deleted": {
1255
+ const price = event.data.object;
1256
+ this.config.logger?.info(
1257
+ `Received webhook ${event.id}: ${event.type} for price ${price.id}`
1258
+ );
1259
+ await this.deletePrice(price.id);
1260
+ break;
1261
+ }
1262
+ case "plan.created":
1263
+ case "plan.updated": {
1264
+ try {
1265
+ const { entity: plan, refetched } = await this.fetchOrUseWebhookData(
1266
+ event.data.object,
1267
+ (id) => this.stripe.plans.retrieve(id)
1268
+ );
1269
+ this.config.logger?.info(
1270
+ `Received webhook ${event.id}: ${event.type} for plan ${plan.id}`
1271
+ );
1272
+ await this.upsertPlans([plan], false, this.getSyncTimestamp(event, refetched));
1273
+ } catch (err) {
1274
+ if (err instanceof import_stripe.default.errors.StripeAPIError && err.code === "resource_missing") {
1275
+ await this.deletePlan(event.data.object.id);
1276
+ } else {
1277
+ throw err;
1278
+ }
1279
+ }
1280
+ break;
1281
+ }
1282
+ case "plan.deleted": {
1283
+ const plan = event.data.object;
1284
+ this.config.logger?.info(`Received webhook ${event.id}: ${event.type} for plan ${plan.id}`);
1285
+ await this.deletePlan(plan.id);
1286
+ break;
1287
+ }
1288
+ case "setup_intent.canceled":
1289
+ case "setup_intent.created":
1290
+ case "setup_intent.requires_action":
1291
+ case "setup_intent.setup_failed":
1292
+ case "setup_intent.succeeded": {
1293
+ const { entity: setupIntent, refetched } = await this.fetchOrUseWebhookData(
1294
+ event.data.object,
1295
+ (id) => this.stripe.setupIntents.retrieve(id),
1296
+ (setupIntent2) => setupIntent2.status === "canceled" || setupIntent2.status === "succeeded"
1297
+ );
1298
+ this.config.logger?.info(
1299
+ `Received webhook ${event.id}: ${event.type} for setupIntent ${setupIntent.id}`
1300
+ );
1301
+ await this.upsertSetupIntents([setupIntent], false, this.getSyncTimestamp(event, refetched));
1302
+ break;
1303
+ }
1304
+ case "subscription_schedule.aborted":
1305
+ case "subscription_schedule.canceled":
1306
+ case "subscription_schedule.completed":
1307
+ case "subscription_schedule.created":
1308
+ case "subscription_schedule.expiring":
1309
+ case "subscription_schedule.released":
1310
+ case "subscription_schedule.updated": {
1311
+ const { entity: subscriptionSchedule, refetched } = await this.fetchOrUseWebhookData(
1312
+ event.data.object,
1313
+ (id) => this.stripe.subscriptionSchedules.retrieve(id),
1314
+ (schedule) => schedule.status === "canceled" || schedule.status === "completed"
1315
+ );
1316
+ this.config.logger?.info(
1317
+ `Received webhook ${event.id}: ${event.type} for subscriptionSchedule ${subscriptionSchedule.id}`
1318
+ );
1319
+ await this.upsertSubscriptionSchedules(
1320
+ [subscriptionSchedule],
1321
+ false,
1322
+ this.getSyncTimestamp(event, refetched)
1323
+ );
1324
+ break;
1325
+ }
1326
+ case "payment_method.attached":
1327
+ case "payment_method.automatically_updated":
1328
+ case "payment_method.detached":
1329
+ case "payment_method.updated": {
1330
+ const { entity: paymentMethod, refetched } = await this.fetchOrUseWebhookData(
1331
+ event.data.object,
1332
+ (id) => this.stripe.paymentMethods.retrieve(id)
1333
+ );
1334
+ this.config.logger?.info(
1335
+ `Received webhook ${event.id}: ${event.type} for paymentMethod ${paymentMethod.id}`
1336
+ );
1337
+ await this.upsertPaymentMethods(
1338
+ [paymentMethod],
1339
+ false,
1340
+ this.getSyncTimestamp(event, refetched)
1341
+ );
1342
+ break;
1343
+ }
1344
+ case "charge.dispute.created":
1345
+ case "charge.dispute.funds_reinstated":
1346
+ case "charge.dispute.funds_withdrawn":
1347
+ case "charge.dispute.updated":
1348
+ case "charge.dispute.closed": {
1349
+ const { entity: dispute, refetched } = await this.fetchOrUseWebhookData(
1350
+ event.data.object,
1351
+ (id) => this.stripe.disputes.retrieve(id),
1352
+ (dispute2) => dispute2.status === "won" || dispute2.status === "lost"
1353
+ );
1354
+ this.config.logger?.info(
1355
+ `Received webhook ${event.id}: ${event.type} for dispute ${dispute.id}`
1356
+ );
1357
+ await this.upsertDisputes([dispute], false, this.getSyncTimestamp(event, refetched));
1358
+ break;
1359
+ }
1360
+ case "payment_intent.amount_capturable_updated":
1361
+ case "payment_intent.canceled":
1362
+ case "payment_intent.created":
1363
+ case "payment_intent.partially_funded":
1364
+ case "payment_intent.payment_failed":
1365
+ case "payment_intent.processing":
1366
+ case "payment_intent.requires_action":
1367
+ case "payment_intent.succeeded": {
1368
+ const { entity: paymentIntent, refetched } = await this.fetchOrUseWebhookData(
1369
+ event.data.object,
1370
+ (id) => this.stripe.paymentIntents.retrieve(id),
1371
+ // Final states - do not re-fetch from API
1372
+ (entity) => entity.status === "canceled" || entity.status === "succeeded"
1373
+ );
1374
+ this.config.logger?.info(
1375
+ `Received webhook ${event.id}: ${event.type} for paymentIntent ${paymentIntent.id}`
1376
+ );
1377
+ await this.upsertPaymentIntents(
1378
+ [paymentIntent],
1379
+ false,
1380
+ this.getSyncTimestamp(event, refetched)
1381
+ );
1382
+ break;
1383
+ }
1384
+ case "credit_note.created":
1385
+ case "credit_note.updated":
1386
+ case "credit_note.voided": {
1387
+ const { entity: creditNote, refetched } = await this.fetchOrUseWebhookData(
1388
+ event.data.object,
1389
+ (id) => this.stripe.creditNotes.retrieve(id),
1390
+ (creditNote2) => creditNote2.status === "void"
1391
+ );
1392
+ this.config.logger?.info(
1393
+ `Received webhook ${event.id}: ${event.type} for creditNote ${creditNote.id}`
1394
+ );
1395
+ await this.upsertCreditNotes([creditNote], false, this.getSyncTimestamp(event, refetched));
1396
+ break;
1397
+ }
1398
+ case "radar.early_fraud_warning.created":
1399
+ case "radar.early_fraud_warning.updated": {
1400
+ const { entity: earlyFraudWarning, refetched } = await this.fetchOrUseWebhookData(
1401
+ event.data.object,
1402
+ (id) => this.stripe.radar.earlyFraudWarnings.retrieve(id)
1403
+ );
1404
+ this.config.logger?.info(
1405
+ `Received webhook ${event.id}: ${event.type} for earlyFraudWarning ${earlyFraudWarning.id}`
1406
+ );
1407
+ await this.upsertEarlyFraudWarning(
1408
+ [earlyFraudWarning],
1409
+ false,
1410
+ this.getSyncTimestamp(event, refetched)
1411
+ );
1412
+ break;
1413
+ }
1414
+ case "refund.created":
1415
+ case "refund.failed":
1416
+ case "refund.updated":
1417
+ case "charge.refund.updated": {
1418
+ const { entity: refund, refetched } = await this.fetchOrUseWebhookData(
1419
+ event.data.object,
1420
+ (id) => this.stripe.refunds.retrieve(id)
1421
+ );
1422
+ this.config.logger?.info(
1423
+ `Received webhook ${event.id}: ${event.type} for refund ${refund.id}`
1424
+ );
1425
+ await this.upsertRefunds([refund], false, this.getSyncTimestamp(event, refetched));
1426
+ break;
1427
+ }
1428
+ case "review.closed":
1429
+ case "review.opened": {
1430
+ const { entity: review, refetched } = await this.fetchOrUseWebhookData(
1431
+ event.data.object,
1432
+ (id) => this.stripe.reviews.retrieve(id)
1433
+ );
1434
+ this.config.logger?.info(
1435
+ `Received webhook ${event.id}: ${event.type} for review ${review.id}`
1436
+ );
1437
+ await this.upsertReviews([review], false, this.getSyncTimestamp(event, refetched));
1438
+ break;
1439
+ }
1440
+ case "entitlements.active_entitlement_summary.updated": {
1441
+ const activeEntitlementSummary = event.data.object;
1442
+ let entitlements = activeEntitlementSummary.entitlements;
1443
+ let refetched = false;
1444
+ if (this.config.revalidateObjectsViaStripeApi?.includes("entitlements")) {
1445
+ const { lastResponse, ...rest } = await this.stripe.entitlements.activeEntitlements.list({
1446
+ customer: activeEntitlementSummary.customer
1447
+ });
1448
+ entitlements = rest;
1449
+ refetched = true;
1450
+ }
1451
+ this.config.logger?.info(
1452
+ `Received webhook ${event.id}: ${event.type} for activeEntitlementSummary for customer ${activeEntitlementSummary.customer}`
1453
+ );
1454
+ await this.deleteRemovedActiveEntitlements(
1455
+ activeEntitlementSummary.customer,
1456
+ entitlements.data.map((entitlement) => entitlement.id)
1457
+ );
1458
+ await this.upsertActiveEntitlements(
1459
+ activeEntitlementSummary.customer,
1460
+ entitlements.data,
1461
+ false,
1462
+ this.getSyncTimestamp(event, refetched)
1463
+ );
1464
+ break;
1465
+ }
1466
+ default:
1467
+ throw new Error("Unhandled webhook event");
1468
+ }
1469
+ }
1470
+ getSyncTimestamp(event, refetched) {
1471
+ return refetched ? (/* @__PURE__ */ new Date()).toISOString() : new Date(event.created * 1e3).toISOString();
1472
+ }
1473
+ shouldRefetchEntity(entity) {
1474
+ return this.config.revalidateObjectsViaStripeApi?.includes(entity.object);
1475
+ }
1476
+ async fetchOrUseWebhookData(entity, fetchFn, entityInFinalState) {
1477
+ if (!entity.id) return { entity, refetched: false };
1478
+ if (entityInFinalState && entityInFinalState(entity)) return { entity, refetched: false };
1479
+ if (this.shouldRefetchEntity(entity)) {
1480
+ const fetchedEntity = await fetchFn(entity.id);
1481
+ return { entity: fetchedEntity, refetched: true };
1482
+ }
1483
+ return { entity, refetched: false };
1484
+ }
1485
+ async syncSingleEntity(stripeId) {
1486
+ if (stripeId.startsWith("cus_")) {
1487
+ return this.stripe.customers.retrieve(stripeId).then((it) => {
1488
+ if (!it || it.deleted) return;
1489
+ return this.upsertCustomers([it]);
1490
+ });
1491
+ } else if (stripeId.startsWith("in_")) {
1492
+ return this.stripe.invoices.retrieve(stripeId).then((it) => this.upsertInvoices([it]));
1493
+ } else if (stripeId.startsWith("price_")) {
1494
+ return this.stripe.prices.retrieve(stripeId).then((it) => this.upsertPrices([it]));
1495
+ } else if (stripeId.startsWith("prod_")) {
1496
+ return this.stripe.products.retrieve(stripeId).then((it) => this.upsertProducts([it]));
1497
+ } else if (stripeId.startsWith("sub_")) {
1498
+ return this.stripe.subscriptions.retrieve(stripeId).then((it) => this.upsertSubscriptions([it]));
1499
+ } else if (stripeId.startsWith("seti_")) {
1500
+ return this.stripe.setupIntents.retrieve(stripeId).then((it) => this.upsertSetupIntents([it]));
1501
+ } else if (stripeId.startsWith("pm_")) {
1502
+ return this.stripe.paymentMethods.retrieve(stripeId).then((it) => this.upsertPaymentMethods([it]));
1503
+ } else if (stripeId.startsWith("dp_") || stripeId.startsWith("du_")) {
1504
+ return this.stripe.disputes.retrieve(stripeId).then((it) => this.upsertDisputes([it]));
1505
+ } else if (stripeId.startsWith("ch_")) {
1506
+ return this.stripe.charges.retrieve(stripeId).then((it) => this.upsertCharges([it], true));
1507
+ } else if (stripeId.startsWith("pi_")) {
1508
+ return this.stripe.paymentIntents.retrieve(stripeId).then((it) => this.upsertPaymentIntents([it]));
1509
+ } else if (stripeId.startsWith("txi_")) {
1510
+ return this.stripe.taxIds.retrieve(stripeId).then((it) => this.upsertTaxIds([it]));
1511
+ } else if (stripeId.startsWith("cn_")) {
1512
+ return this.stripe.creditNotes.retrieve(stripeId).then((it) => this.upsertCreditNotes([it]));
1513
+ } else if (stripeId.startsWith("issfr_")) {
1514
+ return this.stripe.radar.earlyFraudWarnings.retrieve(stripeId).then((it) => this.upsertEarlyFraudWarning([it]));
1515
+ } else if (stripeId.startsWith("prv_")) {
1516
+ return this.stripe.reviews.retrieve(stripeId).then((it) => this.upsertReviews([it]));
1517
+ } else if (stripeId.startsWith("re_")) {
1518
+ return this.stripe.refunds.retrieve(stripeId).then((it) => this.upsertRefunds([it]));
1519
+ } else if (stripeId.startsWith("feat_")) {
1520
+ return this.stripe.entitlements.features.retrieve(stripeId).then((it) => this.upsertFeatures([it]));
1521
+ } else if (stripeId.startsWith("cs_")) {
1522
+ return this.stripe.checkout.sessions.retrieve(stripeId).then((it) => this.upsertCheckoutSessions([it]));
1523
+ }
1524
+ }
1525
+ async syncBackfill(params) {
1526
+ const { object } = params ?? {};
1527
+ let products, prices, customers, checkoutSessions, subscriptions, subscriptionSchedules, invoices, setupIntents, paymentMethods, disputes, charges, paymentIntents, plans, taxIds, creditNotes, earlyFraudWarnings, refunds;
1528
+ switch (object) {
1529
+ case "all":
1530
+ products = await this.syncProducts(params);
1531
+ prices = await this.syncPrices(params);
1532
+ plans = await this.syncPlans(params);
1533
+ customers = await this.syncCustomers(params);
1534
+ subscriptions = await this.syncSubscriptions(params);
1535
+ subscriptionSchedules = await this.syncSubscriptionSchedules(params);
1536
+ invoices = await this.syncInvoices(params);
1537
+ charges = await this.syncCharges(params);
1538
+ setupIntents = await this.syncSetupIntents(params);
1539
+ paymentMethods = await this.syncPaymentMethods(params);
1540
+ paymentIntents = await this.syncPaymentIntents(params);
1541
+ taxIds = await this.syncTaxIds(params);
1542
+ creditNotes = await this.syncCreditNotes(params);
1543
+ disputes = await this.syncDisputes(params);
1544
+ earlyFraudWarnings = await this.syncEarlyFraudWarnings(params);
1545
+ refunds = await this.syncRefunds(params);
1546
+ checkoutSessions = await this.syncCheckoutSessions(params);
1547
+ break;
1548
+ case "customer":
1549
+ customers = await this.syncCustomers(params);
1550
+ break;
1551
+ case "invoice":
1552
+ invoices = await this.syncInvoices(params);
1553
+ break;
1554
+ case "price":
1555
+ prices = await this.syncPrices(params);
1556
+ break;
1557
+ case "product":
1558
+ products = await this.syncProducts(params);
1559
+ break;
1560
+ case "subscription":
1561
+ subscriptions = await this.syncSubscriptions(params);
1562
+ break;
1563
+ case "subscription_schedules":
1564
+ subscriptionSchedules = await this.syncSubscriptionSchedules(params);
1565
+ break;
1566
+ case "setup_intent":
1567
+ setupIntents = await this.syncSetupIntents(params);
1568
+ break;
1569
+ case "payment_method":
1570
+ paymentMethods = await this.syncPaymentMethods(params);
1571
+ break;
1572
+ case "dispute":
1573
+ disputes = await this.syncDisputes(params);
1574
+ break;
1575
+ case "charge":
1576
+ charges = await this.syncCharges(params);
1577
+ break;
1578
+ case "payment_intent":
1579
+ paymentIntents = await this.syncPaymentIntents(params);
1580
+ case "plan":
1581
+ plans = await this.syncPlans(params);
1582
+ break;
1583
+ case "tax_id":
1584
+ taxIds = await this.syncTaxIds(params);
1585
+ break;
1586
+ case "credit_note":
1587
+ creditNotes = await this.syncCreditNotes(params);
1588
+ break;
1589
+ case "early_fraud_warning":
1590
+ earlyFraudWarnings = await this.syncEarlyFraudWarnings(params);
1591
+ break;
1592
+ case "refund":
1593
+ refunds = await this.syncRefunds(params);
1594
+ break;
1595
+ case "checkout_sessions":
1596
+ checkoutSessions = await this.syncCheckoutSessions(params);
1597
+ break;
1598
+ default:
1599
+ break;
1600
+ }
1601
+ return {
1602
+ products,
1603
+ prices,
1604
+ customers,
1605
+ checkoutSessions,
1606
+ subscriptions,
1607
+ subscriptionSchedules,
1608
+ invoices,
1609
+ setupIntents,
1610
+ paymentMethods,
1611
+ disputes,
1612
+ charges,
1613
+ paymentIntents,
1614
+ plans,
1615
+ taxIds,
1616
+ creditNotes,
1617
+ earlyFraudWarnings,
1618
+ refunds
1619
+ };
1620
+ }
1621
+ async syncProducts(syncParams) {
1622
+ this.config.logger?.info("Syncing products");
1623
+ const params = { limit: 100 };
1624
+ if (syncParams?.created) params.created = syncParams?.created;
1625
+ return this.fetchAndUpsert(
1626
+ () => this.stripe.products.list(params),
1627
+ (products) => this.upsertProducts(products)
1628
+ );
1629
+ }
1630
+ async syncPrices(syncParams) {
1631
+ this.config.logger?.info("Syncing prices");
1632
+ const params = { limit: 100 };
1633
+ if (syncParams?.created) params.created = syncParams?.created;
1634
+ return this.fetchAndUpsert(
1635
+ () => this.stripe.prices.list(params),
1636
+ (prices) => this.upsertPrices(prices, syncParams?.backfillRelatedEntities)
1637
+ );
1638
+ }
1639
+ async syncPlans(syncParams) {
1640
+ this.config.logger?.info("Syncing plans");
1641
+ const params = { limit: 100 };
1642
+ if (syncParams?.created) params.created = syncParams?.created;
1643
+ return this.fetchAndUpsert(
1644
+ () => this.stripe.plans.list(params),
1645
+ (plans) => this.upsertPlans(plans, syncParams?.backfillRelatedEntities)
1646
+ );
1647
+ }
1648
+ async syncCustomers(syncParams) {
1649
+ this.config.logger?.info("Syncing customers");
1650
+ const params = { limit: 100 };
1651
+ if (syncParams?.created) params.created = syncParams.created;
1652
+ return this.fetchAndUpsert(
1653
+ () => this.stripe.customers.list(params),
1654
+ // @ts-expect-error
1655
+ (items) => this.upsertCustomers(items)
1656
+ );
1657
+ }
1658
+ async syncSubscriptions(syncParams) {
1659
+ this.config.logger?.info("Syncing subscriptions");
1660
+ const params = { status: "all", limit: 100 };
1661
+ if (syncParams?.created) params.created = syncParams.created;
1662
+ return this.fetchAndUpsert(
1663
+ () => this.stripe.subscriptions.list(params),
1664
+ (items) => this.upsertSubscriptions(items, syncParams?.backfillRelatedEntities)
1665
+ );
1666
+ }
1667
+ async syncSubscriptionSchedules(syncParams) {
1668
+ this.config.logger?.info("Syncing subscription schedules");
1669
+ const params = { limit: 100 };
1670
+ if (syncParams?.created) params.created = syncParams.created;
1671
+ return this.fetchAndUpsert(
1672
+ () => this.stripe.subscriptionSchedules.list(params),
1673
+ (items) => this.upsertSubscriptionSchedules(items, syncParams?.backfillRelatedEntities)
1674
+ );
1675
+ }
1676
+ async syncInvoices(syncParams) {
1677
+ this.config.logger?.info("Syncing invoices");
1678
+ const params = { limit: 100 };
1679
+ if (syncParams?.created) params.created = syncParams.created;
1680
+ return this.fetchAndUpsert(
1681
+ () => this.stripe.invoices.list(params),
1682
+ (items) => this.upsertInvoices(items, syncParams?.backfillRelatedEntities)
1683
+ );
1684
+ }
1685
+ async syncCharges(syncParams) {
1686
+ this.config.logger?.info("Syncing charges");
1687
+ const params = { limit: 100 };
1688
+ if (syncParams?.created) params.created = syncParams.created;
1689
+ return this.fetchAndUpsert(
1690
+ () => this.stripe.charges.list(params),
1691
+ (items) => this.upsertCharges(items, syncParams?.backfillRelatedEntities)
1692
+ );
1693
+ }
1694
+ async syncSetupIntents(syncParams) {
1695
+ this.config.logger?.info("Syncing setup_intents");
1696
+ const params = { limit: 100 };
1697
+ if (syncParams?.created) params.created = syncParams.created;
1698
+ return this.fetchAndUpsert(
1699
+ () => this.stripe.setupIntents.list(params),
1700
+ (items) => this.upsertSetupIntents(items, syncParams?.backfillRelatedEntities)
1701
+ );
1702
+ }
1703
+ async syncPaymentIntents(syncParams) {
1704
+ this.config.logger?.info("Syncing payment_intents");
1705
+ const params = { limit: 100 };
1706
+ if (syncParams?.created) params.created = syncParams.created;
1707
+ return this.fetchAndUpsert(
1708
+ () => this.stripe.paymentIntents.list(params),
1709
+ (items) => this.upsertPaymentIntents(items, syncParams?.backfillRelatedEntities)
1710
+ );
1711
+ }
1712
+ async syncTaxIds(syncParams) {
1713
+ this.config.logger?.info("Syncing tax_ids");
1714
+ const params = { limit: 100 };
1715
+ return this.fetchAndUpsert(
1716
+ () => this.stripe.taxIds.list(params),
1717
+ (items) => this.upsertTaxIds(items, syncParams?.backfillRelatedEntities)
1718
+ );
1719
+ }
1720
+ async syncPaymentMethods(syncParams) {
1721
+ this.config.logger?.info("Syncing payment method");
1722
+ const prepared = (0, import_yesql2.pg)(
1723
+ `select id from "${this.config.schema}"."customers" WHERE deleted <> true;`
1724
+ )([]);
1725
+ const customerIds = await this.postgresClient.query(prepared.text, prepared.values).then(({ rows }) => rows.map((it) => it.id));
1726
+ this.config.logger?.info(`Getting payment methods for ${customerIds.length} customers`);
1727
+ let synced = 0;
1728
+ for (const customerIdChunk of chunkArray(customerIds, 10)) {
1729
+ await Promise.all(
1730
+ customerIdChunk.map(async (customerId) => {
1731
+ const syncResult = await this.fetchAndUpsert(
1732
+ () => this.stripe.paymentMethods.list({
1733
+ limit: 100,
1734
+ customer: customerId
1735
+ }),
1736
+ (items) => this.upsertPaymentMethods(items, syncParams?.backfillRelatedEntities)
1737
+ );
1738
+ synced += syncResult.synced;
1739
+ })
1740
+ );
1741
+ }
1742
+ return { synced };
1743
+ }
1744
+ async syncDisputes(syncParams) {
1745
+ const params = { limit: 100 };
1746
+ if (syncParams?.created) params.created = syncParams.created;
1747
+ return this.fetchAndUpsert(
1748
+ () => this.stripe.disputes.list(params),
1749
+ (items) => this.upsertDisputes(items, syncParams?.backfillRelatedEntities)
1750
+ );
1751
+ }
1752
+ async syncEarlyFraudWarnings(syncParams) {
1753
+ this.config.logger?.info("Syncing early fraud warnings");
1754
+ const params = { limit: 100 };
1755
+ if (syncParams?.created) params.created = syncParams.created;
1756
+ return this.fetchAndUpsert(
1757
+ () => this.stripe.radar.earlyFraudWarnings.list(params),
1758
+ (items) => this.upsertEarlyFraudWarning(items, syncParams?.backfillRelatedEntities)
1759
+ );
1760
+ }
1761
+ async syncRefunds(syncParams) {
1762
+ this.config.logger?.info("Syncing refunds");
1763
+ const params = { limit: 100 };
1764
+ if (syncParams?.created) params.created = syncParams.created;
1765
+ return this.fetchAndUpsert(
1766
+ () => this.stripe.refunds.list(params),
1767
+ (items) => this.upsertRefunds(items, syncParams?.backfillRelatedEntities)
1768
+ );
1769
+ }
1770
+ async syncCreditNotes(syncParams) {
1771
+ this.config.logger?.info("Syncing credit notes");
1772
+ const params = { limit: 100 };
1773
+ if (syncParams?.created) params.created = syncParams?.created;
1774
+ return this.fetchAndUpsert(
1775
+ () => this.stripe.creditNotes.list(params),
1776
+ (creditNotes) => this.upsertCreditNotes(creditNotes)
1777
+ );
1778
+ }
1779
+ async syncFeatures(syncParams) {
1780
+ this.config.logger?.info("Syncing features");
1781
+ const params = { limit: 100, ...syncParams?.pagination };
1782
+ return this.fetchAndUpsert(
1783
+ () => this.stripe.entitlements.features.list(params),
1784
+ (features) => this.upsertFeatures(features)
1785
+ );
1786
+ }
1787
+ async syncEntitlements(customerId, syncParams) {
1788
+ this.config.logger?.info("Syncing entitlements");
1789
+ const params = {
1790
+ customer: customerId,
1791
+ limit: 100,
1792
+ ...syncParams?.pagination
1793
+ };
1794
+ return this.fetchAndUpsert(
1795
+ () => this.stripe.entitlements.activeEntitlements.list(params),
1796
+ (entitlements) => this.upsertActiveEntitlements(customerId, entitlements)
1797
+ );
1798
+ }
1799
+ async syncCheckoutSessions(syncParams) {
1800
+ this.config.logger?.info("Syncing checkout sessions");
1801
+ const params = {
1802
+ limit: 100
1803
+ };
1804
+ if (syncParams?.created) params.created = syncParams.created;
1805
+ return this.fetchAndUpsert(
1806
+ () => this.stripe.checkout.sessions.list(params),
1807
+ (items) => this.upsertCheckoutSessions(items, syncParams?.backfillRelatedEntities)
1808
+ );
1809
+ }
1810
+ async fetchAndUpsert(fetch, upsert) {
1811
+ const items = [];
1812
+ this.config.logger?.info("Fetching items to sync from Stripe");
1813
+ for await (const item of fetch()) {
1814
+ items.push(item);
1815
+ }
1816
+ if (!items.length) return { synced: 0 };
1817
+ this.config.logger?.info(`Upserting ${items.length} items`);
1818
+ const chunkSize = 250;
1819
+ for (let i = 0; i < items.length; i += chunkSize) {
1820
+ const chunk = items.slice(i, i + chunkSize);
1821
+ await upsert(chunk);
1822
+ }
1823
+ this.config.logger?.info("Upserted items");
1824
+ return { synced: items.length };
1825
+ }
1826
+ async upsertCharges(charges, backfillRelatedEntities, syncTimestamp) {
1827
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1828
+ await Promise.all([
1829
+ this.backfillCustomers(getUniqueIds(charges, "customer")),
1830
+ this.backfillInvoices(getUniqueIds(charges, "invoice"))
1831
+ ]);
1832
+ }
1833
+ await this.expandEntity(
1834
+ charges,
1835
+ "refunds",
1836
+ (id) => this.stripe.refunds.list({ charge: id, limit: 100 })
1837
+ );
1838
+ return this.postgresClient.upsertManyWithTimestampProtection(
1839
+ charges,
1840
+ "charges",
1841
+ chargeSchema,
1842
+ syncTimestamp
1843
+ );
1844
+ }
1845
+ async backfillCharges(chargeIds) {
1846
+ const missingChargeIds = await this.postgresClient.findMissingEntries("charges", chargeIds);
1847
+ await this.fetchMissingEntities(
1848
+ missingChargeIds,
1849
+ (id) => this.stripe.charges.retrieve(id)
1850
+ ).then((charges) => this.upsertCharges(charges));
1851
+ }
1852
+ async backfillPaymentIntents(paymentIntentIds) {
1853
+ const missingIds = await this.postgresClient.findMissingEntries(
1854
+ "payment_intents",
1855
+ paymentIntentIds
1856
+ );
1857
+ await this.fetchMissingEntities(
1858
+ missingIds,
1859
+ (id) => this.stripe.paymentIntents.retrieve(id)
1860
+ ).then((paymentIntents) => this.upsertPaymentIntents(paymentIntents));
1861
+ }
1862
+ async upsertCreditNotes(creditNotes, backfillRelatedEntities, syncTimestamp) {
1863
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1864
+ await Promise.all([
1865
+ this.backfillCustomers(getUniqueIds(creditNotes, "customer")),
1866
+ this.backfillInvoices(getUniqueIds(creditNotes, "invoice"))
1867
+ ]);
1868
+ }
1869
+ await this.expandEntity(
1870
+ creditNotes,
1871
+ "lines",
1872
+ (id) => this.stripe.creditNotes.listLineItems(id, { limit: 100 })
1873
+ );
1874
+ return this.postgresClient.upsertManyWithTimestampProtection(
1875
+ creditNotes,
1876
+ "credit_notes",
1877
+ creditNoteSchema,
1878
+ syncTimestamp
1879
+ );
1880
+ }
1881
+ async upsertCheckoutSessions(checkoutSessions, backfillRelatedEntities, syncTimestamp) {
1882
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1883
+ await Promise.all([
1884
+ this.backfillCustomers(getUniqueIds(checkoutSessions, "customer")),
1885
+ this.backfillSubscriptions(getUniqueIds(checkoutSessions, "subscription")),
1886
+ this.backfillPaymentIntents(getUniqueIds(checkoutSessions, "payment_intent")),
1887
+ this.backfillInvoices(getUniqueIds(checkoutSessions, "invoice"))
1888
+ ]);
1889
+ }
1890
+ const rows = await this.postgresClient.upsertManyWithTimestampProtection(
1891
+ checkoutSessions,
1892
+ "checkout_sessions",
1893
+ checkoutSessionSchema,
1894
+ syncTimestamp
1895
+ );
1896
+ await this.fillCheckoutSessionsLineItems(
1897
+ checkoutSessions.map((cs) => cs.id),
1898
+ syncTimestamp
1899
+ );
1900
+ return rows;
1901
+ }
1902
+ async upsertEarlyFraudWarning(earlyFraudWarnings, backfillRelatedEntities, syncTimestamp) {
1903
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1904
+ await Promise.all([
1905
+ this.backfillPaymentIntents(getUniqueIds(earlyFraudWarnings, "payment_intent")),
1906
+ this.backfillCharges(getUniqueIds(earlyFraudWarnings, "charge"))
1907
+ ]);
1908
+ }
1909
+ return this.postgresClient.upsertManyWithTimestampProtection(
1910
+ earlyFraudWarnings,
1911
+ "early_fraud_warnings",
1912
+ earlyFraudWarningSchema,
1913
+ syncTimestamp
1914
+ );
1915
+ }
1916
+ async upsertRefunds(refunds, backfillRelatedEntities, syncTimestamp) {
1917
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1918
+ await Promise.all([
1919
+ this.backfillPaymentIntents(getUniqueIds(refunds, "payment_intent")),
1920
+ this.backfillCharges(getUniqueIds(refunds, "charge"))
1921
+ ]);
1922
+ }
1923
+ return this.postgresClient.upsertManyWithTimestampProtection(
1924
+ refunds,
1925
+ "refunds",
1926
+ refundSchema,
1927
+ syncTimestamp
1928
+ );
1929
+ }
1930
+ async upsertReviews(reviews, backfillRelatedEntities, syncTimestamp) {
1931
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1932
+ await Promise.all([
1933
+ this.backfillPaymentIntents(getUniqueIds(reviews, "payment_intent")),
1934
+ this.backfillCharges(getUniqueIds(reviews, "charge"))
1935
+ ]);
1936
+ }
1937
+ return this.postgresClient.upsertManyWithTimestampProtection(
1938
+ reviews,
1939
+ "reviews",
1940
+ reviewSchema,
1941
+ syncTimestamp
1942
+ );
1943
+ }
1944
+ async upsertCustomers(customers, syncTimestamp) {
1945
+ const deletedCustomers = customers.filter((customer) => customer.deleted);
1946
+ const nonDeletedCustomers = customers.filter((customer) => !customer.deleted);
1947
+ await this.postgresClient.upsertManyWithTimestampProtection(
1948
+ nonDeletedCustomers,
1949
+ "customers",
1950
+ customerSchema,
1951
+ syncTimestamp
1952
+ );
1953
+ await this.postgresClient.upsertManyWithTimestampProtection(
1954
+ deletedCustomers,
1955
+ "customers",
1956
+ customerDeletedSchema,
1957
+ syncTimestamp
1958
+ );
1959
+ return customers;
1960
+ }
1961
+ async backfillCustomers(customerIds) {
1962
+ const missingIds = await this.postgresClient.findMissingEntries("customers", customerIds);
1963
+ await this.fetchMissingEntities(missingIds, (id) => this.stripe.customers.retrieve(id)).then((entries) => this.upsertCustomers(entries)).catch((err) => {
1964
+ this.config.logger?.error(err, "Failed to backfill");
1965
+ throw err;
1966
+ });
1967
+ }
1968
+ async upsertDisputes(disputes, backfillRelatedEntities, syncTimestamp) {
1969
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1970
+ await this.backfillCharges(getUniqueIds(disputes, "charge"));
1971
+ }
1972
+ return this.postgresClient.upsertManyWithTimestampProtection(
1973
+ disputes,
1974
+ "disputes",
1975
+ disputeSchema,
1976
+ syncTimestamp
1977
+ );
1978
+ }
1979
+ async upsertInvoices(invoices, backfillRelatedEntities, syncTimestamp) {
1980
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
1981
+ await Promise.all([
1982
+ this.backfillCustomers(getUniqueIds(invoices, "customer")),
1983
+ this.backfillSubscriptions(getUniqueIds(invoices, "subscription"))
1984
+ ]);
1985
+ }
1986
+ await this.expandEntity(
1987
+ invoices,
1988
+ "lines",
1989
+ (id) => this.stripe.invoices.listLineItems(id, { limit: 100 })
1990
+ );
1991
+ return this.postgresClient.upsertManyWithTimestampProtection(
1992
+ invoices,
1993
+ "invoices",
1994
+ invoiceSchema,
1995
+ syncTimestamp
1996
+ );
1997
+ }
1998
+ backfillInvoices = async (invoiceIds) => {
1999
+ const missingIds = await this.postgresClient.findMissingEntries("invoices", invoiceIds);
2000
+ await this.fetchMissingEntities(missingIds, (id) => this.stripe.invoices.retrieve(id)).then(
2001
+ (entries) => this.upsertInvoices(entries)
2002
+ );
2003
+ };
2004
+ backfillPrices = async (priceIds) => {
2005
+ const missingIds = await this.postgresClient.findMissingEntries("prices", priceIds);
2006
+ await this.fetchMissingEntities(missingIds, (id) => this.stripe.prices.retrieve(id)).then(
2007
+ (entries) => this.upsertPrices(entries)
2008
+ );
2009
+ };
2010
+ async upsertPlans(plans, backfillRelatedEntities, syncTimestamp) {
2011
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2012
+ await this.backfillProducts(getUniqueIds(plans, "product"));
2013
+ }
2014
+ return this.postgresClient.upsertManyWithTimestampProtection(
2015
+ plans,
2016
+ "plans",
2017
+ planSchema,
2018
+ syncTimestamp
2019
+ );
2020
+ }
2021
+ async deletePlan(id) {
2022
+ return this.postgresClient.delete("plans", id);
2023
+ }
2024
+ async upsertPrices(prices, backfillRelatedEntities, syncTimestamp) {
2025
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2026
+ await this.backfillProducts(getUniqueIds(prices, "product"));
2027
+ }
2028
+ return this.postgresClient.upsertManyWithTimestampProtection(
2029
+ prices,
2030
+ "prices",
2031
+ priceSchema,
2032
+ syncTimestamp
2033
+ );
2034
+ }
2035
+ async deletePrice(id) {
2036
+ return this.postgresClient.delete("prices", id);
2037
+ }
2038
+ async upsertProducts(products, syncTimestamp) {
2039
+ return this.postgresClient.upsertManyWithTimestampProtection(
2040
+ products,
2041
+ "products",
2042
+ productSchema,
2043
+ syncTimestamp
2044
+ );
2045
+ }
2046
+ async deleteProduct(id) {
2047
+ return this.postgresClient.delete("products", id);
2048
+ }
2049
+ async backfillProducts(productIds) {
2050
+ const missingProductIds = await this.postgresClient.findMissingEntries("products", productIds);
2051
+ await this.fetchMissingEntities(
2052
+ missingProductIds,
2053
+ (id) => this.stripe.products.retrieve(id)
2054
+ ).then((products) => this.upsertProducts(products));
2055
+ }
2056
+ async upsertPaymentIntents(paymentIntents, backfillRelatedEntities, syncTimestamp) {
2057
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2058
+ await Promise.all([
2059
+ this.backfillCustomers(getUniqueIds(paymentIntents, "customer")),
2060
+ this.backfillInvoices(getUniqueIds(paymentIntents, "invoice"))
2061
+ ]);
2062
+ }
2063
+ return this.postgresClient.upsertManyWithTimestampProtection(
2064
+ paymentIntents,
2065
+ "payment_intents",
2066
+ paymentIntentSchema,
2067
+ syncTimestamp
2068
+ );
2069
+ }
2070
+ async upsertPaymentMethods(paymentMethods, backfillRelatedEntities = false, syncTimestamp) {
2071
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2072
+ await this.backfillCustomers(getUniqueIds(paymentMethods, "customer"));
2073
+ }
2074
+ return this.postgresClient.upsertManyWithTimestampProtection(
2075
+ paymentMethods,
2076
+ "payment_methods",
2077
+ paymentMethodsSchema,
2078
+ syncTimestamp
2079
+ );
2080
+ }
2081
+ async upsertSetupIntents(setupIntents, backfillRelatedEntities, syncTimestamp) {
2082
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2083
+ await this.backfillCustomers(getUniqueIds(setupIntents, "customer"));
2084
+ }
2085
+ return this.postgresClient.upsertManyWithTimestampProtection(
2086
+ setupIntents,
2087
+ "setup_intents",
2088
+ setupIntentsSchema,
2089
+ syncTimestamp
2090
+ );
2091
+ }
2092
+ async upsertTaxIds(taxIds, backfillRelatedEntities, syncTimestamp) {
2093
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2094
+ await this.backfillCustomers(getUniqueIds(taxIds, "customer"));
2095
+ }
2096
+ return this.postgresClient.upsertManyWithTimestampProtection(
2097
+ taxIds,
2098
+ "tax_ids",
2099
+ taxIdSchema,
2100
+ syncTimestamp
2101
+ );
2102
+ }
2103
+ async deleteTaxId(id) {
2104
+ return this.postgresClient.delete("tax_ids", id);
2105
+ }
2106
+ async upsertSubscriptionItems(subscriptionItems, syncTimestamp) {
2107
+ const modifiedSubscriptionItems = subscriptionItems.map((subscriptionItem) => {
2108
+ const priceId = subscriptionItem.price.id.toString();
2109
+ const deleted = subscriptionItem.deleted;
2110
+ const quantity = subscriptionItem.quantity;
2111
+ return {
2112
+ ...subscriptionItem,
2113
+ price: priceId,
2114
+ deleted: deleted ?? false,
2115
+ quantity: quantity ?? null
2116
+ };
2117
+ });
2118
+ await this.postgresClient.upsertManyWithTimestampProtection(
2119
+ modifiedSubscriptionItems,
2120
+ "subscription_items",
2121
+ subscriptionItemSchema,
2122
+ syncTimestamp
2123
+ );
2124
+ }
2125
+ async fillCheckoutSessionsLineItems(checkoutSessionIds, syncTimestamp) {
2126
+ for (const checkoutSessionId of checkoutSessionIds) {
2127
+ const lineItemResponses = [];
2128
+ for await (const lineItem of this.stripe.checkout.sessions.listLineItems(checkoutSessionId, {
2129
+ limit: 100
2130
+ })) {
2131
+ lineItemResponses.push(lineItem);
2132
+ }
2133
+ await this.upsertCheckoutSessionLineItems(lineItemResponses, checkoutSessionId, syncTimestamp);
2134
+ }
2135
+ }
2136
+ async upsertCheckoutSessionLineItems(lineItems, checkoutSessionId, syncTimestamp) {
2137
+ await this.backfillPrices(
2138
+ lineItems.map((lineItem) => lineItem.price?.id?.toString() ?? void 0).filter((id) => id !== void 0)
2139
+ );
2140
+ const modifiedLineItems = lineItems.map((lineItem) => {
2141
+ const priceId = typeof lineItem.price === "object" && lineItem.price?.id ? lineItem.price.id.toString() : lineItem.price?.toString() || null;
2142
+ return {
2143
+ ...lineItem,
2144
+ price: priceId,
2145
+ checkout_session: checkoutSessionId
2146
+ };
2147
+ });
2148
+ await this.postgresClient.upsertManyWithTimestampProtection(
2149
+ modifiedLineItems,
2150
+ "checkout_session_line_items",
2151
+ checkoutSessionLineItemSchema,
2152
+ syncTimestamp
2153
+ );
2154
+ }
2155
+ async markDeletedSubscriptionItems(subscriptionId, currentSubItemIds) {
2156
+ let prepared = (0, import_yesql2.pg)(`
2157
+ select id from "${this.config.schema}"."subscription_items"
2158
+ where subscription = :subscriptionId and deleted = false;
2159
+ `)({ subscriptionId });
2160
+ const { rows } = await this.postgresClient.query(prepared.text, prepared.values);
2161
+ const deletedIds = rows.filter(
2162
+ ({ id }) => currentSubItemIds.includes(id) === false
2163
+ );
2164
+ if (deletedIds.length > 0) {
2165
+ const ids = deletedIds.map(({ id }) => id);
2166
+ prepared = (0, import_yesql2.pg)(`
2167
+ update "${this.config.schema}"."subscription_items"
2168
+ set deleted = true where id=any(:ids::text[]);
2169
+ `)({ ids });
2170
+ const { rowCount } = await await this.postgresClient.query(prepared.text, prepared.values);
2171
+ return { rowCount: rowCount || 0 };
2172
+ } else {
2173
+ return { rowCount: 0 };
2174
+ }
2175
+ }
2176
+ async upsertSubscriptionSchedules(subscriptionSchedules, backfillRelatedEntities, syncTimestamp) {
2177
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2178
+ const customerIds = getUniqueIds(subscriptionSchedules, "customer");
2179
+ await this.backfillCustomers(customerIds);
2180
+ }
2181
+ const rows = await this.postgresClient.upsertManyWithTimestampProtection(
2182
+ subscriptionSchedules,
2183
+ "subscription_schedules",
2184
+ subscriptionScheduleSchema,
2185
+ syncTimestamp
2186
+ );
2187
+ return rows;
2188
+ }
2189
+ async upsertSubscriptions(subscriptions, backfillRelatedEntities, syncTimestamp) {
2190
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2191
+ const customerIds = getUniqueIds(subscriptions, "customer");
2192
+ await this.backfillCustomers(customerIds);
2193
+ }
2194
+ await this.expandEntity(
2195
+ subscriptions,
2196
+ "items",
2197
+ (id) => this.stripe.subscriptionItems.list({ subscription: id, limit: 100 })
2198
+ );
2199
+ const rows = await this.postgresClient.upsertManyWithTimestampProtection(
2200
+ subscriptions,
2201
+ "subscriptions",
2202
+ subscriptionSchema,
2203
+ syncTimestamp
2204
+ );
2205
+ const allSubscriptionItems = subscriptions.flatMap((subscription) => subscription.items.data);
2206
+ await this.upsertSubscriptionItems(allSubscriptionItems, syncTimestamp);
2207
+ const markSubscriptionItemsDeleted = [];
2208
+ for (const subscription of subscriptions) {
2209
+ const subscriptionItems = subscription.items.data;
2210
+ const subItemIds = subscriptionItems.map((x) => x.id);
2211
+ markSubscriptionItemsDeleted.push(
2212
+ this.markDeletedSubscriptionItems(subscription.id, subItemIds)
2213
+ );
2214
+ }
2215
+ await Promise.all(markSubscriptionItemsDeleted);
2216
+ return rows;
2217
+ }
2218
+ async deleteRemovedActiveEntitlements(customerId, currentActiveEntitlementIds) {
2219
+ const prepared = (0, import_yesql2.pg)(`
2220
+ delete from "${this.config.schema}"."active_entitlements"
2221
+ where customer = :customerId and id <> ALL(:currentActiveEntitlementIds::text[]);
2222
+ `)({ customerId, currentActiveEntitlementIds });
2223
+ const { rowCount } = await this.postgresClient.query(prepared.text, prepared.values);
2224
+ return { rowCount: rowCount || 0 };
2225
+ }
2226
+ async upsertFeatures(features, syncTimestamp) {
2227
+ return this.postgresClient.upsertManyWithTimestampProtection(
2228
+ features,
2229
+ "features",
2230
+ featureSchema,
2231
+ syncTimestamp
2232
+ );
2233
+ }
2234
+ async backfillFeatures(featureIds) {
2235
+ const missingFeatureIds = await this.postgresClient.findMissingEntries("features", featureIds);
2236
+ await this.fetchMissingEntities(
2237
+ missingFeatureIds,
2238
+ (id) => this.stripe.entitlements.features.retrieve(id)
2239
+ ).then((features) => this.upsertFeatures(features)).catch((err) => {
2240
+ this.config.logger?.error(err, "Failed to backfill features");
2241
+ throw err;
2242
+ });
2243
+ }
2244
+ async upsertActiveEntitlements(customerId, activeEntitlements, backfillRelatedEntities, syncTimestamp) {
2245
+ if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {
2246
+ await Promise.all([
2247
+ this.backfillCustomers(getUniqueIds(activeEntitlements, "customer")),
2248
+ this.backfillFeatures(getUniqueIds(activeEntitlements, "feature"))
2249
+ ]);
2250
+ }
2251
+ const entitlements = activeEntitlements.map((entitlement) => ({
2252
+ id: entitlement.id,
2253
+ object: entitlement.object,
2254
+ feature: typeof entitlement.feature === "string" ? entitlement.feature : entitlement.feature.id,
2255
+ customer: customerId,
2256
+ livemode: entitlement.livemode,
2257
+ lookup_key: entitlement.lookup_key
2258
+ }));
2259
+ return this.postgresClient.upsertManyWithTimestampProtection(
2260
+ entitlements,
2261
+ "active_entitlements",
2262
+ activeEntitlementSchema,
2263
+ syncTimestamp
2264
+ );
2265
+ }
2266
+ // Managed Webhook CRUD methods
2267
+ async createManagedWebhook(baseUrl, params) {
2268
+ const uuid = (0, import_node_crypto.randomUUID)();
2269
+ const webhookUrl = `${baseUrl}/${uuid}`;
2270
+ const webhook = await this.stripe.webhookEndpoints.create({
2271
+ ...params,
2272
+ url: webhookUrl
2273
+ });
2274
+ const webhookWithUuid = { ...webhook, uuid };
2275
+ await this.upsertManagedWebhooks([webhookWithUuid]);
2276
+ return { webhook, uuid };
2277
+ }
2278
+ async getManagedWebhook(id) {
2279
+ const result = await this.postgresClient.query(
2280
+ `SELECT * FROM "${this.config.schema || DEFAULT_SCHEMA}"."managed_webhooks" WHERE id = $1`,
2281
+ [id]
2282
+ );
2283
+ return result.rows.length > 0 ? result.rows[0] : null;
2284
+ }
2285
+ async listManagedWebhooks() {
2286
+ const result = await this.postgresClient.query(
2287
+ `SELECT * FROM "${this.config.schema || DEFAULT_SCHEMA}"."managed_webhooks" ORDER BY created DESC`
2288
+ );
2289
+ return result.rows;
2290
+ }
2291
+ async updateManagedWebhook(id, params) {
2292
+ const webhook = await this.stripe.webhookEndpoints.update(id, params);
2293
+ const existing = await this.getManagedWebhook(id);
2294
+ const webhookWithUuid = { ...webhook, uuid: existing?.uuid || (0, import_node_crypto.randomUUID)() };
2295
+ await this.upsertManagedWebhooks([webhookWithUuid]);
2296
+ return webhook;
2297
+ }
2298
+ async deleteManagedWebhook(id) {
2299
+ await this.stripe.webhookEndpoints.del(id);
2300
+ return this.postgresClient.delete("managed_webhooks", id);
2301
+ }
2302
+ async upsertManagedWebhooks(webhooks, syncTimestamp) {
2303
+ return this.postgresClient.upsertManyWithTimestampProtection(
2304
+ webhooks,
2305
+ "managed_webhooks",
2306
+ managedWebhookSchema,
2307
+ syncTimestamp
2308
+ );
2309
+ }
2310
+ async backfillSubscriptions(subscriptionIds) {
2311
+ const missingSubscriptionIds = await this.postgresClient.findMissingEntries(
2312
+ "subscriptions",
2313
+ subscriptionIds
2314
+ );
2315
+ await this.fetchMissingEntities(
2316
+ missingSubscriptionIds,
2317
+ (id) => this.stripe.subscriptions.retrieve(id)
2318
+ ).then((subscriptions) => this.upsertSubscriptions(subscriptions));
2319
+ }
2320
+ backfillSubscriptionSchedules = async (subscriptionIds) => {
2321
+ const missingSubscriptionIds = await this.postgresClient.findMissingEntries(
2322
+ "subscription_schedules",
2323
+ subscriptionIds
2324
+ );
2325
+ await this.fetchMissingEntities(
2326
+ missingSubscriptionIds,
2327
+ (id) => this.stripe.subscriptionSchedules.retrieve(id)
2328
+ ).then((subscriptionSchedules) => this.upsertSubscriptionSchedules(subscriptionSchedules));
2329
+ };
2330
+ /**
2331
+ * Stripe only sends the first 10 entries by default, the option will actively fetch all entries.
2332
+ */
2333
+ async expandEntity(entities, property, listFn) {
2334
+ if (!this.config.autoExpandLists) return;
2335
+ for (const entity of entities) {
2336
+ if (entity[property]?.has_more) {
2337
+ const allData = [];
2338
+ for await (const fetchedEntity of listFn(entity.id)) {
2339
+ allData.push(fetchedEntity);
2340
+ }
2341
+ entity[property] = {
2342
+ ...entity[property],
2343
+ data: allData,
2344
+ has_more: false
2345
+ };
2346
+ }
2347
+ }
2348
+ }
2349
+ async fetchMissingEntities(ids, fetch) {
2350
+ if (!ids.length) return [];
2351
+ const entities = [];
2352
+ for (const id of ids) {
2353
+ const entity = await fetch(id);
2354
+ entities.push(entity);
2355
+ }
2356
+ return entities;
2357
+ }
2358
+ };
2359
+ function chunkArray(array, chunkSize) {
2360
+ const result = [];
2361
+ for (let i = 0; i < array.length; i += chunkSize) {
2362
+ result.push(array.slice(i, i + chunkSize));
2363
+ }
2364
+ return result;
2365
+ }
2366
+ // Annotate the CommonJS export names for ESM import in node:
2367
+ 0 && (module.exports = {
2368
+ PostgresClient,
2369
+ StripeAutoSync
2370
+ });