QBO Launch Validation

๐Ÿ–จ Print one-sheet โ†’

End-to-end deposit walkthrough โ€” ProMount by Vancomm entity. Follow phases 1โ€“8 in order, check each box as you go.

Before you start โ€” confirm all of these are live. The automated steps in Phases 4โ€“6 will not fire unless every prerequisite is wired. None of these need re-checking between test runs; they are one-time setup items. The deployed bridge Worker is scoro-qbo-bridge.vancomm.workers.dev.

โšก Run this first โ€” green means ready. The prerequisites below can be auto-checked in one command:

doppler run -p vancomm -c dev_vancomm -- node scripts/qbo-launch-preflight.mjs

It reports PASS / FAIL / MANUAL for each prerequisite. It is read-only โ€” it checks state and creates nothing. The list below is the reference for what each item checks and how to fix it if the preflight flags something.

Test amounts: keep totals small ($5โ€“$10). Real money flows through QBO Payments. Small amounts limit exposure during validation. Deposit formula: deposit = quote sum ร— prepayment_percent (e.g. $1.00 ร— 50% = $0.50).

See a finished example. The live TEST fixtures, end to end: Project 3031 ยท Customer (company 565) ยท Quote 3290 ยท Prepayment 128 ยท Final invoice 2348
1 Create Project manualScoro โ†’ Projects โ†’ New
Do In Scoro (ProMount by Vancomm), create a new Project at vancomm.scoro.com/projects/ with a clearly test-flagged name โ€” e.g., WALKTHROUGH TEST [date]. Leave status at default; assign yourself as manager if required.
Expect Project is saved, appears in the Projects list with a numeric project ID. Note the ID โ€” you will link the quote here.
Verify
  • Project record exists with a numeric ID
  • URL contains promount (not julieh)
Scoro project record โ€” QBO TEST Project in the ProMount by Vancomm entity
Example: the test project created in the promount entity. The project ID appears in the URL and in the record header. Live fixture: Project 3031.
2 Create Customer manualScoro โ†’ Companies โ†’ New Company + Person
Do Create a new Company and a linked Person at vancomm.scoro.com/companies/ with a clearly test-flagged name โ€” e.g., WALKTEST CUSTOMER LLC [date]. Use a real email address you can receive at โ€” QBO will email the deposit invoice to this address when payment is enabled.
Expect Both contacts are saved. The company gets a numeric contact ID. Note it โ€” the Worker needs it for the QBO customer mapping (Prerequisite P-11).
Verify
  • Company record exists with a contact ID
  • Person record is linked to the company
  • Email address on the company or person is reachable
Scoro company record โ€” QBO Test Customer LLC
Example: the test customer company in Scoro. The contact ID appears in the URL bar. Live fixture: Company 565.
3 Create Quote manualScoro โ†’ open Project โ†’ New Quote
Do From inside the Project, create a new Quote (Quotes) linked to the Customer from Phase 2. Add at least one line item (use any V2_Raw Materials product or a test product). Keep the total small ($5โ€“$10). Optionally set a quote-level discount โ€” open the Discount field, type a value (e.g., 5 for 5%), Tab out โ€” the discount line appears on the PDF automatically. Quote PDFs use template 48 (carries the pay-link token).
Expect Quote is saved with a quote number and a total. Note the total โ€” it is the reference for the deposit calculation.
Verify
  • Quote exists with a quote number and total
  • Quote is linked to the correct Project and Customer
  • Quote PDF renders correctly (check via the PDF preview action)
Scoro quote PDF โ€” 5% discount applied, total $0.95
Example: quote PDF with a 5% discount applied. Total $0.95 โ€” the deposit will be 50% of this amount. Live fixture: Quote 3290.
4 Create Prepayment (Deposit Invoice) manual create it in Scoro UI  ยท  auto Worker mints QBO invoice + writes pay-link back

4a. Create the prepayment in Scoro UI

Do From the Quote (or linked Order), navigate to Invoices and select New prepayment invoice. Set the advance percentage to 50%. Save.
Note: Prepayment invoices cannot be created via the API โ€” this UI step is the only way to mint one. The Worker reads and modifies existing prepayments via REST (invoices/prepayments/view / invoices/prepayments/modify) but does not create them.
Expect A prepayment record is created: its own invoice number, sum = full order amount, prepayment_percent = 50, status: unpaid. The c_qbo_invoice_link field is empty at this point.

4b. Wait for the Worker (~30โ€“60 seconds)

Automation โ€” Worker Path A fires (/webhooks/scoro):
  1. Scoro prepayment-created webhook โ†’ Worker /webhooks/scoro
  2. Worker reads the prepayment: sum + prepayment_percent
  3. Computes deposit = quote sum ร— prepayment_percent (e.g. $1.00 ร— 50% = $0.50)
  4. Creates a QBO invoice for the deposit amount โ€” Customer Deposit item (Id 4) โ†’ Customer Deposits liability account (Id 15), with online payment enabled
  5. Fetches the Intuit InvoiceLink (hosted pay page URL)
  6. Writes <a href="[pay URL]">Pay online</a> into c_qbo_invoice_link (field_id 27) on the Scoro prepayment
Caveat A โ€” wait before sending the PDF. The pay-link is populated by the Worker after the QBO sync. If you open or send the prepayment PDF immediately, c_qbo_invoice_link may still be empty. Refresh the Scoro prepayment record after ~60 seconds before sending.
Caveat B โ€” confirm the link renders as clickable, not as literal HTML. The custom field c_qbo_invoice_link (field_id 27) must have "Can add text in HTML format" = ON โ€” check at Settings โ†’ Custom fields (Prerequisite P-8). If that flag is off, the PDF shows <a href=...> as text instead of a link. Prepayment PDFs use template 51.
Verify
  • Scoro prepayment: note its numeric ID
  • Scoro prepayment record: c_qbo_invoice_link field shows a value (not empty)
  • Prepayment PDF: Payment Instructions block shows Pay online as a live hyperlink โ€” not raw markup
  • QBO: a new invoice under the test customer, amount = 50% of order total, online payment enabled
Scoro prepayment record showing the Pay online link in the c_qbo_invoice_link field
Prepayment record after Worker runs โ€” the "Pay online" pay-link is populated in the custom field.
Deposit invoice PDF with a clickable Pay online link in the Payment Instructions block
The deposit-invoice PDF โ€” "Pay online" renders as a clickable hyperlink in the Payment Instructions block. Live fixture: Prepayment 128.
5 Customer Pays the Deposit manual pay via link or direct in QBO  ยท  auto Worker marks Scoro prepayment paid

5a. Pay the deposit

Do Click the Pay online link from the prepayment PDF and complete payment. Alternatively for testing without a real card: open the deposit invoice in QuickBooks Online โ†’ Receive payment โ†’ enter the deposit amount โ†’ save.
Expect QBO records the payment; deposit invoice shows Paid / $0.00 balance.
Verify (QBO)
  • Deposit invoice for the test customer: status Paid, $0.00 balance
  • A QBO payment record exists for the deposit amount
Intuit-hosted invoice page โ€” Mhondoro Capital Partners, $0.50, Paid
The Intuit-hosted invoice page after payment โ€” balance $0.00, status Paid. Payee: Mhondoro Capital Partners, LLC.

5b. Worker reconciles back to Scoro (~30โ€“60 seconds)

Automation โ€” Worker Path B fires (/webhooks/qbo):
  1. QBO fires payment.create webhook โ†’ Worker /webhooks/qbo
  2. Worker verifies Intuit HMAC signature using QBO_WEBHOOK_VERIFIER_TOKEN
  3. Worker fetches the QBO payment record, resolves the linked QBO invoice ID
  4. Worker looks up qbomap:qbo_invoice:{id} in KV โ†’ resolves to Scoro prepayment ID
  5. Worker posts a receipt to Scoro (receipts/modify with prepayment_id set) โ†’ Scoro prepayment flips to paid/reconciled
Verify (Scoro)
  • Prepayment record: status paid / reconciled (no longer shown as outstanding)
  • A receipt for the deposit amount is linked to the prepayment
6 Complete the Order โ€” Final Invoice manual advance order status  ยท  auto Worker creates balance invoice + pay-link

6a. Advance the order to "Ready to Invoice"

Do In Scoro, find the Order linked to your Quote. Advance its status to the stage that triggers billing โ€” the label may be "Ready to Invoice," "Completed," or a configured pipeline stage. Use whichever stage is wired to the trigger from Prerequisite P-3.
Expect Order status updates. Worker Path C begins.
Verify
  • Order shows the correct "Ready to Invoice" (or equivalent) status

6b. Worker creates the final invoice + applies deposit credit (~30โ€“60 seconds)

Automation โ€” Worker Path C fires:
  1. Order-status trigger fires โ†’ Worker /webhooks/scoro
  2. Worker reads the order, finds the linked quote; looks up the prepayment via invoices/prepayments/list
  3. Computes depositPaid = prepayment.sum ร— prepayment_percent / 100
  4. Creates the Scoro final invoice from the order
  5. Applies the deposit credit โ€” balance = order total โˆ’ depositPaid
  6. Creates a QBO balance invoice for the remaining balance (online payment enabled)
  7. Fetches the QBO InvoiceLink and writes it to c_qbo_invoice_link on the Scoro final invoice
Verify (Scoro)
  • Final invoice exists โ€” note its ID
  • Invoice total = order total; deposit amount already credited; balance due = order total โˆ’ deposit
  • c_qbo_invoice_link is populated; final invoice PDF (template 49) shows a clickable Pay online link (same Caveats A & B as Phase 4)
Scoro final invoice #6 showing the deposit credit applied
Final invoice after Worker Path C โ€” the deposit paid in Phase 4โ€“5 is credited, and only the balance remains due. Live fixture: Final invoice 2348.
Verify (QBO)
  • A second QBO invoice exists for the test customer โ€” amount = balance due
  • This balance invoice shows online payment enabled
7 Pay the Balance Invoice manual pay via link or QBO direct  ยท  auto Worker or native connector marks Scoro invoice paid
Do Click the Pay online link from the final invoice PDF (or from the c_qbo_invoice_link field) and pay the remaining balance. Alternatively, record the payment directly in QBO against the balance invoice.
Expect QBO records the balance payment. The Worker (Path B, same as Phase 5b) receives the QBO payment webhook and posts a receipt back to Scoro against the final invoice, marking it paid in full. The native connector's Receipts = To Scoro (P-7) may also close it โ€” either mechanism works; idempotency keys prevent double-credits.
Verify (QBO)
  • QBO balance invoice: status Paid, $0.00 balance
  • A QBO payment record exists for the balance amount
Verify (Scoro)
  • Final invoice status: Paid in full (paid_sum = invoice total; unpaid balance = $0)
  • A receipt for the balance amount is linked to the final invoice
8 Final Reconciliation Checks manualRun after both payments are recorded

Scoro (ProMount by Vancomm entity)

QBO (Mhondoro Capital Partners, LLC)

Math check

Item Expected
Order total (T)[your test amount]
Deposit paid (D)T ร— 50%
Balance paid (B)T โˆ’ D = T ร— 50%
QBO deposit invoice= D
QBO balance invoice= B
D + B= T โœ“

Troubleshooting

Symptom Most likely cause Action
Pay-link blank on prepayment PDF (Phase 4) Worker Path A hasn't fired yet, or fired with an error Wait 60s; refresh the Scoro prepayment. If still blank, check the Worker log viewer
Pay-link shows literal HTML (<a href=...>) "Can add text in HTML format" flag is OFF on c_qbo_invoice_link Scoro โ†’ Settings โ†’ Custom fields โ†’ c_qbo_invoice_link โ†’ enable the flag โ†’ re-run the Worker for that prepayment via /process/prepayment/{id}
Scoro prepayment does not flip to paid after deposit payment (Phase 5) QBO Payments webhook not wired, QBO_WEBHOOK_VERIFIER_TOKEN mismatch, or no qbomap KV entry (Path A failed) Confirm P-4; check Worker logs; if KV entry missing, re-drive Path A first
"customer mapping unresolved" in Worker logs Scoro company โ†’ QBO customer mapping (T-031) not seeded for this customer Seed the mapping in Worker KV: qbomap:scoro_company:{companyId} โ†’ {customerId, billEmail}
Webhook did not fire (no entry in Worker logs) Scoro trigger not wired, or Worker URL is wrong Scoro โ†’ Settings โ†’ Triggers and actions โ†’ verify the trigger exists and points to scoro-qbo-bridge.vancomm.workers.dev/webhooks/scoro
Deposit amount incorrect prepayment_percent incorrect on the Scoro prepayment Open the prepayment โ€” confirm advance percentage is 50 (not 100). If reset, the invoices/prepayments/modify call may have omitted prepayment_percent โ€” check Worker Path A code
Final invoice pay-link blank (Phase 6) Worker Path C failed to get the QBO InvoiceLink Check Worker logs for pathC; confirm QBO Payments is enabled; re-trigger via /process/order-complete/{orderId}
QBO balance invoice exists but Scoro invoice not marked paid (Phase 7) qbomap KV entry for the balance invoice was not written by Path C Check that Path C completed fully; confirm KV key qbomap:qbo_invoice:{balanceInvoiceId} exists
Duplicate receipts in Scoro Both the native connector (P-7) and Worker Path B fired for the same payment Not harmful โ€” Scoro deduplicates by amount/date. If a double-credit occurs, void the duplicate receipt manually in Scoro UI

QBO refresh tokens expire after 100 days.

If the Worker returns token-related errors, re-run the OAuth flow: tailscale funnel 8742 && doppler run -- node scripts/qbo-oauth.mjs โ€” the rotated token is persisted automatically in Worker KV on every successful API call.

Rollback

See deliverables/runbooks/qbo-e2e-quote-to-cash/README.md โ€” the rollback section lists --delete script commands for all QBO artifacts. Scoro receipts have no API delete endpoint; void them manually in Scoro UI.

Related