@happyvertical/accounting 0.80.0 → 0.80.2
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/chunks/OAuthClient-Bv-lzpx8.js +18264 -0
- package/dist/chunks/OAuthClient-Bv-lzpx8.js.map +1 -0
- package/dist/chunks/quickbooks-BF3GrJBw.js +773 -0
- package/dist/chunks/quickbooks-BF3GrJBw.js.map +1 -0
- package/dist/chunks/stripe-BhTiF2Xx.js +644 -0
- package/dist/chunks/stripe-BhTiF2Xx.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +116 -47
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunks/OAuthClient-fC3cd77X.js +0 -17475
- package/dist/chunks/OAuthClient-fC3cd77X.js.map +0 -1
- package/dist/chunks/index-D0bqSiCo.js +0 -893
- package/dist/chunks/index-D0bqSiCo.js.map +0 -1
- package/dist/chunks/index-DO-cM79R.js +0 -772
- package/dist/chunks/index-DO-cM79R.js.map +0 -1
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esmMin = (fn, res, err) => () => {
|
|
10
|
+
if (err) throw err[0];
|
|
11
|
+
try {
|
|
12
|
+
return fn && (res = fn(fn = 0)), res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw err = [e], e;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
18
|
+
var __exportAll = (all, no_symbols) => {
|
|
19
|
+
let target = {};
|
|
20
|
+
for (var name in all) __defProp(target, name, {
|
|
21
|
+
get: all[name],
|
|
22
|
+
enumerable: true
|
|
23
|
+
});
|
|
24
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
25
|
+
return target;
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
29
|
+
key = keys[i];
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
31
|
+
get: ((k) => from[k]).bind(null, key),
|
|
32
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
38
|
+
value: mod,
|
|
39
|
+
enumerable: true
|
|
40
|
+
}) : target, mod));
|
|
41
|
+
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
42
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
|
|
43
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
44
|
+
throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
|
|
45
|
+
});
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/providers/quickbooks/index.ts
|
|
48
|
+
/**
|
|
49
|
+
* QuickBooks Online Provider
|
|
50
|
+
*
|
|
51
|
+
* Implements the AccountingProvider interface for QuickBooks Online.
|
|
52
|
+
* Uses the Intuit OAuth2 library for authentication and the QBO API for operations.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Format a Date to YYYY-MM-DD in local timezone (not UTC)
|
|
56
|
+
* This prevents timezone conversion issues where dates shift by a day
|
|
57
|
+
*/
|
|
58
|
+
function formatLocalDate(date) {
|
|
59
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sleep for a given number of milliseconds
|
|
63
|
+
*/
|
|
64
|
+
function sleep(ms) {
|
|
65
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compare two values for equality (handles dates, objects, etc.)
|
|
69
|
+
*/
|
|
70
|
+
function valuesEqual(a, b) {
|
|
71
|
+
if (a === b) return true;
|
|
72
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
73
|
+
if (typeof a === "number" && typeof b === "number") return Math.abs(a - b) < .01;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* QuickBooks Online accounting provider
|
|
78
|
+
*/
|
|
79
|
+
var QuickBooksProvider = class {
|
|
80
|
+
type = "quickbooks";
|
|
81
|
+
options;
|
|
82
|
+
accessToken = null;
|
|
83
|
+
tokenExpiresAt = null;
|
|
84
|
+
customers;
|
|
85
|
+
invoices;
|
|
86
|
+
vendors;
|
|
87
|
+
bills;
|
|
88
|
+
payments;
|
|
89
|
+
audit;
|
|
90
|
+
webhooks;
|
|
91
|
+
constructor(options) {
|
|
92
|
+
this.options = {
|
|
93
|
+
timeout: 3e4,
|
|
94
|
+
maxRetries: 3,
|
|
95
|
+
...options
|
|
96
|
+
};
|
|
97
|
+
this.customers = new QuickBooksCustomerOperations(this);
|
|
98
|
+
this.invoices = new QuickBooksInvoiceOperations(this);
|
|
99
|
+
this.vendors = new QuickBooksVendorOperations(this);
|
|
100
|
+
this.bills = new QuickBooksBillOperations(this);
|
|
101
|
+
this.payments = new QuickBooksPaymentOperations(this);
|
|
102
|
+
this.audit = new QuickBooksAuditOperations(this);
|
|
103
|
+
this.webhooks = new QuickBooksWebhookOperations(this.options);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the QBO API base URL
|
|
107
|
+
*/
|
|
108
|
+
get baseUrl() {
|
|
109
|
+
return this.options.environment === "production" ? "https://quickbooks.api.intuit.com" : "https://sandbox-quickbooks.api.intuit.com";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the company/realm ID
|
|
113
|
+
*/
|
|
114
|
+
get realmId() {
|
|
115
|
+
return this.options.realmId;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Ensure we have a valid access token, refreshing if needed
|
|
119
|
+
*/
|
|
120
|
+
async ensureAccessToken() {
|
|
121
|
+
if (this.accessToken && this.tokenExpiresAt) {
|
|
122
|
+
if ((/* @__PURE__ */ new Date()).getTime() < this.tokenExpiresAt.getTime() - 300 * 1e3) return this.accessToken;
|
|
123
|
+
}
|
|
124
|
+
const tokens = await this.refreshAccessToken();
|
|
125
|
+
this.accessToken = tokens.accessToken;
|
|
126
|
+
this.tokenExpiresAt = tokens.expiresAt;
|
|
127
|
+
this.options.refreshToken = tokens.refreshToken;
|
|
128
|
+
if (this.options.onTokenRefresh) await this.options.onTokenRefresh(tokens);
|
|
129
|
+
return this.accessToken;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Refresh the OAuth access token
|
|
133
|
+
*/
|
|
134
|
+
async refreshAccessToken() {
|
|
135
|
+
const OAuthClient = (await import("./OAuthClient-Bv-lzpx8.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1))).default;
|
|
136
|
+
const redirectUri = this.options.redirectUri || "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl";
|
|
137
|
+
const oauthClient = new OAuthClient({
|
|
138
|
+
clientId: this.options.clientId,
|
|
139
|
+
clientSecret: this.options.clientSecret,
|
|
140
|
+
environment: this.options.environment === "production" ? "production" : "sandbox",
|
|
141
|
+
redirectUri
|
|
142
|
+
});
|
|
143
|
+
oauthClient.setToken({
|
|
144
|
+
refresh_token: this.options.refreshToken,
|
|
145
|
+
access_token: this.accessToken || ""
|
|
146
|
+
});
|
|
147
|
+
const token = (await oauthClient.refresh()).getJson();
|
|
148
|
+
return {
|
|
149
|
+
accessToken: token.access_token,
|
|
150
|
+
refreshToken: token.refresh_token,
|
|
151
|
+
expiresAt: new Date(Date.now() + token.expires_in * 1e3),
|
|
152
|
+
tokenType: token.token_type
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Make an authenticated request to the QBO API with timeout and retry support
|
|
157
|
+
*/
|
|
158
|
+
async request(method, endpoint, body) {
|
|
159
|
+
const accessToken = await this.ensureAccessToken();
|
|
160
|
+
const url = `${this.baseUrl}/v3/company/${this.realmId}/${endpoint}`;
|
|
161
|
+
const timeout = this.options.timeout || 3e4;
|
|
162
|
+
const maxRetries = this.options.maxRetries || 3;
|
|
163
|
+
let lastError = null;
|
|
164
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
167
|
+
try {
|
|
168
|
+
const response = await fetch(url, {
|
|
169
|
+
method,
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${accessToken}`,
|
|
172
|
+
Accept: "application/json",
|
|
173
|
+
"Content-Type": "application/json"
|
|
174
|
+
},
|
|
175
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
176
|
+
signal: controller.signal
|
|
177
|
+
});
|
|
178
|
+
clearTimeout(timeoutId);
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
const errorText = await response.text();
|
|
181
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) throw new Error(`QBO API error (${response.status}): ${errorText}`);
|
|
182
|
+
throw new Error(`QBO API error (${response.status}): ${errorText}`);
|
|
183
|
+
}
|
|
184
|
+
return await response.json();
|
|
185
|
+
} finally {
|
|
186
|
+
clearTimeout(timeoutId);
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
190
|
+
const statusMatch = lastError.message.match(/QBO API error \((\d+)\)/);
|
|
191
|
+
if (statusMatch) {
|
|
192
|
+
const status = Number.parseInt(statusMatch[1], 10);
|
|
193
|
+
if (status >= 400 && status < 500 && status !== 429) throw lastError;
|
|
194
|
+
}
|
|
195
|
+
if (attempt < maxRetries) await sleep(2 ** attempt * 1e3);
|
|
196
|
+
}
|
|
197
|
+
throw lastError || /* @__PURE__ */ new Error("Request failed after retries");
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Execute a QBO query with pagination support
|
|
201
|
+
*/
|
|
202
|
+
async queryAll(entity, whereClause, maxResults = 1e3) {
|
|
203
|
+
const results = [];
|
|
204
|
+
let startPosition = 1;
|
|
205
|
+
const pageSize = 100;
|
|
206
|
+
while (results.length < maxResults) {
|
|
207
|
+
let query = `SELECT * FROM ${entity}`;
|
|
208
|
+
if (whereClause) query += ` WHERE ${whereClause}`;
|
|
209
|
+
query += ` MAXRESULTS ${pageSize} STARTPOSITION ${startPosition}`;
|
|
210
|
+
const items = (await this.request("GET", `query?query=${encodeURIComponent(query)}`)).QueryResponse[entity] || [];
|
|
211
|
+
results.push(...items);
|
|
212
|
+
if (items.length < pageSize) break;
|
|
213
|
+
startPosition += pageSize;
|
|
214
|
+
}
|
|
215
|
+
return results;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var QuickBooksCustomerOperations = class {
|
|
219
|
+
provider;
|
|
220
|
+
constructor(provider) {
|
|
221
|
+
this.provider = provider;
|
|
222
|
+
}
|
|
223
|
+
async push(customer) {
|
|
224
|
+
const qboCustomer = mapCustomerToQBO(customer);
|
|
225
|
+
return {
|
|
226
|
+
action: "created",
|
|
227
|
+
externalId: (await this.provider.request("POST", "customer", qboCustomer)).Customer.Id,
|
|
228
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async pull(externalId) {
|
|
232
|
+
return mapQBOToCustomer((await this.provider.request("GET", `customer/${externalId}`)).Customer);
|
|
233
|
+
}
|
|
234
|
+
async list(options) {
|
|
235
|
+
const limit = options?.limit || 1e3;
|
|
236
|
+
return (await this.provider.queryAll("Customer", void 0, limit)).map(mapQBOToCustomer);
|
|
237
|
+
}
|
|
238
|
+
async sync(customer) {
|
|
239
|
+
if (customer.externalId) {
|
|
240
|
+
const existing = await this.provider.request("GET", `customer/${customer.externalId}`);
|
|
241
|
+
const qboCustomer = {
|
|
242
|
+
...mapCustomerToQBO(customer),
|
|
243
|
+
Id: customer.externalId,
|
|
244
|
+
SyncToken: existing.Customer.SyncToken
|
|
245
|
+
};
|
|
246
|
+
await this.provider.request("POST", "customer", qboCustomer);
|
|
247
|
+
return {
|
|
248
|
+
action: "updated",
|
|
249
|
+
externalId: customer.externalId,
|
|
250
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return this.push(customer);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
var QuickBooksInvoiceOperations = class {
|
|
257
|
+
provider;
|
|
258
|
+
constructor(provider) {
|
|
259
|
+
this.provider = provider;
|
|
260
|
+
}
|
|
261
|
+
async push(invoice) {
|
|
262
|
+
const qboInvoice = mapInvoiceToQBO(invoice);
|
|
263
|
+
return {
|
|
264
|
+
action: "created",
|
|
265
|
+
externalId: (await this.provider.request("POST", "invoice", qboInvoice)).Invoice.Id,
|
|
266
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async pull(externalId) {
|
|
270
|
+
return mapQBOToInvoice((await this.provider.request("GET", `invoice/${externalId}`)).Invoice);
|
|
271
|
+
}
|
|
272
|
+
async list(options) {
|
|
273
|
+
const limit = options?.limit || 1e3;
|
|
274
|
+
let whereClause;
|
|
275
|
+
if (options?.startDate) {
|
|
276
|
+
whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;
|
|
277
|
+
if (options?.endDate) whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;
|
|
278
|
+
}
|
|
279
|
+
return (await this.provider.queryAll("Invoice", whereClause, limit)).map(mapQBOToInvoice);
|
|
280
|
+
}
|
|
281
|
+
async sync(invoice) {
|
|
282
|
+
if (invoice.externalId) {
|
|
283
|
+
const existing = await this.provider.request("GET", `invoice/${invoice.externalId}`);
|
|
284
|
+
const qboInvoice = {
|
|
285
|
+
...mapInvoiceToQBO(invoice),
|
|
286
|
+
Id: invoice.externalId,
|
|
287
|
+
SyncToken: existing.Invoice.SyncToken
|
|
288
|
+
};
|
|
289
|
+
await this.provider.request("POST", "invoice", qboInvoice);
|
|
290
|
+
return {
|
|
291
|
+
action: "updated",
|
|
292
|
+
externalId: invoice.externalId,
|
|
293
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return this.push(invoice);
|
|
297
|
+
}
|
|
298
|
+
async send(externalId) {
|
|
299
|
+
await this.provider.request("POST", `invoice/${externalId}/send`);
|
|
300
|
+
}
|
|
301
|
+
async void(externalId) {
|
|
302
|
+
const existing = await this.provider.request("GET", `invoice/${externalId}`);
|
|
303
|
+
await this.provider.request("POST", "invoice", {
|
|
304
|
+
Id: externalId,
|
|
305
|
+
SyncToken: existing.Invoice.SyncToken,
|
|
306
|
+
sparse: true,
|
|
307
|
+
PrivateNote: "VOIDED"
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
var QuickBooksVendorOperations = class {
|
|
312
|
+
provider;
|
|
313
|
+
constructor(provider) {
|
|
314
|
+
this.provider = provider;
|
|
315
|
+
}
|
|
316
|
+
async push(vendor) {
|
|
317
|
+
const qboVendor = mapVendorToQBO(vendor);
|
|
318
|
+
return {
|
|
319
|
+
action: "created",
|
|
320
|
+
externalId: (await this.provider.request("POST", "vendor", qboVendor)).Vendor.Id,
|
|
321
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
async pull(externalId) {
|
|
325
|
+
return mapQBOToVendor((await this.provider.request("GET", `vendor/${externalId}`)).Vendor);
|
|
326
|
+
}
|
|
327
|
+
async list(options) {
|
|
328
|
+
const limit = options?.limit || 1e3;
|
|
329
|
+
return (await this.provider.queryAll("Vendor", void 0, limit)).map(mapQBOToVendor);
|
|
330
|
+
}
|
|
331
|
+
async sync(vendor) {
|
|
332
|
+
if (vendor.externalId) {
|
|
333
|
+
const existing = await this.provider.request("GET", `vendor/${vendor.externalId}`);
|
|
334
|
+
const qboVendor = {
|
|
335
|
+
...mapVendorToQBO(vendor),
|
|
336
|
+
Id: vendor.externalId,
|
|
337
|
+
SyncToken: existing.Vendor.SyncToken
|
|
338
|
+
};
|
|
339
|
+
await this.provider.request("POST", "vendor", qboVendor);
|
|
340
|
+
return {
|
|
341
|
+
action: "updated",
|
|
342
|
+
externalId: vendor.externalId,
|
|
343
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return this.push(vendor);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
var QuickBooksBillOperations = class {
|
|
350
|
+
provider;
|
|
351
|
+
constructor(provider) {
|
|
352
|
+
this.provider = provider;
|
|
353
|
+
}
|
|
354
|
+
async push(bill) {
|
|
355
|
+
const qboBill = mapBillToQBO(bill);
|
|
356
|
+
return {
|
|
357
|
+
action: "created",
|
|
358
|
+
externalId: (await this.provider.request("POST", "bill", qboBill)).Bill.Id,
|
|
359
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
async pull(externalId) {
|
|
363
|
+
return mapQBOToBill((await this.provider.request("GET", `bill/${externalId}`)).Bill);
|
|
364
|
+
}
|
|
365
|
+
async list(options) {
|
|
366
|
+
const limit = options?.limit || 1e3;
|
|
367
|
+
let whereClause;
|
|
368
|
+
if (options?.startDate) {
|
|
369
|
+
whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;
|
|
370
|
+
if (options?.endDate) whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;
|
|
371
|
+
}
|
|
372
|
+
return (await this.provider.queryAll("Bill", whereClause, limit)).map(mapQBOToBill);
|
|
373
|
+
}
|
|
374
|
+
async sync(bill) {
|
|
375
|
+
if (bill.externalId) {
|
|
376
|
+
const existing = await this.provider.request("GET", `bill/${bill.externalId}`);
|
|
377
|
+
const qboBill = {
|
|
378
|
+
...mapBillToQBO(bill),
|
|
379
|
+
Id: bill.externalId,
|
|
380
|
+
SyncToken: existing.Bill.SyncToken
|
|
381
|
+
};
|
|
382
|
+
await this.provider.request("POST", "bill", qboBill);
|
|
383
|
+
return {
|
|
384
|
+
action: "updated",
|
|
385
|
+
externalId: bill.externalId,
|
|
386
|
+
syncedAt: /* @__PURE__ */ new Date()
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return this.push(bill);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
var QuickBooksPaymentOperations = class {
|
|
393
|
+
provider;
|
|
394
|
+
constructor(provider) {
|
|
395
|
+
this.provider = provider;
|
|
396
|
+
}
|
|
397
|
+
async pull(externalId) {
|
|
398
|
+
return mapQBOToPayment((await this.provider.request("GET", `payment/${externalId}`)).Payment);
|
|
399
|
+
}
|
|
400
|
+
async list(options) {
|
|
401
|
+
const limit = options?.limit || 1e3;
|
|
402
|
+
let whereClause;
|
|
403
|
+
if (options?.startDate) {
|
|
404
|
+
whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;
|
|
405
|
+
if (options?.endDate) whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;
|
|
406
|
+
}
|
|
407
|
+
return (await this.provider.queryAll("Payment", whereClause, limit)).map(mapQBOToPayment);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var QuickBooksAuditOperations = class {
|
|
411
|
+
provider;
|
|
412
|
+
constructor(provider) {
|
|
413
|
+
this.provider = provider;
|
|
414
|
+
}
|
|
415
|
+
async reconcileCustomers(locals) {
|
|
416
|
+
return reconcileRecords(locals, await this.provider.customers.list({ limit: 1e4 }), (l) => l.externalId, (e) => e.externalId, compareCustomer);
|
|
417
|
+
}
|
|
418
|
+
async reconcileInvoices(locals, dateRange) {
|
|
419
|
+
return reconcileRecords(locals, await this.provider.invoices.list({
|
|
420
|
+
limit: 1e4,
|
|
421
|
+
startDate: dateRange?.start,
|
|
422
|
+
endDate: dateRange?.end
|
|
423
|
+
}), (l) => l.externalId, (e) => e.externalId, compareInvoice);
|
|
424
|
+
}
|
|
425
|
+
async reconcileVendors(locals) {
|
|
426
|
+
return reconcileRecords(locals, await this.provider.vendors.list({ limit: 1e4 }), (l) => l.externalId, (e) => e.externalId, compareVendor);
|
|
427
|
+
}
|
|
428
|
+
async reconcileBills(locals, dateRange) {
|
|
429
|
+
return reconcileRecords(locals, await this.provider.bills.list({
|
|
430
|
+
limit: 1e4,
|
|
431
|
+
startDate: dateRange?.start,
|
|
432
|
+
endDate: dateRange?.end
|
|
433
|
+
}), (l) => l.externalId, (e) => e.externalId, compareBill);
|
|
434
|
+
}
|
|
435
|
+
async reconcilePayments(locals, dateRange) {
|
|
436
|
+
return reconcileRecords(locals, await this.provider.payments.list({
|
|
437
|
+
limit: 1e4,
|
|
438
|
+
startDate: dateRange?.start,
|
|
439
|
+
endDate: dateRange?.end
|
|
440
|
+
}), (l) => l.externalId, (e) => e.externalId, comparePayment);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Generic reconciliation function with field comparison
|
|
445
|
+
*/
|
|
446
|
+
function reconcileRecords(locals, externals, getLocalExternalId, getExternalId, compareFields) {
|
|
447
|
+
const externalMap = new Map(externals.map((e) => [getExternalId(e), e]));
|
|
448
|
+
const matchedExternalIds = /* @__PURE__ */ new Set();
|
|
449
|
+
const matched = [];
|
|
450
|
+
const localOnly = [];
|
|
451
|
+
const discrepancies = [];
|
|
452
|
+
for (const local of locals) {
|
|
453
|
+
const externalId = getLocalExternalId(local);
|
|
454
|
+
if (externalId && externalMap.has(externalId)) {
|
|
455
|
+
const external = externalMap.get(externalId);
|
|
456
|
+
matchedExternalIds.add(externalId);
|
|
457
|
+
const differences = compareFields(local, external);
|
|
458
|
+
if (differences.length === 0) matched.push({
|
|
459
|
+
local,
|
|
460
|
+
external,
|
|
461
|
+
status: "identical"
|
|
462
|
+
});
|
|
463
|
+
else discrepancies.push({
|
|
464
|
+
local,
|
|
465
|
+
external,
|
|
466
|
+
differences
|
|
467
|
+
});
|
|
468
|
+
} else localOnly.push(local);
|
|
469
|
+
}
|
|
470
|
+
const externalOnly = externals.filter((e) => !matchedExternalIds.has(getExternalId(e)));
|
|
471
|
+
return {
|
|
472
|
+
provider: "quickbooks",
|
|
473
|
+
auditedAt: /* @__PURE__ */ new Date(),
|
|
474
|
+
matched,
|
|
475
|
+
localOnly,
|
|
476
|
+
externalOnly,
|
|
477
|
+
discrepancies,
|
|
478
|
+
summary: {
|
|
479
|
+
total: locals.length + externalOnly.length,
|
|
480
|
+
matched: matched.length,
|
|
481
|
+
localOnly: localOnly.length,
|
|
482
|
+
externalOnly: externalOnly.length,
|
|
483
|
+
discrepancies: discrepancies.length
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function compareCustomer(local, external) {
|
|
488
|
+
const diffs = [];
|
|
489
|
+
if (local.name !== external.name) diffs.push({
|
|
490
|
+
field: "name",
|
|
491
|
+
localValue: local.name,
|
|
492
|
+
externalValue: external.name
|
|
493
|
+
});
|
|
494
|
+
if (local.email !== external.email) diffs.push({
|
|
495
|
+
field: "email",
|
|
496
|
+
localValue: local.email,
|
|
497
|
+
externalValue: external.email
|
|
498
|
+
});
|
|
499
|
+
return diffs;
|
|
500
|
+
}
|
|
501
|
+
function compareInvoice(local, external) {
|
|
502
|
+
const diffs = [];
|
|
503
|
+
if (!valuesEqual(local.totalAmount, external.totalAmount)) diffs.push({
|
|
504
|
+
field: "totalAmount",
|
|
505
|
+
localValue: local.totalAmount,
|
|
506
|
+
externalValue: external.totalAmount
|
|
507
|
+
});
|
|
508
|
+
if (local.invoiceNumber !== external.invoiceNumber) diffs.push({
|
|
509
|
+
field: "invoiceNumber",
|
|
510
|
+
localValue: local.invoiceNumber,
|
|
511
|
+
externalValue: external.invoiceNumber
|
|
512
|
+
});
|
|
513
|
+
return diffs;
|
|
514
|
+
}
|
|
515
|
+
function compareVendor(local, external) {
|
|
516
|
+
const diffs = [];
|
|
517
|
+
if (local.name !== external.name) diffs.push({
|
|
518
|
+
field: "name",
|
|
519
|
+
localValue: local.name,
|
|
520
|
+
externalValue: external.name
|
|
521
|
+
});
|
|
522
|
+
if (local.email !== external.email) diffs.push({
|
|
523
|
+
field: "email",
|
|
524
|
+
localValue: local.email,
|
|
525
|
+
externalValue: external.email
|
|
526
|
+
});
|
|
527
|
+
return diffs;
|
|
528
|
+
}
|
|
529
|
+
function compareBill(local, external) {
|
|
530
|
+
const diffs = [];
|
|
531
|
+
if (!valuesEqual(local.totalAmount, external.totalAmount)) diffs.push({
|
|
532
|
+
field: "totalAmount",
|
|
533
|
+
localValue: local.totalAmount,
|
|
534
|
+
externalValue: external.totalAmount
|
|
535
|
+
});
|
|
536
|
+
return diffs;
|
|
537
|
+
}
|
|
538
|
+
function comparePayment(local, external) {
|
|
539
|
+
const diffs = [];
|
|
540
|
+
if (!valuesEqual(local.amount, external.amount)) diffs.push({
|
|
541
|
+
field: "amount",
|
|
542
|
+
localValue: local.amount,
|
|
543
|
+
externalValue: external.amount
|
|
544
|
+
});
|
|
545
|
+
return diffs;
|
|
546
|
+
}
|
|
547
|
+
var QuickBooksWebhookOperations = class {
|
|
548
|
+
options;
|
|
549
|
+
constructor(options) {
|
|
550
|
+
this.options = options;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Verify webhook signature using HMAC-SHA256
|
|
554
|
+
* @see https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/verify-webhooks
|
|
555
|
+
*/
|
|
556
|
+
verify(payload, signature, secret) {
|
|
557
|
+
const verifierToken = secret || this.options.webhookVerifierToken;
|
|
558
|
+
if (!verifierToken) throw new Error("Webhook verifier token not provided. Pass it as the secret parameter or configure webhookVerifierToken in options.");
|
|
559
|
+
const hmac = createHmac("sha256", verifierToken);
|
|
560
|
+
hmac.update(payload);
|
|
561
|
+
const expectedSignature = hmac.digest("base64");
|
|
562
|
+
if (signature.length !== expectedSignature.length) return false;
|
|
563
|
+
let result = 0;
|
|
564
|
+
for (let i = 0; i < signature.length; i++) result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);
|
|
565
|
+
return result === 0;
|
|
566
|
+
}
|
|
567
|
+
parse(payload) {
|
|
568
|
+
let data;
|
|
569
|
+
try {
|
|
570
|
+
data = JSON.parse(payload);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
const message = error instanceof Error ? error.message : "Unknown JSON parse error";
|
|
573
|
+
throw new Error(`Invalid QuickBooks webhook payload: ${message}`);
|
|
574
|
+
}
|
|
575
|
+
const firstEvent = (data.eventNotifications || [])[0]?.dataChangeEvent?.entities?.[0];
|
|
576
|
+
return {
|
|
577
|
+
type: firstEvent?.operation || "unknown",
|
|
578
|
+
provider: "quickbooks",
|
|
579
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
580
|
+
payload: data,
|
|
581
|
+
resourceType: mapQBOResourceType(firstEvent?.name),
|
|
582
|
+
resourceId: firstEvent?.id
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
function mapQBOResourceType(name) {
|
|
587
|
+
switch (name?.toLowerCase()) {
|
|
588
|
+
case "customer": return "customer";
|
|
589
|
+
case "invoice": return "invoice";
|
|
590
|
+
case "payment": return "payment";
|
|
591
|
+
case "vendor": return "vendor";
|
|
592
|
+
case "bill": return "bill";
|
|
593
|
+
default: return;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function mapCustomerToQBO(customer) {
|
|
597
|
+
return {
|
|
598
|
+
DisplayName: customer.name,
|
|
599
|
+
PrimaryEmailAddr: customer.email ? { Address: customer.email } : void 0,
|
|
600
|
+
PrimaryPhone: customer.phone ? { FreeFormNumber: customer.phone } : void 0,
|
|
601
|
+
BillAddr: mapAddressToQBO(customer.billingAddress),
|
|
602
|
+
ShipAddr: mapAddressToQBO(customer.shippingAddress),
|
|
603
|
+
CurrencyRef: customer.currency ? { value: customer.currency } : void 0
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function mapQBOToCustomer(qbo) {
|
|
607
|
+
return {
|
|
608
|
+
externalId: qbo.Id,
|
|
609
|
+
provider: "quickbooks",
|
|
610
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
611
|
+
name: qbo.DisplayName,
|
|
612
|
+
email: qbo.PrimaryEmailAddr?.Address,
|
|
613
|
+
phone: qbo.PrimaryPhone?.FreeFormNumber,
|
|
614
|
+
billingAddress: mapQBOToAddress(qbo.BillAddr),
|
|
615
|
+
balance: qbo.Balance,
|
|
616
|
+
currency: qbo.CurrencyRef?.value,
|
|
617
|
+
raw: qbo
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function mapInvoiceToQBO(invoice) {
|
|
621
|
+
return {
|
|
622
|
+
CustomerRef: { value: invoice.customerExternalId || invoice.customerId },
|
|
623
|
+
DocNumber: invoice.invoiceNumber,
|
|
624
|
+
TxnDate: formatLocalDate(invoice.issueDate),
|
|
625
|
+
DueDate: formatLocalDate(invoice.dueDate),
|
|
626
|
+
Line: invoice.lineItems.map((item, idx) => ({
|
|
627
|
+
LineNum: idx + 1,
|
|
628
|
+
Description: item.description,
|
|
629
|
+
Amount: item.amount ?? item.quantity * item.unitPrice,
|
|
630
|
+
DetailType: "SalesItemLineDetail",
|
|
631
|
+
SalesItemLineDetail: {
|
|
632
|
+
Qty: item.quantity,
|
|
633
|
+
UnitPrice: item.unitPrice
|
|
634
|
+
}
|
|
635
|
+
})),
|
|
636
|
+
CurrencyRef: invoice.currency ? { value: invoice.currency } : void 0,
|
|
637
|
+
CustomerMemo: invoice.memo ? { value: invoice.memo } : void 0
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function mapQBOToInvoice(qbo) {
|
|
641
|
+
const balance = qbo.Balance ?? 0;
|
|
642
|
+
const totalAmount = qbo.TotalAmt ?? 0;
|
|
643
|
+
return {
|
|
644
|
+
externalId: qbo.Id,
|
|
645
|
+
provider: "quickbooks",
|
|
646
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
647
|
+
invoiceNumber: qbo.DocNumber || "",
|
|
648
|
+
customerExternalId: qbo.CustomerRef?.value || "",
|
|
649
|
+
issueDate: new Date(qbo.TxnDate),
|
|
650
|
+
dueDate: new Date(qbo.DueDate),
|
|
651
|
+
subtotal: totalAmount - (qbo.TxnTaxDetail?.TotalTax ?? 0),
|
|
652
|
+
taxAmount: qbo.TxnTaxDetail?.TotalTax ?? 0,
|
|
653
|
+
totalAmount,
|
|
654
|
+
amountPaid: totalAmount - balance,
|
|
655
|
+
balance,
|
|
656
|
+
status: mapInvoiceStatus(balance, totalAmount, qbo.DueDate),
|
|
657
|
+
currency: qbo.CurrencyRef?.value || "USD",
|
|
658
|
+
raw: qbo
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function mapInvoiceStatus(balance, total, dueDate) {
|
|
662
|
+
if (balance === 0) return "paid";
|
|
663
|
+
if (balance < total) return "viewed";
|
|
664
|
+
if (new Date(dueDate) < /* @__PURE__ */ new Date()) return "overdue";
|
|
665
|
+
return "sent";
|
|
666
|
+
}
|
|
667
|
+
function mapVendorToQBO(vendor) {
|
|
668
|
+
return {
|
|
669
|
+
DisplayName: vendor.name,
|
|
670
|
+
PrimaryEmailAddr: vendor.email ? { Address: vendor.email } : void 0,
|
|
671
|
+
PrimaryPhone: vendor.phone ? { FreeFormNumber: vendor.phone } : void 0,
|
|
672
|
+
BillAddr: mapAddressToQBO(vendor.address),
|
|
673
|
+
TaxIdentifier: vendor.taxId,
|
|
674
|
+
CurrencyRef: vendor.currency ? { value: vendor.currency } : void 0
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function mapQBOToVendor(qbo) {
|
|
678
|
+
return {
|
|
679
|
+
externalId: qbo.Id,
|
|
680
|
+
provider: "quickbooks",
|
|
681
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
682
|
+
name: qbo.DisplayName,
|
|
683
|
+
email: qbo.PrimaryEmailAddr?.Address,
|
|
684
|
+
phone: qbo.PrimaryPhone?.FreeFormNumber,
|
|
685
|
+
address: mapQBOToAddress(qbo.BillAddr),
|
|
686
|
+
balance: qbo.Balance,
|
|
687
|
+
currency: qbo.CurrencyRef?.value,
|
|
688
|
+
raw: qbo
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function mapBillToQBO(bill) {
|
|
692
|
+
return {
|
|
693
|
+
VendorRef: { value: bill.vendorExternalId || bill.vendorId },
|
|
694
|
+
DocNumber: bill.billNumber,
|
|
695
|
+
TxnDate: formatLocalDate(bill.billDate),
|
|
696
|
+
DueDate: formatLocalDate(bill.dueDate),
|
|
697
|
+
Line: bill.lineItems.map((item, idx) => ({
|
|
698
|
+
LineNum: idx + 1,
|
|
699
|
+
Description: item.description,
|
|
700
|
+
Amount: item.amount ?? item.quantity * item.unitPrice,
|
|
701
|
+
DetailType: "AccountBasedExpenseLineDetail",
|
|
702
|
+
AccountBasedExpenseLineDetail: { AccountRef: item.accountCode ? { value: item.accountCode } : void 0 }
|
|
703
|
+
})),
|
|
704
|
+
CurrencyRef: bill.currency ? { value: bill.currency } : void 0
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function mapQBOToBill(qbo) {
|
|
708
|
+
const balance = qbo.Balance ?? 0;
|
|
709
|
+
const totalAmount = qbo.TotalAmt ?? 0;
|
|
710
|
+
return {
|
|
711
|
+
externalId: qbo.Id,
|
|
712
|
+
provider: "quickbooks",
|
|
713
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
714
|
+
billNumber: qbo.DocNumber,
|
|
715
|
+
vendorExternalId: qbo.VendorRef?.value || "",
|
|
716
|
+
billDate: new Date(qbo.TxnDate),
|
|
717
|
+
dueDate: new Date(qbo.DueDate),
|
|
718
|
+
subtotal: totalAmount - (qbo.TxnTaxDetail?.TotalTax ?? 0),
|
|
719
|
+
taxAmount: qbo.TxnTaxDetail?.TotalTax ?? 0,
|
|
720
|
+
totalAmount,
|
|
721
|
+
amountPaid: totalAmount - balance,
|
|
722
|
+
balance,
|
|
723
|
+
status: mapBillStatus(balance, totalAmount, qbo.DueDate),
|
|
724
|
+
currency: qbo.CurrencyRef?.value || "USD",
|
|
725
|
+
raw: qbo
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function mapBillStatus(balance, total, dueDate) {
|
|
729
|
+
if (balance === 0) return "paid";
|
|
730
|
+
if (new Date(dueDate) < /* @__PURE__ */ new Date()) return "overdue";
|
|
731
|
+
return "pending";
|
|
732
|
+
}
|
|
733
|
+
function mapQBOToPayment(qbo) {
|
|
734
|
+
return {
|
|
735
|
+
externalId: qbo.Id,
|
|
736
|
+
provider: "quickbooks",
|
|
737
|
+
syncedAt: /* @__PURE__ */ new Date(),
|
|
738
|
+
amount: qbo.TotalAmt ?? 0,
|
|
739
|
+
currency: qbo.CurrencyRef?.value || "USD",
|
|
740
|
+
paidAt: new Date(qbo.TxnDate),
|
|
741
|
+
method: qbo.PaymentMethodRef?.name,
|
|
742
|
+
transactionId: qbo.PaymentRefNum,
|
|
743
|
+
invoiceExternalIds: qbo.Line?.filter((l) => l.LinkedTxn).flatMap((l) => l.LinkedTxn?.map((t) => t.TxnId) || []),
|
|
744
|
+
status: "completed",
|
|
745
|
+
raw: qbo
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function mapAddressToQBO(address) {
|
|
749
|
+
if (!address) return void 0;
|
|
750
|
+
return {
|
|
751
|
+
Line1: address.street1,
|
|
752
|
+
Line2: address.street2,
|
|
753
|
+
City: address.city,
|
|
754
|
+
CountrySubDivisionCode: address.state,
|
|
755
|
+
PostalCode: address.postalCode,
|
|
756
|
+
Country: address.country
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function mapQBOToAddress(qbo) {
|
|
760
|
+
if (!qbo) return void 0;
|
|
761
|
+
return {
|
|
762
|
+
street1: qbo.Line1,
|
|
763
|
+
street2: qbo.Line2,
|
|
764
|
+
city: qbo.City,
|
|
765
|
+
state: qbo.CountrySubDivisionCode,
|
|
766
|
+
postalCode: qbo.PostalCode,
|
|
767
|
+
country: qbo.Country
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
//#endregion
|
|
771
|
+
export { QuickBooksProvider, __toCommonJS as a, __require as i, __esmMin as n, __exportAll as r, __commonJSMin as t };
|
|
772
|
+
|
|
773
|
+
//# sourceMappingURL=quickbooks-BF3GrJBw.js.map
|