stripe-experiment-sync 1.0.13 → 1.0.15-beta.1766078819
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5J4LJ44K.js +400 -0
- package/dist/{chunk-5SS5ZEQF.js → chunk-7G2ZYKBP.js} +27 -10
- package/dist/{chunk-2GSABFXH.js → chunk-ENHIM76M.js} +806 -209
- package/dist/{chunk-RCU5ZXAX.js → chunk-TPFENIK3.js} +3 -1
- package/dist/cli/index.cjs +850 -250
- package/dist/cli/index.js +7 -6
- package/dist/cli/lib.cjs +847 -248
- package/dist/cli/lib.d.cts +2 -0
- package/dist/cli/lib.d.ts +2 -0
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +807 -208
- package/dist/index.d.cts +110 -3
- package/dist/index.d.ts +110 -3
- package/dist/index.js +2 -2
- package/dist/migrations/0059_sigma_subscription_item_change_events_v2_beta.sql +61 -0
- package/dist/migrations/0060_sigma_exchange_rates_from_usd.sql +38 -0
- package/dist/supabase/index.cjs +18 -33
- package/dist/supabase/index.d.cts +1 -5
- package/dist/supabase/index.d.ts +1 -5
- package/dist/supabase/index.js +2 -2
- package/package.json +3 -1
- package/dist/chunk-O2N4AEQS.js +0 -417
package/dist/index.cjs
CHANGED
|
@@ -46,7 +46,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
46
46
|
// package.json
|
|
47
47
|
var package_default = {
|
|
48
48
|
name: "stripe-experiment-sync",
|
|
49
|
-
version: "1.0.
|
|
49
|
+
version: "1.0.15-beta.1766078819",
|
|
50
50
|
private: false,
|
|
51
51
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
52
52
|
type: "module",
|
|
@@ -86,6 +86,7 @@ var package_default = {
|
|
|
86
86
|
dotenv: "^16.4.7",
|
|
87
87
|
express: "^4.18.2",
|
|
88
88
|
inquirer: "^12.3.0",
|
|
89
|
+
papaparse: "5.4.1",
|
|
89
90
|
pg: "^8.16.3",
|
|
90
91
|
"pg-node-migrations": "0.0.8",
|
|
91
92
|
stripe: "^17.7.0",
|
|
@@ -97,6 +98,7 @@ var package_default = {
|
|
|
97
98
|
"@types/express": "^4.17.21",
|
|
98
99
|
"@types/inquirer": "^9.0.7",
|
|
99
100
|
"@types/node": "^24.10.1",
|
|
101
|
+
"@types/papaparse": "5.3.16",
|
|
100
102
|
"@types/pg": "^8.15.5",
|
|
101
103
|
"@types/ws": "^8.5.13",
|
|
102
104
|
"@types/yesql": "^4.1.4",
|
|
@@ -126,14 +128,60 @@ var package_default = {
|
|
|
126
128
|
};
|
|
127
129
|
|
|
128
130
|
// src/stripeSync.ts
|
|
129
|
-
var
|
|
131
|
+
var import_stripe3 = __toESM(require("stripe"), 1);
|
|
130
132
|
var import_yesql2 = require("yesql");
|
|
131
133
|
|
|
132
134
|
// src/database/postgres.ts
|
|
133
135
|
var import_pg = __toESM(require("pg"), 1);
|
|
134
136
|
var import_yesql = require("yesql");
|
|
137
|
+
|
|
138
|
+
// src/database/QueryUtils.ts
|
|
139
|
+
var QueryUtils = class _QueryUtils {
|
|
140
|
+
constructor() {
|
|
141
|
+
}
|
|
142
|
+
static quoteIdent(name) {
|
|
143
|
+
return `"${name}"`;
|
|
144
|
+
}
|
|
145
|
+
static quotedList(names) {
|
|
146
|
+
return names.map(_QueryUtils.quoteIdent).join(", ");
|
|
147
|
+
}
|
|
148
|
+
static buildInsertParts(columns) {
|
|
149
|
+
const columnsSql = columns.map((c) => _QueryUtils.quoteIdent(c.column)).join(", ");
|
|
150
|
+
const valuesSql = columns.map((c, i) => {
|
|
151
|
+
const placeholder = `$${i + 1}`;
|
|
152
|
+
return `${placeholder}::${c.pgType}`;
|
|
153
|
+
}).join(", ");
|
|
154
|
+
const params = columns.map((c) => c.value);
|
|
155
|
+
return { columnsSql, valuesSql, params };
|
|
156
|
+
}
|
|
157
|
+
static buildRawJsonUpsertQuery(schema, table, columns, conflictTarget) {
|
|
158
|
+
const { columnsSql, valuesSql, params } = _QueryUtils.buildInsertParts(columns);
|
|
159
|
+
const conflictSql = _QueryUtils.quotedList(conflictTarget);
|
|
160
|
+
const tsParamIdx = columns.findIndex((c) => c.column === "_last_synced_at") + 1;
|
|
161
|
+
if (tsParamIdx <= 0) {
|
|
162
|
+
throw new Error("buildRawJsonUpsertQuery requires _last_synced_at column");
|
|
163
|
+
}
|
|
164
|
+
const sql3 = `
|
|
165
|
+
INSERT INTO ${_QueryUtils.quoteIdent(schema)}.${_QueryUtils.quoteIdent(table)} (${columnsSql})
|
|
166
|
+
VALUES (${valuesSql})
|
|
167
|
+
ON CONFLICT (${conflictSql})
|
|
168
|
+
DO UPDATE SET
|
|
169
|
+
"_raw_data" = EXCLUDED."_raw_data",
|
|
170
|
+
"_last_synced_at" = $${tsParamIdx},
|
|
171
|
+
"_account_id" = EXCLUDED."_account_id"
|
|
172
|
+
WHERE ${_QueryUtils.quoteIdent(table)}."_last_synced_at" IS NULL
|
|
173
|
+
OR ${_QueryUtils.quoteIdent(table)}."_last_synced_at" < $${tsParamIdx}
|
|
174
|
+
RETURNING *
|
|
175
|
+
`;
|
|
176
|
+
return { sql: sql3, params };
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/database/postgres.ts
|
|
135
181
|
var ORDERED_STRIPE_TABLES = [
|
|
182
|
+
"exchange_rates_from_usd",
|
|
136
183
|
"subscription_items",
|
|
184
|
+
"subscription_item_change_events_v2_beta",
|
|
137
185
|
"subscriptions",
|
|
138
186
|
"subscription_schedules",
|
|
139
187
|
"checkout_session_line_items",
|
|
@@ -203,7 +251,7 @@ var PostgresClient = class {
|
|
|
203
251
|
}
|
|
204
252
|
return results.flatMap((it) => it.rows);
|
|
205
253
|
}
|
|
206
|
-
async upsertManyWithTimestampProtection(entries, table, accountId, syncTimestamp) {
|
|
254
|
+
async upsertManyWithTimestampProtection(entries, table, accountId, syncTimestamp, upsertOptions) {
|
|
207
255
|
const timestamp = syncTimestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
208
256
|
if (!entries.length) return [];
|
|
209
257
|
const chunkSize = 5;
|
|
@@ -238,20 +286,33 @@ var PostgresClient = class {
|
|
|
238
286
|
const prepared = (0, import_yesql.pg)(upsertSql, { useNullForMissing: true })(cleansed);
|
|
239
287
|
queries.push(this.pool.query(prepared.text, prepared.values));
|
|
240
288
|
} else {
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
289
|
+
const conflictTarget = upsertOptions?.conflictTarget ?? ["id"];
|
|
290
|
+
const extraColumns = upsertOptions?.extraColumns ?? [];
|
|
291
|
+
if (!conflictTarget.length) {
|
|
292
|
+
throw new Error(`Invalid upsert config for ${table}: conflictTarget must be non-empty`);
|
|
293
|
+
}
|
|
294
|
+
const columns = [
|
|
295
|
+
{ column: "_raw_data", pgType: "jsonb", value: JSON.stringify(entry) },
|
|
296
|
+
...extraColumns.map((c) => ({
|
|
297
|
+
column: c.column,
|
|
298
|
+
pgType: c.pgType,
|
|
299
|
+
value: entry[c.entryKey]
|
|
300
|
+
})),
|
|
301
|
+
{ column: "_last_synced_at", pgType: "timestamptz", value: timestamp },
|
|
302
|
+
{ column: "_account_id", pgType: "text", value: accountId }
|
|
303
|
+
];
|
|
304
|
+
for (const c of columns) {
|
|
305
|
+
if (c.value === void 0) {
|
|
306
|
+
throw new Error(`Missing required value for ${table}.${c.column}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const { sql: upsertSql, params } = QueryUtils.buildRawJsonUpsertQuery(
|
|
310
|
+
this.config.schema,
|
|
311
|
+
table,
|
|
312
|
+
columns,
|
|
313
|
+
conflictTarget
|
|
314
|
+
);
|
|
315
|
+
queries.push(this.pool.query(upsertSql, params));
|
|
255
316
|
}
|
|
256
317
|
});
|
|
257
318
|
results.push(...await Promise.all(queries));
|
|
@@ -651,7 +712,12 @@ var PostgresClient = class {
|
|
|
651
712
|
} else {
|
|
652
713
|
await this.query(
|
|
653
714
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
654
|
-
SET cursor =
|
|
715
|
+
SET cursor = CASE
|
|
716
|
+
WHEN cursor IS NULL THEN $4
|
|
717
|
+
WHEN (cursor COLLATE "C") < ($4::text COLLATE "C") THEN $4
|
|
718
|
+
ELSE cursor
|
|
719
|
+
END,
|
|
720
|
+
updated_at = now()
|
|
655
721
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
656
722
|
[accountId, runStartedAt, object, cursor]
|
|
657
723
|
);
|
|
@@ -662,10 +728,17 @@ var PostgresClient = class {
|
|
|
662
728
|
* This considers completed, error, AND running runs to ensure recovery syncs
|
|
663
729
|
* don't re-process data that was already synced before a crash.
|
|
664
730
|
* A 'running' status with a cursor means the process was killed mid-sync.
|
|
731
|
+
*
|
|
732
|
+
* Handles two cursor formats:
|
|
733
|
+
* - Numeric: compared as bigint for correct ordering
|
|
734
|
+
* - Composite cursors: compared as strings with COLLATE "C"
|
|
665
735
|
*/
|
|
666
736
|
async getLastCompletedCursor(accountId, object) {
|
|
667
737
|
const result = await this.query(
|
|
668
|
-
`SELECT
|
|
738
|
+
`SELECT CASE
|
|
739
|
+
WHEN BOOL_OR(o.cursor !~ '^\\d+$') THEN MAX(o.cursor COLLATE "C")
|
|
740
|
+
ELSE MAX(CASE WHEN o.cursor ~ '^\\d+$' THEN o.cursor::bigint END)::text
|
|
741
|
+
END as cursor
|
|
669
742
|
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
670
743
|
WHERE o."_account_id" = $1
|
|
671
744
|
AND o.object = $2
|
|
@@ -950,6 +1023,269 @@ function hashApiKey(apiKey) {
|
|
|
950
1023
|
return (0, import_crypto.createHash)("sha256").update(apiKey).digest("hex");
|
|
951
1024
|
}
|
|
952
1025
|
|
|
1026
|
+
// src/sigma/sigmaApi.ts
|
|
1027
|
+
var import_papaparse = __toESM(require("papaparse"), 1);
|
|
1028
|
+
var import_stripe2 = __toESM(require("stripe"), 1);
|
|
1029
|
+
var STRIPE_FILES_BASE = "https://files.stripe.com/v1";
|
|
1030
|
+
function sleep2(ms) {
|
|
1031
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1032
|
+
}
|
|
1033
|
+
function parseCsvObjects(csv) {
|
|
1034
|
+
const input = csv.replace(/^\uFEFF/, "");
|
|
1035
|
+
const parsed = import_papaparse.default.parse(input, {
|
|
1036
|
+
header: true,
|
|
1037
|
+
skipEmptyLines: "greedy"
|
|
1038
|
+
});
|
|
1039
|
+
if (parsed.errors.length > 0) {
|
|
1040
|
+
throw new Error(`Failed to parse Sigma CSV: ${parsed.errors[0]?.message ?? "unknown error"}`);
|
|
1041
|
+
}
|
|
1042
|
+
return parsed.data.filter((row) => row && Object.keys(row).length > 0).map(
|
|
1043
|
+
(row) => Object.fromEntries(
|
|
1044
|
+
Object.entries(row).map(([k, v]) => [k, v == null || v === "" ? null : String(v)])
|
|
1045
|
+
)
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
function normalizeSigmaTimestampToIso(value) {
|
|
1049
|
+
const v = value.trim();
|
|
1050
|
+
if (!v) return null;
|
|
1051
|
+
const hasExplicitTz = /z$|[+-]\d{2}:?\d{2}$/i.test(v);
|
|
1052
|
+
const isoish = v.includes("T") ? v : v.replace(" ", "T");
|
|
1053
|
+
const candidate = hasExplicitTz ? isoish : `${isoish}Z`;
|
|
1054
|
+
const d = new Date(candidate);
|
|
1055
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
1056
|
+
return d.toISOString();
|
|
1057
|
+
}
|
|
1058
|
+
async function fetchStripeText(url, apiKey, options) {
|
|
1059
|
+
const res = await fetch(url, {
|
|
1060
|
+
...options,
|
|
1061
|
+
headers: {
|
|
1062
|
+
...options.headers ?? {},
|
|
1063
|
+
Authorization: `Bearer ${apiKey}`
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
const text = await res.text();
|
|
1067
|
+
if (!res.ok) {
|
|
1068
|
+
throw new Error(`Sigma file download error (${res.status}) for ${url}: ${text}`);
|
|
1069
|
+
}
|
|
1070
|
+
return text;
|
|
1071
|
+
}
|
|
1072
|
+
async function runSigmaQueryAndDownloadCsv(params) {
|
|
1073
|
+
const pollTimeoutMs = params.pollTimeoutMs ?? 5 * 60 * 1e3;
|
|
1074
|
+
const pollIntervalMs = params.pollIntervalMs ?? 2e3;
|
|
1075
|
+
const stripe = new import_stripe2.default(params.apiKey);
|
|
1076
|
+
const created = await stripe.rawRequest("POST", "/v1/sigma/query_runs", {
|
|
1077
|
+
sql: params.sql
|
|
1078
|
+
});
|
|
1079
|
+
const queryRunId = created.id;
|
|
1080
|
+
const start = Date.now();
|
|
1081
|
+
let current = created;
|
|
1082
|
+
while (current.status === "running") {
|
|
1083
|
+
if (Date.now() - start > pollTimeoutMs) {
|
|
1084
|
+
throw new Error(`Sigma query run timed out after ${pollTimeoutMs}ms: ${queryRunId}`);
|
|
1085
|
+
}
|
|
1086
|
+
await sleep2(pollIntervalMs);
|
|
1087
|
+
current = await stripe.rawRequest(
|
|
1088
|
+
"GET",
|
|
1089
|
+
`/v1/sigma/query_runs/${queryRunId}`,
|
|
1090
|
+
{}
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
if (current.status !== "succeeded") {
|
|
1094
|
+
throw new Error(
|
|
1095
|
+
`Sigma query run did not succeed (status=${current.status}) id=${queryRunId} error=${JSON.stringify(
|
|
1096
|
+
current.error
|
|
1097
|
+
)}`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const fileId = current.result?.file;
|
|
1101
|
+
if (!fileId) {
|
|
1102
|
+
throw new Error(`Sigma query run succeeded but result.file is missing (id=${queryRunId})`);
|
|
1103
|
+
}
|
|
1104
|
+
const csv = await fetchStripeText(
|
|
1105
|
+
`${STRIPE_FILES_BASE}/files/${fileId}/contents`,
|
|
1106
|
+
params.apiKey,
|
|
1107
|
+
{ method: "GET" }
|
|
1108
|
+
);
|
|
1109
|
+
return { queryRunId, fileId, csv };
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/sigma/sigmaIngestionConfigs.ts
|
|
1113
|
+
var SIGMA_INGESTION_CONFIGS = {
|
|
1114
|
+
subscription_item_change_events_v2_beta: {
|
|
1115
|
+
sigmaTable: "subscription_item_change_events_v2_beta",
|
|
1116
|
+
destinationTable: "subscription_item_change_events_v2_beta",
|
|
1117
|
+
pageSize: 1e4,
|
|
1118
|
+
cursor: {
|
|
1119
|
+
version: 1,
|
|
1120
|
+
columns: [
|
|
1121
|
+
{ column: "event_timestamp", type: "timestamp" },
|
|
1122
|
+
{ column: "event_type", type: "string" },
|
|
1123
|
+
{ column: "subscription_item_id", type: "string" }
|
|
1124
|
+
]
|
|
1125
|
+
},
|
|
1126
|
+
upsert: {
|
|
1127
|
+
conflictTarget: ["_account_id", "event_timestamp", "event_type", "subscription_item_id"],
|
|
1128
|
+
extraColumns: [
|
|
1129
|
+
{ column: "event_timestamp", pgType: "timestamptz", entryKey: "event_timestamp" },
|
|
1130
|
+
{ column: "event_type", pgType: "text", entryKey: "event_type" },
|
|
1131
|
+
{ column: "subscription_item_id", pgType: "text", entryKey: "subscription_item_id" }
|
|
1132
|
+
]
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
exchange_rates_from_usd: {
|
|
1136
|
+
sigmaTable: "exchange_rates_from_usd",
|
|
1137
|
+
destinationTable: "exchange_rates_from_usd",
|
|
1138
|
+
pageSize: 1e4,
|
|
1139
|
+
cursor: {
|
|
1140
|
+
version: 1,
|
|
1141
|
+
columns: [
|
|
1142
|
+
{ column: "date", type: "string" },
|
|
1143
|
+
{ column: "sell_currency", type: "string" }
|
|
1144
|
+
]
|
|
1145
|
+
},
|
|
1146
|
+
upsert: {
|
|
1147
|
+
conflictTarget: ["_account_id", "date", "sell_currency"],
|
|
1148
|
+
extraColumns: [
|
|
1149
|
+
{ column: "date", pgType: "date", entryKey: "date" },
|
|
1150
|
+
{ column: "sell_currency", pgType: "text", entryKey: "sell_currency" }
|
|
1151
|
+
]
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
// src/sigma/sigmaIngestion.ts
|
|
1157
|
+
var SIGMA_CURSOR_DELIM = "";
|
|
1158
|
+
function escapeSigmaSqlStringLiteral(value) {
|
|
1159
|
+
return value.replace(/'/g, "''");
|
|
1160
|
+
}
|
|
1161
|
+
function formatSigmaTimestampForSqlLiteral(date) {
|
|
1162
|
+
return date.toISOString().replace("T", " ").replace("Z", "");
|
|
1163
|
+
}
|
|
1164
|
+
function decodeSigmaCursorValues(spec, cursor) {
|
|
1165
|
+
const prefix = `v${spec.version}${SIGMA_CURSOR_DELIM}`;
|
|
1166
|
+
if (!cursor.startsWith(prefix)) {
|
|
1167
|
+
throw new Error(
|
|
1168
|
+
`Unrecognized Sigma cursor format (expected prefix ${JSON.stringify(prefix)}): ${cursor}`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
const parts = cursor.split(SIGMA_CURSOR_DELIM);
|
|
1172
|
+
const expected = 1 + spec.columns.length;
|
|
1173
|
+
if (parts.length !== expected) {
|
|
1174
|
+
throw new Error(`Malformed Sigma cursor: expected ${expected} parts, got ${parts.length}`);
|
|
1175
|
+
}
|
|
1176
|
+
return parts.slice(1);
|
|
1177
|
+
}
|
|
1178
|
+
function encodeSigmaCursor(spec, values) {
|
|
1179
|
+
if (values.length !== spec.columns.length) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`Cannot encode Sigma cursor: expected ${spec.columns.length} values, got ${values.length}`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
for (const v of values) {
|
|
1185
|
+
if (v.includes(SIGMA_CURSOR_DELIM)) {
|
|
1186
|
+
throw new Error("Cannot encode Sigma cursor: value contains delimiter character");
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return [`v${spec.version}`, ...values].join(SIGMA_CURSOR_DELIM);
|
|
1190
|
+
}
|
|
1191
|
+
function sigmaSqlLiteralForCursorValue(spec, rawValue) {
|
|
1192
|
+
switch (spec.type) {
|
|
1193
|
+
case "timestamp": {
|
|
1194
|
+
const d = new Date(rawValue);
|
|
1195
|
+
if (Number.isNaN(d.getTime())) {
|
|
1196
|
+
throw new Error(`Invalid timestamp cursor value for ${spec.column}: ${rawValue}`);
|
|
1197
|
+
}
|
|
1198
|
+
return `timestamp '${formatSigmaTimestampForSqlLiteral(d)}'`;
|
|
1199
|
+
}
|
|
1200
|
+
case "number": {
|
|
1201
|
+
if (!/^-?\d+(\.\d+)?$/.test(rawValue)) {
|
|
1202
|
+
throw new Error(`Invalid numeric cursor value for ${spec.column}: ${rawValue}`);
|
|
1203
|
+
}
|
|
1204
|
+
return rawValue;
|
|
1205
|
+
}
|
|
1206
|
+
case "string":
|
|
1207
|
+
return `'${escapeSigmaSqlStringLiteral(rawValue)}'`;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
function buildSigmaCursorWhereClause(spec, cursorValues) {
|
|
1211
|
+
if (cursorValues.length !== spec.columns.length) {
|
|
1212
|
+
throw new Error(
|
|
1213
|
+
`Cannot build Sigma cursor predicate: expected ${spec.columns.length} values, got ${cursorValues.length}`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
const cols = spec.columns.map((c) => c.column);
|
|
1217
|
+
const lits = spec.columns.map((c, i) => sigmaSqlLiteralForCursorValue(c, cursorValues[i] ?? ""));
|
|
1218
|
+
const ors = [];
|
|
1219
|
+
for (let i = 0; i < cols.length; i++) {
|
|
1220
|
+
const ands = [];
|
|
1221
|
+
for (let j = 0; j < i; j++) {
|
|
1222
|
+
ands.push(`${cols[j]} = ${lits[j]}`);
|
|
1223
|
+
}
|
|
1224
|
+
ands.push(`${cols[i]} > ${lits[i]}`);
|
|
1225
|
+
ors.push(`(${ands.join(" AND ")})`);
|
|
1226
|
+
}
|
|
1227
|
+
return ors.join(" OR ");
|
|
1228
|
+
}
|
|
1229
|
+
function buildSigmaQuery(config, cursor) {
|
|
1230
|
+
const select = config.select === void 0 || config.select === "*" ? "*" : config.select.join(", ");
|
|
1231
|
+
const whereParts = [];
|
|
1232
|
+
if (config.additionalWhere) {
|
|
1233
|
+
whereParts.push(`(${config.additionalWhere})`);
|
|
1234
|
+
}
|
|
1235
|
+
if (cursor) {
|
|
1236
|
+
const values = decodeSigmaCursorValues(config.cursor, cursor);
|
|
1237
|
+
const predicate = buildSigmaCursorWhereClause(config.cursor, values);
|
|
1238
|
+
whereParts.push(`(${predicate})`);
|
|
1239
|
+
}
|
|
1240
|
+
const whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(" AND ")}` : "";
|
|
1241
|
+
const orderBy = config.cursor.columns.map((c) => c.column).join(", ");
|
|
1242
|
+
return [
|
|
1243
|
+
`SELECT ${select} FROM ${config.sigmaTable}`,
|
|
1244
|
+
whereClause,
|
|
1245
|
+
`ORDER BY ${orderBy} ASC`,
|
|
1246
|
+
`LIMIT ${config.pageSize}`
|
|
1247
|
+
].filter(Boolean).join(" ");
|
|
1248
|
+
}
|
|
1249
|
+
function defaultSigmaRowToEntry(config, row) {
|
|
1250
|
+
const out = { ...row };
|
|
1251
|
+
for (const col of config.cursor.columns) {
|
|
1252
|
+
const raw = row[col.column];
|
|
1253
|
+
if (raw == null) {
|
|
1254
|
+
throw new Error(`Sigma row missing required cursor column: ${col.column}`);
|
|
1255
|
+
}
|
|
1256
|
+
if (col.type === "timestamp") {
|
|
1257
|
+
const normalized = normalizeSigmaTimestampToIso(raw);
|
|
1258
|
+
if (!normalized) {
|
|
1259
|
+
throw new Error(`Sigma row has invalid timestamp for ${col.column}: ${raw}`);
|
|
1260
|
+
}
|
|
1261
|
+
out[col.column] = normalized;
|
|
1262
|
+
} else if (col.type === "string") {
|
|
1263
|
+
const v = raw.trim();
|
|
1264
|
+
if (!v) {
|
|
1265
|
+
throw new Error(`Sigma row has empty string for required cursor column: ${col.column}`);
|
|
1266
|
+
}
|
|
1267
|
+
out[col.column] = v;
|
|
1268
|
+
} else {
|
|
1269
|
+
const v = raw.trim();
|
|
1270
|
+
if (!v) {
|
|
1271
|
+
throw new Error(`Sigma row has empty value for required cursor column: ${col.column}`);
|
|
1272
|
+
}
|
|
1273
|
+
out[col.column] = v;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1278
|
+
function sigmaCursorFromEntry(config, entry) {
|
|
1279
|
+
const values = config.cursor.columns.map((c) => {
|
|
1280
|
+
const raw = entry[c.column];
|
|
1281
|
+
if (raw == null) {
|
|
1282
|
+
throw new Error(`Cannot build cursor: entry missing ${c.column}`);
|
|
1283
|
+
}
|
|
1284
|
+
return String(raw);
|
|
1285
|
+
});
|
|
1286
|
+
return encodeSigmaCursor(config.cursor, values);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
953
1289
|
// src/stripeSync.ts
|
|
954
1290
|
function getUniqueIds(entries, key) {
|
|
955
1291
|
const set = new Set(
|
|
@@ -960,7 +1296,7 @@ function getUniqueIds(entries, key) {
|
|
|
960
1296
|
var StripeSync = class {
|
|
961
1297
|
constructor(config) {
|
|
962
1298
|
this.config = config;
|
|
963
|
-
const baseStripe = new
|
|
1299
|
+
const baseStripe = new import_stripe3.default(config.stripeSecretKey, {
|
|
964
1300
|
// https://github.com/stripe/stripe-node#configuration
|
|
965
1301
|
// @ts-ignore
|
|
966
1302
|
apiVersion: config.stripeApiVersion,
|
|
@@ -1361,6 +1697,17 @@ var StripeSync = class {
|
|
|
1361
1697
|
listFn: (p) => this.stripe.checkout.sessions.list(p),
|
|
1362
1698
|
upsertFn: (items, id) => this.upsertCheckoutSessions(items, id),
|
|
1363
1699
|
supportsCreatedFilter: true
|
|
1700
|
+
},
|
|
1701
|
+
// Sigma-backed resources
|
|
1702
|
+
subscription_item_change_events_v2_beta: {
|
|
1703
|
+
order: 18,
|
|
1704
|
+
supportsCreatedFilter: false,
|
|
1705
|
+
sigma: SIGMA_INGESTION_CONFIGS.subscription_item_change_events_v2_beta
|
|
1706
|
+
},
|
|
1707
|
+
exchange_rates_from_usd: {
|
|
1708
|
+
order: 19,
|
|
1709
|
+
supportsCreatedFilter: false,
|
|
1710
|
+
sigma: SIGMA_INGESTION_CONFIGS.exchange_rates_from_usd
|
|
1364
1711
|
}
|
|
1365
1712
|
};
|
|
1366
1713
|
async processEvent(event) {
|
|
@@ -1393,7 +1740,13 @@ var StripeSync = class {
|
|
|
1393
1740
|
* Order is determined by the `order` field in resourceRegistry.
|
|
1394
1741
|
*/
|
|
1395
1742
|
getSupportedSyncObjects() {
|
|
1396
|
-
|
|
1743
|
+
const all = Object.entries(this.resourceRegistry).sort(([, a], [, b]) => a.order - b.order).map(([key]) => key);
|
|
1744
|
+
if (!this.config.enableSigma) {
|
|
1745
|
+
return all.filter(
|
|
1746
|
+
(o) => o !== "subscription_item_change_events_v2_beta" && o !== "exchange_rates_from_usd"
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
return all;
|
|
1397
1750
|
}
|
|
1398
1751
|
// Event handler methods
|
|
1399
1752
|
async handleChargeEvent(event, accountId) {
|
|
@@ -1472,7 +1825,7 @@ var StripeSync = class {
|
|
|
1472
1825
|
);
|
|
1473
1826
|
await this.upsertProducts([product], accountId, this.getSyncTimestamp(event, refetched));
|
|
1474
1827
|
} catch (err) {
|
|
1475
|
-
if (err instanceof
|
|
1828
|
+
if (err instanceof import_stripe3.default.errors.StripeAPIError && err.code === "resource_missing") {
|
|
1476
1829
|
const product = event.data.object;
|
|
1477
1830
|
await this.deleteProduct(product.id);
|
|
1478
1831
|
} else {
|
|
@@ -1492,7 +1845,7 @@ var StripeSync = class {
|
|
|
1492
1845
|
);
|
|
1493
1846
|
await this.upsertPrices([price], accountId, false, this.getSyncTimestamp(event, refetched));
|
|
1494
1847
|
} catch (err) {
|
|
1495
|
-
if (err instanceof
|
|
1848
|
+
if (err instanceof import_stripe3.default.errors.StripeAPIError && err.code === "resource_missing") {
|
|
1496
1849
|
const price = event.data.object;
|
|
1497
1850
|
await this.deletePrice(price.id);
|
|
1498
1851
|
} else {
|
|
@@ -1512,7 +1865,7 @@ var StripeSync = class {
|
|
|
1512
1865
|
);
|
|
1513
1866
|
await this.upsertPlans([plan], accountId, false, this.getSyncTimestamp(event, refetched));
|
|
1514
1867
|
} catch (err) {
|
|
1515
|
-
if (err instanceof
|
|
1868
|
+
if (err instanceof import_stripe3.default.errors.StripeAPIError && err.code === "resource_missing") {
|
|
1516
1869
|
const plan = event.data.object;
|
|
1517
1870
|
await this.deletePlan(plan.id);
|
|
1518
1871
|
} else {
|
|
@@ -1759,10 +2112,10 @@ var StripeSync = class {
|
|
|
1759
2112
|
let cursor = null;
|
|
1760
2113
|
if (!params?.created) {
|
|
1761
2114
|
if (objRun?.cursor) {
|
|
1762
|
-
cursor =
|
|
2115
|
+
cursor = objRun.cursor;
|
|
1763
2116
|
} else {
|
|
1764
2117
|
const lastCursor = await this.postgresClient.getLastCompletedCursor(accountId, resourceName);
|
|
1765
|
-
cursor = lastCursor
|
|
2118
|
+
cursor = lastCursor ?? null;
|
|
1766
2119
|
}
|
|
1767
2120
|
}
|
|
1768
2121
|
const result = await this.fetchOnePage(
|
|
@@ -1817,9 +2170,18 @@ var StripeSync = class {
|
|
|
1817
2170
|
throw new Error(`Unsupported object type for processNext: ${object}`);
|
|
1818
2171
|
}
|
|
1819
2172
|
try {
|
|
2173
|
+
if (config.sigma) {
|
|
2174
|
+
return await this.fetchOneSigmaPage(
|
|
2175
|
+
accountId,
|
|
2176
|
+
resourceName,
|
|
2177
|
+
runStartedAt,
|
|
2178
|
+
cursor,
|
|
2179
|
+
config.sigma
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
1820
2182
|
const listParams = { limit };
|
|
1821
2183
|
if (config.supportsCreatedFilter) {
|
|
1822
|
-
const created = params?.created ?? (cursor ? { gte: cursor } : void 0);
|
|
2184
|
+
const created = params?.created ?? (cursor && /^\d+$/.test(cursor) ? { gte: Number.parseInt(cursor, 10) } : void 0);
|
|
1823
2185
|
if (created) {
|
|
1824
2186
|
listParams.created = created;
|
|
1825
2187
|
}
|
|
@@ -1864,6 +2226,97 @@ var StripeSync = class {
|
|
|
1864
2226
|
throw error;
|
|
1865
2227
|
}
|
|
1866
2228
|
}
|
|
2229
|
+
async getSigmaFallbackCursorFromDestination(accountId, sigmaConfig) {
|
|
2230
|
+
const cursorCols = sigmaConfig.cursor.columns;
|
|
2231
|
+
const selectCols = cursorCols.map((c) => `"${c.column}"`).join(", ");
|
|
2232
|
+
const orderBy = cursorCols.map((c) => `"${c.column}" DESC`).join(", ");
|
|
2233
|
+
const result = await this.postgresClient.query(
|
|
2234
|
+
`SELECT ${selectCols}
|
|
2235
|
+
FROM "stripe"."${sigmaConfig.destinationTable}"
|
|
2236
|
+
WHERE "_account_id" = $1
|
|
2237
|
+
ORDER BY ${orderBy}
|
|
2238
|
+
LIMIT 1`,
|
|
2239
|
+
[accountId]
|
|
2240
|
+
);
|
|
2241
|
+
if (result.rows.length === 0) return null;
|
|
2242
|
+
const row = result.rows[0];
|
|
2243
|
+
const entryForCursor = {};
|
|
2244
|
+
for (const c of cursorCols) {
|
|
2245
|
+
const v = row[c.column];
|
|
2246
|
+
if (v == null) {
|
|
2247
|
+
throw new Error(
|
|
2248
|
+
`Sigma fallback cursor query returned null for ${sigmaConfig.destinationTable}.${c.column}`
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
if (c.type === "timestamp") {
|
|
2252
|
+
const d = v instanceof Date ? v : new Date(String(v));
|
|
2253
|
+
if (Number.isNaN(d.getTime())) {
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
`Sigma fallback cursor query returned invalid timestamp for ${sigmaConfig.destinationTable}.${c.column}: ${String(
|
|
2256
|
+
v
|
|
2257
|
+
)}`
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
entryForCursor[c.column] = d.toISOString();
|
|
2261
|
+
} else {
|
|
2262
|
+
entryForCursor[c.column] = String(v);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
return sigmaCursorFromEntry(sigmaConfig, entryForCursor);
|
|
2266
|
+
}
|
|
2267
|
+
async fetchOneSigmaPage(accountId, resourceName, runStartedAt, cursor, sigmaConfig) {
|
|
2268
|
+
if (!this.config.stripeSecretKey) {
|
|
2269
|
+
throw new Error("Sigma sync requested but stripeSecretKey is not configured.");
|
|
2270
|
+
}
|
|
2271
|
+
if (resourceName !== sigmaConfig.destinationTable) {
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
`Sigma sync config mismatch: resourceName=${resourceName} destinationTable=${sigmaConfig.destinationTable}`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
const effectiveCursor = cursor ?? await this.getSigmaFallbackCursorFromDestination(accountId, sigmaConfig);
|
|
2277
|
+
const sigmaSql = buildSigmaQuery(sigmaConfig, effectiveCursor);
|
|
2278
|
+
this.config.logger?.info(
|
|
2279
|
+
{ object: resourceName, pageSize: sigmaConfig.pageSize, hasCursor: Boolean(effectiveCursor) },
|
|
2280
|
+
"Sigma sync: running query"
|
|
2281
|
+
);
|
|
2282
|
+
const { queryRunId, fileId, csv } = await runSigmaQueryAndDownloadCsv({
|
|
2283
|
+
apiKey: this.config.stripeSecretKey,
|
|
2284
|
+
sql: sigmaSql,
|
|
2285
|
+
logger: this.config.logger
|
|
2286
|
+
});
|
|
2287
|
+
const rows = parseCsvObjects(csv);
|
|
2288
|
+
if (rows.length === 0) {
|
|
2289
|
+
await this.postgresClient.completeObjectSync(accountId, runStartedAt, resourceName);
|
|
2290
|
+
return { processed: 0, hasMore: false, runStartedAt };
|
|
2291
|
+
}
|
|
2292
|
+
const entries = rows.map(
|
|
2293
|
+
(row) => defaultSigmaRowToEntry(sigmaConfig, row)
|
|
2294
|
+
);
|
|
2295
|
+
this.config.logger?.info(
|
|
2296
|
+
{ object: resourceName, rows: entries.length, queryRunId, fileId },
|
|
2297
|
+
"Sigma sync: upserting rows"
|
|
2298
|
+
);
|
|
2299
|
+
await this.postgresClient.upsertManyWithTimestampProtection(
|
|
2300
|
+
entries,
|
|
2301
|
+
resourceName,
|
|
2302
|
+
accountId,
|
|
2303
|
+
void 0,
|
|
2304
|
+
sigmaConfig.upsert
|
|
2305
|
+
);
|
|
2306
|
+
await this.postgresClient.incrementObjectProgress(
|
|
2307
|
+
accountId,
|
|
2308
|
+
runStartedAt,
|
|
2309
|
+
resourceName,
|
|
2310
|
+
entries.length
|
|
2311
|
+
);
|
|
2312
|
+
const newCursor = sigmaCursorFromEntry(sigmaConfig, entries[entries.length - 1]);
|
|
2313
|
+
await this.postgresClient.updateObjectCursor(accountId, runStartedAt, resourceName, newCursor);
|
|
2314
|
+
const hasMore = rows.length === sigmaConfig.pageSize;
|
|
2315
|
+
if (!hasMore) {
|
|
2316
|
+
await this.postgresClient.completeObjectSync(accountId, runStartedAt, resourceName);
|
|
2317
|
+
}
|
|
2318
|
+
return { processed: entries.length, hasMore, runStartedAt };
|
|
2319
|
+
}
|
|
1867
2320
|
/**
|
|
1868
2321
|
* Process all pages for all (or specified) object types until complete.
|
|
1869
2322
|
*
|
|
@@ -1992,6 +2445,12 @@ var StripeSync = class {
|
|
|
1992
2445
|
case "checkout_sessions":
|
|
1993
2446
|
results.checkoutSessions = result;
|
|
1994
2447
|
break;
|
|
2448
|
+
case "subscription_item_change_events_v2_beta":
|
|
2449
|
+
results.subscriptionItemChangeEventsV2Beta = result;
|
|
2450
|
+
break;
|
|
2451
|
+
case "exchange_rates_from_usd":
|
|
2452
|
+
results.exchangeRatesFromUsd = result;
|
|
2453
|
+
break;
|
|
1995
2454
|
}
|
|
1996
2455
|
}
|
|
1997
2456
|
}
|
|
@@ -2017,30 +2476,41 @@ var StripeSync = class {
|
|
|
2017
2476
|
const customerIds = await this.postgresClient.query(prepared.text, prepared.values).then(({ rows }) => rows.map((it) => it.id));
|
|
2018
2477
|
this.config.logger?.info(`Getting payment methods for ${customerIds.length} customers`);
|
|
2019
2478
|
let synced = 0;
|
|
2020
|
-
|
|
2479
|
+
const chunkSize = this.config.maxConcurrentCustomers ?? 10;
|
|
2480
|
+
for (const customerIdChunk of chunkArray(customerIds, chunkSize)) {
|
|
2021
2481
|
await Promise.all(
|
|
2022
2482
|
customerIdChunk.map(async (customerId) => {
|
|
2023
2483
|
const CHECKPOINT_SIZE = 100;
|
|
2024
2484
|
let currentBatch = [];
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
)
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
currentBatch.length
|
|
2042
|
-
|
|
2043
|
-
|
|
2485
|
+
let hasMore = true;
|
|
2486
|
+
let startingAfter = void 0;
|
|
2487
|
+
while (hasMore) {
|
|
2488
|
+
const response = await this.stripe.paymentMethods.list({
|
|
2489
|
+
limit: 100,
|
|
2490
|
+
customer: customerId,
|
|
2491
|
+
...startingAfter ? { starting_after: startingAfter } : {}
|
|
2492
|
+
});
|
|
2493
|
+
for (const item of response.data) {
|
|
2494
|
+
currentBatch.push(item);
|
|
2495
|
+
if (currentBatch.length >= CHECKPOINT_SIZE) {
|
|
2496
|
+
await this.upsertPaymentMethods(
|
|
2497
|
+
currentBatch,
|
|
2498
|
+
accountId,
|
|
2499
|
+
syncParams?.backfillRelatedEntities
|
|
2500
|
+
);
|
|
2501
|
+
synced += currentBatch.length;
|
|
2502
|
+
await this.postgresClient.incrementObjectProgress(
|
|
2503
|
+
accountId,
|
|
2504
|
+
runStartedAt,
|
|
2505
|
+
resourceName,
|
|
2506
|
+
currentBatch.length
|
|
2507
|
+
);
|
|
2508
|
+
currentBatch = [];
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
hasMore = response.has_more;
|
|
2512
|
+
if (response.data.length > 0) {
|
|
2513
|
+
startingAfter = response.data[response.data.length - 1].id;
|
|
2044
2514
|
}
|
|
2045
2515
|
}
|
|
2046
2516
|
if (currentBatch.length > 0) {
|
|
@@ -2084,7 +2554,7 @@ var StripeSync = class {
|
|
|
2084
2554
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2085
2555
|
}
|
|
2086
2556
|
return this.fetchAndUpsert(
|
|
2087
|
-
() => this.stripe.products.list(params),
|
|
2557
|
+
(pagination) => this.stripe.products.list({ ...params, ...pagination }),
|
|
2088
2558
|
(products) => this.upsertProducts(products, accountId),
|
|
2089
2559
|
accountId,
|
|
2090
2560
|
"products",
|
|
@@ -2104,7 +2574,7 @@ var StripeSync = class {
|
|
|
2104
2574
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2105
2575
|
}
|
|
2106
2576
|
return this.fetchAndUpsert(
|
|
2107
|
-
() => this.stripe.prices.list(params),
|
|
2577
|
+
(pagination) => this.stripe.prices.list({ ...params, ...pagination }),
|
|
2108
2578
|
(prices) => this.upsertPrices(prices, accountId, syncParams?.backfillRelatedEntities),
|
|
2109
2579
|
accountId,
|
|
2110
2580
|
"prices",
|
|
@@ -2124,7 +2594,7 @@ var StripeSync = class {
|
|
|
2124
2594
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2125
2595
|
}
|
|
2126
2596
|
return this.fetchAndUpsert(
|
|
2127
|
-
() => this.stripe.plans.list(params),
|
|
2597
|
+
(pagination) => this.stripe.plans.list({ ...params, ...pagination }),
|
|
2128
2598
|
(plans) => this.upsertPlans(plans, accountId, syncParams?.backfillRelatedEntities),
|
|
2129
2599
|
accountId,
|
|
2130
2600
|
"plans",
|
|
@@ -2144,7 +2614,7 @@ var StripeSync = class {
|
|
|
2144
2614
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2145
2615
|
}
|
|
2146
2616
|
return this.fetchAndUpsert(
|
|
2147
|
-
() => this.stripe.customers.list(params),
|
|
2617
|
+
(pagination) => this.stripe.customers.list({ ...params, ...pagination }),
|
|
2148
2618
|
// @ts-expect-error
|
|
2149
2619
|
(items) => this.upsertCustomers(items, accountId),
|
|
2150
2620
|
accountId,
|
|
@@ -2165,7 +2635,7 @@ var StripeSync = class {
|
|
|
2165
2635
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2166
2636
|
}
|
|
2167
2637
|
return this.fetchAndUpsert(
|
|
2168
|
-
() => this.stripe.subscriptions.list(params),
|
|
2638
|
+
(pagination) => this.stripe.subscriptions.list({ ...params, ...pagination }),
|
|
2169
2639
|
(items) => this.upsertSubscriptions(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2170
2640
|
accountId,
|
|
2171
2641
|
"subscriptions",
|
|
@@ -2188,7 +2658,7 @@ var StripeSync = class {
|
|
|
2188
2658
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2189
2659
|
}
|
|
2190
2660
|
return this.fetchAndUpsert(
|
|
2191
|
-
() => this.stripe.subscriptionSchedules.list(params),
|
|
2661
|
+
(pagination) => this.stripe.subscriptionSchedules.list({ ...params, ...pagination }),
|
|
2192
2662
|
(items) => this.upsertSubscriptionSchedules(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2193
2663
|
accountId,
|
|
2194
2664
|
"subscription_schedules",
|
|
@@ -2209,7 +2679,7 @@ var StripeSync = class {
|
|
|
2209
2679
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2210
2680
|
}
|
|
2211
2681
|
return this.fetchAndUpsert(
|
|
2212
|
-
() => this.stripe.invoices.list(params),
|
|
2682
|
+
(pagination) => this.stripe.invoices.list({ ...params, ...pagination }),
|
|
2213
2683
|
(items) => this.upsertInvoices(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2214
2684
|
accountId,
|
|
2215
2685
|
"invoices",
|
|
@@ -2229,7 +2699,7 @@ var StripeSync = class {
|
|
|
2229
2699
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2230
2700
|
}
|
|
2231
2701
|
return this.fetchAndUpsert(
|
|
2232
|
-
() => this.stripe.charges.list(params),
|
|
2702
|
+
(pagination) => this.stripe.charges.list({ ...params, ...pagination }),
|
|
2233
2703
|
(items) => this.upsertCharges(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2234
2704
|
accountId,
|
|
2235
2705
|
"charges",
|
|
@@ -2249,7 +2719,7 @@ var StripeSync = class {
|
|
|
2249
2719
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2250
2720
|
}
|
|
2251
2721
|
return this.fetchAndUpsert(
|
|
2252
|
-
() => this.stripe.setupIntents.list(params),
|
|
2722
|
+
(pagination) => this.stripe.setupIntents.list({ ...params, ...pagination }),
|
|
2253
2723
|
(items) => this.upsertSetupIntents(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2254
2724
|
accountId,
|
|
2255
2725
|
"setup_intents",
|
|
@@ -2272,7 +2742,7 @@ var StripeSync = class {
|
|
|
2272
2742
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2273
2743
|
}
|
|
2274
2744
|
return this.fetchAndUpsert(
|
|
2275
|
-
() => this.stripe.paymentIntents.list(params),
|
|
2745
|
+
(pagination) => this.stripe.paymentIntents.list({ ...params, ...pagination }),
|
|
2276
2746
|
(items) => this.upsertPaymentIntents(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2277
2747
|
accountId,
|
|
2278
2748
|
"payment_intents",
|
|
@@ -2287,7 +2757,7 @@ var StripeSync = class {
|
|
|
2287
2757
|
const accountId = await this.getAccountId();
|
|
2288
2758
|
const params = { limit: 100 };
|
|
2289
2759
|
return this.fetchAndUpsert(
|
|
2290
|
-
() => this.stripe.taxIds.list(params),
|
|
2760
|
+
(pagination) => this.stripe.taxIds.list({ ...params, ...pagination }),
|
|
2291
2761
|
(items) => this.upsertTaxIds(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2292
2762
|
accountId,
|
|
2293
2763
|
"tax_ids",
|
|
@@ -2308,30 +2778,41 @@ var StripeSync = class {
|
|
|
2308
2778
|
const customerIds = await this.postgresClient.query(prepared.text, prepared.values).then(({ rows }) => rows.map((it) => it.id));
|
|
2309
2779
|
this.config.logger?.info(`Getting payment methods for ${customerIds.length} customers`);
|
|
2310
2780
|
let synced = 0;
|
|
2311
|
-
|
|
2781
|
+
const chunkSize = this.config.maxConcurrentCustomers ?? 10;
|
|
2782
|
+
for (const customerIdChunk of chunkArray(customerIds, chunkSize)) {
|
|
2312
2783
|
await Promise.all(
|
|
2313
2784
|
customerIdChunk.map(async (customerId) => {
|
|
2314
2785
|
const CHECKPOINT_SIZE = 100;
|
|
2315
2786
|
let currentBatch = [];
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
)
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
currentBatch.length
|
|
2333
|
-
|
|
2334
|
-
|
|
2787
|
+
let hasMore = true;
|
|
2788
|
+
let startingAfter = void 0;
|
|
2789
|
+
while (hasMore) {
|
|
2790
|
+
const response = await this.stripe.paymentMethods.list({
|
|
2791
|
+
limit: 100,
|
|
2792
|
+
customer: customerId,
|
|
2793
|
+
...startingAfter ? { starting_after: startingAfter } : {}
|
|
2794
|
+
});
|
|
2795
|
+
for (const item of response.data) {
|
|
2796
|
+
currentBatch.push(item);
|
|
2797
|
+
if (currentBatch.length >= CHECKPOINT_SIZE) {
|
|
2798
|
+
await this.upsertPaymentMethods(
|
|
2799
|
+
currentBatch,
|
|
2800
|
+
accountId,
|
|
2801
|
+
syncParams?.backfillRelatedEntities
|
|
2802
|
+
);
|
|
2803
|
+
synced += currentBatch.length;
|
|
2804
|
+
await this.postgresClient.incrementObjectProgress(
|
|
2805
|
+
accountId,
|
|
2806
|
+
runStartedAt,
|
|
2807
|
+
"payment_methods",
|
|
2808
|
+
currentBatch.length
|
|
2809
|
+
);
|
|
2810
|
+
currentBatch = [];
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
hasMore = response.has_more;
|
|
2814
|
+
if (response.data.length > 0) {
|
|
2815
|
+
startingAfter = response.data[response.data.length - 1].id;
|
|
2335
2816
|
}
|
|
2336
2817
|
}
|
|
2337
2818
|
if (currentBatch.length > 0) {
|
|
@@ -2368,7 +2849,7 @@ var StripeSync = class {
|
|
|
2368
2849
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2369
2850
|
}
|
|
2370
2851
|
return this.fetchAndUpsert(
|
|
2371
|
-
() => this.stripe.disputes.list(params),
|
|
2852
|
+
(pagination) => this.stripe.disputes.list({ ...params, ...pagination }),
|
|
2372
2853
|
(items) => this.upsertDisputes(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2373
2854
|
accountId,
|
|
2374
2855
|
"disputes",
|
|
@@ -2391,7 +2872,7 @@ var StripeSync = class {
|
|
|
2391
2872
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2392
2873
|
}
|
|
2393
2874
|
return this.fetchAndUpsert(
|
|
2394
|
-
() => this.stripe.radar.earlyFraudWarnings.list(params),
|
|
2875
|
+
(pagination) => this.stripe.radar.earlyFraudWarnings.list({ ...params, ...pagination }),
|
|
2395
2876
|
(items) => this.upsertEarlyFraudWarning(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2396
2877
|
accountId,
|
|
2397
2878
|
"early_fraud_warnings",
|
|
@@ -2412,7 +2893,7 @@ var StripeSync = class {
|
|
|
2412
2893
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2413
2894
|
}
|
|
2414
2895
|
return this.fetchAndUpsert(
|
|
2415
|
-
() => this.stripe.refunds.list(params),
|
|
2896
|
+
(pagination) => this.stripe.refunds.list({ ...params, ...pagination }),
|
|
2416
2897
|
(items) => this.upsertRefunds(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2417
2898
|
accountId,
|
|
2418
2899
|
"refunds",
|
|
@@ -2432,7 +2913,7 @@ var StripeSync = class {
|
|
|
2432
2913
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2433
2914
|
}
|
|
2434
2915
|
return this.fetchAndUpsert(
|
|
2435
|
-
() => this.stripe.creditNotes.list(params),
|
|
2916
|
+
(pagination) => this.stripe.creditNotes.list({ ...params, ...pagination }),
|
|
2436
2917
|
(creditNotes) => this.upsertCreditNotes(creditNotes, accountId),
|
|
2437
2918
|
accountId,
|
|
2438
2919
|
"credit_notes",
|
|
@@ -2494,7 +2975,7 @@ var StripeSync = class {
|
|
|
2494
2975
|
this.config.logger?.info(`Incremental sync from cursor: ${cursor}`);
|
|
2495
2976
|
}
|
|
2496
2977
|
return this.fetchAndUpsert(
|
|
2497
|
-
() => this.stripe.checkout.sessions.list(params),
|
|
2978
|
+
(pagination) => this.stripe.checkout.sessions.list({ ...params, ...pagination }),
|
|
2498
2979
|
(items) => this.upsertCheckoutSessions(items, accountId, syncParams?.backfillRelatedEntities),
|
|
2499
2980
|
accountId,
|
|
2500
2981
|
"checkout_sessions",
|
|
@@ -2548,31 +3029,42 @@ var StripeSync = class {
|
|
|
2548
3029
|
try {
|
|
2549
3030
|
this.config.logger?.info("Fetching items to sync from Stripe");
|
|
2550
3031
|
try {
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
currentBatch
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
...currentBatch.map((i) => i.created || 0)
|
|
2565
|
-
);
|
|
2566
|
-
if (maxCreated > 0) {
|
|
2567
|
-
await this.postgresClient.updateObjectCursor(
|
|
3032
|
+
let hasMore = true;
|
|
3033
|
+
let startingAfter = void 0;
|
|
3034
|
+
while (hasMore) {
|
|
3035
|
+
const response = await fetch2(
|
|
3036
|
+
startingAfter ? { starting_after: startingAfter } : void 0
|
|
3037
|
+
);
|
|
3038
|
+
for (const item of response.data) {
|
|
3039
|
+
currentBatch.push(item);
|
|
3040
|
+
if (currentBatch.length >= CHECKPOINT_SIZE) {
|
|
3041
|
+
this.config.logger?.info(`Upserting batch of ${currentBatch.length} items`);
|
|
3042
|
+
await upsert(currentBatch, accountId);
|
|
3043
|
+
totalSynced += currentBatch.length;
|
|
3044
|
+
await this.postgresClient.incrementObjectProgress(
|
|
2568
3045
|
accountId,
|
|
2569
3046
|
runStartedAt,
|
|
2570
3047
|
resourceName,
|
|
2571
|
-
|
|
3048
|
+
currentBatch.length
|
|
2572
3049
|
);
|
|
2573
|
-
|
|
3050
|
+
const maxCreated = Math.max(
|
|
3051
|
+
...currentBatch.map((i) => i.created || 0)
|
|
3052
|
+
);
|
|
3053
|
+
if (maxCreated > 0) {
|
|
3054
|
+
await this.postgresClient.updateObjectCursor(
|
|
3055
|
+
accountId,
|
|
3056
|
+
runStartedAt,
|
|
3057
|
+
resourceName,
|
|
3058
|
+
String(maxCreated)
|
|
3059
|
+
);
|
|
3060
|
+
this.config.logger?.info(`Checkpoint: cursor updated to ${maxCreated}`);
|
|
3061
|
+
}
|
|
3062
|
+
currentBatch = [];
|
|
2574
3063
|
}
|
|
2575
|
-
|
|
3064
|
+
}
|
|
3065
|
+
hasMore = response.has_more;
|
|
3066
|
+
if (response.data.length > 0) {
|
|
3067
|
+
startingAfter = response.data[response.data.length - 1].id;
|
|
2576
3068
|
}
|
|
2577
3069
|
}
|
|
2578
3070
|
if (currentBatch.length > 0) {
|
|
@@ -2940,10 +3432,18 @@ var StripeSync = class {
|
|
|
2940
3432
|
async fillCheckoutSessionsLineItems(checkoutSessionIds, accountId, syncTimestamp) {
|
|
2941
3433
|
for (const checkoutSessionId of checkoutSessionIds) {
|
|
2942
3434
|
const lineItemResponses = [];
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3435
|
+
let hasMore = true;
|
|
3436
|
+
let startingAfter = void 0;
|
|
3437
|
+
while (hasMore) {
|
|
3438
|
+
const response = await this.stripe.checkout.sessions.listLineItems(checkoutSessionId, {
|
|
3439
|
+
limit: 100,
|
|
3440
|
+
...startingAfter ? { starting_after: startingAfter } : {}
|
|
3441
|
+
});
|
|
3442
|
+
lineItemResponses.push(...response.data);
|
|
3443
|
+
hasMore = response.has_more;
|
|
3444
|
+
if (response.data.length > 0) {
|
|
3445
|
+
startingAfter = response.data[response.data.length - 1].id;
|
|
3446
|
+
}
|
|
2947
3447
|
}
|
|
2948
3448
|
await this.upsertCheckoutSessionLineItems(
|
|
2949
3449
|
lineItemResponses,
|
|
@@ -3257,14 +3757,25 @@ var StripeSync = class {
|
|
|
3257
3757
|
};
|
|
3258
3758
|
/**
|
|
3259
3759
|
* Stripe only sends the first 10 entries by default, the option will actively fetch all entries.
|
|
3760
|
+
* Uses manual pagination - each fetch() gets automatic retry protection.
|
|
3260
3761
|
*/
|
|
3261
3762
|
async expandEntity(entities, property, listFn) {
|
|
3262
3763
|
if (!this.config.autoExpandLists) return;
|
|
3263
3764
|
for (const entity of entities) {
|
|
3264
3765
|
if (entity[property]?.has_more) {
|
|
3265
3766
|
const allData = [];
|
|
3266
|
-
|
|
3267
|
-
|
|
3767
|
+
let hasMore = true;
|
|
3768
|
+
let startingAfter = void 0;
|
|
3769
|
+
while (hasMore) {
|
|
3770
|
+
const response = await listFn(
|
|
3771
|
+
entity.id,
|
|
3772
|
+
startingAfter ? { starting_after: startingAfter } : void 0
|
|
3773
|
+
);
|
|
3774
|
+
allData.push(...response.data);
|
|
3775
|
+
hasMore = response.has_more;
|
|
3776
|
+
if (response.data.length > 0) {
|
|
3777
|
+
startingAfter = response.data[response.data.length - 1].id;
|
|
3778
|
+
}
|
|
3268
3779
|
}
|
|
3269
3780
|
entity[property] = {
|
|
3270
3781
|
...entity[property],
|
|
@@ -3389,7 +3900,9 @@ async function runMigrations(config) {
|
|
|
3389
3900
|
var import_ws = __toESM(require("ws"), 1);
|
|
3390
3901
|
var CLI_VERSION = "1.33.0";
|
|
3391
3902
|
var PONG_WAIT = 10 * 1e3;
|
|
3392
|
-
var PING_PERIOD = PONG_WAIT *
|
|
3903
|
+
var PING_PERIOD = PONG_WAIT * 9 / 10;
|
|
3904
|
+
var CONNECT_ATTEMPT_WAIT = 10 * 1e3;
|
|
3905
|
+
var DEFAULT_RECONNECT_INTERVAL = 60 * 1e3;
|
|
3393
3906
|
function getClientUserAgent() {
|
|
3394
3907
|
return JSON.stringify({
|
|
3395
3908
|
name: "stripe-cli",
|
|
@@ -3418,129 +3931,215 @@ async function createCliSession(stripeApiKey) {
|
|
|
3418
3931
|
}
|
|
3419
3932
|
return await response.json();
|
|
3420
3933
|
}
|
|
3934
|
+
function sleep3(ms) {
|
|
3935
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3936
|
+
}
|
|
3421
3937
|
async function createStripeWebSocketClient(options) {
|
|
3422
3938
|
const { stripeApiKey, onEvent, onReady, onError, onClose } = options;
|
|
3423
3939
|
const session = await createCliSession(stripeApiKey);
|
|
3940
|
+
const reconnectInterval = session.reconnect_delay ? session.reconnect_delay * 1e3 : DEFAULT_RECONNECT_INTERVAL;
|
|
3424
3941
|
let ws = null;
|
|
3425
3942
|
let pingInterval = null;
|
|
3943
|
+
let reconnectTimer = null;
|
|
3426
3944
|
let connected = false;
|
|
3427
|
-
let
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3945
|
+
let shouldRun = true;
|
|
3946
|
+
let lastPongReceived = Date.now();
|
|
3947
|
+
let notifyCloseResolve = null;
|
|
3948
|
+
let stopResolve = null;
|
|
3949
|
+
function cleanupConnection() {
|
|
3950
|
+
if (pingInterval) {
|
|
3951
|
+
clearInterval(pingInterval);
|
|
3952
|
+
pingInterval = null;
|
|
3953
|
+
}
|
|
3954
|
+
if (reconnectTimer) {
|
|
3955
|
+
clearTimeout(reconnectTimer);
|
|
3956
|
+
reconnectTimer = null;
|
|
3957
|
+
}
|
|
3958
|
+
if (ws) {
|
|
3959
|
+
ws.removeAllListeners();
|
|
3960
|
+
if (ws.readyState === import_ws.default.OPEN || ws.readyState === import_ws.default.CONNECTING) {
|
|
3961
|
+
ws.close(1e3, "Resetting connection");
|
|
3436
3962
|
}
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3963
|
+
ws = null;
|
|
3964
|
+
}
|
|
3965
|
+
connected = false;
|
|
3966
|
+
}
|
|
3967
|
+
function setupWebSocket() {
|
|
3968
|
+
return new Promise((resolve, reject) => {
|
|
3969
|
+
lastPongReceived = Date.now();
|
|
3970
|
+
const wsUrl = `${session.websocket_url}?websocket_feature=${encodeURIComponent(session.websocket_authorized_feature)}`;
|
|
3971
|
+
ws = new import_ws.default(wsUrl, {
|
|
3972
|
+
headers: {
|
|
3973
|
+
"Accept-Encoding": "identity",
|
|
3974
|
+
"User-Agent": `Stripe/v1 stripe-cli/${CLI_VERSION}`,
|
|
3975
|
+
"X-Stripe-Client-User-Agent": getClientUserAgent(),
|
|
3976
|
+
"Websocket-Id": session.websocket_id
|
|
3445
3977
|
}
|
|
3446
|
-
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
ws.on("message", async (data) => {
|
|
3452
|
-
try {
|
|
3453
|
-
const message = JSON.parse(data.toString());
|
|
3454
|
-
const ack = {
|
|
3455
|
-
type: "event_ack",
|
|
3456
|
-
event_id: message.webhook_id,
|
|
3457
|
-
webhook_conversation_id: message.webhook_conversation_id,
|
|
3458
|
-
webhook_id: message.webhook_id
|
|
3459
|
-
};
|
|
3460
|
-
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3461
|
-
ws.send(JSON.stringify(ack));
|
|
3978
|
+
});
|
|
3979
|
+
const connectionTimeout = setTimeout(() => {
|
|
3980
|
+
if (ws && ws.readyState === import_ws.default.CONNECTING) {
|
|
3981
|
+
ws.terminate();
|
|
3982
|
+
reject(new Error("WebSocket connection timeout"));
|
|
3462
3983
|
}
|
|
3463
|
-
|
|
3984
|
+
}, CONNECT_ATTEMPT_WAIT);
|
|
3985
|
+
ws.on("pong", () => {
|
|
3986
|
+
lastPongReceived = Date.now();
|
|
3987
|
+
});
|
|
3988
|
+
ws.on("open", () => {
|
|
3989
|
+
clearTimeout(connectionTimeout);
|
|
3990
|
+
connected = true;
|
|
3991
|
+
pingInterval = setInterval(() => {
|
|
3992
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3993
|
+
const timeSinceLastPong = Date.now() - lastPongReceived;
|
|
3994
|
+
if (timeSinceLastPong > PONG_WAIT) {
|
|
3995
|
+
if (onError) {
|
|
3996
|
+
onError(new Error(`WebSocket stale: no pong in ${timeSinceLastPong}ms`));
|
|
3997
|
+
}
|
|
3998
|
+
if (notifyCloseResolve) {
|
|
3999
|
+
notifyCloseResolve();
|
|
4000
|
+
notifyCloseResolve = null;
|
|
4001
|
+
}
|
|
4002
|
+
ws.terminate();
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
ws.ping();
|
|
4006
|
+
}
|
|
4007
|
+
}, PING_PERIOD);
|
|
4008
|
+
if (onReady) {
|
|
4009
|
+
onReady(session.secret);
|
|
4010
|
+
}
|
|
4011
|
+
resolve();
|
|
4012
|
+
});
|
|
4013
|
+
ws.on("message", async (data) => {
|
|
3464
4014
|
try {
|
|
3465
|
-
const
|
|
3466
|
-
|
|
3467
|
-
type: "
|
|
3468
|
-
|
|
4015
|
+
const message = JSON.parse(data.toString());
|
|
4016
|
+
const ack = {
|
|
4017
|
+
type: "event_ack",
|
|
4018
|
+
event_id: message.webhook_id,
|
|
3469
4019
|
webhook_conversation_id: message.webhook_conversation_id,
|
|
3470
|
-
|
|
3471
|
-
status: result?.status ?? 200,
|
|
3472
|
-
http_headers: {},
|
|
3473
|
-
body: JSON.stringify({
|
|
3474
|
-
event_type: result?.event_type,
|
|
3475
|
-
event_id: result?.event_id,
|
|
3476
|
-
database_url: result?.databaseUrl,
|
|
3477
|
-
error: result?.error
|
|
3478
|
-
}),
|
|
3479
|
-
request_headers: message.http_headers,
|
|
3480
|
-
request_body: message.event_payload,
|
|
3481
|
-
notification_id: message.webhook_id
|
|
4020
|
+
webhook_id: message.webhook_id
|
|
3482
4021
|
};
|
|
4022
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
4023
|
+
ws.send(JSON.stringify(ack));
|
|
4024
|
+
}
|
|
4025
|
+
let response;
|
|
4026
|
+
try {
|
|
4027
|
+
const result = await onEvent(message);
|
|
4028
|
+
response = {
|
|
4029
|
+
type: "webhook_response",
|
|
4030
|
+
webhook_id: message.webhook_id,
|
|
4031
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
4032
|
+
forward_url: "stripe-sync-engine",
|
|
4033
|
+
status: result?.status ?? 200,
|
|
4034
|
+
http_headers: {},
|
|
4035
|
+
body: JSON.stringify({
|
|
4036
|
+
event_type: result?.event_type,
|
|
4037
|
+
event_id: result?.event_id,
|
|
4038
|
+
database_url: result?.databaseUrl,
|
|
4039
|
+
error: result?.error
|
|
4040
|
+
}),
|
|
4041
|
+
request_headers: message.http_headers,
|
|
4042
|
+
request_body: message.event_payload,
|
|
4043
|
+
notification_id: message.webhook_id
|
|
4044
|
+
};
|
|
4045
|
+
} catch (err) {
|
|
4046
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4047
|
+
response = {
|
|
4048
|
+
type: "webhook_response",
|
|
4049
|
+
webhook_id: message.webhook_id,
|
|
4050
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
4051
|
+
forward_url: "stripe-sync-engine",
|
|
4052
|
+
status: 500,
|
|
4053
|
+
http_headers: {},
|
|
4054
|
+
body: JSON.stringify({ error: errorMessage }),
|
|
4055
|
+
request_headers: message.http_headers,
|
|
4056
|
+
request_body: message.event_payload,
|
|
4057
|
+
notification_id: message.webhook_id
|
|
4058
|
+
};
|
|
4059
|
+
if (onError) {
|
|
4060
|
+
onError(err instanceof Error ? err : new Error(errorMessage));
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
4064
|
+
ws.send(JSON.stringify(response));
|
|
4065
|
+
}
|
|
3483
4066
|
} catch (err) {
|
|
3484
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3485
|
-
response = {
|
|
3486
|
-
type: "webhook_response",
|
|
3487
|
-
webhook_id: message.webhook_id,
|
|
3488
|
-
webhook_conversation_id: message.webhook_conversation_id,
|
|
3489
|
-
forward_url: "stripe-sync-engine",
|
|
3490
|
-
status: 500,
|
|
3491
|
-
http_headers: {},
|
|
3492
|
-
body: JSON.stringify({ error: errorMessage }),
|
|
3493
|
-
request_headers: message.http_headers,
|
|
3494
|
-
request_body: message.event_payload,
|
|
3495
|
-
notification_id: message.webhook_id
|
|
3496
|
-
};
|
|
3497
4067
|
if (onError) {
|
|
3498
|
-
onError(err instanceof Error ? err : new Error(
|
|
4068
|
+
onError(err instanceof Error ? err : new Error(String(err)));
|
|
3499
4069
|
}
|
|
3500
4070
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
} catch (err) {
|
|
4071
|
+
});
|
|
4072
|
+
ws.on("error", (error) => {
|
|
4073
|
+
clearTimeout(connectionTimeout);
|
|
3505
4074
|
if (onError) {
|
|
3506
|
-
onError(
|
|
4075
|
+
onError(error);
|
|
3507
4076
|
}
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
4077
|
+
if (!connected) {
|
|
4078
|
+
reject(error);
|
|
4079
|
+
}
|
|
4080
|
+
});
|
|
4081
|
+
ws.on("close", (code, reason) => {
|
|
4082
|
+
clearTimeout(connectionTimeout);
|
|
4083
|
+
connected = false;
|
|
4084
|
+
if (pingInterval) {
|
|
4085
|
+
clearInterval(pingInterval);
|
|
4086
|
+
pingInterval = null;
|
|
4087
|
+
}
|
|
4088
|
+
if (onClose) {
|
|
4089
|
+
onClose(code, reason.toString());
|
|
4090
|
+
}
|
|
4091
|
+
if (notifyCloseResolve) {
|
|
4092
|
+
notifyCloseResolve();
|
|
4093
|
+
notifyCloseResolve = null;
|
|
4094
|
+
}
|
|
4095
|
+
});
|
|
3514
4096
|
});
|
|
3515
|
-
|
|
4097
|
+
}
|
|
4098
|
+
async function runLoop() {
|
|
4099
|
+
while (shouldRun) {
|
|
3516
4100
|
connected = false;
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
4101
|
+
let connectError = null;
|
|
4102
|
+
do {
|
|
4103
|
+
try {
|
|
4104
|
+
await setupWebSocket();
|
|
4105
|
+
connectError = null;
|
|
4106
|
+
} catch (err) {
|
|
4107
|
+
connectError = err instanceof Error ? err : new Error(String(err));
|
|
4108
|
+
if (onError) {
|
|
4109
|
+
onError(connectError);
|
|
4110
|
+
}
|
|
4111
|
+
if (shouldRun) {
|
|
4112
|
+
await sleep3(CONNECT_ATTEMPT_WAIT);
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
} while (connectError && shouldRun);
|
|
4116
|
+
if (!shouldRun) break;
|
|
4117
|
+
await new Promise((resolve) => {
|
|
4118
|
+
notifyCloseResolve = resolve;
|
|
4119
|
+
stopResolve = resolve;
|
|
4120
|
+
reconnectTimer = setTimeout(() => {
|
|
4121
|
+
cleanupConnection();
|
|
4122
|
+
resolve();
|
|
4123
|
+
}, reconnectInterval);
|
|
4124
|
+
});
|
|
4125
|
+
if (reconnectTimer) {
|
|
4126
|
+
clearTimeout(reconnectTimer);
|
|
4127
|
+
reconnectTimer = null;
|
|
3529
4128
|
}
|
|
3530
|
-
|
|
4129
|
+
notifyCloseResolve = null;
|
|
4130
|
+
stopResolve = null;
|
|
4131
|
+
}
|
|
4132
|
+
cleanupConnection();
|
|
3531
4133
|
}
|
|
3532
|
-
|
|
4134
|
+
runLoop();
|
|
3533
4135
|
return {
|
|
3534
4136
|
close: () => {
|
|
3535
|
-
|
|
3536
|
-
if (
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
}
|
|
3540
|
-
if (ws) {
|
|
3541
|
-
ws.close(1e3, "Connection Done");
|
|
3542
|
-
ws = null;
|
|
4137
|
+
shouldRun = false;
|
|
4138
|
+
if (stopResolve) {
|
|
4139
|
+
stopResolve();
|
|
4140
|
+
stopResolve = null;
|
|
3543
4141
|
}
|
|
4142
|
+
cleanupConnection();
|
|
3544
4143
|
},
|
|
3545
4144
|
isConnected: () => connected
|
|
3546
4145
|
};
|