Skip to content

Commit 75579c9

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(quickbooks): support sandbox environment
1 parent 5e1a67d commit 75579c9

9 files changed

Lines changed: 89 additions & 2 deletions

File tree

apps/docs/content/docs/en/integrations/quickbooks.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ List vendors from QuickBooks Online
3030
| `activeOnly` | boolean | No | Only return active vendors |
3131
| `startPosition` | string | No | One-based start position for QuickBooks query pagination |
3232
| `maxResults` | string | No | Maximum number of vendors to return, up to 1000 |
33+
| `apiEnvironment` | string | No | QuickBooks API environment: production or sandbox. Defaults to production. |
3334
| `minorVersion` | string | No | QuickBooks Accounting API minor version. Defaults to 75. |
3435

3536
#### Output
@@ -57,6 +58,7 @@ List purchase orders from QuickBooks Online
5758
| `realmId` | string | Yes | QuickBooks company ID returned by Intuit as realmId during OAuth |
5859
| `startPosition` | string | No | One-based start position for QuickBooks query pagination |
5960
| `maxResults` | string | No | Maximum number of purchase orders to return, up to 1000 |
61+
| `apiEnvironment` | string | No | QuickBooks API environment: production or sandbox. Defaults to production. |
6062
| `minorVersion` | string | No | QuickBooks Accounting API minor version. Defaults to 75. |
6163

6264
#### Output
@@ -85,6 +87,7 @@ List bills from QuickBooks Online
8587
| `realmId` | string | Yes | QuickBooks company ID returned by Intuit as realmId during OAuth |
8688
| `startPosition` | string | No | One-based start position for QuickBooks query pagination |
8789
| `maxResults` | string | No | Maximum number of bills to return, up to 1000 |
90+
| `apiEnvironment` | string | No | QuickBooks API environment: production or sandbox. Defaults to production. |
8891
| `minorVersion` | string | No | QuickBooks Accounting API minor version. Defaults to 75. |
8992

9093
#### Output
@@ -114,6 +117,7 @@ Run a QuickBooks Online Accounting API query
114117
| --------- | ---- | -------- | ----------- |
115118
| `realmId` | string | Yes | QuickBooks company ID returned by Intuit as realmId during OAuth |
116119
| `query` | string | Yes | QuickBooks SQL-like query, e.g. SELECT * FROM Vendor MAXRESULTS 10 |
120+
| `apiEnvironment` | string | No | QuickBooks API environment: production or sandbox. Defaults to production. |
117121
| `minorVersion` | string | No | QuickBooks Accounting API minor version. Defaults to 75. |
118122

119123
#### Output

apps/sim/blocks/blocks/quickbooks.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
101101
placeholder: '75',
102102
mode: 'advanced',
103103
},
104+
{
105+
id: 'apiEnvironment',
106+
title: 'Environment',
107+
type: 'dropdown',
108+
options: [
109+
{ label: 'Production', id: 'production' },
110+
{ label: 'Sandbox', id: 'sandbox' },
111+
],
112+
value: () => 'production',
113+
mode: 'advanced',
114+
},
104115
],
105116
tools: {
106117
access: [
@@ -118,6 +129,7 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
118129
startPosition: params.startPosition,
119130
maxResults: params.maxResults,
120131
query: params.query,
132+
apiEnvironment: params.apiEnvironment,
121133
minorVersion: params.minorVersion,
122134
}),
123135
},
@@ -133,6 +145,10 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
133145
startPosition: { type: 'string', description: 'One-based start position for pagination' },
134146
maxResults: { type: 'string', description: 'Maximum number of records to return, up to 1000' },
135147
query: { type: 'string', description: 'QuickBooks SQL-like query to execute' },
148+
apiEnvironment: {
149+
type: 'string',
150+
description: 'QuickBooks API environment: production or sandbox',
151+
},
136152
minorVersion: { type: 'string', description: 'QuickBooks Accounting API minor version' },
137153
},
138154
outputs: {

apps/sim/tools/quickbooks/list_bills.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export const quickBooksListBillsTool: ToolConfig<QuickBooksListParams, QuickBook
4242
visibility: 'user-or-llm',
4343
description: 'Maximum number of bills to return, up to 1000',
4444
},
45+
apiEnvironment: {
46+
type: 'string',
47+
required: false,
48+
visibility: 'user-or-llm',
49+
description: 'QuickBooks API environment: production or sandbox. Defaults to production.',
50+
},
4551
minorVersion: {
4652
type: 'string',
4753
required: false,
@@ -55,6 +61,7 @@ export const quickBooksListBillsTool: ToolConfig<QuickBooksListParams, QuickBook
5561
buildQuickBooksQueryUrl({
5662
realmId: params.realmId,
5763
query: buildQuickBooksListQuery('Bill', params),
64+
apiEnvironment: params.apiEnvironment,
5865
minorVersion: params.minorVersion,
5966
}),
6067
method: 'GET',

apps/sim/tools/quickbooks/list_purchase_orders.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export const quickBooksListPurchaseOrdersTool: ToolConfig<
4545
visibility: 'user-or-llm',
4646
description: 'Maximum number of purchase orders to return, up to 1000',
4747
},
48+
apiEnvironment: {
49+
type: 'string',
50+
required: false,
51+
visibility: 'user-or-llm',
52+
description: 'QuickBooks API environment: production or sandbox. Defaults to production.',
53+
},
4854
minorVersion: {
4955
type: 'string',
5056
required: false,
@@ -58,6 +64,7 @@ export const quickBooksListPurchaseOrdersTool: ToolConfig<
5864
buildQuickBooksQueryUrl({
5965
realmId: params.realmId,
6066
query: buildQuickBooksListQuery('PurchaseOrder', params),
67+
apiEnvironment: params.apiEnvironment,
6168
minorVersion: params.minorVersion,
6269
}),
6370
method: 'GET',

apps/sim/tools/quickbooks/list_vendors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export const quickBooksListVendorsTool: ToolConfig<QuickBooksListParams, QuickBo
4949
visibility: 'user-or-llm',
5050
description: 'Maximum number of vendors to return, up to 1000',
5151
},
52+
apiEnvironment: {
53+
type: 'string',
54+
required: false,
55+
visibility: 'user-or-llm',
56+
description: 'QuickBooks API environment: production or sandbox. Defaults to production.',
57+
},
5258
minorVersion: {
5359
type: 'string',
5460
required: false,
@@ -62,6 +68,7 @@ export const quickBooksListVendorsTool: ToolConfig<QuickBooksListParams, QuickBo
6268
buildQuickBooksQueryUrl({
6369
realmId: params.realmId,
6470
query: buildQuickBooksListQuery('Vendor', params),
71+
apiEnvironment: params.apiEnvironment,
6572
minorVersion: params.minorVersion,
6673
}),
6774
method: 'GET',

apps/sim/tools/quickbooks/query.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export const quickBooksQueryTool: ToolConfig<QuickBooksQueryParams, QuickBooksQu
3636
visibility: 'user-or-llm',
3737
description: 'QuickBooks SQL-like query, e.g. SELECT * FROM Vendor MAXRESULTS 10',
3838
},
39+
apiEnvironment: {
40+
type: 'string',
41+
required: false,
42+
visibility: 'user-or-llm',
43+
description: 'QuickBooks API environment: production or sandbox. Defaults to production.',
44+
},
3945
minorVersion: {
4046
type: 'string',
4147
required: false,

apps/sim/tools/quickbooks/quickbooks.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@ describe('QuickBooks tools', () => {
4444
expect(requestBody(params)).toBe('SELECT * FROM Vendor MAXRESULTS 10')
4545
})
4646

47+
it('targets the QuickBooks sandbox API host when requested', () => {
48+
const query = buildQuickBooksListQuery('Bill', { maxResults: '10' })
49+
const url = buildQuickBooksQueryUrl({
50+
realmId: '123145',
51+
query,
52+
apiEnvironment: 'sandbox',
53+
})
54+
55+
expect(url).toBe(
56+
'https://sandbox-quickbooks.api.intuit.com/v3/company/123145/query?minorversion=75&query=SELECT+*+FROM+Bill+MAXRESULTS+10'
57+
)
58+
})
59+
60+
it('rejects unknown QuickBooks API environments', () => {
61+
expect(() =>
62+
buildQuickBooksQueryEndpoint({
63+
realmId: '123145',
64+
apiEnvironment: 'development',
65+
})
66+
).toThrow('QuickBooks environment must be production or sandbox')
67+
})
68+
4769
it('rejects invalid pagination before calling QuickBooks', () => {
4870
expect(() =>
4971
buildQuickBooksListQuery('Bill', {

apps/sim/tools/quickbooks/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import type { ToolResponse } from '@/tools/types'
22

33
export type QuickBooksEntityName = 'Vendor' | 'PurchaseOrder' | 'Bill'
4+
export type QuickBooksEnvironment = 'production' | 'sandbox'
45

56
export interface QuickBooksBaseParams {
67
accessToken: string
78
realmId: string
9+
apiEnvironment?: QuickBooksEnvironment | string
810
minorVersion?: string
911
}
1012

apps/sim/tools/quickbooks/utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { truncate } from '@sim/utils/string'
22
import type {
33
QuickBooksEntityName,
4+
QuickBooksEnvironment,
45
QuickBooksFault,
56
QuickBooksQueryEnvelope,
67
QuickBooksRecord,
78
} from '@/tools/quickbooks/types'
89

9-
export const QUICKBOOKS_API_BASE = 'https://quickbooks.api.intuit.com'
10+
export const QUICKBOOKS_API_BASES: Record<QuickBooksEnvironment, string> = {
11+
production: 'https://quickbooks.api.intuit.com',
12+
sandbox: 'https://sandbox-quickbooks.api.intuit.com',
13+
}
1014
export const QUICKBOOKS_MINOR_VERSION = '75'
1115

1216
const QUICKBOOKS_MAX_RESULTS_LIMIT = 1000
@@ -22,28 +26,40 @@ export function buildQuickBooksHeaders(accessToken: string): Record<string, stri
2226

2327
export function buildQuickBooksQueryEndpoint(params: {
2428
realmId: string
29+
apiEnvironment?: QuickBooksEnvironment | string
2530
minorVersion?: string
2631
}): string {
2732
const realmId = params.realmId.trim()
2833
if (!realmId) {
2934
throw new Error('QuickBooks Company ID is required')
3035
}
3136

32-
const url = new URL(`${QUICKBOOKS_API_BASE}/v3/company/${encodeURIComponent(realmId)}/query`)
37+
const url = new URL(
38+
`${getQuickBooksApiBase(params.apiEnvironment)}/v3/company/${encodeURIComponent(realmId)}/query`
39+
)
3340
url.searchParams.set('minorversion', normalizeMinorVersion(params.minorVersion))
3441
return url.toString()
3542
}
3643

3744
export function buildQuickBooksQueryUrl(params: {
3845
realmId: string
3946
query: string
47+
apiEnvironment?: QuickBooksEnvironment | string
4048
minorVersion?: string
4149
}): string {
4250
const url = new URL(buildQuickBooksQueryEndpoint(params))
4351
url.searchParams.set('query', normalizeQuickBooksQuery(params.query))
4452
return url.toString()
4553
}
4654

55+
export function getQuickBooksApiBase(environment?: QuickBooksEnvironment | string): string {
56+
const normalized = environment?.trim() || 'production'
57+
if (normalized === 'production' || normalized === 'sandbox') {
58+
return QUICKBOOKS_API_BASES[normalized]
59+
}
60+
throw new Error('QuickBooks environment must be production or sandbox')
61+
}
62+
4763
export function normalizeQuickBooksQuery(query: string): string {
4864
const trimmed = query.trim()
4965
if (!trimmed) {

0 commit comments

Comments
 (0)