Skip to content

A Node Js App for calculating sales tax using QuickBooks Online Sales Tax API via GraphQL and the Intuit SDK.

Notifications You must be signed in to change notification settings

IntuitDeveloper/SampleApp-Salestax-NodeJS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

6 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

QuickBooks Sales Tax API

A Node Js App for calculating sales tax using QuickBooks Online Sales Tax API via GraphQL and the Intuit SDK.

Features

  • Complete Sales Tax Operations: Full tax calculation and lookup capabilities
    • βœ… Calculate: Real-time tax calculations for transactions
    • βœ… Customer Integration: Automatic QuickBooks customer lookup and resolution
  • GraphQL Integration: Uses QuickBooks Sales Tax GraphQL API for efficient tax operations
    • Tax Calculation Mutation: indirectTaxCalculateSaleTransactionTax for real-time calculations
    • Dynamic Variables: Automatic conversion of REST requests to GraphQL variables
    • Schema Compliance: Exact implementation of QuickBooks Sales Tax mutation structure
  • Dynamic Input Processing: No hardcoded values - all data comes from request input
    • Customer ID: Uses provided customerId or auto-fetches first available QuickBooks customer
    • Addresses: Requires shipFromAddress or businessAddress input for shipping calculations
    • Item IDs: Accepts QuickBooks-compatible numeric itemId values or defaults to "1"
  • Intuit Node JS SDK: Built with official Intuit Node JS SDK for QuickBooks API v3 with OAuth 2.0

OAuth Implementation

This API implements OAuth 2.0 as per Intuit Node JS specifications:

Required OAuth Scopes

The API requires the following OAuth scopes for full functionality:

  • com.intuit.quickbooks.accounting - Access to QuickBooks accounting data and customer information
  • indirect-tax.tax-calculation.quickbooks - Required for Sales Tax API access
  • openid, profile, email, phone, address - User identity information

Note: The indirect-tax.tax-calculation.quickbooks scope is essential for accessing QuickBooks Sales Tax GraphQL API endpoints.

Endpoints

  • Production: https://qb.api.intuit.com/graphql
  • Sandbox: https://qb-sandbox.api.intuit.com/graphql

Prerequisites

  • Node.js: Version 12.0.0 or higher (recommended: 14.x or 16.x)
  • npm: Comes with Node.js installation

Check Node.js Version

node --version
npm --version

Getting Started

  1. Install Dependencies

    npm install
  2. Configure QuickBooks App

  3. Run the Application

    node index.js
  4. Complete OAuth Authentication

    • Navigate to http://localhost:3000
    • Click on the 'Connect to Quickbooks' Button
    • Complete the QuickBooks OAuth flow
    • It Navigates you back to the Home Page where you can now have access to the Sales Tax UI.
  5. Test the API

    • Navigate to http://localhost:3000 for the web interface
    • Use the UI to perform the various operations.

Configuration

Create a .env file with your QuickBooks app credentials:

PORT=3000

CLIENT_ID: PUT_YOUR_CLIENT_ID_HERE
CLIENT_SECRET: PUT_YOUR_CLIENT_SECRET_HERE
ENVIRONMENT: PUT_THE_ENVIRONMENT_HERE
REDIRECT_URI: http://localhost:3000/api/auth/

Authentication

This API requires OAuth 2.0 authentication with QuickBooks Online. The authentication flow includes:

  1. OAuth Authorization: Visit /api/auth/login to start the flow
  2. Scope Configuration: Configurable scopes
  3. Sales Tax Permissions: Requires indirect-tax.tax-calculation.quickbooks scope for Sales Tax API access

Example OAuth Flow

# 1. Initiate OAuth
curl "http://localhost:3000/api/auth/login"

# 2. Complete authorization in browser, then test API

API Endpoints

OAuth Management

  • GET /api/auth/login - Initiate OAuth authorization with QuickBooks
  • GET /api/auth/callback - Handle OAuth callback from QuickBooks
  • GET /api/auth/retrieveToken - Retrieve Token

Sales Tax Operations

  • POST /api/quickbook/salestax/indirect-tax - Calculate tax for a transaction (uses indirectTaxCalculateSaleTransactionTax mutation)
  • GET /api/quickbook/salestax/customers - Get QuickBooks customers for testing and integration

GraphQL Implementation

This API implements the exact QuickBooks Sales Tax GraphQL schema:

Mutation Used

mutation IndirectTaxCalculateSaleTransactionTax($input: IndirectTax_TaxCalculationInput!) {
  indirectTaxCalculateSaleTransactionTax(input: $input) {
    taxCalculation {
      transactionDate
      taxTotals {
        totalTaxAmountExcludingShipping {
          value
        }
      }
      subject {
        customer {
          id
        }
      }
      shipping {
        shipToAddress {
          rawAddress {
            ... on IndirectTax_FreeFormAddress {
              freeformAddressLine
            }
          }
        }
        shipFromAddress {
          rawAddress {
            ... on IndirectTax_FreeFormAddress {
              freeformAddressLine
            }
          }
        }
      }
      lineItems {
        edges {
          node {
            numberOfUnits
            pricePerUnitExcludingTaxes {
              value
            }
          }
        }
        nodes {
          productVariantTaxability {
            product {
              id
            }
          }
        }
      }
    }
  }
}

Input Variables Format

{
  "input": {
    "transactionDate": "2025-06-17",
    "subject": {
      "qbCustomerId": "1"
    },
    "shipping": {
      "shipFromAddress": {
        "freeFormAddressLine": "2600 Marine Way, Mountain View, CA 94043"
      },
      "shipToAddress": {
        "freeFormAddressLine": "2600 Marine Way, Mountain View, CA 94043"
      }
    },
    "lineItems": [
      {
        "numberOfUnits": 1,
        "pricePerUnitExcludingTaxes": {
          "value": 10.95
        },
        "productVariantTaxability": {
          "productVariantId": "1"
        }
      }
    ]
  }
}

API Input Requirements Summary

πŸ”„ Dynamic Input Implementation

All APIs now require input data and do not rely on hardcoded values:

Main Calculate Endpoint (POST /api/quickbook/salestax/indirect-tax)

  • Required: customerAddress (complete address object)
  • Required: Either shipFromAddress OR businessAddress (complete address object)
  • Optional: customerId (auto-fetches first customer if not provided)
  • Optional: itemId (accepts numeric strings like "1", "2", "3", etc. - defaults to "1")
  • Optional: transactionDate (defaults to current date)

Customers Endpoint

  • No input required: Returns all available QuickBooks customers for integration

🚨 Important Validation Rules

  1. Ship-From Address: Must provide either shipFromAddress OR businessAddress
  2. Customer ID: If not provided, system auto-fetches first available customer
  3. Address Completeness: All address fields (city, state, postal code) are required
  4. Item IDs: Must be numeric strings ("1", "2", "3", etc.) - custom alphanumeric IDs will fail QuickBooks validation

API Usage Examples

1. Calculate Sales Tax for Transaction

βœ… Dynamic Input Version (No Hardcoded Values):

curl -X POST "http://localhost:3000/api/quickbook/salestax/indirect-tax" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
  "transaction": {
      "transactionDate": "2025-06-17",
      "customerId": "1",
      "customerAddress": {
        "line1": "2600 Marine Way",
        "city": "Mountain View", 
        "state": "CA",
        "postalCode": "94043"
      },
      "shipFromAddress": {
        "line1": "123 Business St",
        "city": "San Francisco",
        "state": "CA",
        "postalCode": "94102"
      },
      "lines": [
        {
          "amount": 10.95,
          "description": "Test Product",
          "itemId": "2"
        }
      ]
    }
  }'

Alternative with businessAddress (instead of shipFromAddress):

curl -X POST "http://localhost:3000/api/quickbook/salestax/indirect-tax" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
  "transaction": {
      "transactionDate": "2025-06-17",
      "customerAddress": {
        "line1": "2600 Marine Way",
        "city": "Mountain View", 
        "state": "CA",
        "postalCode": "94043"
      },
      "businessAddress": {
        "line1": "123 Business St",
        "city": "San Francisco",
        "state": "CA",
        "postalCode": "94102"
      },
      "lines": [
        {
          "amount": 10.95,
          "description": "Test Product"
        }
      ]
    }
  }'

πŸ” ItemId Validation Requirements

βœ… Valid ItemId Values:

  • Numeric strings: "1", "2", "3", "10", etc.
  • No itemId provided: Defaults to "1"

❌ Invalid ItemId Values:

  • Custom alphanumeric: "CUSTOM-123", "PROD-456"
  • Generated patterns: "item_1", "product_abc"

Note: Either shipFromAddress OR businessAddress is required. If customerId is not provided, the system will automatically use the first available QuickBooks customer. ItemId must be a numeric string to pass QuickBooks validation.

GraphQL Mutation Used:

mutation IndirectTaxCalculateSaleTransactionTax($input: IndirectTax_TaxCalculationInput!) {
  indirectTaxCalculateSaleTransactionTax(input: $input) {
    taxCalculation {
      transactionDate
      taxTotals {
        totalTaxAmountExcludingShipping {
          value
        }
      }
      subject {
        customer {
          id
        }
      }
      shipping {
        shipToAddress {
          rawAddress {
            ... on IndirectTax_FreeFormAddress {
              freeformAddressLine
            }
          }
        }
        shipFromAddress {
          rawAddress {
            ... on IndirectTax_FreeFormAddress {
              freeformAddressLine
            }
          }
        }
      }
      lineItems {
        edges {
          node {
            numberOfUnits
            pricePerUnitExcludingTaxes {
              value
            }
          }
        }
        nodes {
          productVariantTaxability {
            product {
              id
            }
          }
        }
      }
    }
  }
}

Response:

{
  "success": true,
  "data": {
    "totalTaxAmount": 1.0,
    "totalAmount": 11.95,
    "lines": [
      {
        "lineNumber": 1,
        "amount": 10.95,
        "taxAmount": 1.0,
        "taxRate": 0.0913242009132420091324200913,
        "description": "Test Product",
        "taxBreakdown": [
          {
            "taxType": "Sales Tax",
            "taxName": "QuickBooks Sales Tax",
            "taxRate": 0.0913242009132420091324200913,
            "taxAmount": 1.0,
            "jurisdiction": "Mountain View, CA",
            "taxableAmount": 10.95
          }
        ]
      }
    ],
    "transactionDate": "2025-06-17T00:00:00"
  }
}

2. Get QuickBooks Customers

curl -X GET "http://localhost:5038/api/salestax/customers" \
  -H "Accept: application/json"

Response:

{
  "success": true,
  "data": [
    {
      "id": "1",
      "name": "",
      "companyName": "Amy's Bird Sanctuary",
      "active": true
    },
    {
      "id": "2",
      "name": "",
      "companyName": "Bill's Windsurf Shop",
      "active": true
    }
  ]
}

Project Structure

β”œβ”€β”€ graphql/salesTax
β”‚   β”œβ”€β”€ inirectTax.js                   # GraphQL query and variables for calculating tax
β”œβ”€β”€ pages/
β”‚   β”œβ”€β”€ index.html                      # HTML for web UI
β”‚   └── style.css                       # Styling for the UI
β”œβ”€β”€ routes/
β”‚   β”œβ”€β”€ oauth=route.js                  # routes for authentication flow
β”‚   β”œβ”€β”€ sales-tax-route.cs              # routes for sales tax
β”œβ”€β”€ services/                       
β”‚   β”œβ”€β”€ auth-service.js                 # service for authentication
β”‚   └── sales-tax-service.js            # service for sales tax
β”œβ”€β”€ env                                 # Configs
β”œβ”€β”€ index.js                            # Application startup

GraphQL Implementation Details

This API implements the QuickBooks Sales Tax GraphQL schema with the following operations:

Tax Calculation Mutation (indirectTaxCalculateSaleTransactionTax)

  • Purpose: Calculate real-time sales tax for transactions
  • Method: CalculateSaleTransactionTaxAsync()
  • Endpoint: POST /api/quickbook/salestax/indirect-tax
  • GraphQL Type: Mutation with IndirectTax_TaxCalculationInput input type
  • Features: Dynamic customer lookup, address processing, line item support

Customer Integration (QuickBooks REST API)

  • Purpose: Retrieve QuickBooks customer data for tax calculations
  • Method: GetCustomersAsync()
  • Endpoint: GET /api/quickbook/salestax/customers
  • Integration: Uses QuickBooks REST API v3 for customer data

Dependencies

  • GraphQL.Client - GraphQL client for Node Js (graphql-request)
  • IntuitNodeSDK - Official Intuit Node JS SDK for OAuth
  • Express - Node Express

Implementation Details

OAuth 2.0 Flow

Authorization: Uses Intuit Node Js SDK's OAuth2Client with proper environment handling

GraphQL Integration

  • Real API Calls: Direct integration with QuickBooks GraphQL endpoint
  • Dynamic Variables: Converts REST request to GraphQL variables automatically
  • Customer Lookup: Automatically retrieves QuickBooks customer ID for transactions
  • Error Handling: Comprehensive error logging and debugging support

Security Considerations

  • OAuth state parameter validation
  • Granular permission scope
  • HTTPS redirect URI recommended for production

Development

The application uses:

  • Intuit Node JS SDK for OAuth 2.0
  • GraphQL.Client for API communication

Troubleshooting

Common Issues

  1. "Cannot find module 'node:events'" Error:

    • Root Cause: Using Express v5.x with Node.js < 18.0.0
    • Solution: Upgrade to Node.js 18+ or use Express v4.x
    • Check Version: Run node --version (should be 12.0.0+ for this project)
    • Fix: Delete node_modules and package-lock.json, then run npm install
  2. "Cannot find package 'graphql'" Error:

    • Root Cause: Missing graphql peer dependency for graphql-request
    • Solution: Install the missing dependency
    • Fix: Run npm install graphql or npm install (package.json updated with graphql dependency)
  3. "Invalid URI or environment" Error:

    • Ensure DiscoveryDocument is set to https://appcenter.intuit.com/api/v1/connection/oauth2
    • Verify Environment is set to "sandbox" or "production"
  4. "Access Denied" GraphQL Error:

    • Verify both required scopes are present in ProjectScopes
    • Ensure QuickBooks company has sales tax enabled
    • Check that OAuth token includes indirect-tax.tax-calculation.quickbooks scope
  5. "-37109" Application Error:

    • Configure sales tax settings in QuickBooks company
    • Enable tax agencies and rates for the addresses being tested
    • Verify customer exists in QuickBooks (or use hardcoded customer ID "1")
  6. "INV-GraphQL expression=Validation failed" Error:

    • Root Cause: Invalid itemId format
    • Solution: Use numeric string itemIds only ("1", "2", "3", etc.)
    • Avoid: Custom alphanumeric itemIds ("CUSTOM-123", "PROD-456")
    • Default: If no itemId provided, system defaults to "1"

Debugging

  • Check application logs for detailed GraphQL request/response information
  • Use /api/oauth/retrieveTOken to verify token exists

Production Deployment

For production:

  1. Update .env with production credentials
  2. Change Environment to "production"
  3. Update GraphQLEndpoint to "https://qb.api.intuit.com/graphql"
  4. Update BaseUrl to "https://quickbooks.api.intuit.com"
  5. Use HTTPS for redirect URI (required by QuickBooks)

Testing & Validation

The implementation has been comprehensively tested with real QuickBooks sandbox data:

βœ… OAuth 2.0 Integration

  • Scope Configuration: Both required scopes (com.intuit.quickbooks.accounting + indirect-tax.tax-calculation.quickbooks) working
  • Authorization Flow: Complete OAuth flow using Intuit Node JS SDK

βœ… GraphQL API Integration

  • Real API Calls: Direct integration with https://qb-sandbox.api.intuit.com/graphql
  • Exact Schema Implementation: IndirectTaxCalculateSaleTransactionTax mutation with proper input structure
  • Customer Integration: Automatic QuickBooks customer lookup and ID resolution
  • Dynamic Variables: Request data converted to GraphQL variables correctly

βœ… All Endpoints Validated (Dynamic Input Implementation)

Endpoint Status Input Requirements Test Results
POST /api/quickbook/salestax/indirect-tax βœ… Working Requires customerAddress + (shipFromAddress OR businessAddress) + numeric itemId $10.95 β†’ $1.00 tax with itemId="1"
GET /api/quickbook/salestax/customers βœ… Working No input required Returns QuickBooks customer list for dynamic lookup

Environment Setup

  • Sandbox: Use qb-sandbox.api.intuit.com endpoints
  • Production: Use qb.api.intuit.com endpoints
  • Port: Default is 3000, configurable in env

License

This project is for demonstration purposes and follows QuickBooks API terms of service.

About

A Node Js App for calculating sales tax using QuickBooks Online Sales Tax API via GraphQL and the Intuit SDK.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published