Skip to content

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

  1. Intuit Developer Account: You need an Intuit Developer account and a QuickBooks Online app
  2. QuickBooks Online Account: An active QuickBooks Online company (sandbox or production)
  3. 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-id

Supabase Edge Function Secrets

Configure these secrets in Supabase Dashboard → Edge Functions → Secrets:

Secret NameDescription
INTUIT_CLIENT_IDYour Intuit app's Client ID
INTUIT_CLIENT_SECRETYour Intuit app's Client Secret
QB_OAUTH_REDIRECT_BASE_URLCRITICAL: Must match VITE_QB_OAUTH_REDIRECT_BASE_URL (see below)
QUICKBOOKS_SANDBOXSet 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:

  1. Set VITE_QB_OAUTH_REDIRECT_BASE_URL in your client environment (e.g., https://supabase.equipqr.app)
  2. Set QB_OAUTH_REDIRECT_BASE_URL in your Edge Function secrets to the same value
  3. Register this URL in the Intuit Developer Portal as a Redirect URI
EnvironmentVITE_QB_OAUTH_REDIRECT_BASE_URL / QB_OAUTH_REDIRECT_BASE_URL
Productionhttps://supabase.equipqr.app
Previewhttps://supabase.preview.equipqr.app
Localhttp://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 NameDescriptionWhere to Find
service_role_keySupabase service role keyDashboard → Settings → API → Project API keys → service_role (secret)
supabase_urlSupabase project URLDashboard → Settings → API → Project URL

Configure separately for each environment (preview, production).

Setting Up Intuit Developer App

  1. Go to Intuit Developer Portal
  2. Create a new app or use an existing one
  3. 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 from supabase/config.toml / npx supabase status)
      • Set VITE_QB_OAUTH_REDIRECT_BASE_URL to match the base URL
    • Scopes: com.intuit.quickbooks.accounting
  4. Copy the Client ID and Client Secret

Usage

1. Connecting QuickBooks

  1. Navigate to Organization SettingsIntegrations
  2. Click Connect to QuickBooks Online
  3. Authorize the connection in the Intuit OAuth window
  4. Return to EquipQR to confirm the connection

2. Mapping Teams to Customers

Before exporting invoices, map each team to a QuickBooks customer:

  1. Navigate to Teams → Select a team
  2. Find the QuickBooks Customer card
  3. Click Select Customer to search and map a customer
  4. The mapping is saved automatically

3. Exporting Work Orders as Invoices

  1. Navigate to a work order detail page
  2. Click the Take Action dropdown
  3. Select Export to QuickBooks
  4. 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 quantity 1 line 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 (Qty 1, 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 === 1 only), 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):

SecretPurpose
QBO_INVOICE_LABOR_ITEM_NAMEDisplay name for the Labor item (default Labor)
QBO_INVOICE_PARTS_ITEM_NAMEDisplay name for summarized Parts (default Parts)
QBO_INVOICE_TRUCK_SUPPLIES_ITEM_NAMELegacy name — invoice export no longer emits a separate Truck Supplies line (amounts roll into Parts)
QBO_INVOICE_OTHER_ITEM_NAMELegacy name — invoice export no longer emits separate Other lines
QBO_INVOICE_ITEM_INCOME_ACCOUNT_IDPrefer this Income account Id when auto-creating items
QBO_INVOICE_ITEM_INCOME_ACCOUNT_NAMEElse match this exact active Income account Name
QBO_INVOICE_PARTS_ITEM_TYPEIgnored 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:

  1. Query active QuickBooks Item by exact Name (any type). If found, reuse its Id.
  2. 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

TablePurpose
quickbooks_credentialsStores OAuth tokens (encrypted)
quickbooks_oauth_sessionsTemporary OAuth session state
quickbooks_team_customersTeam-to-customer mappings
quickbooks_export_logsExport history and status

Edge Functions

FunctionPurpose
quickbooks-oauth-callbackHandles OAuth callback
quickbooks-refresh-tokensBackground token refresh (called by pg_cron every 15 min)
quickbooks-search-customersCustomer search API
quickbooks-export-invoiceInvoice creation/update

Scheduled Jobs

JobSchedulePurpose
refresh-quickbooks-tokensEvery 15 minutesRefreshes 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_ID is set in your environment
  • Verify the Intuit app credentials are correct

"Failed to connect QuickBooks" / "oauth_failed" error

  • Most common cause: redirect_uri mismatch between client and server
  • Verify VITE_QB_OAUTH_REDIRECT_BASE_URL (client) matches QB_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_ID or QBO_INVOICE_ITEM_INCOME_ACCOUNT_NAME so 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 quickbooks

QuickBooks 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.ts

Local Development

  1. Set up a QuickBooks sandbox company
  2. Configure sandbox credentials
  3. Set QUICKBOOKS_SANDBOX=true in edge function secrets
  4. Use ngrok or 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:

  1. Check the browser console for errors
  2. Review Supabase edge function logs
  3. Verify environment configuration
  4. Contact support with export logs from the database

Last updated:

EquipQR Help Center — product app at equipqr.app · status at status.equipqr.app