@happyvertical/accounting 0.103.7 → 0.104.1
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/README.md +126 -0
- package/dist/chunks/{errors-BHWEFjkq.js → errors-DIqXkm4s.js} +4 -1
- package/dist/chunks/errors-DIqXkm4s.js.map +1 -0
- package/dist/chunks/{quickbooks-BgH78wY7.js → quickbooks-T3C4dO8R.js} +158 -7
- package/dist/chunks/quickbooks-T3C4dO8R.js.map +1 -0
- package/dist/chunks/{invoice-request-s7E51rMr.js → receivable-transfer-Ciy9jFYu.js} +262 -15
- package/dist/chunks/receivable-transfer-Ciy9jFYu.js.map +1 -0
- package/dist/chunks/{stripe-CECceZH5.js → stripe-DOJLldVK.js} +2 -2
- package/dist/chunks/{stripe-CECceZH5.js.map → stripe-DOJLldVK.js.map} +1 -1
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/providers/quickbooks/index.d.ts +5 -7
- package/dist/providers/quickbooks/index.d.ts.map +1 -1
- package/dist/providers/quickbooks/invoice-request.d.ts +1 -0
- package/dist/providers/quickbooks/invoice-request.d.ts.map +1 -1
- package/dist/providers/quickbooks/receivable-operations.d.ts +22 -0
- package/dist/providers/quickbooks/receivable-operations.d.ts.map +1 -0
- package/dist/providers/quickbooks/receivable-transfer.d.ts +89 -0
- package/dist/providers/quickbooks/receivable-transfer.d.ts.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/chunks/errors-BHWEFjkq.js.map +0 -1
- package/dist/chunks/invoice-request-s7E51rMr.js.map +0 -1
- package/dist/chunks/quickbooks-BgH78wY7.js.map +0 -1
package/README.md
CHANGED
|
@@ -504,3 +504,129 @@ After `webhooks.verify` succeeds, the parsed Stripe webhook exposes its provider
|
|
|
504
504
|
Persist that identifier in a durable inbox before applying side effects; the
|
|
505
505
|
in-process parser alone cannot deduplicate deliveries across restarts or
|
|
506
506
|
replicas.
|
|
507
|
+
|
|
508
|
+
### Ordered compound tax snapshots
|
|
509
|
+
|
|
510
|
+
A frozen component may set `compound: true` and `taxableAmount` (currency major
|
|
511
|
+
units). It then taxes the line's net amount plus **all preceding components in
|
|
512
|
+
that line's ordered snapshot**. Its explicit basis must equal that sum of stored
|
|
513
|
+
facts; neither a replacement rate nor a jurisdiction rule is inferred. Different
|
|
514
|
+
applicable line subsets use their corresponding realm tax codes.
|
|
515
|
+
|
|
516
|
+
Compound lines also require verified configuration evidence:
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
quickbooksMapping: {
|
|
520
|
+
itemRef: '123',
|
|
521
|
+
taxCodeRef: 'configured-compound-code',
|
|
522
|
+
taxComponents: [
|
|
523
|
+
{ taxRateRef: 'gst-rate', compound: false },
|
|
524
|
+
{ taxRateRef: 'provincial-rate', compound: true },
|
|
525
|
+
],
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
The host must verify this sequence against the target company's existing TaxCode,
|
|
530
|
+
including its applicability and ordering, and persist the verification provenance
|
|
531
|
+
with tenant configuration. The SDK requires an exact match with frozen component
|
|
532
|
+
references and flags; it cannot verify a remote realm from an ID alone and does
|
|
533
|
+
not create or modify tax codes. Missing, duplicate, unknown or mismatched order
|
|
534
|
+
metadata fails before a request can be sent. Tax-only dependencies other than
|
|
535
|
+
all previous applicable components are outside this contract.
|
|
536
|
+
|
|
537
|
+
Exclusive validation uses preceding stored rounded/allocated taxes. Inclusive
|
|
538
|
+
validation extracts exact ordered rational factors and checks the same permitted
|
|
539
|
+
floor/ceiling allocation as additive snapshots. Inclusive discount projection
|
|
540
|
+
uses those factors too. Stored component amounts and original rates are preserved
|
|
541
|
+
in `TxnTaxDetail.TaxLine`; an explicit basis is aggregated by rate into
|
|
542
|
+
`TaxLineDetail.NetAmountTaxable`. Additive snapshots without these new fields
|
|
543
|
+
retain their prior payload bytes and retry descriptor hashes. Credit memos use
|
|
544
|
+
the same representation. This preserves source facts; sandbox acceptance and
|
|
545
|
+
provider readback still require the host's authorized integration verification.
|
|
546
|
+
|
|
547
|
+
Intuit's [TaxRateDetail schema](https://static.developer.intuit.com/sdkdocs/qbv3doc/ipp-v3-java-devkit-javadoc/com/intuit/ipp/data/TaxRateDetail.html)
|
|
548
|
+
defines applicability/order, including
|
|
549
|
+
[TaxOnAmountPlusTax](https://static.developer.intuit.com/sdkdocs/qbv3doc/ipp-v3-java-devkit-javadoc/com/intuit/ipp/data/TaxTypeApplicablityEnum.html).
|
|
550
|
+
Its [TaxLineDetail reference](https://static.developer.intuit.com/sdkdocs/qbv3doc/ippdotnetdevkitv3/html/393902ec-f79c-bb8f-82fd-0f3bd296dd27.htm)
|
|
551
|
+
documents the applicable-rate taxable base and realm rate-reference constraint.
|
|
552
|
+
# QuickBooks receivable transfers
|
|
553
|
+
|
|
554
|
+
`AccountingProvider.receivableTransfers` is an optional capability. The QuickBooks
|
|
555
|
+
provider implements reviewed receivable reclassification using JournalEntry and
|
|
556
|
+
zero-cash Payment requests. It does not emulate a native retainage API, create
|
|
557
|
+
sales items, reduce invoice gross/tax, or initiate bank settlement.
|
|
558
|
+
|
|
559
|
+
The caller supplies an immutable, versioned mapping and review evidence:
|
|
560
|
+
`accountingBasis: 'accrual'`, `taxTiming: 'invoice-tax-point'`,
|
|
561
|
+
`releaseDueDates: 'host-managed'`, realm/environment, customer, currency and
|
|
562
|
+
distinct AR/retained-asset/optional bank accounts. Other accounting/tax timing
|
|
563
|
+
policies are unsupported. The provider verifies active customer/accounts, account
|
|
564
|
+
types and the realm's home currency before writing. Foreign-currency transfers
|
|
565
|
+
are unsupported; no exchange rate is inferred. Account CurrencyRef, when present,
|
|
566
|
+
must agree. Missing transaction currency or financial fields fails closed.
|
|
567
|
+
|
|
568
|
+
`prepareQuickBooksReceivableRequest(input)` returns a persistable descriptor.
|
|
569
|
+
Amounts are positive safe integer **minor units**, exactly representable on the
|
|
570
|
+
currency grid. Source type/ID/customer, effective date, evidence and mapping version
|
|
571
|
+
are bound into its hash and provider note; journal lines repeat source/customer
|
|
572
|
+
evidence. No provider DueDate is invented for release journals: the host owns
|
|
573
|
+
release due dates and customer-facing collection documents.
|
|
574
|
+
|
|
575
|
+
| Kind | Debit | Credit | Readback meaning |
|
|
576
|
+
| --- | --- | --- | --- |
|
|
577
|
+
| withhold | retained asset | customer AR | receivable reclassification |
|
|
578
|
+
| release | customer AR | retained asset | released receivable |
|
|
579
|
+
| cash-refund | customer AR | bank | recorded cash outflow, not verified settlement |
|
|
580
|
+
| reverse-cash-refund | bank | customer AR | recorded reversal, not bank execution |
|
|
581
|
+
| application | existing Invoice/JournalEntry | existing CreditMemo/JournalEntry | noncash application, TotalAmt = 0 |
|
|
582
|
+
|
|
583
|
+
`provider.receivableTransfers.execute(prepared)` verifies scope, mapping and
|
|
584
|
+
source customer/currency before posting. Applications also inspect current debit
|
|
585
|
+
and credit capacity. Journal capacity uses the complete paginated customer payment
|
|
586
|
+
history, rejecting ambiguous multi-transaction allocation lines instead of
|
|
587
|
+
allocating them arbitrarily. These reads are not a remote transaction lock: hosts
|
|
588
|
+
must serialize their own workflows and reconcile changes made by other provider
|
|
589
|
+
users. A stale or rejected application must not be recorded as completed locally.
|
|
590
|
+
|
|
591
|
+
Readback validates exact postings/linkage, amounts, source evidence, customer,
|
|
592
|
+
currency and date. It returns `nonCashAppliedMinor` separately from
|
|
593
|
+
`recordedCashChangeMinor`. `providerProcessedCashMinor` is zero and
|
|
594
|
+
`bankSettlementVerified` is false, including cash-refund journals. Ordinary invoice
|
|
595
|
+
`TotalAmt - Balance` includes noncash applications and must not be treated as cash
|
|
596
|
+
received in a retention workflow.
|
|
597
|
+
|
|
598
|
+
## Durability and recovery
|
|
599
|
+
|
|
600
|
+
Persist the complete descriptor before execution. Its hash binds bytes and
|
|
601
|
+
provenance; it does **not** prove that a provider operation happened exactly once.
|
|
602
|
+
The request uses QuickBooks' documented `requestid` query parameter (1–50 URL-safe
|
|
603
|
+
characters) through the existing SDK duplicate-handling transport. A host must
|
|
604
|
+
retain that identity across transport retries and may never silently replace it
|
|
605
|
+
with a fresh identity following an unknown outcome.
|
|
606
|
+
|
|
607
|
+
Each journal and application is a separate provider write. The SDK supplies no
|
|
608
|
+
cross-request transaction or persistent workflow. The host must persist per-step
|
|
609
|
+
in-flight/unknown/rejected/confirmed states and retain successful earlier steps
|
|
610
|
+
when a later step fails. `read(prepared, externalId)` reconciles a known remote ID
|
|
611
|
+
without writing. Unknown IDs/outcomes require authoritative provider reconciliation
|
|
612
|
+
before progressing; an input hash or a missing list result is not proof of absence.
|
|
613
|
+
Replaying `execute` after a completed application can fail its current-capacity
|
|
614
|
+
preflight; use persisted confirmation/readback instead. Reversal commands require
|
|
615
|
+
the host's approved source lineage and conserved refund/release capacity; they are
|
|
616
|
+
not an automatic recovery action.
|
|
617
|
+
|
|
618
|
+
The public contract is covered by local mocked provider tests. **No live sandbox
|
|
619
|
+
certification or production write has been performed.** Deployment certification
|
|
620
|
+
must verify realm-specific journal/customer application behavior and echoed fields.
|
|
621
|
+
|
|
622
|
+
## Provider evidence
|
|
623
|
+
|
|
624
|
+
- [Payment API](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/Payment): Invoice, CreditMemo and JournalEntry links; zero-total credit application example.
|
|
625
|
+
- [Journal line contract](https://static.developer.intuit.com/sdkdocs/qbv3doc/ippdotnetdevkitv3/html/62627c63-90a3-c87a-ef9b-8b4b06daee08.htm): AccountRef, PostingType and customer Entity.
|
|
626
|
+
- [Journal contract](https://static.developer.intuit.com/sdkdocs/qbv3doc/ippdotnetdevkitv3/html/0b5f8961-f32e-e396-3a39-7b3e241434e8.htm): transaction currency/date; no assumed release due-date field.
|
|
627
|
+
- [Intuit API best practices](https://blogs.a.intuit.com/2018/09/10/quickbooks-online-api-best-practices/): request-ID duplicate handling. This is distinct from host durable reconciliation.
|
|
628
|
+
|
|
629
|
+
A receivable-transfer POST whose response cannot be reconciled throws
|
|
630
|
+
`QuickBooksWriteError` with `outcome: "unknown"`, the original request identity
|
|
631
|
+
and payload hash, and `externalId` when returned by the provider. Use that ID for
|
|
632
|
+
read-only reconciliation; a malformed success never authorizes a new write identity.
|
|
@@ -52,6 +52,8 @@ var QuickBooksWriteError = class extends Error {
|
|
|
52
52
|
endpoint;
|
|
53
53
|
requestId;
|
|
54
54
|
payloadHash;
|
|
55
|
+
/** Returned identity retained for read-only reconciliation of an uncertain write. */
|
|
56
|
+
externalId;
|
|
55
57
|
status;
|
|
56
58
|
constructor(details) {
|
|
57
59
|
super(`QuickBooks write outcome ${details.outcome}; reconcile before creating a new request identity`, { cause: details.cause });
|
|
@@ -62,6 +64,7 @@ var QuickBooksWriteError = class extends Error {
|
|
|
62
64
|
this.endpoint = details.endpoint;
|
|
63
65
|
this.requestId = details.requestId;
|
|
64
66
|
this.payloadHash = details.payloadHash;
|
|
67
|
+
this.externalId = details.externalId;
|
|
65
68
|
this.status = details.status;
|
|
66
69
|
}
|
|
67
70
|
};
|
|
@@ -86,4 +89,4 @@ var messages = {
|
|
|
86
89
|
//#endregion
|
|
87
90
|
export { QuickBooksWriteError as n, StripeApiError as r, QuickBooksOAuthError as t };
|
|
88
91
|
|
|
89
|
-
//# sourceMappingURL=errors-
|
|
92
|
+
//# sourceMappingURL=errors-DIqXkm4s.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-DIqXkm4s.js","names":[],"sources":["../../src/errors.ts"],"sourcesContent":["/**\n * Provider error types, kept free of provider modules so the package entry\n * point can export them without loading optional integrations.\n */\n\n/**\n * Error thrown for a non-2xx Stripe API response. The message is unchanged\n * from earlier releases (`Stripe API error (<status>): <body>`); the parsed\n * Stripe error fields are exposed for callers that branch on them.\n */\nexport class StripeApiError extends Error {\n readonly status: number;\n /** Stripe error type, for example `card_error` or `invalid_request_error`. */\n readonly type?: string;\n /** Stripe error code, for example `card_declined` or `resource_missing`. */\n readonly code?: string;\n /** Issuer decline code for card errors. */\n readonly declineCode?: string;\n /** Stripe's human-readable error message (never contains card data). */\n readonly stripeMessage?: string;\n /** The PaymentIntent a failed confirmation left behind, if any. */\n readonly paymentIntent?: unknown;\n\n constructor(status: number, body: string) {\n super(`Stripe API error (${status}): ${body}`);\n this.name = 'StripeApiError';\n this.status = status;\n let parsed: { error?: Record<string, unknown> } | undefined;\n try {\n parsed = JSON.parse(body) as { error?: Record<string, unknown> };\n } catch {\n // Non-JSON bodies (proxies, outages) keep only the status and message.\n parsed = undefined;\n }\n const error = parsed?.error;\n if (error && typeof error === 'object') {\n this.type = str(error.type);\n this.code = str(error.code);\n this.declineCode = str(error.decline_code);\n this.stripeMessage = str(error.message);\n this.paymentIntent = error.payment_intent;\n }\n }\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value ? value : undefined;\n}\n\n/** A write was rejected, or may have committed and requires caller reconciliation. */\nexport class QuickBooksWriteError extends Error {\n readonly code = 'QBO_WRITE_ERROR';\n readonly outcome: 'unknown' | 'rejected';\n readonly realmId: string;\n readonly environment: 'sandbox' | 'production';\n readonly endpoint: string;\n readonly requestId?: string;\n readonly payloadHash?: string;\n /** Returned identity retained for read-only reconciliation of an uncertain write. */\n readonly externalId?: string;\n readonly status?: number;\n\n constructor(details: {\n outcome: 'unknown' | 'rejected';\n realmId: string;\n environment: 'sandbox' | 'production';\n endpoint: string;\n requestId?: string;\n payloadHash?: string;\n externalId?: string;\n status?: number;\n cause: unknown;\n }) {\n super(\n `QuickBooks write outcome ${details.outcome}; reconcile before creating a new request identity`,\n { cause: details.cause },\n );\n this.name = 'QuickBooksWriteError';\n this.outcome = details.outcome;\n this.realmId = details.realmId;\n this.environment = details.environment;\n this.endpoint = details.endpoint;\n this.requestId = details.requestId;\n this.payloadHash = details.payloadHash;\n this.externalId = details.externalId;\n this.status = details.status;\n }\n}\n\nexport type QuickBooksOAuthErrorCode =\n | 'INVALID_CONFIGURATION'\n | 'INVALID_STATE'\n | 'INVALID_CALLBACK'\n | 'INVALID_TOKEN'\n | 'AUTHORIZATION_FAILED'\n | 'EXCHANGE_FAILED'\n | 'REVOCATION_FAILED';\n\n/** Sanitized QuickBooks OAuth lifecycle failure safe for application logs. */\nexport class QuickBooksOAuthError extends Error {\n constructor(readonly code: QuickBooksOAuthErrorCode) {\n super(messages[code]);\n this.name = 'QuickBooksOAuthError';\n }\n}\n\nconst messages: Record<QuickBooksOAuthErrorCode, string> = {\n INVALID_CONFIGURATION: 'QuickBooks OAuth configuration is invalid',\n INVALID_STATE: 'QuickBooks OAuth state validation failed',\n INVALID_CALLBACK: 'QuickBooks OAuth callback is invalid',\n INVALID_TOKEN: 'QuickBooks OAuth token is invalid',\n AUTHORIZATION_FAILED: 'QuickBooks OAuth authorization failed',\n EXCHANGE_FAILED: 'QuickBooks OAuth token exchange failed',\n REVOCATION_FAILED: 'QuickBooks OAuth token revocation failed',\n};\n"],"mappings":";;;;;;;;;;AAUA,IAAa,iBAAb,cAAoC,MAAM;CACxC;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;CAEA,YAAY,QAAgB,MAAc;EACxC,MAAM,qBAAqB,OAAO,KAAK,MAAM;EAC7C,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,IAAI;EAC1B,QAAQ;GAEN,SAAS,KAAA;EACX;EACA,MAAM,QAAQ,QAAQ;EACtB,IAAI,SAAS,OAAO,UAAU,UAAU;GACtC,KAAK,OAAO,IAAI,MAAM,IAAI;GAC1B,KAAK,OAAO,IAAI,MAAM,IAAI;GAC1B,KAAK,cAAc,IAAI,MAAM,YAAY;GACzC,KAAK,gBAAgB,IAAI,MAAM,OAAO;GACtC,KAAK,gBAAgB,MAAM;EAC7B;CACF;AACF;AAEA,SAAS,IAAI,OAAoC;CAC/C,OAAO,OAAO,UAAU,YAAY,QAAQ,QAAQ,KAAA;AACtD;;AAGA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAgB;CAChB;CACA;CACA;CACA;CACA;CACA;;CAEA;CACA;CAEA,YAAY,SAUT;EACD,MACE,4BAA4B,QAAQ,QAAQ,qDAC5C,EAAE,OAAO,QAAQ,MAAM,CACzB;EACA,KAAK,OAAO;EACZ,KAAK,UAAU,QAAQ;EACvB,KAAK,UAAU,QAAQ;EACvB,KAAK,cAAc,QAAQ;EAC3B,KAAK,WAAW,QAAQ;EACxB,KAAK,YAAY,QAAQ;EACzB,KAAK,cAAc,QAAQ;EAC3B,KAAK,aAAa,QAAQ;EAC1B,KAAK,SAAS,QAAQ;CACxB;AACF;;AAYA,IAAa,uBAAb,cAA0C,MAAM;CACzB;CAArB,YAAY,MAAyC;EACnD,MAAM,SAAS,KAAK;EADD,KAAA,OAAA;EAEnB,KAAK,OAAO;CACd;AACF;AAEA,IAAM,WAAqD;CACzD,uBAAuB;CACvB,eAAe;CACf,kBAAkB;CAClB,eAAe;CACf,sBAAsB;CACtB,iBAAiB;CACjB,mBAAmB;AACrB"}
|
|
@@ -1,6 +1,143 @@
|
|
|
1
|
-
import { n as QuickBooksWriteError } from "./errors-
|
|
2
|
-
import { a as
|
|
3
|
-
import { createHmac } from "node:crypto";
|
|
1
|
+
import { n as QuickBooksWriteError } from "./errors-DIqXkm4s.js";
|
|
2
|
+
import { a as validateQuickBooksReceivableRequest, d as validateQuickBooksInvoiceRequest, f as currencyMinorUnitFactor, i as validateQuickBooksReceivableMapping, o as mapCreditMemoToQBO, p as isCurrencyMinorUnit, r as reconcileQuickBooksReceivableRecord, s as mapInvoiceToQBO, t as mapQuickBooksReceivableRequest, u as validateQuickBooksCreditMemoRequest } from "./receivable-transfer-Ciy9jFYu.js";
|
|
3
|
+
import { createHash, createHmac } from "node:crypto";
|
|
4
|
+
//#region src/providers/quickbooks/receivable-operations.ts
|
|
5
|
+
/** Each operation is separately durable in the host. This class does not pretend QBO supports a saga transaction. */
|
|
6
|
+
var QuickBooksReceivableTransfers = class {
|
|
7
|
+
provider;
|
|
8
|
+
constructor(provider) {
|
|
9
|
+
this.provider = provider;
|
|
10
|
+
}
|
|
11
|
+
assertScope(mapping) {
|
|
12
|
+
validateQuickBooksReceivableMapping(mapping);
|
|
13
|
+
if (mapping.realmId !== this.provider.realmId || mapping.environment !== this.provider.environment) throw new Error("Receivable mapping belongs to another provider realm or environment");
|
|
14
|
+
}
|
|
15
|
+
async entity(type, id) {
|
|
16
|
+
if (!/^[A-Za-z0-9._-]{1,100}$/.test(id)) throw new Error("Unsupported provider entity reference");
|
|
17
|
+
const value = (await this.provider.request("GET", `${type.toLowerCase()}/${encodeURIComponent(id)}`))[type];
|
|
18
|
+
if (!value || value.Id !== id) throw new Error(`Provider ${type} identity differs`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
async verifyMapping(mapping) {
|
|
22
|
+
this.assertScope(mapping);
|
|
23
|
+
const currencies = (await this.provider.request("GET", "preferences")).Preferences?.CurrencyPrefs;
|
|
24
|
+
if (currencies?.HomeCurrency?.value !== mapping.currency || typeof currencies?.MultiCurrencyEnabled !== "boolean") throw new Error("Receivable transfers require verified home currency; foreign exchange is unsupported");
|
|
25
|
+
const customer = await this.entity("Customer", mapping.customerRef);
|
|
26
|
+
if (customer.Active !== true || (customer.CurrencyRef?.value ?? (currencies.MultiCurrencyEnabled === false ? mapping.currency : void 0)) !== mapping.currency) throw new Error("Receivable customer must be active and use the verified home currency");
|
|
27
|
+
for (const [ref, type] of [
|
|
28
|
+
[mapping.receivablesAccountRef, "Accounts Receivable"],
|
|
29
|
+
[mapping.retainedAssetAccountRef, "Other Current Asset"],
|
|
30
|
+
...mapping.cashAccountRef ? [[mapping.cashAccountRef, "Bank"]] : []
|
|
31
|
+
]) {
|
|
32
|
+
const account = await this.entity("Account", ref);
|
|
33
|
+
if (account.Active !== true || account.AccountType !== type || account.CurrencyRef !== void 0 && account.CurrencyRef?.value !== mapping.currency) throw new Error("Receivable account type, activity or currency differs from the reviewed mapping");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
mappingHash: createHash("sha256").update(JSON.stringify(mapping)).digest("hex"),
|
|
37
|
+
homeCurrency: mapping.currency
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
currency(record, mapping) {
|
|
41
|
+
if (record.CurrencyRef?.value !== mapping.currency) throw new Error("Provider transaction currency differs");
|
|
42
|
+
}
|
|
43
|
+
minor(amount, currency) {
|
|
44
|
+
if (typeof amount !== "number" || !isCurrencyMinorUnit(amount, currency)) throw new Error("Provider amount is missing or outside the exact currency grid");
|
|
45
|
+
const value = Math.round(amount * currencyMinorUnitFactor(currency));
|
|
46
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Provider amount is outside supported safe minor units");
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
journalDirection(record, mapping) {
|
|
50
|
+
this.currency(record, mapping);
|
|
51
|
+
if (!Array.isArray(record.Line)) throw new Error("Provider journal lines are missing");
|
|
52
|
+
const ar = record.Line.filter((line) => line.JournalEntryLineDetail?.AccountRef?.value === mapping.receivablesAccountRef);
|
|
53
|
+
if (ar.length !== 1) throw new Error("Provider receivable journal needs exactly one reviewed AR line");
|
|
54
|
+
const detail = ar[0].JournalEntryLineDetail;
|
|
55
|
+
if (detail.Entity?.Type !== "Customer" || detail.Entity?.EntityRef?.value !== mapping.customerRef || !["Debit", "Credit"].includes(detail.PostingType)) throw new Error("Provider journal customer or AR direction differs");
|
|
56
|
+
return {
|
|
57
|
+
kind: detail.PostingType === "Debit" ? "debit" : "credit",
|
|
58
|
+
amountMinor: this.minor(ar[0].Amount, mapping.currency)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async customerPayments(mapping) {
|
|
62
|
+
const rows = [];
|
|
63
|
+
for (let page = 0; page < 1e4; page++) {
|
|
64
|
+
const query = `SELECT * FROM Payment WHERE CustomerRef = '${mapping.customerRef}' STARTPOSITION ${page * 100 + 1} MAXRESULTS 100`;
|
|
65
|
+
const result = await this.provider.request("GET", `query?query=${encodeURIComponent(query)}`);
|
|
66
|
+
if (!result.QueryResponse) throw new Error("Provider payment query omitted its result");
|
|
67
|
+
const batch = result.QueryResponse.Payment ?? [];
|
|
68
|
+
if (!Array.isArray(batch)) throw new Error("Provider payment query is malformed");
|
|
69
|
+
rows.push(...batch);
|
|
70
|
+
if (batch.length < 100) return rows;
|
|
71
|
+
}
|
|
72
|
+
throw new Error("Complete provider payment history is required before receivable application");
|
|
73
|
+
}
|
|
74
|
+
async available(ref, mapping, direction) {
|
|
75
|
+
const record = await this.entity(ref.type, ref.id);
|
|
76
|
+
this.currency(record, mapping);
|
|
77
|
+
if (ref.type !== "JournalEntry") {
|
|
78
|
+
if (record.CustomerRef?.value !== mapping.customerRef) throw new Error("Application transaction belongs to another customer");
|
|
79
|
+
return this.minor(ref.type === "Invoice" ? record.Balance : record.RemainingCredit, mapping.currency);
|
|
80
|
+
}
|
|
81
|
+
const journal = this.journalDirection(record, mapping);
|
|
82
|
+
if (journal.kind !== direction) throw new Error("Application journal has the wrong receivable direction");
|
|
83
|
+
let used = 0n;
|
|
84
|
+
for (const payment of await this.customerPayments(mapping)) {
|
|
85
|
+
if (payment.CustomerRef?.value !== mapping.customerRef) throw new Error("Payment query crossed customer authority");
|
|
86
|
+
for (const line of payment.Line ?? []) {
|
|
87
|
+
const links = line.LinkedTxn ?? [];
|
|
88
|
+
if (links.some((link) => link.TxnType === "JournalEntry" && link.TxnId === ref.id)) {
|
|
89
|
+
if (links.length !== 1) throw new Error("Ambiguous multi-transaction payment allocation requires reconciliation");
|
|
90
|
+
this.currency(payment, mapping);
|
|
91
|
+
used += BigInt(this.minor(line.Amount, mapping.currency));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const available = BigInt(journal.amountMinor) - used;
|
|
96
|
+
if (available < 0n) throw new Error("Provider journal is overapplied and requires reconciliation");
|
|
97
|
+
return Number(available);
|
|
98
|
+
}
|
|
99
|
+
async preflight(request) {
|
|
100
|
+
await this.verifyMapping(request.input.mapping);
|
|
101
|
+
const input = request.input, source = await this.entity(input.source.type, input.source.id);
|
|
102
|
+
this.currency(source, input.mapping);
|
|
103
|
+
if (input.source.type === "JournalEntry") this.journalDirection(source, input.mapping);
|
|
104
|
+
else if (source.CustomerRef?.value !== input.mapping.customerRef) throw new Error("Source provider transaction belongs to another customer");
|
|
105
|
+
if (input.kind === "application") {
|
|
106
|
+
const debit = await this.available(input.debit, input.mapping, "debit"), credit = await this.available(input.credit, input.mapping, "credit");
|
|
107
|
+
if (input.amountMinor > debit || input.amountMinor > credit) throw new Error("Noncash application exceeds current source debit or credit capacity");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async execute(request) {
|
|
111
|
+
const retained = structuredClone(request);
|
|
112
|
+
validateQuickBooksReceivableRequest(retained, this.provider.realmId, this.provider.environment);
|
|
113
|
+
const body = mapQuickBooksReceivableRequest(retained.input);
|
|
114
|
+
await this.preflight(retained);
|
|
115
|
+
const response = await this.provider.request("POST", retained.endpoint, body, retained);
|
|
116
|
+
const type = retained.endpoint === "journalentry" ? "JournalEntry" : "Payment";
|
|
117
|
+
try {
|
|
118
|
+
return reconcileQuickBooksReceivableRecord(retained, response[type]);
|
|
119
|
+
} catch (cause) {
|
|
120
|
+
const externalId = response?.[type]?.Id;
|
|
121
|
+
throw new QuickBooksWriteError({
|
|
122
|
+
outcome: "unknown",
|
|
123
|
+
realmId: retained.realmId,
|
|
124
|
+
environment: retained.environment,
|
|
125
|
+
endpoint: retained.endpoint,
|
|
126
|
+
requestId: retained.requestId,
|
|
127
|
+
payloadHash: retained.payloadHash,
|
|
128
|
+
externalId: typeof externalId === "string" && externalId ? externalId : void 0,
|
|
129
|
+
cause
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async read(request, externalId) {
|
|
134
|
+
const retained = structuredClone(request);
|
|
135
|
+
validateQuickBooksReceivableRequest(retained, this.provider.realmId, this.provider.environment);
|
|
136
|
+
const type = retained.endpoint === "journalentry" ? "JournalEntry" : "Payment";
|
|
137
|
+
return reconcileQuickBooksReceivableRecord(retained, await this.entity(type, externalId));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
//#endregion
|
|
4
141
|
//#region src/providers/quickbooks/index.ts
|
|
5
142
|
/**
|
|
6
143
|
* QuickBooks Online Provider
|
|
@@ -35,6 +172,7 @@ function valuesEqual(a, b) {
|
|
|
35
172
|
*/
|
|
36
173
|
var QuickBooksProvider = class {
|
|
37
174
|
type = "quickbooks";
|
|
175
|
+
receivableTransfers;
|
|
38
176
|
options;
|
|
39
177
|
accessToken = null;
|
|
40
178
|
tokenExpiresAt = null;
|
|
@@ -52,6 +190,7 @@ var QuickBooksProvider = class {
|
|
|
52
190
|
maxRetries: 3,
|
|
53
191
|
...options
|
|
54
192
|
};
|
|
193
|
+
this.receivableTransfers = new QuickBooksReceivableTransfers(this);
|
|
55
194
|
this.customers = new QuickBooksCustomerOperations(this);
|
|
56
195
|
this.invoices = new QuickBooksInvoiceOperations(this);
|
|
57
196
|
this.creditMemos = new QuickBooksCreditMemoOperations(this);
|
|
@@ -70,6 +209,9 @@ var QuickBooksProvider = class {
|
|
|
70
209
|
/**
|
|
71
210
|
* Get the company/realm ID
|
|
72
211
|
*/
|
|
212
|
+
get environment() {
|
|
213
|
+
return this.options.environment === "production" ? "production" : "sandbox";
|
|
214
|
+
}
|
|
73
215
|
get realmId() {
|
|
74
216
|
return this.options.realmId;
|
|
75
217
|
}
|
|
@@ -119,8 +261,16 @@ var QuickBooksProvider = class {
|
|
|
119
261
|
const realmId = this.realmId;
|
|
120
262
|
const environment = this.options.environment === "production" ? "production" : "sandbox";
|
|
121
263
|
if (createRequest) {
|
|
122
|
-
if (method !== "POST" || ![
|
|
123
|
-
|
|
264
|
+
if (method !== "POST" || ![
|
|
265
|
+
"invoice",
|
|
266
|
+
"creditmemo",
|
|
267
|
+
"journalentry",
|
|
268
|
+
"payment"
|
|
269
|
+
].includes(endpoint)) throw new Error("QuickBooks request identity is only supported for prepared invoice, credit memo, journal or noncash application creation");
|
|
270
|
+
if (endpoint === "journalentry" || endpoint === "payment") {
|
|
271
|
+
if (!("endpoint" in createRequest) || createRequest.endpoint !== endpoint) throw new Error("Receivable endpoint differs from request identity");
|
|
272
|
+
validateQuickBooksReceivableRequest(createRequest, realmId, environment, serializedBody);
|
|
273
|
+
} else if (endpoint === "invoice") validateQuickBooksInvoiceRequest(createRequest, realmId, environment, serializedBody);
|
|
124
274
|
else validateQuickBooksCreditMemoRequest(createRequest, realmId, environment, serializedBody);
|
|
125
275
|
}
|
|
126
276
|
const requestId = createRequest?.requestId;
|
|
@@ -135,7 +285,8 @@ var QuickBooksProvider = class {
|
|
|
135
285
|
"Bill",
|
|
136
286
|
"Invoice",
|
|
137
287
|
"CreditMemo",
|
|
138
|
-
"Payment"
|
|
288
|
+
"Payment",
|
|
289
|
+
"JournalEntry"
|
|
139
290
|
].find((entity) => entity.toLowerCase() === endpoint.split(/[/?]/, 1)[0]);
|
|
140
291
|
const accessToken = await this.ensureAccessToken();
|
|
141
292
|
let uncertain = requestId !== void 0;
|
|
@@ -770,4 +921,4 @@ function mapQBOToAddress(qbo) {
|
|
|
770
921
|
//#endregion
|
|
771
922
|
export { QuickBooksProvider };
|
|
772
923
|
|
|
773
|
-
//# sourceMappingURL=quickbooks-
|
|
924
|
+
//# sourceMappingURL=quickbooks-T3C4dO8R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quickbooks-T3C4dO8R.js","names":[],"sources":["../../src/providers/quickbooks/receivable-operations.ts","../../src/providers/quickbooks/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { QuickBooksWriteError } from '../../errors.js';\nimport type { QuickBooksProvider } from './index.js';\nimport { currencyMinorUnitFactor, isCurrencyMinorUnit } from './line-amount.js';\nimport {\n mapQuickBooksReceivableRequest,\n type QuickBooksReceivableMapping,\n type QuickBooksReceivableReadback,\n type QuickBooksReceivableRequest,\n type QuickBooksReceivableTransferOperations,\n reconcileQuickBooksReceivableRecord,\n validateQuickBooksReceivableMapping,\n validateQuickBooksReceivableRequest,\n} from './receivable-transfer.js';\n\ntype ProviderRecord = Record<string, any>;\n/** Each operation is separately durable in the host. This class does not pretend QBO supports a saga transaction. */\nexport class QuickBooksReceivableTransfers\n implements QuickBooksReceivableTransferOperations\n{\n constructor(private provider: QuickBooksProvider) {}\n private assertScope(mapping: QuickBooksReceivableMapping) {\n validateQuickBooksReceivableMapping(mapping);\n if (\n mapping.realmId !== this.provider.realmId ||\n mapping.environment !== this.provider.environment\n )\n throw new Error(\n 'Receivable mapping belongs to another provider realm or environment',\n );\n }\n private async entity(type: string, id: string): Promise<ProviderRecord> {\n if (!/^[A-Za-z0-9._-]{1,100}$/.test(id))\n throw new Error('Unsupported provider entity reference');\n const result = await this.provider.request<Record<string, ProviderRecord>>(\n 'GET',\n `${type.toLowerCase()}/${encodeURIComponent(id)}`,\n );\n const value = result[type];\n if (!value || value.Id !== id)\n throw new Error(`Provider ${type} identity differs`);\n return value;\n }\n async verifyMapping(mapping: QuickBooksReceivableMapping) {\n this.assertScope(mapping);\n const prefs = await this.provider.request<{ Preferences?: ProviderRecord }>(\n 'GET',\n 'preferences',\n );\n const currencies = prefs.Preferences?.CurrencyPrefs;\n if (\n currencies?.HomeCurrency?.value !== mapping.currency ||\n typeof currencies?.MultiCurrencyEnabled !== 'boolean'\n )\n throw new Error(\n 'Receivable transfers require verified home currency; foreign exchange is unsupported',\n );\n const customer = await this.entity('Customer', mapping.customerRef);\n if (\n customer.Active !== true ||\n (customer.CurrencyRef?.value ??\n (currencies.MultiCurrencyEnabled === false\n ? mapping.currency\n : undefined)) !== mapping.currency\n )\n throw new Error(\n 'Receivable customer must be active and use the verified home currency',\n );\n for (const [ref, type] of [\n [mapping.receivablesAccountRef, 'Accounts Receivable'],\n [mapping.retainedAssetAccountRef, 'Other Current Asset'],\n ...(mapping.cashAccountRef ? [[mapping.cashAccountRef, 'Bank']] : []),\n ]) {\n const account = await this.entity('Account', ref);\n if (\n account.Active !== true ||\n account.AccountType !== type ||\n (account.CurrencyRef !== undefined &&\n account.CurrencyRef?.value !== mapping.currency)\n )\n throw new Error(\n 'Receivable account type, activity or currency differs from the reviewed mapping',\n );\n }\n return {\n mappingHash: createHash('sha256')\n .update(JSON.stringify(mapping))\n .digest('hex'),\n homeCurrency: mapping.currency,\n };\n }\n private currency(\n record: ProviderRecord,\n mapping: QuickBooksReceivableMapping,\n ) {\n if (record.CurrencyRef?.value !== mapping.currency)\n throw new Error('Provider transaction currency differs');\n }\n private minor(amount: unknown, currency: string): number {\n if (typeof amount !== 'number' || !isCurrencyMinorUnit(amount, currency))\n throw new Error(\n 'Provider amount is missing or outside the exact currency grid',\n );\n const value = Math.round(amount * currencyMinorUnitFactor(currency)!);\n if (!Number.isSafeInteger(value) || value < 0)\n throw new Error('Provider amount is outside supported safe minor units');\n return value;\n }\n private journalDirection(\n record: ProviderRecord,\n mapping: QuickBooksReceivableMapping,\n ): { kind: 'debit' | 'credit'; amountMinor: number } {\n this.currency(record, mapping);\n if (!Array.isArray(record.Line))\n throw new Error('Provider journal lines are missing');\n const ar = record.Line.filter(\n (line: ProviderRecord) =>\n line.JournalEntryLineDetail?.AccountRef?.value ===\n mapping.receivablesAccountRef,\n );\n if (ar.length !== 1)\n throw new Error(\n 'Provider receivable journal needs exactly one reviewed AR line',\n );\n const detail = ar[0].JournalEntryLineDetail;\n if (\n detail.Entity?.Type !== 'Customer' ||\n detail.Entity?.EntityRef?.value !== mapping.customerRef ||\n !['Debit', 'Credit'].includes(detail.PostingType)\n )\n throw new Error('Provider journal customer or AR direction differs');\n return {\n kind: detail.PostingType === 'Debit' ? 'debit' : 'credit',\n amountMinor: this.minor(ar[0].Amount, mapping.currency),\n };\n }\n private async customerPayments(\n mapping: QuickBooksReceivableMapping,\n ): Promise<ProviderRecord[]> {\n const rows: ProviderRecord[] = [];\n // Complete pagination is mandatory for capacity; no ordinary list default cap may truncate it.\n for (let page = 0; page < 10000; page++) {\n const query = `SELECT * FROM Payment WHERE CustomerRef = '${mapping.customerRef}' STARTPOSITION ${page * 100 + 1} MAXRESULTS 100`;\n const result = await this.provider.request<{\n QueryResponse?: { Payment?: ProviderRecord[] };\n }>('GET', `query?query=${encodeURIComponent(query)}`);\n if (!result.QueryResponse)\n throw new Error('Provider payment query omitted its result');\n const batch = result.QueryResponse.Payment ?? [];\n if (!Array.isArray(batch))\n throw new Error('Provider payment query is malformed');\n rows.push(...batch);\n if (batch.length < 100) return rows;\n }\n throw new Error(\n 'Complete provider payment history is required before receivable application',\n );\n }\n private async available(\n ref: { type: string; id: string },\n mapping: QuickBooksReceivableMapping,\n direction: 'debit' | 'credit',\n ) {\n const record = await this.entity(ref.type, ref.id);\n this.currency(record, mapping);\n if (ref.type !== 'JournalEntry') {\n if (record.CustomerRef?.value !== mapping.customerRef)\n throw new Error('Application transaction belongs to another customer');\n return this.minor(\n ref.type === 'Invoice' ? record.Balance : record.RemainingCredit,\n mapping.currency,\n );\n }\n const journal = this.journalDirection(record, mapping);\n if (journal.kind !== direction)\n throw new Error('Application journal has the wrong receivable direction');\n let used = 0n;\n for (const payment of await this.customerPayments(mapping)) {\n if (payment.CustomerRef?.value !== mapping.customerRef)\n throw new Error('Payment query crossed customer authority');\n for (const line of payment.Line ?? []) {\n const links = line.LinkedTxn ?? [];\n if (\n links.some(\n (link: ProviderRecord) =>\n link.TxnType === 'JournalEntry' && link.TxnId === ref.id,\n )\n ) {\n if (links.length !== 1)\n throw new Error(\n 'Ambiguous multi-transaction payment allocation requires reconciliation',\n );\n this.currency(payment, mapping);\n used += BigInt(this.minor(line.Amount, mapping.currency));\n }\n }\n }\n const available = BigInt(journal.amountMinor) - used;\n if (available < 0n)\n throw new Error(\n 'Provider journal is overapplied and requires reconciliation',\n );\n return Number(available);\n }\n private async preflight(request: QuickBooksReceivableRequest) {\n await this.verifyMapping(request.input.mapping);\n const input = request.input,\n source = await this.entity(input.source.type, input.source.id);\n this.currency(source, input.mapping);\n if (input.source.type === 'JournalEntry')\n this.journalDirection(source, input.mapping);\n else if (source.CustomerRef?.value !== input.mapping.customerRef)\n throw new Error(\n 'Source provider transaction belongs to another customer',\n );\n if (input.kind === 'application') {\n const debit = await this.available(input.debit, input.mapping, 'debit'),\n credit = await this.available(input.credit, input.mapping, 'credit');\n if (input.amountMinor > debit || input.amountMinor > credit)\n throw new Error(\n 'Noncash application exceeds current source debit or credit capacity',\n );\n }\n }\n async execute(\n request: QuickBooksReceivableRequest,\n ): Promise<QuickBooksReceivableReadback> {\n // Reconstruct and retain exact bytes before any asynchronous metadata lookup.\n const retained = structuredClone(request);\n validateQuickBooksReceivableRequest(\n retained,\n this.provider.realmId,\n this.provider.environment,\n );\n const body = mapQuickBooksReceivableRequest(retained.input);\n await this.preflight(retained);\n const response = await this.provider.request<\n Record<string, ProviderRecord>\n >('POST', retained.endpoint, body, retained);\n const type =\n retained.endpoint === 'journalentry' ? 'JournalEntry' : 'Payment';\n // A malformed success is unresolved, never a confirmed operation.\n try {\n return reconcileQuickBooksReceivableRecord(retained, response[type]);\n } catch (cause) {\n const externalId = response?.[type]?.Id;\n throw new QuickBooksWriteError({\n outcome: 'unknown',\n realmId: retained.realmId,\n environment: retained.environment,\n endpoint: retained.endpoint,\n requestId: retained.requestId,\n payloadHash: retained.payloadHash,\n externalId:\n typeof externalId === 'string' && externalId ? externalId : undefined,\n cause,\n });\n }\n }\n async read(\n request: QuickBooksReceivableRequest,\n externalId: string,\n ): Promise<QuickBooksReceivableReadback> {\n const retained = structuredClone(request);\n validateQuickBooksReceivableRequest(\n retained,\n this.provider.realmId,\n this.provider.environment,\n );\n const type =\n retained.endpoint === 'journalentry' ? 'JournalEntry' : 'Payment';\n return reconcileQuickBooksReceivableRecord(\n retained,\n await this.entity(type, externalId),\n );\n }\n}\n","import { QuickBooksReceivableTransfers } from './receivable-operations.js';\nimport {\n type QuickBooksReceivableRequest,\n validateQuickBooksReceivableRequest,\n} from './receivable-transfer.js';\n/**\n * QuickBooks Online Provider\n *\n * Implements the AccountingProvider interface for QuickBooks Online.\n * Uses the Intuit OAuth2 library for authentication and the QBO API for operations.\n */\n\n/// <reference path=\"../../intuit-oauth.d.ts\" />\n\nimport { createHmac } from 'node:crypto';\nimport { QuickBooksWriteError } from '../../errors.js';\nimport type {\n AccountingProvider,\n Address,\n AuditDiscrepancy,\n AuditMatch,\n AuditOperations,\n AuditReport,\n BillInput,\n BillOperations,\n CreditMemoInput,\n CreditMemoOperations,\n CustomerInput,\n CustomerOperations,\n DateRange,\n ExternalBill,\n ExternalCustomer,\n ExternalInvoice,\n ExternalPayment,\n ExternalRecord,\n ExternalVendor,\n FieldDiff,\n InvoiceInput,\n InvoiceOperations,\n ListOptions,\n PaymentInput,\n PaymentOperations,\n QuickBooksCreditMemoRequest,\n QuickBooksInvoiceRequest,\n QuickBooksOptions,\n SyncResult,\n TokenSet,\n VendorInput,\n VendorOperations,\n WebhookEvent,\n WebhookOperations,\n} from '../../types.js';\nimport {\n mapCreditMemoToQBO,\n mapInvoiceToQBO,\n validateQuickBooksCreditMemoRequest,\n validateQuickBooksInvoiceRequest,\n} from './invoice-request.js';\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Format a Date to YYYY-MM-DD in local timezone (not UTC)\n * This prevents timezone conversion issues where dates shift by a day\n */\nfunction formatLocalDate(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Sleep for a given number of milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Compare two values for equality (handles dates, objects, etc.)\n */\nfunction valuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n if (typeof a === 'number' && typeof b === 'number') {\n // Allow for floating point precision issues\n return Math.abs(a - b) < 0.01;\n }\n return false;\n}\n\n// =============================================================================\n// QuickBooks Provider\n// =============================================================================\n\n/**\n * QuickBooks Online accounting provider\n */\nexport class QuickBooksProvider implements AccountingProvider {\n readonly type = 'quickbooks' as const;\n readonly receivableTransfers: QuickBooksReceivableTransfers;\n\n private options: QuickBooksOptions;\n private accessToken: string | null = null;\n private tokenExpiresAt: Date | null = null;\n\n readonly customers: CustomerOperations;\n readonly invoices: InvoiceOperations;\n readonly creditMemos: CreditMemoOperations;\n readonly vendors: VendorOperations;\n readonly bills: BillOperations;\n readonly payments: PaymentOperations;\n readonly audit: AuditOperations;\n readonly webhooks: WebhookOperations;\n\n constructor(options: QuickBooksOptions) {\n this.options = {\n timeout: 30000,\n maxRetries: 3,\n ...options,\n };\n\n this.receivableTransfers = new QuickBooksReceivableTransfers(this);\n // Initialize operation handlers\n this.customers = new QuickBooksCustomerOperations(this);\n this.invoices = new QuickBooksInvoiceOperations(this);\n this.creditMemos = new QuickBooksCreditMemoOperations(this);\n this.vendors = new QuickBooksVendorOperations(this);\n this.bills = new QuickBooksBillOperations(this);\n this.payments = new QuickBooksPaymentOperations(this);\n this.audit = new QuickBooksAuditOperations(this);\n this.webhooks = new QuickBooksWebhookOperations(this.options);\n }\n\n /**\n * Get the QBO API base URL\n */\n get baseUrl(): string {\n return this.options.environment === 'production'\n ? 'https://quickbooks.api.intuit.com'\n : 'https://sandbox-quickbooks.api.intuit.com';\n }\n\n /**\n * Get the company/realm ID\n */\n get environment(): 'sandbox' | 'production' {\n return this.options.environment === 'production' ? 'production' : 'sandbox';\n }\n\n get realmId(): string {\n return this.options.realmId;\n }\n\n /**\n * Ensure we have a valid access token, refreshing if needed\n */\n async ensureAccessToken(): Promise<string> {\n // Check if current token is still valid (with 5 min buffer)\n if (this.accessToken && this.tokenExpiresAt) {\n const bufferMs = 5 * 60 * 1000;\n if (new Date().getTime() < this.tokenExpiresAt.getTime() - bufferMs) {\n return this.accessToken;\n }\n }\n\n // Refresh the token\n const tokens = await this.refreshAccessToken();\n this.accessToken = tokens.accessToken;\n this.tokenExpiresAt = tokens.expiresAt;\n\n // Update stored refresh token so subsequent refreshes use the new token\n this.options.refreshToken = tokens.refreshToken;\n\n // Notify callback if provided\n if (this.options.onTokenRefresh) {\n await this.options.onTokenRefresh(tokens);\n }\n\n return this.accessToken;\n }\n\n /**\n * Refresh the OAuth access token\n */\n private async refreshAccessToken(): Promise<TokenSet> {\n // Import intuit-oauth dynamically\n const OAuthClient = (await import('intuit-oauth')).default;\n\n const redirectUri =\n this.options.redirectUri ||\n 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl';\n\n const oauthClient = new OAuthClient({\n clientId: this.options.clientId,\n clientSecret: this.options.clientSecret,\n environment:\n this.options.environment === 'production' ? 'production' : 'sandbox',\n redirectUri,\n });\n\n oauthClient.setToken({\n refresh_token: this.options.refreshToken,\n access_token: this.accessToken || '',\n });\n\n const response = await oauthClient.refresh();\n const token = response.getJson();\n\n return {\n accessToken: token.access_token,\n refreshToken: token.refresh_token,\n expiresAt: new Date(Date.now() + token.expires_in * 1000),\n tokenType: token.token_type,\n };\n }\n\n /**\n * Make an authenticated request to the QBO API with timeout and retry support\n */\n async request<T>(\n method: 'GET' | 'POST' | 'DELETE',\n endpoint: string,\n body?: unknown,\n createRequest?:\n | QuickBooksInvoiceRequest\n | QuickBooksCreditMemoRequest\n | QuickBooksReceivableRequest,\n ): Promise<T> {\n // Freeze identity and bytes before any asynchronous work.\n const serializedBody =\n body === undefined ? undefined : JSON.stringify(body);\n const realmId = this.realmId;\n const environment =\n this.options.environment === 'production' ? 'production' : 'sandbox';\n if (createRequest) {\n if (\n method !== 'POST' ||\n !['invoice', 'creditmemo', 'journalentry', 'payment'].includes(endpoint)\n ) {\n throw new Error(\n 'QuickBooks request identity is only supported for prepared invoice, credit memo, journal or noncash application creation',\n );\n }\n if (endpoint === 'journalentry' || endpoint === 'payment') {\n if (\n !('endpoint' in createRequest) ||\n createRequest.endpoint !== endpoint\n )\n throw new Error('Receivable endpoint differs from request identity');\n validateQuickBooksReceivableRequest(\n createRequest,\n realmId,\n environment,\n serializedBody,\n );\n } else if (endpoint === 'invoice')\n validateQuickBooksInvoiceRequest(\n createRequest as QuickBooksInvoiceRequest,\n realmId,\n environment,\n serializedBody,\n );\n else\n validateQuickBooksCreditMemoRequest(\n createRequest as QuickBooksCreditMemoRequest,\n realmId,\n environment,\n serializedBody,\n );\n }\n const requestId = createRequest?.requestId;\n const payloadHash = createRequest?.payloadHash;\n const url = new URL(`${this.baseUrl}/v3/company/${realmId}/${endpoint}`);\n if (requestId) url.searchParams.set('requestid', requestId);\n const timeout = this.options.timeout ?? 30000;\n const maxRetries =\n method === 'GET' || requestId ? (this.options.maxRetries ?? 3) : 0;\n const responseEntity = [\n 'Customer',\n 'Vendor',\n 'Bill',\n 'Invoice',\n 'CreditMemo',\n 'Payment',\n 'JournalEntry',\n ].find((entity) => entity.toLowerCase() === endpoint.split(/[/?]/, 1)[0]);\n const accessToken = await this.ensureAccessToken();\n // A persisted identity may have been sent by an earlier worker. Without a\n // durable attempt history, even its first observed 4xx cannot prove that\n // the logical write was never accepted. Fail closed across process retries.\n let uncertain = requestId !== undefined;\n\n for (let attempt = 0; ; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n let status: number | undefined;\n let failure: unknown;\n try {\n const response = await fetch(url.toString(), {\n method,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: serializedBody,\n signal: controller.signal,\n });\n status = response.status;\n if (!response.ok) {\n throw new Error(\n `QBO API error (${status}): ${await response.text()}`,\n );\n }\n const result = await response.json();\n // Every supported write must return a usable remote identity. Validate\n // inside this boundary so malformed successes cannot escape reconciliation.\n if (\n method !== 'GET' &&\n responseEntity &&\n (typeof result?.[responseEntity]?.Id !== 'string' ||\n !result[responseEntity].Id.trim())\n ) {\n throw new Error(`QBO response is missing ${responseEntity}.Id`);\n }\n return result as T;\n } catch (error) {\n failure = error;\n uncertain ||=\n status === undefined ||\n status >= 500 ||\n (status >= 200 && status < 300);\n } finally {\n clearTimeout(timeoutId);\n }\n const retryable =\n status === undefined ||\n status === 429 ||\n status >= 500 ||\n (status >= 200 && status < 300);\n if (!retryable || attempt >= maxRetries) {\n if (method !== 'GET') {\n throw new QuickBooksWriteError({\n realmId,\n environment,\n endpoint,\n requestId,\n status,\n payloadHash,\n outcome: uncertain ? 'unknown' : 'rejected',\n cause: failure,\n });\n }\n throw failure;\n }\n await sleep(2 ** attempt * 1000);\n }\n }\n\n /**\n * Execute a QBO query with pagination support\n */\n async queryAll<T>(\n entity: string,\n whereClause?: string,\n maxResults = 1000,\n ): Promise<T[]> {\n const results: T[] = [];\n let startPosition = 1;\n const pageSize = 100; // QBO max is 1000, but smaller pages are safer\n\n while (results.length < maxResults) {\n let query = `SELECT * FROM ${entity}`;\n if (whereClause) {\n query += ` WHERE ${whereClause}`;\n }\n query += ` MAXRESULTS ${pageSize} STARTPOSITION ${startPosition}`;\n\n const response = await this.request<{\n QueryResponse: Record<string, T[] | undefined>;\n }>('GET', `query?query=${encodeURIComponent(query)}`);\n\n const items = response.QueryResponse[entity] || [];\n results.push(...items);\n\n // If we got fewer than pageSize, we've reached the end\n if (items.length < pageSize) {\n break;\n }\n\n startPosition += pageSize;\n }\n\n return results;\n }\n}\n\n// =============================================================================\n// Customer Operations\n// =============================================================================\n\nclass QuickBooksCustomerOperations implements CustomerOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async push(customer: CustomerInput): Promise<SyncResult> {\n const qboCustomer = mapCustomerToQBO(customer);\n\n const response = await this.provider.request<{ Customer: QBOCustomer }>(\n 'POST',\n 'customer',\n qboCustomer,\n );\n\n return {\n action: 'created',\n externalId: response.Customer.Id,\n syncedAt: new Date(),\n };\n }\n\n async pull(externalId: string): Promise<ExternalCustomer> {\n const response = await this.provider.request<{ Customer: QBOCustomer }>(\n 'GET',\n `customer/${externalId}`,\n );\n\n return mapQBOToCustomer(response.Customer);\n }\n\n async list(options?: ListOptions): Promise<ExternalCustomer[]> {\n const limit = options?.limit || 1000;\n const customers = await this.provider.queryAll<QBOCustomer>(\n 'Customer',\n undefined,\n limit,\n );\n return customers.map(mapQBOToCustomer);\n }\n\n async sync(customer: CustomerInput): Promise<SyncResult> {\n if (customer.externalId) {\n // Update existing\n const existing = await this.provider.request<{ Customer: QBOCustomer }>(\n 'GET',\n `customer/${customer.externalId}`,\n );\n\n const qboCustomer: QBOCustomerUpdate = {\n ...mapCustomerToQBO(customer),\n Id: customer.externalId,\n SyncToken: existing.Customer.SyncToken,\n };\n\n await this.provider.request('POST', 'customer', qboCustomer);\n\n return {\n action: 'updated',\n externalId: customer.externalId,\n syncedAt: new Date(),\n };\n }\n\n return this.push(customer);\n }\n}\n\n// =============================================================================\n// Invoice Operations\n// =============================================================================\n\nclass QuickBooksInvoiceOperations implements InvoiceOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async push(invoice: InvoiceInput): Promise<SyncResult> {\n if (invoice.externalId && invoice.quickbooksRequest)\n throw new Error(\n 'QuickBooks invoice request identity cannot be used for updates',\n );\n const qboInvoice = mapInvoiceToQBO(invoice);\n\n const response = await this.provider.request<{ Invoice: QBOInvoice }>(\n 'POST',\n 'invoice',\n qboInvoice,\n invoice.quickbooksRequest,\n );\n\n return {\n action: 'created',\n externalId: response.Invoice.Id,\n syncedAt: new Date(),\n };\n }\n\n async pull(externalId: string): Promise<ExternalInvoice> {\n const response = await this.provider.request<{ Invoice: QBOInvoice }>(\n 'GET',\n `invoice/${externalId}`,\n );\n\n return mapQBOToInvoice(response.Invoice);\n }\n\n async list(options?: ListOptions): Promise<ExternalInvoice[]> {\n const limit = options?.limit || 1000;\n let whereClause: string | undefined;\n\n if (options?.startDate) {\n whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;\n if (options?.endDate) {\n whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;\n }\n }\n\n const invoices = await this.provider.queryAll<QBOInvoice>(\n 'Invoice',\n whereClause,\n limit,\n );\n return invoices.map(mapQBOToInvoice);\n }\n\n async sync(invoice: InvoiceInput): Promise<SyncResult> {\n if (invoice.externalId) {\n if (invoice.quickbooksRequest)\n throw new Error(\n 'QuickBooks invoice request identity cannot be used for updates',\n );\n const existing = await this.provider.request<{ Invoice: QBOInvoice }>(\n 'GET',\n `invoice/${invoice.externalId}`,\n );\n\n const qboInvoice: QBOInvoiceUpdate = {\n ...mapInvoiceToQBO(invoice),\n Id: invoice.externalId,\n SyncToken: existing.Invoice.SyncToken,\n };\n\n await this.provider.request('POST', 'invoice', qboInvoice);\n\n return {\n action: 'updated',\n externalId: invoice.externalId,\n syncedAt: new Date(),\n };\n }\n\n return this.push(invoice);\n }\n\n async send(externalId: string): Promise<void> {\n await this.provider.request('POST', `invoice/${externalId}/send`);\n }\n\n async void(externalId: string): Promise<void> {\n const existing = await this.provider.request<{ Invoice: QBOInvoice }>(\n 'GET',\n `invoice/${externalId}`,\n );\n\n await this.provider.request('POST', 'invoice', {\n Id: externalId,\n SyncToken: existing.Invoice.SyncToken,\n sparse: true,\n // QBO doesn't have a Void field - you delete the invoice instead\n // or create a credit memo. For now, we'll use sparse update to mark private note\n PrivateNote: 'VOIDED',\n });\n }\n}\n\n// =============================================================================\n// Credit memo operations\n// =============================================================================\n\nclass QuickBooksCreditMemoOperations implements CreditMemoOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async push(creditMemo: CreditMemoInput): Promise<SyncResult> {\n if (creditMemo.externalId && creditMemo.quickbooksCreditMemoRequest)\n throw new Error(\n 'QuickBooks credit memo request identity cannot be used for updates',\n );\n const mapped = mapCreditMemoToQBO(creditMemo);\n if (\n creditMemo.quickbooksCreditMemoRequest &&\n creditMemo.quickbooksCreditMemoRequest.creditedInvoiceExternalId !==\n creditMemo.creditedInvoiceExternalId\n )\n throw new Error(\n 'QuickBooks credit memo request linkage mismatch; reconcile the original request',\n );\n const response = await this.provider.request<{\n CreditMemo: { Id: string };\n }>('POST', 'creditmemo', mapped, creditMemo.quickbooksCreditMemoRequest);\n return {\n action: 'created',\n externalId: response.CreditMemo.Id,\n syncedAt: new Date(),\n };\n }\n\n async sync(creditMemo: CreditMemoInput): Promise<SyncResult> {\n if (!creditMemo.externalId) return this.push(creditMemo);\n if (creditMemo.quickbooksCreditMemoRequest)\n throw new Error(\n 'QuickBooks credit memo request identity cannot be used for updates',\n );\n // Refuse malformed frozen snapshots before authenticating or contacting QBO.\n const mapped = mapCreditMemoToQBO(creditMemo);\n const existing = await this.provider.request<{\n CreditMemo: { SyncToken: string };\n }>('GET', `creditmemo/${creditMemo.externalId}`);\n await this.provider.request('POST', 'creditmemo', {\n ...mapped,\n Id: creditMemo.externalId,\n SyncToken: existing.CreditMemo.SyncToken,\n });\n return {\n action: 'updated',\n externalId: creditMemo.externalId,\n syncedAt: new Date(),\n };\n }\n}\n\n// =============================================================================\n// Vendor Operations\n// =============================================================================\n\nclass QuickBooksVendorOperations implements VendorOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async push(vendor: VendorInput): Promise<SyncResult> {\n const qboVendor = mapVendorToQBO(vendor);\n\n const response = await this.provider.request<{ Vendor: QBOVendor }>(\n 'POST',\n 'vendor',\n qboVendor,\n );\n\n return {\n action: 'created',\n externalId: response.Vendor.Id,\n syncedAt: new Date(),\n };\n }\n\n async pull(externalId: string): Promise<ExternalVendor> {\n const response = await this.provider.request<{ Vendor: QBOVendor }>(\n 'GET',\n `vendor/${externalId}`,\n );\n\n return mapQBOToVendor(response.Vendor);\n }\n\n async list(options?: ListOptions): Promise<ExternalVendor[]> {\n const limit = options?.limit || 1000;\n const vendors = await this.provider.queryAll<QBOVendor>(\n 'Vendor',\n undefined,\n limit,\n );\n return vendors.map(mapQBOToVendor);\n }\n\n async sync(vendor: VendorInput): Promise<SyncResult> {\n if (vendor.externalId) {\n const existing = await this.provider.request<{ Vendor: QBOVendor }>(\n 'GET',\n `vendor/${vendor.externalId}`,\n );\n\n const qboVendor: QBOVendorUpdate = {\n ...mapVendorToQBO(vendor),\n Id: vendor.externalId,\n SyncToken: existing.Vendor.SyncToken,\n };\n\n await this.provider.request('POST', 'vendor', qboVendor);\n\n return {\n action: 'updated',\n externalId: vendor.externalId,\n syncedAt: new Date(),\n };\n }\n\n return this.push(vendor);\n }\n}\n\n// =============================================================================\n// Bill Operations\n// =============================================================================\n\nclass QuickBooksBillOperations implements BillOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async push(bill: BillInput): Promise<SyncResult> {\n const qboBill = mapBillToQBO(bill);\n\n const response = await this.provider.request<{ Bill: QBOBill }>(\n 'POST',\n 'bill',\n qboBill,\n );\n\n return {\n action: 'created',\n externalId: response.Bill.Id,\n syncedAt: new Date(),\n };\n }\n\n async pull(externalId: string): Promise<ExternalBill> {\n const response = await this.provider.request<{ Bill: QBOBill }>(\n 'GET',\n `bill/${externalId}`,\n );\n\n return mapQBOToBill(response.Bill);\n }\n\n async list(options?: ListOptions): Promise<ExternalBill[]> {\n const limit = options?.limit || 1000;\n let whereClause: string | undefined;\n\n if (options?.startDate) {\n whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;\n if (options?.endDate) {\n whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;\n }\n }\n\n const bills = await this.provider.queryAll<QBOBill>(\n 'Bill',\n whereClause,\n limit,\n );\n return bills.map(mapQBOToBill);\n }\n\n async sync(bill: BillInput): Promise<SyncResult> {\n if (bill.externalId) {\n const existing = await this.provider.request<{ Bill: QBOBill }>(\n 'GET',\n `bill/${bill.externalId}`,\n );\n\n const qboBill: QBOBillUpdate = {\n ...mapBillToQBO(bill),\n Id: bill.externalId,\n SyncToken: existing.Bill.SyncToken,\n };\n\n await this.provider.request('POST', 'bill', qboBill);\n\n return {\n action: 'updated',\n externalId: bill.externalId,\n syncedAt: new Date(),\n };\n }\n\n return this.push(bill);\n }\n}\n\n// =============================================================================\n// Payment Operations\n// =============================================================================\n\nclass QuickBooksPaymentOperations implements PaymentOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async pull(externalId: string): Promise<ExternalPayment> {\n const response = await this.provider.request<{ Payment: QBOPayment }>(\n 'GET',\n `payment/${externalId}`,\n );\n\n return mapQBOToPayment(response.Payment);\n }\n\n async list(options?: ListOptions): Promise<ExternalPayment[]> {\n const limit = options?.limit || 1000;\n let whereClause: string | undefined;\n\n if (options?.startDate) {\n whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;\n if (options?.endDate) {\n whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;\n }\n }\n\n const payments = await this.provider.queryAll<QBOPayment>(\n 'Payment',\n whereClause,\n limit,\n );\n return payments.map(mapQBOToPayment);\n }\n}\n\n// =============================================================================\n// Audit Operations\n// =============================================================================\n\nclass QuickBooksAuditOperations implements AuditOperations {\n constructor(private provider: QuickBooksProvider) {}\n\n async reconcileCustomers(\n locals: CustomerInput[],\n ): Promise<AuditReport<CustomerInput>> {\n const externals = await this.provider.customers.list({ limit: 10000 });\n return reconcileRecords(\n locals,\n externals,\n (l) => l.externalId,\n (e) => e.externalId,\n compareCustomer,\n );\n }\n\n async reconcileInvoices(\n locals: InvoiceInput[],\n dateRange?: DateRange,\n ): Promise<AuditReport<InvoiceInput>> {\n const externals = await this.provider.invoices.list({\n limit: 10000,\n startDate: dateRange?.start,\n endDate: dateRange?.end,\n });\n return reconcileRecords(\n locals,\n externals,\n (l) => l.externalId,\n (e) => e.externalId,\n compareInvoice,\n );\n }\n\n async reconcileVendors(\n locals: VendorInput[],\n ): Promise<AuditReport<VendorInput>> {\n const externals = await this.provider.vendors.list({ limit: 10000 });\n return reconcileRecords(\n locals,\n externals,\n (l) => l.externalId,\n (e) => e.externalId,\n compareVendor,\n );\n }\n\n async reconcileBills(\n locals: BillInput[],\n dateRange?: DateRange,\n ): Promise<AuditReport<BillInput>> {\n const externals = await this.provider.bills.list({\n limit: 10000,\n startDate: dateRange?.start,\n endDate: dateRange?.end,\n });\n return reconcileRecords(\n locals,\n externals,\n (l) => l.externalId,\n (e) => e.externalId,\n compareBill,\n );\n }\n\n async reconcilePayments(\n locals: PaymentInput[],\n dateRange?: DateRange,\n ): Promise<AuditReport<PaymentInput>> {\n const externals = await this.provider.payments.list({\n limit: 10000,\n startDate: dateRange?.start,\n endDate: dateRange?.end,\n });\n return reconcileRecords(\n locals,\n externals,\n (l) => l.externalId,\n (e) => e.externalId,\n comparePayment,\n );\n }\n}\n\n/**\n * Generic reconciliation function with field comparison\n */\nfunction reconcileRecords<TLocal, TExternal extends ExternalRecord>(\n locals: TLocal[],\n externals: TExternal[],\n getLocalExternalId: (local: TLocal) => string | undefined,\n getExternalId: (external: TExternal) => string,\n compareFields: (local: TLocal, external: TExternal) => FieldDiff[],\n): AuditReport<TLocal> {\n const externalMap = new Map(externals.map((e) => [getExternalId(e), e]));\n const matchedExternalIds = new Set<string>();\n\n const matched: AuditMatch<TLocal>[] = [];\n const localOnly: TLocal[] = [];\n const discrepancies: AuditDiscrepancy<TLocal>[] = [];\n\n for (const local of locals) {\n const externalId = getLocalExternalId(local);\n if (externalId && externalMap.has(externalId)) {\n const external = externalMap.get(externalId)!;\n matchedExternalIds.add(externalId);\n\n // Compare fields\n const differences = compareFields(local, external);\n\n if (differences.length === 0) {\n matched.push({\n local,\n external,\n status: 'identical',\n });\n } else {\n discrepancies.push({\n local,\n external,\n differences,\n });\n }\n } else {\n localOnly.push(local);\n }\n }\n\n const externalOnly = externals.filter(\n (e) => !matchedExternalIds.has(getExternalId(e)),\n );\n\n return {\n provider: 'quickbooks',\n auditedAt: new Date(),\n matched,\n localOnly,\n externalOnly,\n discrepancies,\n summary: {\n total: locals.length + externalOnly.length,\n matched: matched.length,\n localOnly: localOnly.length,\n externalOnly: externalOnly.length,\n discrepancies: discrepancies.length,\n },\n };\n}\n\n// Field comparison functions\nfunction compareCustomer(\n local: CustomerInput,\n external: ExternalCustomer,\n): FieldDiff[] {\n const diffs: FieldDiff[] = [];\n\n if (local.name !== external.name) {\n diffs.push({\n field: 'name',\n localValue: local.name,\n externalValue: external.name,\n });\n }\n if (local.email !== external.email) {\n diffs.push({\n field: 'email',\n localValue: local.email,\n externalValue: external.email,\n });\n }\n\n return diffs;\n}\n\nfunction compareInvoice(\n local: InvoiceInput,\n external: ExternalInvoice,\n): FieldDiff[] {\n const diffs: FieldDiff[] = [];\n\n if (!valuesEqual(local.totalAmount, external.totalAmount)) {\n diffs.push({\n field: 'totalAmount',\n localValue: local.totalAmount,\n externalValue: external.totalAmount,\n });\n }\n if (local.invoiceNumber !== external.invoiceNumber) {\n diffs.push({\n field: 'invoiceNumber',\n localValue: local.invoiceNumber,\n externalValue: external.invoiceNumber,\n });\n }\n\n return diffs;\n}\n\nfunction compareVendor(\n local: VendorInput,\n external: ExternalVendor,\n): FieldDiff[] {\n const diffs: FieldDiff[] = [];\n\n if (local.name !== external.name) {\n diffs.push({\n field: 'name',\n localValue: local.name,\n externalValue: external.name,\n });\n }\n if (local.email !== external.email) {\n diffs.push({\n field: 'email',\n localValue: local.email,\n externalValue: external.email,\n });\n }\n\n return diffs;\n}\n\nfunction compareBill(local: BillInput, external: ExternalBill): FieldDiff[] {\n const diffs: FieldDiff[] = [];\n\n if (!valuesEqual(local.totalAmount, external.totalAmount)) {\n diffs.push({\n field: 'totalAmount',\n localValue: local.totalAmount,\n externalValue: external.totalAmount,\n });\n }\n\n return diffs;\n}\n\nfunction comparePayment(\n local: PaymentInput,\n external: ExternalPayment,\n): FieldDiff[] {\n const diffs: FieldDiff[] = [];\n\n if (!valuesEqual(local.amount, external.amount)) {\n diffs.push({\n field: 'amount',\n localValue: local.amount,\n externalValue: external.amount,\n });\n }\n\n return diffs;\n}\n\n// =============================================================================\n// Webhook Operations\n// =============================================================================\n\nclass QuickBooksWebhookOperations implements WebhookOperations {\n constructor(private options: QuickBooksOptions) {}\n\n /**\n * Verify webhook signature using HMAC-SHA256\n * @see https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/verify-webhooks\n */\n verify(payload: string, signature: string, secret: string): boolean {\n // Use provided secret or fall back to configured webhook verifier token\n const verifierToken = secret || this.options.webhookVerifierToken;\n\n if (!verifierToken) {\n throw new Error(\n 'Webhook verifier token not provided. Pass it as the secret parameter or configure webhookVerifierToken in options.',\n );\n }\n\n // QBO uses HMAC-SHA256 and base64 encodes the signature\n const hmac = createHmac('sha256', verifierToken);\n hmac.update(payload);\n const expectedSignature = hmac.digest('base64');\n\n // Constant-time comparison to prevent timing attacks\n if (signature.length !== expectedSignature.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < signature.length; i++) {\n result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n }\n return result === 0;\n }\n\n parse(payload: string): WebhookEvent {\n let data: QBOWebhookPayload;\n try {\n data = JSON.parse(payload) as QBOWebhookPayload;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown JSON parse error';\n throw new Error(`Invalid QuickBooks webhook payload: ${message}`);\n }\n\n // QBO webhook payload structure\n const eventNotifications = data.eventNotifications || [];\n const firstEvent = eventNotifications[0]?.dataChangeEvent?.entities?.[0];\n\n return {\n type: firstEvent?.operation || 'unknown',\n provider: 'quickbooks',\n timestamp: new Date(),\n payload: data,\n resourceType: mapQBOResourceType(firstEvent?.name),\n resourceId: firstEvent?.id,\n };\n }\n}\n\nfunction mapQBOResourceType(\n name?: string,\n): WebhookEvent['resourceType'] | undefined {\n switch (name?.toLowerCase()) {\n case 'customer':\n return 'customer';\n case 'invoice':\n return 'invoice';\n case 'payment':\n return 'payment';\n case 'vendor':\n return 'vendor';\n case 'bill':\n return 'bill';\n default:\n return undefined;\n }\n}\n\n// =============================================================================\n// Mapping Functions\n// =============================================================================\n\nfunction mapCustomerToQBO(customer: CustomerInput): QBOCustomerCreate {\n return {\n DisplayName: customer.name,\n PrimaryEmailAddr: customer.email ? { Address: customer.email } : undefined,\n PrimaryPhone: customer.phone\n ? { FreeFormNumber: customer.phone }\n : undefined,\n BillAddr: mapAddressToQBO(customer.billingAddress),\n ShipAddr: mapAddressToQBO(customer.shippingAddress),\n CurrencyRef: customer.currency ? { value: customer.currency } : undefined,\n };\n}\n\nfunction mapQBOToCustomer(qbo: QBOCustomer): ExternalCustomer {\n return {\n externalId: qbo.Id,\n provider: 'quickbooks',\n syncedAt: new Date(),\n name: qbo.DisplayName,\n email: qbo.PrimaryEmailAddr?.Address,\n phone: qbo.PrimaryPhone?.FreeFormNumber,\n billingAddress: mapQBOToAddress(qbo.BillAddr),\n balance: qbo.Balance,\n currency: qbo.CurrencyRef?.value,\n raw: qbo,\n };\n}\n\nfunction mapQBOToInvoice(qbo: QBOInvoice): ExternalInvoice {\n const balance = qbo.Balance ?? 0;\n const totalAmount = qbo.TotalAmt ?? 0;\n\n return {\n externalId: qbo.Id,\n provider: 'quickbooks',\n syncedAt: new Date(),\n invoiceNumber: qbo.DocNumber || '',\n customerExternalId: qbo.CustomerRef?.value || '',\n issueDate: new Date(qbo.TxnDate),\n dueDate: new Date(qbo.DueDate),\n subtotal: totalAmount - (qbo.TxnTaxDetail?.TotalTax ?? 0),\n taxAmount: qbo.TxnTaxDetail?.TotalTax ?? 0,\n totalAmount,\n amountPaid: totalAmount - balance,\n balance,\n status: mapInvoiceStatus(balance, totalAmount, qbo.DueDate),\n currency: qbo.CurrencyRef?.value || 'USD',\n raw: qbo,\n };\n}\n\nfunction mapInvoiceStatus(\n balance: number,\n total: number,\n dueDate: string,\n): ExternalInvoice['status'] {\n if (balance === 0) return 'paid';\n if (balance < total) return 'viewed'; // Partial payment\n if (new Date(dueDate) < new Date()) return 'overdue';\n return 'sent';\n}\n\nfunction mapVendorToQBO(vendor: VendorInput): QBOVendorCreate {\n return {\n DisplayName: vendor.name,\n PrimaryEmailAddr: vendor.email ? { Address: vendor.email } : undefined,\n PrimaryPhone: vendor.phone ? { FreeFormNumber: vendor.phone } : undefined,\n BillAddr: mapAddressToQBO(vendor.address),\n TaxIdentifier: vendor.taxId,\n CurrencyRef: vendor.currency ? { value: vendor.currency } : undefined,\n };\n}\n\nfunction mapQBOToVendor(qbo: QBOVendor): ExternalVendor {\n return {\n externalId: qbo.Id,\n provider: 'quickbooks',\n syncedAt: new Date(),\n name: qbo.DisplayName,\n email: qbo.PrimaryEmailAddr?.Address,\n phone: qbo.PrimaryPhone?.FreeFormNumber,\n address: mapQBOToAddress(qbo.BillAddr),\n balance: qbo.Balance,\n currency: qbo.CurrencyRef?.value,\n raw: qbo,\n };\n}\n\nfunction mapBillToQBO(bill: BillInput): QBOBillCreate {\n return {\n VendorRef: { value: bill.vendorExternalId || bill.vendorId },\n DocNumber: bill.billNumber,\n TxnDate: formatLocalDate(bill.billDate),\n DueDate: formatLocalDate(bill.dueDate),\n Line: bill.lineItems.map((item, idx) => ({\n LineNum: idx + 1,\n Description: item.description,\n Amount: item.amount ?? item.quantity * item.unitPrice,\n DetailType: 'AccountBasedExpenseLineDetail' as const,\n AccountBasedExpenseLineDetail: {\n AccountRef: item.accountCode ? { value: item.accountCode } : undefined,\n },\n })),\n CurrencyRef: bill.currency ? { value: bill.currency } : undefined,\n };\n}\n\nfunction mapQBOToBill(qbo: QBOBill): ExternalBill {\n const balance = qbo.Balance ?? 0;\n const totalAmount = qbo.TotalAmt ?? 0;\n\n return {\n externalId: qbo.Id,\n provider: 'quickbooks',\n syncedAt: new Date(),\n billNumber: qbo.DocNumber,\n vendorExternalId: qbo.VendorRef?.value || '',\n billDate: new Date(qbo.TxnDate),\n dueDate: new Date(qbo.DueDate),\n subtotal: totalAmount - (qbo.TxnTaxDetail?.TotalTax ?? 0),\n taxAmount: qbo.TxnTaxDetail?.TotalTax ?? 0,\n totalAmount,\n amountPaid: totalAmount - balance,\n balance,\n status: mapBillStatus(balance, totalAmount, qbo.DueDate),\n currency: qbo.CurrencyRef?.value || 'USD',\n raw: qbo,\n };\n}\n\nfunction mapBillStatus(\n balance: number,\n total: number,\n dueDate: string,\n): ExternalBill['status'] {\n if (balance === 0) return 'paid';\n if (new Date(dueDate) < new Date()) return 'overdue';\n return 'pending';\n}\n\nfunction mapQBOToPayment(qbo: QBOPayment): ExternalPayment {\n return {\n externalId: qbo.Id,\n provider: 'quickbooks',\n syncedAt: new Date(),\n amount: qbo.TotalAmt ?? 0,\n currency: qbo.CurrencyRef?.value || 'USD',\n paidAt: new Date(qbo.TxnDate),\n method: qbo.PaymentMethodRef?.name,\n transactionId: qbo.PaymentRefNum,\n invoiceExternalIds: qbo.Line?.filter((l) => l.LinkedTxn).flatMap(\n (l) => l.LinkedTxn?.map((t) => t.TxnId) || [],\n ),\n status: 'completed',\n raw: qbo,\n };\n}\n\nfunction mapAddressToQBO(address?: Address): QBOAddress | undefined {\n if (!address) return undefined;\n return {\n Line1: address.street1,\n Line2: address.street2,\n City: address.city,\n CountrySubDivisionCode: address.state,\n PostalCode: address.postalCode,\n Country: address.country,\n };\n}\n\nfunction mapQBOToAddress(qbo?: QBOAddress): Address | undefined {\n if (!qbo) return undefined;\n return {\n street1: qbo.Line1,\n street2: qbo.Line2,\n city: qbo.City,\n state: qbo.CountrySubDivisionCode,\n postalCode: qbo.PostalCode,\n country: qbo.Country,\n };\n}\n\n// =============================================================================\n// QBO API Types (internal, strongly typed)\n// =============================================================================\n\ninterface QBORef {\n value: string;\n name?: string;\n}\n\ninterface QBOAddress {\n Line1?: string;\n Line2?: string;\n City?: string;\n CountrySubDivisionCode?: string;\n PostalCode?: string;\n Country?: string;\n}\n\ninterface QBOEmail {\n Address: string;\n}\n\ninterface QBOPhone {\n FreeFormNumber: string;\n}\n\n// Customer types\ninterface QBOCustomerBase {\n DisplayName: string;\n PrimaryEmailAddr?: QBOEmail;\n PrimaryPhone?: QBOPhone;\n BillAddr?: QBOAddress;\n ShipAddr?: QBOAddress;\n Balance?: number;\n CurrencyRef?: QBORef;\n}\n\ninterface QBOCustomerCreate extends QBOCustomerBase {}\n\ninterface QBOCustomerUpdate extends QBOCustomerBase {\n Id: string;\n SyncToken: string;\n}\n\ninterface QBOCustomer extends QBOCustomerBase {\n Id: string;\n SyncToken: string;\n}\n\n// Invoice types\ninterface QBOInvoiceLine {\n LineNum: number;\n Description?: string;\n Amount: number;\n DetailType: 'SalesItemLineDetail';\n SalesItemLineDetail?: {\n Qty?: number;\n UnitPrice?: number;\n ItemRef?: QBORef;\n TaxCodeRef?: QBORef;\n TaxInclusiveAmt?: number;\n };\n}\n\ninterface QBOInvoiceBase {\n CustomerRef: QBORef;\n DocNumber?: string;\n TxnDate: string;\n DueDate: string;\n Line: QBOInvoiceLine[];\n TotalAmt?: number;\n Balance?: number;\n TxnTaxDetail?: {\n TotalTax?: number;\n };\n GlobalTaxCalculation?: 'TaxExcluded' | 'TaxInclusive' | 'NotApplicable';\n CurrencyRef?: QBORef;\n CustomerMemo?: { value: string };\n}\n\ninterface QBOInvoiceUpdate extends QBOInvoiceBase {\n Id: string;\n SyncToken: string;\n}\n\ninterface QBOInvoice extends QBOInvoiceBase {\n Id: string;\n SyncToken: string;\n}\n\n// Vendor types\ninterface QBOVendorBase {\n DisplayName: string;\n PrimaryEmailAddr?: QBOEmail;\n PrimaryPhone?: QBOPhone;\n BillAddr?: QBOAddress;\n Balance?: number;\n TaxIdentifier?: string;\n CurrencyRef?: QBORef;\n}\n\ninterface QBOVendorCreate extends QBOVendorBase {}\n\ninterface QBOVendorUpdate extends QBOVendorBase {\n Id: string;\n SyncToken: string;\n}\n\ninterface QBOVendor extends QBOVendorBase {\n Id: string;\n SyncToken: string;\n}\n\n// Bill types\ninterface QBOBillLine {\n LineNum: number;\n Description?: string;\n Amount: number;\n DetailType: 'AccountBasedExpenseLineDetail';\n AccountBasedExpenseLineDetail?: {\n AccountRef?: QBORef;\n };\n}\n\ninterface QBOBillBase {\n VendorRef: QBORef;\n DocNumber?: string;\n TxnDate: string;\n DueDate: string;\n Line: QBOBillLine[];\n TotalAmt?: number;\n Balance?: number;\n TxnTaxDetail?: {\n TotalTax?: number;\n };\n CurrencyRef?: QBORef;\n}\n\ninterface QBOBillCreate extends QBOBillBase {}\n\ninterface QBOBillUpdate extends QBOBillBase {\n Id: string;\n SyncToken: string;\n}\n\ninterface QBOBill extends QBOBillBase {\n Id: string;\n SyncToken: string;\n}\n\n// Payment types\ninterface QBOPaymentLine {\n Amount: number;\n LinkedTxn?: {\n TxnId: string;\n TxnType: string;\n }[];\n}\n\ninterface QBOPayment {\n Id: string;\n SyncToken: string;\n TxnDate: string;\n TotalAmt?: number;\n CustomerRef?: QBORef;\n PaymentMethodRef?: QBORef;\n PaymentRefNum?: string;\n Line?: QBOPaymentLine[];\n CurrencyRef?: QBORef;\n}\n\n// Webhook types\ninterface QBOWebhookPayload {\n eventNotifications?: {\n realmId: string;\n dataChangeEvent?: {\n entities?: {\n name: string;\n id: string;\n operation: string;\n lastUpdated: string;\n }[];\n };\n }[];\n}\n"],"mappings":"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"}
|