Appearance
QuickBooks Online Integration
This document describes how to set up and use the QuickBooks Online integration in EquipQR. This integration allows organizations to export work orders as draft invoices directly to QuickBooks.
Overview
The QuickBooks integration provides:
- OAuth 2.0 Connection: Secure authorization flow to connect QuickBooks accounts
- Team-Customer Mapping: Associate EquipQR teams with QuickBooks customers
- Invoice Export: Export completed work orders as draft invoices with cost details
Prerequisites
- Intuit Developer Account: You need an Intuit Developer account and a QuickBooks Online app
- QuickBooks Online Account: An active QuickBooks Online company (sandbox or production)
- Admin/Owner Access: Only organization admins and owners can manage the QuickBooks integration
Environment Configuration
Client-Side Environment Variables
Add to your .env file:
env
# Enable QuickBooks integration (set to 'true' to enable)
VITE_ENABLE_QUICKBOOKS=false
# Intuit OAuth Client ID (public, for initiating OAuth)
VITE_INTUIT_CLIENT_ID=your-client-idSupabase Edge Function Secrets
Configure these secrets in Supabase Dashboard → Edge Functions → Secrets:
| Secret Name | Description |
|---|---|
INTUIT_CLIENT_ID | Your Intuit app's Client ID |
INTUIT_CLIENT_SECRET | Your Intuit app's Client Secret |
QB_OAUTH_REDIRECT_BASE_URL | CRITICAL: Must match VITE_QB_OAUTH_REDIRECT_BASE_URL (see below) |
QUICKBOOKS_SANDBOX | Set to "true" for sandbox, "false" for production |
⚠️ Important: OAuth Redirect URL Configuration
OAuth 2.0 requires that the redirect_uri used during authorization must exactly match the redirect_uri used during token exchange. If you're using a custom domain for Supabase:
- Set
VITE_QB_OAUTH_REDIRECT_BASE_URLin your client environment (e.g.,https://supabase.equipqr.app) - Set
QB_OAUTH_REDIRECT_BASE_URLin your Edge Function secrets to the same value - Register this URL in the Intuit Developer Portal as a Redirect URI
| Environment | VITE_QB_OAUTH_REDIRECT_BASE_URL / QB_OAUTH_REDIRECT_BASE_URL |
|---|---|
| Production | https://supabase.equipqr.app |
| Preview | https://supabase.preview.equipqr.app |
| Local | http://localhost:54321 (or port from supabase/config.toml / npx supabase status) |
If these values don't match, OAuth will fail with "oauth_failed" error.
Vault Secrets (Token Refresh Scheduler)
The token refresh scheduler requires vault secrets. Run this SQL in each Supabase environment:
sql
INSERT INTO vault.secrets (name, secret)
VALUES
('service_role_key', '<your-service-role-key>'),
('supabase_url', 'https://<your-project-ref>.supabase.co');| Secret Name | Description | Where to Find |
|---|---|---|
service_role_key | Supabase service role key | Dashboard → Settings → API → Project API keys → service_role (secret) |
supabase_url | Supabase project URL | Dashboard → Settings → API → Project URL |
Configure separately for each environment (preview, production).
Setting Up Intuit Developer App
- Go to Intuit Developer Portal
- Create a new app or use an existing one
- Configure OAuth settings:
- Redirect URI:
{your-base-url}/functions/v1/quickbooks-oauth-callback- Default:
https://your-project-ref.supabase.co/functions/v1/quickbooks-oauth-callback - Custom domain:
https://supabase.yourdomain.com/functions/v1/quickbooks-oauth-callback - Local dev:
http://localhost:54321/functions/v1/quickbooks-oauth-callback(or port fromsupabase/config.toml/npx supabase status) - Set
VITE_QB_OAUTH_REDIRECT_BASE_URLto match the base URL
- Default:
- Scopes:
com.intuit.quickbooks.accounting
- Redirect URI:
- Copy the Client ID and Client Secret
Usage
1. Connecting QuickBooks
- Navigate to Organization Settings → Integrations
- Click Connect to QuickBooks Online
- Authorize the connection in the Intuit OAuth window
- Return to EquipQR to confirm the connection
2. Mapping Teams to Customers
Before exporting invoices, map each team to a QuickBooks customer:
- Navigate to Teams → Select a team
- Find the QuickBooks Customer card
- Click Select Customer to search and map a customer
- The mapping is saved automatically
3. Exporting Work Orders as Invoices
- Navigate to a work order detail page
- Click the Take Action dropdown
- Select Export to QuickBooks
- The draft invoice is created in QuickBooks
Requirements for export:
- Work order must be assigned to a team
- The team must have a QuickBooks customer mapping
- User must be an admin or owner
Invoice Details
Exported draft invoices include summarized billing lines mapped from EquipQR work-order costs (EquipQR remains the source of truth for itemized inventory and labor detail):
- Labor (
SalesItemLineDetail): Billable labor from work-order cost rows titled Labor / Labor - … (no inventory link). If no labor cost row exists but technicians logged hours, a Labor line can be generated from the configured default hourly rate (see Edge secrets). Quantity reflects logged hours when present; otherwise a single quantity1line at the blended rate. - Parts (
SalesItemLineDetail): One summarized non-inventory line for all other work-order costs — manual parts/materials, inventory consumption lines, and any former truck/fee-style rows (Qty1,UnitPrice= total dollars). EquipQR does not sync inventory quantities or COGS into QuickBooks.
Customer-facing descriptions on the primary line (Labor when present, otherwise Parts) can include:
- Preventative maintenance context when a PM record exists: template name, all-OK summary (
condition === 1only), or exception rows only; PM notes; then Public notes: from non-private work-order notes.
Private Note (unchanged): EquipQR work order ID, dates, private notes, and full itemized cost breakdown (per cost row).
Customer Memo: Timeline + resolution summary (unchanged).
QuickBooks products & Edge Function secrets
Pre-create Labor as a Service item and Parts as a Non-inventory item in QuickBooks Products & services, or allow EquipQR to auto-create them when missing.
Optional Edge Function secrets (Supabase → Edge Functions → Secrets):
| Secret | Purpose |
|---|---|
QBO_INVOICE_LABOR_ITEM_NAME | Display name for the Labor item (default Labor) |
QBO_INVOICE_PARTS_ITEM_NAME | Display name for summarized Parts (default Parts) |
QBO_INVOICE_TRUCK_SUPPLIES_ITEM_NAME | Legacy name — invoice export no longer emits a separate Truck Supplies line (amounts roll into Parts) |
QBO_INVOICE_OTHER_ITEM_NAME | Legacy name — invoice export no longer emits separate Other lines |
QBO_INVOICE_ITEM_INCOME_ACCOUNT_ID | Prefer this Income account Id when auto-creating items |
QBO_INVOICE_ITEM_INCOME_ACCOUNT_NAME | Else match this exact active Income account Name |
QBO_INVOICE_PARTS_ITEM_TYPE | Ignored except NonInventory — unsupported values fall back safely |
Deprecated: QBO_INVOICE_PARTS_ITEM_PREFIX — invoice export no longer emits one QuickBooks line per part using Part: <description>; use summarized Parts via QBO_INVOICE_PARTS_ITEM_NAME instead.
Item resolution behavior:
- Query active QuickBooks Item by exact Name (any type). If found, reuse its Id.
- If missing, create Labor as Service and Parts as NonInventory, using the resolved Income account above or the first active Income account.
Architecture
Database Tables
| Table | Purpose |
|---|---|
quickbooks_credentials | Stores OAuth tokens (encrypted) |
quickbooks_oauth_sessions | Temporary OAuth session state |
quickbooks_team_customers | Team-to-customer mappings |
quickbooks_export_logs | Export history and status |
Edge Functions
| Function | Purpose |
|---|---|
quickbooks-oauth-callback | Handles OAuth callback |
quickbooks-refresh-tokens | Background token refresh (called by pg_cron every 15 min) |
quickbooks-search-customers | Customer search API |
quickbooks-export-invoice | Invoice creation/update |
Scheduled Jobs
| Job | Schedule | Purpose |
|---|---|---|
refresh-quickbooks-tokens | Every 15 minutes | Refreshes access tokens expiring within 15 minutes to prevent connection drops |
Security
- Token Storage: Access and refresh tokens are stored server-side only
- RLS Policies: All tables have Row Level Security restricting access to admin/owner roles
- OAuth State: CSRF protection via state parameter and session validation
- Feature Flag: Integration can be disabled via environment variable
Troubleshooting
Connection Issues
"QuickBooks is not configured"
- Ensure
VITE_INTUIT_CLIENT_IDis set in your environment - Verify the Intuit app credentials are correct
"Failed to connect QuickBooks" / "oauth_failed" error
- Most common cause:
redirect_urimismatch between client and server - Verify
VITE_QB_OAUTH_REDIRECT_BASE_URL(client) matchesQB_OAUTH_REDIRECT_BASE_URL(Edge Function secret) - Ensure both match what's registered in the Intuit Developer Portal
- Check Edge Function logs for detailed error messages
"Authorization has expired"
- Click Reconnect QuickBooks to re-authorize
- Refresh tokens expire after 100 days without use
Export Issues
"Team does not have a QuickBooks customer mapping"
- Navigate to team settings and map a QuickBooks customer
"Work order must be assigned to a team"
- Assign the work order to a team before exporting
"Failed to create invoice"
- Check the QuickBooks API logs in Supabase
- Ensure the customer still exists in QuickBooks
- Verify the QuickBooks company has proper permissions
"Could not find or create a valid Service Item" / income account errors
- Ensure your QuickBooks company has at least one active Income account
- Optionally set
QBO_INVOICE_ITEM_INCOME_ACCOUNT_IDorQBO_INVOICE_ITEM_INCOME_ACCOUNT_NAMEso auto-created Labor / Parts items attach to the correct account - Confirm Labor and Parts products exist (or allow EquipQR to create them)
API Rate Limits
QuickBooks API has rate limits. If you encounter throttling:
- Reduce frequency of customer searches
- Batch export operations during off-peak hours
Development
Running Tests
bash
npm test -- --grep quickbooksQuickBooks export invoice (Deno)
From supabase/functions (uses deno.json):
bash
deno test --allow-env --allow-net=quickbooks.api.intuit.com,sandbox-quickbooks.api.intuit.com ./quickbooks-export-invoice/quickbooks-export-invoice.deno.test.tsLocal Development
- Set up a QuickBooks sandbox company
- Configure sandbox credentials
- Set
QUICKBOOKS_SANDBOX=truein edge function secrets - Use
ngrokor similar for local OAuth callback testing
Feature Flag
The integration is controlled by a client-side feature flag:
QuickBooks Integration (VITE_ENABLE_QUICKBOOKS):
typescript
import { isQuickBooksEnabled } from '@/lib/flags';
if (isQuickBooksEnabled()) {
// Show QuickBooks features
}API Reference
Service Methods
typescript
import {
getConnectionStatus,
searchCustomers,
exportInvoice,
getTeamCustomerMapping,
updateTeamCustomerMapping,
} from '@/services/quickbooks';
// Check connection status
const status = await getConnectionStatus(organizationId);
// Search customers
const { customers } = await searchCustomers(organizationId, 'search query');
// Export invoice
const result = await exportInvoice(workOrderId);React Hooks
typescript
import { useQuickBooksConnection } from '@/hooks/useQuickBooksConnection';
import { useQuickBooksCustomers } from '@/hooks/useQuickBooksCustomers';
import { useExportToQuickBooks } from '@/hooks/useExportToQuickBooks';
// Get connection status
const { data: connectionStatus } = useQuickBooksConnection(organizationId);
// Search customers
const { data: customers } = useQuickBooksCustomers(organizationId, searchQuery);
// Export mutation
const exportMutation = useExportToQuickBooks();
exportMutation.mutate(workOrderId);Support
For issues with the QuickBooks integration:
- Check the browser console for errors
- Review Supabase edge function logs
- Verify environment configuration
- Contact support with export logs from the database