@happyvertical/accounting 0.74.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/dist/chunks/OAuthClient-fC3cd77X.js +17475 -0
- package/dist/chunks/OAuthClient-fC3cd77X.js.map +1 -0
- package/dist/chunks/index-D0bqSiCo.js +893 -0
- package/dist/chunks/index-D0bqSiCo.js.map +1 -0
- package/dist/chunks/index-DO-cM79R.js +772 -0
- package/dist/chunks/index-DO-cM79R.js.map +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/quickbooks/index.d.ts +43 -0
- package/dist/providers/quickbooks/index.d.ts.map +1 -0
- package/dist/providers/stripe/index.d.ts +26 -0
- package/dist/providers/stripe/index.d.ts.map +1 -0
- package/dist/types.d.ts +670 -0
- package/dist/types.d.ts.map +1 -0
- package/metadata.json +30 -0
- package/package.json +58 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import { randomUUID, createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
class StripeProvider {
|
|
3
|
+
type = "stripe";
|
|
4
|
+
options;
|
|
5
|
+
fetchImpl;
|
|
6
|
+
customers;
|
|
7
|
+
invoices;
|
|
8
|
+
vendors;
|
|
9
|
+
bills;
|
|
10
|
+
payments;
|
|
11
|
+
audit;
|
|
12
|
+
webhooks;
|
|
13
|
+
billing;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = {
|
|
16
|
+
timeout: 3e4,
|
|
17
|
+
maxRetries: 3,
|
|
18
|
+
apiBaseUrl: "https://api.stripe.com",
|
|
19
|
+
webhookTolerance: 300,
|
|
20
|
+
...options
|
|
21
|
+
};
|
|
22
|
+
this.fetchImpl = options.fetch || fetch;
|
|
23
|
+
this.customers = new StripeCustomerOperations(this);
|
|
24
|
+
this.invoices = new StripeInvoiceOperations(this);
|
|
25
|
+
this.vendors = new UnsupportedVendorOperations();
|
|
26
|
+
this.bills = new UnsupportedBillOperations();
|
|
27
|
+
this.payments = new StripePaymentOperations(this);
|
|
28
|
+
this.audit = new StripeAuditOperations(this);
|
|
29
|
+
this.webhooks = new StripeWebhookOperations(this.options);
|
|
30
|
+
this.billing = new StripeBillingOperationsImpl(this);
|
|
31
|
+
}
|
|
32
|
+
async request(method, endpoint, params) {
|
|
33
|
+
const timeout = this.options.timeout || 3e4;
|
|
34
|
+
const maxRetries = this.options.maxRetries || 3;
|
|
35
|
+
const idempotencyKey = method === "POST" ? `sdk-${randomUUID()}` : null;
|
|
36
|
+
let lastError = null;
|
|
37
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
40
|
+
try {
|
|
41
|
+
const url = this.buildUrl(endpoint, method === "GET" ? params : {});
|
|
42
|
+
const body = method === "POST" && params ? encodeStripeForm(params).toString() : void 0;
|
|
43
|
+
const headers = {
|
|
44
|
+
Authorization: `Bearer ${this.options.secretKey}`,
|
|
45
|
+
Accept: "application/json"
|
|
46
|
+
};
|
|
47
|
+
if (method === "POST") {
|
|
48
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
49
|
+
if (idempotencyKey) {
|
|
50
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (this.options.apiVersion) {
|
|
54
|
+
headers["Stripe-Version"] = this.options.apiVersion;
|
|
55
|
+
}
|
|
56
|
+
const response = await this.fetchImpl(url, {
|
|
57
|
+
method,
|
|
58
|
+
headers,
|
|
59
|
+
body,
|
|
60
|
+
signal: controller.signal
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errorText = await response.text();
|
|
64
|
+
const error = new Error(
|
|
65
|
+
`Stripe API error (${response.status}): ${errorText}`
|
|
66
|
+
);
|
|
67
|
+
if (response.status < 500 && response.status !== 429) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
lastError = error;
|
|
71
|
+
} else {
|
|
72
|
+
const text = await response.text();
|
|
73
|
+
return text ? JSON.parse(text) : {};
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
77
|
+
const statusMatch = lastError.message.match(
|
|
78
|
+
/Stripe API error \((\d+)\)/
|
|
79
|
+
);
|
|
80
|
+
if (statusMatch) {
|
|
81
|
+
const status = Number.parseInt(statusMatch[1], 10);
|
|
82
|
+
if (status >= 400 && status < 500 && status !== 429) {
|
|
83
|
+
throw lastError;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
clearTimeout(timeoutId);
|
|
88
|
+
}
|
|
89
|
+
if (attempt < maxRetries) {
|
|
90
|
+
await sleep(2 ** attempt * 500);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw lastError || new Error("Stripe request failed after retries");
|
|
94
|
+
}
|
|
95
|
+
buildUrl(endpoint, params) {
|
|
96
|
+
const baseUrl = this.options.apiBaseUrl || "https://api.stripe.com";
|
|
97
|
+
const url = new URL(endpoint, baseUrl);
|
|
98
|
+
if (params && Object.keys(params).length > 0) {
|
|
99
|
+
for (const [key, value] of encodeStripeForm(params).entries()) {
|
|
100
|
+
url.searchParams.append(key, value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return url.toString();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
class StripeCustomerOperations {
|
|
107
|
+
constructor(provider) {
|
|
108
|
+
this.provider = provider;
|
|
109
|
+
}
|
|
110
|
+
async push(customer) {
|
|
111
|
+
const response = await this.provider.request(
|
|
112
|
+
"POST",
|
|
113
|
+
"/v1/customers",
|
|
114
|
+
mapCustomerToStripe(customer)
|
|
115
|
+
);
|
|
116
|
+
return {
|
|
117
|
+
action: "created",
|
|
118
|
+
externalId: response.id,
|
|
119
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async pull(externalId) {
|
|
123
|
+
const customer = await this.provider.request(
|
|
124
|
+
"GET",
|
|
125
|
+
`/v1/customers/${encodeURIComponent(externalId)}`
|
|
126
|
+
);
|
|
127
|
+
return mapStripeCustomer(customer);
|
|
128
|
+
}
|
|
129
|
+
async list(options = {}) {
|
|
130
|
+
const response = await this.provider.request("GET", "/v1/customers", mapListOptions(options));
|
|
131
|
+
return response.data.map(mapStripeCustomer);
|
|
132
|
+
}
|
|
133
|
+
async sync(customer) {
|
|
134
|
+
if (!customer.externalId) {
|
|
135
|
+
return this.push(customer);
|
|
136
|
+
}
|
|
137
|
+
await this.provider.request(
|
|
138
|
+
"POST",
|
|
139
|
+
`/v1/customers/${encodeURIComponent(customer.externalId)}`,
|
|
140
|
+
mapCustomerToStripe(customer)
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
action: "updated",
|
|
144
|
+
externalId: customer.externalId,
|
|
145
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
class StripeInvoiceOperations {
|
|
150
|
+
constructor(provider) {
|
|
151
|
+
this.provider = provider;
|
|
152
|
+
}
|
|
153
|
+
async push(invoice) {
|
|
154
|
+
if (!invoice.customerExternalId) {
|
|
155
|
+
throw new Error("Stripe invoices require customerExternalId");
|
|
156
|
+
}
|
|
157
|
+
for (const lineItem of invoice.lineItems) {
|
|
158
|
+
await this.provider.request("POST", "/v1/invoiceitems", {
|
|
159
|
+
customer: invoice.customerExternalId,
|
|
160
|
+
currency: invoice.currency || "usd",
|
|
161
|
+
description: lineItem.description,
|
|
162
|
+
quantity: lineItem.quantity,
|
|
163
|
+
unit_amount_decimal: String(Math.round(lineItem.unitPrice * 100)),
|
|
164
|
+
metadata: normalizeMetadata({
|
|
165
|
+
local_sku: lineItem.sku,
|
|
166
|
+
local_discount: lineItem.discount,
|
|
167
|
+
local_tax_rate: lineItem.taxRate
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const response = await this.provider.request(
|
|
172
|
+
"POST",
|
|
173
|
+
"/v1/invoices",
|
|
174
|
+
mapInvoiceToStripe(invoice)
|
|
175
|
+
);
|
|
176
|
+
return {
|
|
177
|
+
action: "created",
|
|
178
|
+
externalId: response.id,
|
|
179
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async pull(externalId) {
|
|
183
|
+
const invoice = await this.provider.request(
|
|
184
|
+
"GET",
|
|
185
|
+
`/v1/invoices/${encodeURIComponent(externalId)}`
|
|
186
|
+
);
|
|
187
|
+
return mapStripeInvoice(invoice);
|
|
188
|
+
}
|
|
189
|
+
async list(options = {}) {
|
|
190
|
+
const response = await this.provider.request("GET", "/v1/invoices", mapListOptions(options));
|
|
191
|
+
return response.data.map(mapStripeInvoice);
|
|
192
|
+
}
|
|
193
|
+
async sync(invoice) {
|
|
194
|
+
if (!invoice.externalId) {
|
|
195
|
+
return this.push(invoice);
|
|
196
|
+
}
|
|
197
|
+
await this.provider.request(
|
|
198
|
+
"POST",
|
|
199
|
+
`/v1/invoices/${encodeURIComponent(invoice.externalId)}`,
|
|
200
|
+
mapInvoiceUpdateToStripe(invoice)
|
|
201
|
+
);
|
|
202
|
+
return {
|
|
203
|
+
action: "updated",
|
|
204
|
+
externalId: invoice.externalId,
|
|
205
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
async send(externalId) {
|
|
209
|
+
await this.provider.request(
|
|
210
|
+
"POST",
|
|
211
|
+
`/v1/invoices/${encodeURIComponent(externalId)}/send`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
async void(externalId) {
|
|
215
|
+
await this.provider.request(
|
|
216
|
+
"POST",
|
|
217
|
+
`/v1/invoices/${encodeURIComponent(externalId)}/void`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
class StripePaymentOperations {
|
|
222
|
+
constructor(provider) {
|
|
223
|
+
this.provider = provider;
|
|
224
|
+
}
|
|
225
|
+
async pull(externalId) {
|
|
226
|
+
const paymentIntent = await this.provider.request(
|
|
227
|
+
"GET",
|
|
228
|
+
`/v1/payment_intents/${encodeURIComponent(externalId)}`
|
|
229
|
+
);
|
|
230
|
+
return mapStripePaymentIntent(paymentIntent);
|
|
231
|
+
}
|
|
232
|
+
async list(options = {}) {
|
|
233
|
+
const response = await this.provider.request("GET", "/v1/payment_intents", mapListOptions(options));
|
|
234
|
+
return response.data.map(mapStripePaymentIntent);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
class StripeBillingOperationsImpl {
|
|
238
|
+
constructor(provider) {
|
|
239
|
+
this.provider = provider;
|
|
240
|
+
}
|
|
241
|
+
async createCheckoutSession(input) {
|
|
242
|
+
const response = await this.provider.request(
|
|
243
|
+
"POST",
|
|
244
|
+
"/v1/checkout/sessions",
|
|
245
|
+
mapCheckoutSessionToStripe(input)
|
|
246
|
+
);
|
|
247
|
+
return {
|
|
248
|
+
externalId: response.id,
|
|
249
|
+
url: response.url || null,
|
|
250
|
+
customerExternalId: response.customer || void 0,
|
|
251
|
+
subscriptionExternalId: response.subscription || void 0,
|
|
252
|
+
paymentIntentExternalId: response.payment_intent || void 0,
|
|
253
|
+
raw: response
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async createCustomerPortalSession(input) {
|
|
257
|
+
const response = await this.provider.request(
|
|
258
|
+
"POST",
|
|
259
|
+
"/v1/billing_portal/sessions",
|
|
260
|
+
{
|
|
261
|
+
customer: input.customerExternalId,
|
|
262
|
+
return_url: input.returnUrl,
|
|
263
|
+
configuration: input.configurationExternalId
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
return {
|
|
267
|
+
externalId: response.id,
|
|
268
|
+
url: response.url,
|
|
269
|
+
raw: response
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async retrieveSubscriptionStatus(subscriptionExternalId) {
|
|
273
|
+
const subscription = await this.provider.request(
|
|
274
|
+
"GET",
|
|
275
|
+
`/v1/subscriptions/${encodeURIComponent(subscriptionExternalId)}`
|
|
276
|
+
);
|
|
277
|
+
return mapStripeSubscription(subscription);
|
|
278
|
+
}
|
|
279
|
+
async listCustomerSubscriptions(customerExternalId) {
|
|
280
|
+
const response = await this.provider.request("GET", "/v1/subscriptions", {
|
|
281
|
+
customer: customerExternalId,
|
|
282
|
+
status: "all",
|
|
283
|
+
limit: 100
|
|
284
|
+
});
|
|
285
|
+
return response.data.map(mapStripeSubscription);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
class UnsupportedVendorOperations {
|
|
289
|
+
async push(_vendor) {
|
|
290
|
+
throw new Error("Stripe does not support vendor operations");
|
|
291
|
+
}
|
|
292
|
+
async pull(_externalId) {
|
|
293
|
+
throw new Error("Stripe does not support vendor operations");
|
|
294
|
+
}
|
|
295
|
+
async list(_options) {
|
|
296
|
+
throw new Error("Stripe does not support vendor operations");
|
|
297
|
+
}
|
|
298
|
+
async sync(_vendor) {
|
|
299
|
+
throw new Error("Stripe does not support vendor operations");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
class UnsupportedBillOperations {
|
|
303
|
+
async push(_bill) {
|
|
304
|
+
throw new Error("Stripe does not support bill operations");
|
|
305
|
+
}
|
|
306
|
+
async pull(_externalId) {
|
|
307
|
+
throw new Error("Stripe does not support bill operations");
|
|
308
|
+
}
|
|
309
|
+
async list(_options) {
|
|
310
|
+
throw new Error("Stripe does not support bill operations");
|
|
311
|
+
}
|
|
312
|
+
async sync(_bill) {
|
|
313
|
+
throw new Error("Stripe does not support bill operations");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
class StripeAuditOperations {
|
|
317
|
+
constructor(provider) {
|
|
318
|
+
this.provider = provider;
|
|
319
|
+
}
|
|
320
|
+
async reconcileCustomers(locals) {
|
|
321
|
+
const externals = await this.provider.customers.list({ limit: 100 });
|
|
322
|
+
return reconcileRecords("stripe", locals, externals, compareCustomer);
|
|
323
|
+
}
|
|
324
|
+
async reconcileInvoices(locals, dateRange) {
|
|
325
|
+
const externals = await this.provider.invoices.list({
|
|
326
|
+
limit: 100,
|
|
327
|
+
startDate: dateRange?.start,
|
|
328
|
+
endDate: dateRange?.end
|
|
329
|
+
});
|
|
330
|
+
return reconcileRecords("stripe", locals, externals, compareInvoice);
|
|
331
|
+
}
|
|
332
|
+
async reconcileVendors(_locals) {
|
|
333
|
+
throw new Error("Stripe does not support vendor operations");
|
|
334
|
+
}
|
|
335
|
+
async reconcileBills(_locals) {
|
|
336
|
+
throw new Error("Stripe does not support bill operations");
|
|
337
|
+
}
|
|
338
|
+
async reconcilePayments(locals, dateRange) {
|
|
339
|
+
const externals = await this.provider.payments.list({
|
|
340
|
+
limit: 100,
|
|
341
|
+
startDate: dateRange?.start,
|
|
342
|
+
endDate: dateRange?.end
|
|
343
|
+
});
|
|
344
|
+
return reconcileRecords("stripe", locals, externals, comparePayment);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
class StripeWebhookOperations {
|
|
348
|
+
constructor(options) {
|
|
349
|
+
this.options = options;
|
|
350
|
+
}
|
|
351
|
+
verify(payload, signature, secret) {
|
|
352
|
+
const endpointSecret = secret || this.options.webhookSecret;
|
|
353
|
+
if (!endpointSecret) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
"Stripe webhook secret not provided. Pass it as the secret parameter or configure webhookSecret in options."
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const parsed = parseStripeSignatureHeader(signature);
|
|
359
|
+
if (!parsed.timestamp || parsed.signatures.length === 0) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
const tolerance = this.options.webhookTolerance ?? 300;
|
|
363
|
+
const timestamp = Number.parseInt(parsed.timestamp, 10);
|
|
364
|
+
if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1e3 - timestamp) > tolerance) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
const signedPayload = `${parsed.timestamp}.${payload}`;
|
|
368
|
+
const expected = createHmac("sha256", endpointSecret).update(signedPayload).digest("hex");
|
|
369
|
+
return parsed.signatures.some(
|
|
370
|
+
(value) => constantTimeEqualHex(value, expected)
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
parse(payload) {
|
|
374
|
+
let event;
|
|
375
|
+
try {
|
|
376
|
+
event = JSON.parse(payload);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
const message = error instanceof Error ? error.message : "Unknown JSON parse error";
|
|
379
|
+
throw new Error(`Invalid Stripe webhook payload: ${message}`);
|
|
380
|
+
}
|
|
381
|
+
const resource = event?.data?.object;
|
|
382
|
+
return {
|
|
383
|
+
type: event?.type || "unknown",
|
|
384
|
+
provider: "stripe",
|
|
385
|
+
timestamp: unixToDate(event?.created) || /* @__PURE__ */ new Date(),
|
|
386
|
+
payload: event,
|
|
387
|
+
resourceType: mapStripeResourceType(
|
|
388
|
+
resource?.object || void 0,
|
|
389
|
+
event?.type || void 0
|
|
390
|
+
),
|
|
391
|
+
resourceId: typeof resource?.id === "string" ? resource.id : void 0
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function encodeStripeForm(params) {
|
|
396
|
+
const form = new URLSearchParams();
|
|
397
|
+
for (const [key, value] of Object.entries(params)) {
|
|
398
|
+
appendStripeFormValue(form, key, value);
|
|
399
|
+
}
|
|
400
|
+
return form;
|
|
401
|
+
}
|
|
402
|
+
function appendStripeFormValue(form, key, value) {
|
|
403
|
+
if (value === void 0 || value === null) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (value instanceof Date) {
|
|
407
|
+
form.append(key, String(toUnixSeconds(value)));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (Array.isArray(value)) {
|
|
411
|
+
value.forEach((item, index) => {
|
|
412
|
+
appendStripeFormValue(form, `${key}[${index}]`, item);
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (typeof value === "object") {
|
|
417
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
418
|
+
appendStripeFormValue(form, `${key}[${childKey}]`, childValue);
|
|
419
|
+
}
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
form.append(key, String(value));
|
|
423
|
+
}
|
|
424
|
+
function mapCustomerToStripe(customer) {
|
|
425
|
+
const stripeCustomer = {
|
|
426
|
+
name: customer.name,
|
|
427
|
+
email: customer.email,
|
|
428
|
+
phone: customer.phone,
|
|
429
|
+
address: customer.billingAddress ? {
|
|
430
|
+
line1: customer.billingAddress.street1,
|
|
431
|
+
line2: customer.billingAddress.street2,
|
|
432
|
+
city: customer.billingAddress.city,
|
|
433
|
+
state: customer.billingAddress.state,
|
|
434
|
+
postal_code: customer.billingAddress.postalCode,
|
|
435
|
+
country: customer.billingAddress.country
|
|
436
|
+
} : void 0,
|
|
437
|
+
metadata: normalizeMetadata({
|
|
438
|
+
local_id: customer.id,
|
|
439
|
+
payment_terms: customer.paymentTerms,
|
|
440
|
+
...customer.metadata
|
|
441
|
+
})
|
|
442
|
+
};
|
|
443
|
+
if (customer.taxExempt !== void 0) {
|
|
444
|
+
stripeCustomer.tax_exempt = customer.taxExempt ? "exempt" : "none";
|
|
445
|
+
}
|
|
446
|
+
return stripeCustomer;
|
|
447
|
+
}
|
|
448
|
+
function mapInvoiceToStripe(invoice) {
|
|
449
|
+
return {
|
|
450
|
+
customer: invoice.customerExternalId,
|
|
451
|
+
collection_method: "send_invoice",
|
|
452
|
+
due_date: invoice.dueDate,
|
|
453
|
+
auto_advance: false,
|
|
454
|
+
pending_invoice_items_behavior: "include",
|
|
455
|
+
metadata: normalizeMetadata({
|
|
456
|
+
local_id: invoice.id,
|
|
457
|
+
invoice_number: invoice.invoiceNumber,
|
|
458
|
+
reference: invoice.reference,
|
|
459
|
+
memo: invoice.memo,
|
|
460
|
+
tax_amount: invoice.taxAmount,
|
|
461
|
+
subtotal: invoice.subtotal,
|
|
462
|
+
total_amount: invoice.totalAmount,
|
|
463
|
+
...invoice.metadata
|
|
464
|
+
})
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function mapInvoiceUpdateToStripe(invoice) {
|
|
468
|
+
return {
|
|
469
|
+
due_date: invoice.dueDate,
|
|
470
|
+
metadata: normalizeMetadata({
|
|
471
|
+
local_id: invoice.id,
|
|
472
|
+
invoice_number: invoice.invoiceNumber,
|
|
473
|
+
reference: invoice.reference,
|
|
474
|
+
memo: invoice.memo,
|
|
475
|
+
...invoice.metadata
|
|
476
|
+
})
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function mapCheckoutSessionToStripe(input) {
|
|
480
|
+
return {
|
|
481
|
+
mode: input.mode || "subscription",
|
|
482
|
+
success_url: input.successUrl,
|
|
483
|
+
cancel_url: input.cancelUrl,
|
|
484
|
+
customer: input.customerExternalId,
|
|
485
|
+
customer_email: input.customerExternalId ? void 0 : input.customerEmail,
|
|
486
|
+
client_reference_id: input.clientReferenceId,
|
|
487
|
+
allow_promotion_codes: input.allowPromotionCodes,
|
|
488
|
+
line_items: input.lineItems.map(mapCheckoutLineItem),
|
|
489
|
+
metadata: normalizeMetadata(input.metadata || {})
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function mapCheckoutLineItem(lineItem) {
|
|
493
|
+
if (lineItem.price) {
|
|
494
|
+
return {
|
|
495
|
+
price: lineItem.price,
|
|
496
|
+
quantity: lineItem.quantity || 1
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (!lineItem.priceData) {
|
|
500
|
+
throw new Error("Stripe checkout line item requires price or priceData");
|
|
501
|
+
}
|
|
502
|
+
const productData = lineItem.priceData.product ? void 0 : { name: lineItem.priceData.productName || "Subscription" };
|
|
503
|
+
return {
|
|
504
|
+
quantity: lineItem.quantity || 1,
|
|
505
|
+
price_data: {
|
|
506
|
+
currency: lineItem.priceData.currency,
|
|
507
|
+
unit_amount: lineItem.priceData.unitAmount,
|
|
508
|
+
product: lineItem.priceData.product,
|
|
509
|
+
product_data: productData,
|
|
510
|
+
recurring: lineItem.priceData.recurring ? {
|
|
511
|
+
interval: lineItem.priceData.recurring.interval,
|
|
512
|
+
interval_count: lineItem.priceData.recurring.intervalCount
|
|
513
|
+
} : void 0
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function mapListOptions(options) {
|
|
518
|
+
return {
|
|
519
|
+
limit: options.limit,
|
|
520
|
+
starting_after: typeof options.offset === "string" ? options.offset : void 0,
|
|
521
|
+
created: {
|
|
522
|
+
gte: options.startDate,
|
|
523
|
+
lte: options.endDate
|
|
524
|
+
},
|
|
525
|
+
status: options.status
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function mapStripeCustomer(customer) {
|
|
529
|
+
return {
|
|
530
|
+
externalId: customer.id,
|
|
531
|
+
provider: "stripe",
|
|
532
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
533
|
+
name: customer.name || "",
|
|
534
|
+
email: customer.email || void 0,
|
|
535
|
+
phone: customer.phone || void 0,
|
|
536
|
+
billingAddress: customer.address ? {
|
|
537
|
+
street1: customer.address.line1 || void 0,
|
|
538
|
+
street2: customer.address.line2 || void 0,
|
|
539
|
+
city: customer.address.city || void 0,
|
|
540
|
+
state: customer.address.state || void 0,
|
|
541
|
+
postalCode: customer.address.postal_code || void 0,
|
|
542
|
+
country: customer.address.country || void 0
|
|
543
|
+
} : void 0,
|
|
544
|
+
balance: customer.balance === null || customer.balance === void 0 ? void 0 : centsToMoney(customer.balance),
|
|
545
|
+
currency: customer.currency || void 0,
|
|
546
|
+
raw: customer
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function mapStripeInvoice(invoice) {
|
|
550
|
+
return {
|
|
551
|
+
externalId: invoice.id,
|
|
552
|
+
provider: "stripe",
|
|
553
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
554
|
+
invoiceNumber: invoice.number || invoice.id,
|
|
555
|
+
customerExternalId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id || "",
|
|
556
|
+
issueDate: unixToDate(invoice.created) || /* @__PURE__ */ new Date(),
|
|
557
|
+
dueDate: unixToDate(invoice.due_date) || unixToDate(invoice.created) || /* @__PURE__ */ new Date(),
|
|
558
|
+
subtotal: centsToMoney(invoice.subtotal || 0),
|
|
559
|
+
taxAmount: centsToMoney(invoice.tax || 0),
|
|
560
|
+
totalAmount: centsToMoney(invoice.total || 0),
|
|
561
|
+
amountPaid: centsToMoney(invoice.amount_paid || 0),
|
|
562
|
+
balance: centsToMoney(invoice.amount_remaining || 0),
|
|
563
|
+
status: mapStripeInvoiceStatus(invoice.status, invoice.due_date),
|
|
564
|
+
currency: invoice.currency || "usd",
|
|
565
|
+
raw: invoice
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function mapStripePaymentIntent(paymentIntent) {
|
|
569
|
+
return {
|
|
570
|
+
externalId: paymentIntent.id,
|
|
571
|
+
provider: "stripe",
|
|
572
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
573
|
+
amount: centsToMoney(paymentIntent.amount || 0),
|
|
574
|
+
currency: paymentIntent.currency || "usd",
|
|
575
|
+
paidAt: unixToDate(paymentIntent.created) || /* @__PURE__ */ new Date(),
|
|
576
|
+
method: paymentIntent.payment_method_types?.[0],
|
|
577
|
+
transactionId: paymentIntent.latest_charge || paymentIntent.id,
|
|
578
|
+
invoiceExternalIds: paymentIntent.invoice ? [paymentIntent.invoice] : void 0,
|
|
579
|
+
status: mapStripePaymentStatus(paymentIntent.status),
|
|
580
|
+
raw: paymentIntent
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function mapStripeSubscription(subscription) {
|
|
584
|
+
return {
|
|
585
|
+
externalId: subscription.id,
|
|
586
|
+
status: subscription.status,
|
|
587
|
+
customerExternalId: typeof subscription.customer === "string" ? subscription.customer : subscription.customer.id,
|
|
588
|
+
currentPeriodStart: unixToDate(subscription.current_period_start),
|
|
589
|
+
currentPeriodEnd: unixToDate(subscription.current_period_end),
|
|
590
|
+
cancelAtPeriodEnd: Boolean(subscription.cancel_at_period_end),
|
|
591
|
+
canceledAt: unixToDate(subscription.canceled_at),
|
|
592
|
+
trialEnd: unixToDate(subscription.trial_end),
|
|
593
|
+
raw: subscription
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function mapStripeInvoiceStatus(status, dueDate) {
|
|
597
|
+
switch (status) {
|
|
598
|
+
case "draft":
|
|
599
|
+
return "draft";
|
|
600
|
+
case "paid":
|
|
601
|
+
return "paid";
|
|
602
|
+
case "void":
|
|
603
|
+
return "voided";
|
|
604
|
+
case "open":
|
|
605
|
+
if (dueDate && dueDate * 1e3 < Date.now()) {
|
|
606
|
+
return "overdue";
|
|
607
|
+
}
|
|
608
|
+
return "sent";
|
|
609
|
+
default:
|
|
610
|
+
return "sent";
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function mapStripePaymentStatus(status) {
|
|
614
|
+
switch (status) {
|
|
615
|
+
case "succeeded":
|
|
616
|
+
return "completed";
|
|
617
|
+
case "processing":
|
|
618
|
+
case "requires_action":
|
|
619
|
+
case "requires_capture":
|
|
620
|
+
case "requires_confirmation":
|
|
621
|
+
return "pending";
|
|
622
|
+
case "canceled":
|
|
623
|
+
case "requires_payment_method":
|
|
624
|
+
return "failed";
|
|
625
|
+
default:
|
|
626
|
+
return "pending";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function mapStripeResourceType(object, eventType) {
|
|
630
|
+
if (object === "customer" || eventType?.startsWith("customer.")) {
|
|
631
|
+
return "customer";
|
|
632
|
+
}
|
|
633
|
+
if (object === "invoice" || eventType?.startsWith("invoice.")) {
|
|
634
|
+
return "invoice";
|
|
635
|
+
}
|
|
636
|
+
if (object === "payment_intent" || object === "charge" || eventType?.startsWith("payment_intent.") || eventType?.startsWith("charge.")) {
|
|
637
|
+
return "payment";
|
|
638
|
+
}
|
|
639
|
+
return void 0;
|
|
640
|
+
}
|
|
641
|
+
function normalizeMetadata(metadata) {
|
|
642
|
+
const normalized = {};
|
|
643
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
644
|
+
if (value !== void 0 && value !== null) {
|
|
645
|
+
normalized[key] = String(value);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return normalized;
|
|
649
|
+
}
|
|
650
|
+
function parseStripeSignatureHeader(signature) {
|
|
651
|
+
const parts = signature.split(",").map((part) => part.trim());
|
|
652
|
+
const parsed = {
|
|
653
|
+
signatures: []
|
|
654
|
+
};
|
|
655
|
+
for (const part of parts) {
|
|
656
|
+
const separatorIndex = part.indexOf("=");
|
|
657
|
+
if (separatorIndex === -1) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const key = part.slice(0, separatorIndex);
|
|
661
|
+
const value = part.slice(separatorIndex + 1);
|
|
662
|
+
if (key === "t") {
|
|
663
|
+
parsed.timestamp = value;
|
|
664
|
+
}
|
|
665
|
+
if (key === "v1" && value) {
|
|
666
|
+
parsed.signatures.push(value);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return parsed;
|
|
670
|
+
}
|
|
671
|
+
function constantTimeEqualHex(left, right) {
|
|
672
|
+
try {
|
|
673
|
+
const leftBuffer = Buffer.from(left, "hex");
|
|
674
|
+
const rightBuffer = Buffer.from(right, "hex");
|
|
675
|
+
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
|
|
676
|
+
} catch {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function reconcileRecords(provider, locals, externals, compare) {
|
|
681
|
+
const externalById = new Map(
|
|
682
|
+
externals.map((external) => [external.externalId, external])
|
|
683
|
+
);
|
|
684
|
+
const matched = [];
|
|
685
|
+
const discrepancies = [];
|
|
686
|
+
const localOnly = [];
|
|
687
|
+
const seenExternalIds = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const local of locals) {
|
|
689
|
+
const external = local.externalId ? externalById.get(local.externalId) : void 0;
|
|
690
|
+
if (!external) {
|
|
691
|
+
localOnly.push(local);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
seenExternalIds.add(external.externalId);
|
|
695
|
+
const differences = compare(local, external);
|
|
696
|
+
if (differences.length === 0) {
|
|
697
|
+
matched.push({ local, external, status: "identical" });
|
|
698
|
+
} else {
|
|
699
|
+
discrepancies.push({ local, external, differences });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const externalOnly = externals.filter(
|
|
703
|
+
(external) => !seenExternalIds.has(external.externalId)
|
|
704
|
+
);
|
|
705
|
+
return {
|
|
706
|
+
provider,
|
|
707
|
+
auditedAt: /* @__PURE__ */ new Date(),
|
|
708
|
+
matched,
|
|
709
|
+
localOnly,
|
|
710
|
+
externalOnly,
|
|
711
|
+
discrepancies,
|
|
712
|
+
summary: {
|
|
713
|
+
total: locals.length + externalOnly.length,
|
|
714
|
+
matched: matched.length,
|
|
715
|
+
localOnly: localOnly.length,
|
|
716
|
+
externalOnly: externalOnly.length,
|
|
717
|
+
discrepancies: discrepancies.length
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function compareCustomer(local, external) {
|
|
722
|
+
const customer = external;
|
|
723
|
+
return [
|
|
724
|
+
compareField("name", local.name, customer.name),
|
|
725
|
+
compareField("email", local.email, customer.email)
|
|
726
|
+
].filter((diff) => Boolean(diff));
|
|
727
|
+
}
|
|
728
|
+
function compareInvoice(local, external) {
|
|
729
|
+
const invoice = external;
|
|
730
|
+
return [
|
|
731
|
+
compareField("totalAmount", local.totalAmount, invoice.totalAmount),
|
|
732
|
+
compareField("currency", local.currency || "usd", invoice.currency)
|
|
733
|
+
].filter((diff) => Boolean(diff));
|
|
734
|
+
}
|
|
735
|
+
function comparePayment(local, external) {
|
|
736
|
+
const payment = external;
|
|
737
|
+
return [
|
|
738
|
+
compareField("amount", local.amount, payment.amount),
|
|
739
|
+
compareField("currency", local.currency || "usd", payment.currency)
|
|
740
|
+
].filter((diff) => Boolean(diff));
|
|
741
|
+
}
|
|
742
|
+
function compareField(field, localValue, externalValue) {
|
|
743
|
+
if (valuesEqual(localValue, externalValue)) {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
return { field, localValue, externalValue };
|
|
747
|
+
}
|
|
748
|
+
function valuesEqual(left, right) {
|
|
749
|
+
if (left === right) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
753
|
+
return Math.abs(left - right) < 0.01;
|
|
754
|
+
}
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
function centsToMoney(cents) {
|
|
758
|
+
return cents / 100;
|
|
759
|
+
}
|
|
760
|
+
function toUnixSeconds(date) {
|
|
761
|
+
return Math.floor(date.getTime() / 1e3);
|
|
762
|
+
}
|
|
763
|
+
function unixToDate(value) {
|
|
764
|
+
return value === null || value === void 0 ? void 0 : new Date(value * 1e3);
|
|
765
|
+
}
|
|
766
|
+
function sleep(ms) {
|
|
767
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
768
|
+
}
|
|
769
|
+
export {
|
|
770
|
+
StripeProvider
|
|
771
|
+
};
|
|
772
|
+
//# sourceMappingURL=index-DO-cM79R.js.map
|