From fec355d4ebf5393595e74792da3569ade9a5570c Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Sun, 4 Oct 2026 18:16:05 -0400 Subject: [PATCH 1/4] =?UTF-8?q?docs(quickbooks):=20record=20#178=20pre-fli?= =?UTF-8?q?ght=20status=20=E2=80=94=20verified=20vs=20pending=20evidence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a findings-status section: what is proven by code/data evidence (prod connection health, unset mappings, empty sync records, real paid checkouts, active CoA webhooks) versus what remains pending manual QBO/Stripe UI inspection, including why — both providers' production secrets are Vercel sensitive env vars unreachable outside production compute. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../quickbooks-accounting-sync.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/architecture/quickbooks-accounting-sync.md b/docs/architecture/quickbooks-accounting-sync.md index eb68dfd..149dea3 100644 --- a/docs/architecture/quickbooks-accounting-sync.md +++ b/docs/architecture/quickbooks-accounting-sync.md @@ -104,6 +104,29 @@ Until items 1–5 are answered in writing, treat the model below as cash-side records (deposits, fees), and it must change if the existing path already posts revenue. +### Production pre-flight findings — status + +Tracked in issue #178. Evidence gathered programmatically so far +(read-only; production QBO and Stripe secrets are Vercel `sensitive` +env vars — unretrievable outside production compute, so the +integration/ledger questions below can only be answered by inspection +in the QBO and Stripe UIs): + +| Finding | Status | +| --- | --- | +| Production connection live and healthy (`Deep Dive Brews, BV`, country CW) | ✅ verified (Firestore `qboConnections/production`, connection-check audit logs) | +| All six accounting mappings unset (`qboConfig/accountingMapping` absent) | ✅ verified | +| No sync records exist (`qboSyncRecords` empty) | ✅ verified | +| Real Stripe payments settle through the tool (`checkout.session.completed` → `paid` applied in prod) | ✅ verified (`stripeEvents`, runtime logs) | +| Prod chart of accounts actively maintained (`Account` webhook events on the prod realm) | ✅ verified (`qboWebhookReceipts`) | +| Account/Item/Customer/TaxCode discovery returns data (one page each) | ✅ verified (`qbo.api.entities` logs) | +| Which integration/feed creates Stripe-related entries today | ⏳ pending — QBO UI inspection (#178) | +| Whether a Stripe clearing/merchant-fee account exists and what it receives | ⏳ pending — QBO UI inspection | +| Whether per-charge revenue records already exist in the books | ⏳ pending — QBO UI inspection | +| Payout gross/net/fee split and bank-feed matching behavior | ⏳ pending — Stripe + QBO UI inspection | +| CW tax configuration and required `GlobalTaxCalculation` value | ⏳ pending — QBO tax settings + accountant | +| QBO home currency / multicurrency vs USD card sales | ⏳ pending — QBO company settings | + ## 3. Double-counting risks | If the app posts… | …and the existing Stripe/bank path already posts… | Result | From 35c83ff699f8739d46d98226dafb7177c30e18d8 Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Sun, 4 Oct 2026 18:43:09 -0400 Subject: [PATCH 2/4] docs(quickbooks): narrow two pre-flight findings to what logs prove "Account webhooks received for the prod realm" does not establish the chart of accounts is actively maintained, and successful discovery queries do not prove non-empty result sets. States the narrower verified facts. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- docs/architecture/quickbooks-accounting-sync.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/quickbooks-accounting-sync.md b/docs/architecture/quickbooks-accounting-sync.md index 149dea3..fb2d89b 100644 --- a/docs/architecture/quickbooks-accounting-sync.md +++ b/docs/architecture/quickbooks-accounting-sync.md @@ -118,8 +118,8 @@ in the QBO and Stripe UIs): | All six accounting mappings unset (`qboConfig/accountingMapping` absent) | ✅ verified | | No sync records exist (`qboSyncRecords` empty) | ✅ verified | | Real Stripe payments settle through the tool (`checkout.session.completed` → `paid` applied in prod) | ✅ verified (`stripeEvents`, runtime logs) | -| Prod chart of accounts actively maintained (`Account` webhook events on the prod realm) | ✅ verified (`qboWebhookReceipts`) | -| Account/Item/Customer/TaxCode discovery returns data (one page each) | ✅ verified (`qbo.api.entities` logs) | +| `Account` webhook notifications received for the production realm | ✅ verified (`qboWebhookReceipts`) | +| Account/Item/Customer/TaxCode discovery queries succeed (one page each; result counts not logged) | ✅ verified (`qbo.api.entities` logs) | | Which integration/feed creates Stripe-related entries today | ⏳ pending — QBO UI inspection (#178) | | Whether a Stripe clearing/merchant-fee account exists and what it receives | ⏳ pending — QBO UI inspection | | Whether per-charge revenue records already exist in the books | ⏳ pending — QBO UI inspection | From b927497b11477bd9974b2d41257b6b7bc43225f8 Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Sun, 4 Oct 2026 20:11:36 -0400 Subject: [PATCH 3/4] =?UTF-8?q?docs(quickbooks):=20record=20#178=20resolut?= =?UTF-8?q?ion=20=E2=80=94=20two=20Stripe=20lanes,=20revenue=20leg=20neede?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual production inspection resolved the core double-counting question: an Ollie/Spreedly lane already books wholesale invoice revenue/tax in QBO, while a proven live DDB-admin payment left no QBO record at all. The DDB integration posts the revenue leg only for positive-identified DDB payments, into the configured Stripe clearing/balance account — never Ollie activity, never Undeposited Funds, never the bank. Mixed-source payouts stay with #181. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../quickbooks-accounting-sync.md | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/docs/architecture/quickbooks-accounting-sync.md b/docs/architecture/quickbooks-accounting-sync.md index fb2d89b..6deea6e 100644 --- a/docs/architecture/quickbooks-accounting-sync.md +++ b/docs/architecture/quickbooks-accounting-sync.md @@ -64,7 +64,13 @@ existing):** refunds, happen in the **Stripe Dashboard — the app never learns of them** (no `charge.refunded` subscription). -## 2. Existing Stripe → QBO behavior — NOT YET DETERMINED +## 2. Existing Stripe → QBO behavior — RESOLVED (#178) + +The decisive question is answered: see +[Two Stripe lanes](#two-stripe-lanes--the-decisive-finding-178-resolved) +below — an existing Ollie/Spreedly lane books wholesale revenue in QBO, +while DDB-admin payments book nothing. The inspection checklist below is +retained for how the answer was reached and what remains open. **This is the load-bearing unknown.** Per ops context, the production company already has Stripe-related banking/integration activity, but the @@ -120,12 +126,46 @@ in the QBO and Stripe UIs): | Real Stripe payments settle through the tool (`checkout.session.completed` → `paid` applied in prod) | ✅ verified (`stripeEvents`, runtime logs) | | `Account` webhook notifications received for the production realm | ✅ verified (`qboWebhookReceipts`) | | Account/Item/Customer/TaxCode discovery queries succeed (one page each; result counts not logged) | ✅ verified (`qbo.api.entities` logs) | -| Which integration/feed creates Stripe-related entries today | ⏳ pending — QBO UI inspection (#178) | -| Whether a Stripe clearing/merchant-fee account exists and what it receives | ⏳ pending — QBO UI inspection | -| Whether per-charge revenue records already exist in the books | ⏳ pending — QBO UI inspection | -| Payout gross/net/fee split and bank-feed matching behavior | ⏳ pending — Stripe + QBO UI inspection | -| CW tax configuration and required `GlobalTaxCalculation` value | ⏳ pending — QBO tax settings + accountant | -| QBO home currency / multicurrency vs USD card sales | ⏳ pending — QBO company settings | +| Which integration/feed creates Stripe-related entries today | ✅ **resolved (#178)** — see "Two Stripe lanes" below | +| Whether a Stripe clearing/merchant-fee account exists and what it receives | ✅ resolved — the company has a dedicated `Stripe Balance` account, plus Undeposited Funds, a credit-card/merchant-fee expense account, and the Mercury bank account | +| Whether per-charge revenue records already exist in the books | ✅ resolved — **two lanes**: Ollie invoices book wholesale revenue/tax; DDB-admin payments book nothing (proven: a live settled $5.00 payment has no QBO record) | +| Payout gross/net/fee split and bank-feed matching behavior | ✅ partially — Stripe payouts arrive into QBO as transfers involving Undeposited Funds; payouts can be **mixed-source** (Ollie + DDB on the same Stripe account); fee bookkeeping is partly manual | +| CW tax configuration and required `GlobalTaxCalculation` value | ⏳ pending — QBO tax settings + accountant (#184); launch posture stays no-tax-posted | +| QBO home currency / multicurrency vs USD card sales | ⏳ pending — QBO company settings; flagged before enabling writes | + +### Two Stripe lanes — the decisive finding (#178 resolved) + +Manual inspection of the production books established that **the same +Stripe account serves multiple source systems**: + +- **Lane A — Ollie / Spreedly wholesale.** Ollie creates QBO Invoices + (revenue + tax already booked in QBO), then collects via Stripe. + The Stripe charges carry Ollie metadata (e.g. an `order_id` matching + the QBO invoice number and `connect_agent` identifying the external + system). **DDB must never post revenue for these** — Ollie owns that + lane end to end. +- **Lane B — DDB admin payments.** A live $5.00 payment created + through `/admin/payments` (purpose `other`, internal payment id in + Stripe metadata) settled with a real Charge (fee $0.45, net $4.55) + and has **no QBO record at all**. The DDB integration must post this + revenue leg itself. + +Existing invoice payments in the books deposit to **Undeposited +Funds**, and Stripe payouts were observed arriving as transfers +involving Undeposited Funds — but the company also has a dedicated +**`Stripe Balance`** account intentionally used to model money +remaining inside Stripe (including a retained balance for +refunds/fees). DDB Sales Receipts deposit to the **configured Stripe +clearing/balance account**, not Undeposited Funds and never directly +to the bank. Because payouts mix Ollie and DDB charges, payout-level +reconciliation is a separate design problem (#181) — never per-payment +concern of this integration. + +Classification rule: a payment qualifies for posting only through +**positive DDB identity** — it must be a canonical record in the app's +own `payments` collection (whose Stripe ids the tool itself issued and +verified), never inferred from amount/date matching, the absence of +`connect_agent`, or "not Ollie" heuristics. ## 3. Double-counting risks @@ -187,16 +227,19 @@ each settled payment to an existing QBO record; flag unmatched). ## 5. Recommended model -**Per settled Stripe payment: one Sales Receipt, gross, into a Stripe -clearing account. The app writes nothing else — no deposits, no fee -entries, no journals.** +**Per settled DDB-admin payment: one Sales Receipt, gross, into the +configured Stripe clearing/balance account. The app writes nothing else +— no deposits, no fee entries, no journals, and nothing at all for +Ollie or other non-DDB Stripe activity.** Only payments with positive +DDB identity (a canonical `payments` record the tool itself issued and +verified through Stripe) qualify — see the classification rule in §2. | Element | Decision | | --- | --- | | QBO entity per payment | `SalesReceipt` | | Amount | **Gross** in QBO decimal currency units — `amountMinor` is integer **cents**, so the boundary must convert (`amountMinor / 100`, e.g. `10000` → `100.0`). The same conversion applies to RefundReceipts | | Transaction date | `TxnDate` = the payment's **`paidAt`** settlement date (refund's `refundedAt` for RefundReceipts) — never the worker's processing date, so delayed retries and backfills land in the correct accounting period | -| Cash target | `DepositToAccountRef` = mapped **Stripe clearing account** — *not* a bank account, *not* Undeposited Funds unless inspection says UF is how the existing feed clears | +| Cash target | `DepositToAccountRef` = mapped **Stripe clearing/balance account** (the company's `Stripe Balance` account is the expected choice) — *not* a bank account, *not* Undeposited Funds, and never hardcoded | | Income split | Line `ItemRef` by `purpose` → mapped income item | | Customer | One generic customer (mapped fallback) — see §6 | | Payment method | Optional `PaymentMethodRef` ("Stripe") if a suitable method exists — nice-to-have, not required | From 917d1253d4caeaa15f3f0d9596d0cf24b147eea4 Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Sun, 4 Oct 2026 20:18:49 -0400 Subject: [PATCH 4/4] docs(quickbooks): drop stale revenue gate from pre-flight checklist The lane-level revenue question is resolved; the remaining checklist covers configuration/payout detail for #181/#184, not whether existing lanes already post the same revenue. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../quickbooks-accounting-sync.md | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/architecture/quickbooks-accounting-sync.md b/docs/architecture/quickbooks-accounting-sync.md index 6deea6e..82a67e3 100644 --- a/docs/architecture/quickbooks-accounting-sync.md +++ b/docs/architecture/quickbooks-accounting-sync.md @@ -72,16 +72,18 @@ below — an existing Ollie/Spreedly lane books wholesale revenue in QBO, while DDB-admin payments book nothing. The inspection checklist below is retained for how the answer was reached and what remains open. -**This is the load-bearing unknown.** Per ops context, the production -company already has Stripe-related banking/integration activity, but the -exact mechanism is not visible to this codebase, and production QBO -secrets (`QBO_CLIENT_SECRET`, `QBO_TOKEN_ENCRYPTION_KEY`) are Vercel -`sensitive` env vars — not retrievable for a programmatic read. Nothing -in the app can currently enumerate which integrations the company has or -which records they create. +The lane-level revenue question is **resolved** — see the two-lane +finding below: Ollie/Spreedly invoices book wholesale revenue and tax in +QBO, while a proven live DDB-admin payment left no QBO record. How it +was reached: production QBO and Stripe secrets are Vercel `sensitive` +env vars — unretrievable outside production compute — so the books were +inspected manually in the QBO and Stripe UIs. -**Required manual inspection (Chad, ~15 min in the QBO UI) before any -write is enabled:** +The remaining checks below cover configuration and payout detail — they +inform mapping choices and the #181/#184 follow-ups, but they do not +re-open the revenue-ownership decision. + +**Remaining pre-flight checks (informative, not revenue-gating):** 1. **Apps → My Apps / connected apps** — is there a Stripe integration (e.g. "Stripe for QuickBooks", "Synder", "A2X", a custom connector)? @@ -105,10 +107,10 @@ write is enabled:** tax at all (Curaçao OB regime), and are tour/tasting sales taxable? What TaxCodes did discovery return for the prod company? -Until items 1–5 are answered in writing, treat the model below as -*conditional*: it is correct if the existing Stripe path supplies only -cash-side records (deposits, fees), and it must change if the existing -path already posts revenue. +Items 2–5 already have partial answers from the manual inspection +(`Stripe Balance` clearing account exists; payouts arrive via +Undeposited-Funds transfers and may be mixed-source; fee bookkeeping is +partly manual). Item 6 stays pending with #184. ### Production pre-flight findings — status