Skip to content

Commit 339ec6e

Browse files
committed
feat(square): add Square integration with 34 commerce operations
Add a Square integration (API-key auth via personal access token) covering payments, refunds, customers, locations, orders, invoices, catalog, and inventory. Catalog image upload routes through an internal API endpoint using the shared UserFile handling pattern. Adds a dedicated square-errors extractor.
1 parent ae075f8 commit 339ec6e

51 files changed

Lines changed: 6659 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/components/icons.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,17 @@ export function WhatsAppIcon(props: SVGProps<SVGSVGElement>) {
19581958
)
19591959
}
19601960

1961+
export function SquareIcon(props: SVGProps<SVGSVGElement>) {
1962+
return (
1963+
<svg {...props} viewBox='0 0 1999.98 501.42' xmlns='http://www.w3.org/2000/svg'>
1964+
<path
1965+
fill='#fff'
1966+
d='M501.42,83.79v333.84c0,46.27-37.5,83.79-83.79,83.79H83.79c-46.28,0-83.79-37.5-83.79-83.79V83.79C0,37.51,37.52,0,83.79,0h333.84c46.29,0,83.79,37.5,83.79,83.79h0ZM410.22,117.64c0-14.61-11.85-26.45-26.45-26.45H117.62c-14.61,0-26.45,11.84-26.45,26.45v266.19c0,14.61,11.84,26.45,26.45,26.45h266.17c14.61,0,26.45-11.85,26.45-26.45V117.64h-.02ZM182.31,197.59c0-8.43,6.79-15.26,15.17-15.26h106.4c8.39,0,15.17,6.84,15.17,15.26v106.24c0,8.43-6.75,15.26-15.17,15.26h-106.4c-8.39,0-15.17-6.84-15.17-15.26v-106.24ZM778.93,221.94l-3.85-.86c-41.04-9.31-65.81-14.93-65.81-42,0-24.2,23.02-41.11,55.98-41.11,30.52,0,53.74,12.76,73.08,40.16,1.11,1.57,2.84,2.61,4.74,2.84,1.89.23,3.79-.35,5.23-1.59l32.16-27.71c2.68-2.31,3.15-6.22,1.1-9.09-24.19-33.89-67.01-54.12-114.56-54.12-31.56,0-60.34,9.26-81.04,26.08-21.73,17.65-33.21,41.93-33.21,70.23,0,63.76,54.74,76.94,98.71,87.53,4.45,1.08,8.77,2.1,12.95,3.08,39.74,9.36,66,15.54,66,43.74s-24.04,45.48-61.24,45.48c-33.71,0-64.35-17.1-86.28-48.14-1.1-1.55-2.8-2.59-4.68-2.84s-3.73.28-5.2,1.49l-33.86,27.99c-2.72,2.25-3.28,6.14-1.3,9.05,25.63,37.64,76.48,61.97,129.56,61.97,32.56,0,62.52-9.57,84.36-26.95,23.27-18.51,35.57-44.01,35.57-73.73,0-67.27-57.62-80.13-108.45-91.48l.04-.02ZM1126.33,177.73h-40.76c-3.74,0-6.78,3.04-6.78,6.78v19.06c-12.6-14.21-33.77-30.22-65.18-30.22s-56.88,12.32-75.37,35.62c-17.16,21.63-26.62,51.73-26.62,84.75s9.45,63.12,26.62,84.75c18.49,23.31,44.56,35.62,75.37,35.62,26.63,0,49.1-9.45,65.18-27.37v107.92c0,3.74,3.04,6.78,6.78,6.78h40.76c3.74,0,6.78-3.04,6.78-6.78V184.51c0-3.74-3.04-6.78-6.78-6.78ZM1080.1,287.16v13.57c0,39.86-21.97,65.61-55.98,65.61-36.15,0-57.74-27.15-57.74-72.61s21.58-72.61,57.74-72.61c34.01,0,55.98,25.93,55.98,66.05h0ZM1360.59,177.73h-40.76c-3.74,0-6.78,3.04-6.78,6.78v130.66c0,32.45-23.32,49.42-46.36,49.42-26.03,0-39.79-15.58-39.79-45.04v-135.03c0-3.74-3.04-6.78-6.78-6.78h-40.76c-3.74,0-6.78,3.04-6.78,6.78v146.41c0,50.53,30.93,83.17,78.8,83.17,23.76,0,43.95-9.67,61.67-29.56v17.96c0,3.74,3.04,6.78,6.78,6.78h40.76c3.74,0,6.78-3.04,6.78-6.78v-217.99c0-3.74-3.04-6.78-6.78-6.78h0ZM1607.92,367.06c-8.79-.24-12.72-4.78-12.72-14.7v-98.9c0-51.66-31.71-80.11-89.3-80.11-44.71,0-80.07,23.24-94.59,62.18-.66,1.78-.52,3.78.39,5.47.93,1.73,2.57,2.98,4.48,3.42l37.83,8.71c3.37.77,6.78-1.14,7.94-4.45,6.74-19.11,20.01-28.01,41.76-28.01,25.53,0,38.48,11.62,38.48,34.54v3.2l-62.73,12.98c-53.04,11.03-77.74,34.25-77.74,73.09s32.22,68.73,78.36,68.73c28.25,0,51.2-8.69,66.46-25.16,9.13,17.73,33.93,26.29,62.3,21.35,3.24-.57,5.6-3.38,5.6-6.69v-28.9c0-3.63-2.93-6.67-6.52-6.77v.02ZM1542.2,300.53v26.89c0,27.56-27.28,41.98-54.24,41.98-20.26,0-32.35-10.13-32.35-27.1,0-19.37,14.63-26.41,38.23-31.5l48.36-10.27ZM1774.34,177.32c-3.17-.52-6.47-.77-10.09-.77-27.5,0-50.92,12.35-62.11,32.47v-24.51c0-3.74-3.04-6.78-6.78-6.78h-40.76c-3.74,0-6.78,3.04-6.78,6.78v217.99c0,3.74,3.04,6.78,6.78,6.78h40.76c3.74,0,6.78-3.04,6.78-6.78v-114.9c0-34.27,23.2-57.3,57.74-57.3,4.7,0,8.63.17,12.74.56,1.89.18,3.79-.45,5.2-1.73s2.22-3.11,2.22-5.02v-40.09c0-3.34-2.39-6.16-5.69-6.7h-.01ZM1973.74,206.72c-18.32-21.83-44.82-33.36-76.62-33.36s-59.09,12.34-79.33,34.76c-19.98,22.12-30.98,52.52-30.98,85.61,0,70.87,46.26,120.37,112.49,120.37,43.9,0,79.17-21.53,96.77-59.08.8-1.7.85-3.61.14-5.37-.71-1.76-2.14-3.15-3.91-3.82l-33.69-12.76c-3.39-1.28-7.27.37-8.63,3.68-8.08,19.63-26.56,30.9-50.68,30.9-33.08,0-56.1-23.59-60.26-61.64h154.16c3.74,0,6.78-3.04,6.78-6.78v-11.63c0-31.99-9.32-60.72-26.25-80.88h.01ZM1945.21,264.82h-103.37c7.73-28.91,27.66-45.45,54.84-45.45,34.72,0,47.8,23.3,48.52,45.45h.01Z'
1967+
/>
1968+
</svg>
1969+
)
1970+
}
1971+
19611972
export function StripeIcon(props: SVGProps<SVGSVGElement>) {
19621973
return (
19631974
<svg

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ import {
191191
SlackIcon,
192192
SmtpIcon,
193193
SQSIcon,
194+
SquareIcon,
194195
SshIcon,
195196
STSIcon,
196197
STTIcon,
@@ -439,6 +440,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
439440
slack: SlackIcon,
440441
smtp: SmtpIcon,
441442
sqs: SQSIcon,
443+
square: SquareIcon,
442444
ssh: SshIcon,
443445
stagehand: StagehandIcon,
444446
stripe: StripeIcon,

apps/docs/content/docs/en/integrations/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@
192192
"slack",
193193
"smtp",
194194
"sqs",
195+
"square",
195196
"ssh",
196197
"stagehand",
197198
"stripe",

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

Lines changed: 1472 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { createLogger } from '@sim/logger'
2+
import { getErrorMessage } from '@sim/utils/errors'
3+
import { generateId } from '@sim/utils/id'
4+
import { type NextRequest, NextResponse } from 'next/server'
5+
import { squareCatalogImageContract } from '@/lib/api/contracts/tools/square'
6+
import { parseRequest } from '@/lib/api/server'
7+
import { checkInternalAuth } from '@/lib/auth/hybrid'
8+
import { generateRequestId } from '@/lib/core/utils/request'
9+
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
10+
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
11+
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
12+
import { assertToolFileAccess } from '@/app/api/files/authorization'
13+
import { SQUARE_API_VERSION, SQUARE_BASE_URL } from '@/tools/square/types'
14+
15+
export const dynamic = 'force-dynamic'
16+
17+
const logger = createLogger('SquareCatalogImageAPI')
18+
19+
export const POST = withRouteHandler(async (request: NextRequest) => {
20+
const requestId = generateRequestId()
21+
22+
try {
23+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
24+
25+
if (!authResult.success || !authResult.userId) {
26+
logger.warn(`[${requestId}] Unauthorized Square catalog image upload: ${authResult.error}`)
27+
return NextResponse.json(
28+
{ success: false, error: authResult.error || 'Authentication required' },
29+
{ status: 401 }
30+
)
31+
}
32+
33+
const parsed = await parseRequest(squareCatalogImageContract, request, {})
34+
if (!parsed.success) return parsed.response
35+
const validatedData = parsed.data.body
36+
37+
let fileBuffer: Buffer
38+
let fileName: string
39+
let mimeType = 'application/octet-stream'
40+
41+
if (validatedData.file) {
42+
const userFiles = processFilesToUserFiles(
43+
[validatedData.file as RawFileInput],
44+
requestId,
45+
logger
46+
)
47+
48+
if (userFiles.length === 0) {
49+
return NextResponse.json({ success: false, error: 'Invalid file input' }, { status: 400 })
50+
}
51+
52+
const userFile = userFiles[0]
53+
const denied = await assertToolFileAccess(userFile.key, authResult.userId, requestId, logger)
54+
if (denied) return denied
55+
56+
fileBuffer = await downloadFileFromStorage(userFile, requestId, logger)
57+
fileName = validatedData.fileName || userFile.name
58+
if (userFile.type) mimeType = userFile.type
59+
} else if (validatedData.fileContent) {
60+
fileBuffer = Buffer.from(validatedData.fileContent, 'base64')
61+
fileName = validatedData.fileName || 'image'
62+
} else {
63+
return NextResponse.json({ success: false, error: 'File is required' }, { status: 400 })
64+
}
65+
66+
const imageRequest: Record<string, unknown> = {
67+
idempotency_key: validatedData.idempotencyKey || generateId(),
68+
image: {
69+
type: 'IMAGE',
70+
id: '#square_catalog_image',
71+
image_data: validatedData.caption ? { caption: validatedData.caption } : {},
72+
},
73+
}
74+
if (validatedData.objectId) imageRequest.object_id = validatedData.objectId
75+
76+
const formData = new FormData()
77+
formData.append('request', JSON.stringify(imageRequest))
78+
formData.append(
79+
'image_file',
80+
new Blob([new Uint8Array(fileBuffer)], { type: mimeType }),
81+
fileName
82+
)
83+
84+
const response = await fetch(`${SQUARE_BASE_URL}/v2/catalog/images`, {
85+
method: 'POST',
86+
headers: {
87+
Authorization: `Bearer ${validatedData.accessToken}`,
88+
'Square-Version': SQUARE_API_VERSION,
89+
},
90+
body: formData,
91+
})
92+
93+
const data = await response.json()
94+
95+
if (!response.ok) {
96+
const errorMessage = data?.errors?.[0]?.detail || 'Failed to upload catalog image'
97+
logger.error(`[${requestId}] Square API error:`, { status: response.status, data })
98+
return NextResponse.json({ success: false, error: errorMessage }, { status: response.status })
99+
}
100+
101+
const object = data.image ?? {}
102+
103+
return NextResponse.json({
104+
success: true,
105+
output: {
106+
object,
107+
metadata: {
108+
id: object.id ?? '',
109+
type: object.type ?? null,
110+
version: object.version ?? null,
111+
},
112+
},
113+
})
114+
} catch (error) {
115+
logger.error(`[${requestId}] Unexpected error:`, error)
116+
return NextResponse.json(
117+
{ success: false, error: getErrorMessage(error, 'Unknown error') },
118+
{ status: 500 }
119+
)
120+
}
121+
})

0 commit comments

Comments
 (0)