Square Invoice Setup Checklist¶
Reference for integrating Square invoices into a Next.js + Cloudflare Workers project.
Prerequisites¶
- Client has a Square account and has created an application in developer.squareup.com
- Client can provide:
- Production Access Token (from Credentials → Production)
- Location ID (from Locations → your business location)
Phase 1: Local Development¶
1.1 Create the Square REST Client¶
File: lib/square.ts
Key points:
- Use pure fetch, no SDK
- Pin API version (e.g., 2024-01-18)
- Expose helper functions: findOrCreateCustomer(), sendInvoice()
- Read env lazily inside functions (not at module load) — allows tests to stub env
Critical API Requirements:
- findOrCreateCustomer: searches for customer by email, creates if missing
- sendInvoice: MUST create an Order first (with line items), then create invoice with order_id attached
- Without order_id, Square returns: order_id is required
- The Order is where the amount lives; the invoice's BALANCE payment request draws from the Order total
- Invoice creation REQUIRES accepted_payment_methods (e.g., { card: true })
- Without it, Square returns: accepted_payment_methods is required
1.2 Create Unit Tests¶
File: lib/__tests__/square.test.ts
Mock approach:
- Use vi.stubGlobal('fetch', fetchMock) and vi.stubEnv() for each test
- Mock returns realistic Square response shape
- Important: Unit tests with mocked fetch do NOT catch API contract violations (missing required fields)
- The order_id and accepted_payment_methods bugs only surfaced during real API testing
1.3 Create the Invoice Form Component¶
File: components/admin/InvoiceForm.tsx
UX details:
- Add a $ prefix to the amount field for clarity (use <div className="relative"> with absolute-positioned $ span)
- First name / Last name: 25-char max (Square field limit)
- Amount: decimal input, validated server-side (no negative, min $0.01, max $50,000)
- Due date: optional, date picker with min=today
- Phone: optional
- Email: required
1.4 Create the API Route¶
File: app/api/admin/invoices/route.ts
Flow:
1. Check session (auth guard)
2. Parse + validate form (via parseInvoiceForm())
3. Call findOrCreateCustomer()
4. Call sendInvoice()
5. Return { invoiceNumber, publicUrl } on success
6. Return { error } with 503 on SquareConfigError, 502 on other errors
1.5 Local Environment Setup¶
File: .env.local (gitignored)
SQUARE_ACCESS_TOKEN=<sandbox-or-test-token>
SQUARE_LOCATION_ID=<sandbox-location-id>
SQUARE_ENVIRONMENT=sandbox
1.6 Test Locally¶
npm run dev
# Navigate to /admin/invoices
# Submit test invoice
# Verify email arrives (Square Sandbox sends real emails)
Phase 2: Live API Testing (Before Production Deploy)¶
DO THIS BEFORE DEPLOYING — the unit tests won't catch API contract errors.
2.1 Create a Standalone Test Script¶
Use a Node script (not vitest) that:
- Reads .env.local with real production credentials
- Calls the actual lib/square.ts functions (no mocks)
- Hits the real Square production API
- Creates a test invoice and publishes it
Example flow:
// Create customer
const customerId = await findOrCreateCustomer({...})
// Create invoice (will hit Square and expose missing required fields)
const result = await sendInvoice({...})
2.2 Expected Bugs (That Unit Tests Miss)¶
If you see these errors, the lib is incomplete:
| Error | Fix |
|---|---|
order_id is required |
sendInvoice must create an Order first via /v2/orders POST, then attach it via order_id in the invoice |
accepted_payment_methods is required |
Add accepted_payment_methods: { card: true } to invoice creation payload |
2.3 Cleanup¶
After confirming the flow works: - Void the test invoice from the client's Square Dashboard (status → CANCELED) - Delete the test script - Update unit tests to reflect the real 3-call flow (order → create → publish)
Phase 3: Production Deploy¶
3.1 Gather Production Credentials from Client¶
From their Square developer account: - Production Access Token (not sandbox) - Production Location ID
3.2 Update Configuration¶
File: wrangler.jsonc
"vars": {
"SQUARE_LOCATION_ID": "<production-location-id>",
"SQUARE_ENVIRONMENT": "production"
}
Set the secret in Cloudflare:
npx wrangler secret put SQUARE_ACCESS_TOKEN
# Paste: <production-access-token>
3.3 Commit and Deploy¶
git add wrangler.jsonc
git commit --author="Claude Sonnet 4.6 <noreply@anthropic.com>" -m "config(square): set production location ID"
git push
# Workers Builds will auto-deploy (if configured; otherwise: npm run deploy:cf)
3.4 Production Smoke Test¶
- Navigate to
/adminon the live site - Log in
- Go to Invoices
- Send a test invoice to your email
- Verify:
- Email arrives from Square
- Invoice shows in client's Square Dashboard
- Invoice has correct amount, description, due date
- Client cancels the invoice from their Square Dashboard (cleanup)
Common Pitfalls¶
| Pitfall | Prevention |
|---|---|
| Unit tests pass but production fails (missing fields) | Always run a real API test against production before deploying. Mocked tests are not sufficient. |
Typos in env var names (e.g., SQAURE_LOCATION_ID) |
grep for the var name across .env, wrangler.jsonc, code; use consistent SQUARE prefix everywhere |
| Credentials stored in git | .env.local and .env should be gitignored; use wrangler secret put for production |
| Invoice sent but customer doesn't receive email | Verify delivery_method is set to EMAIL in invoice creation; check client's email spam folder |
| Orders and invoices accumulate from test runs | Always void test invoices after confirmation testing |
| 503 error "Square is not configured" | Missing or empty SQUARE_ACCESS_TOKEN or SQUARE_LOCATION_ID in env |
Files Checklist¶
- [ ]
lib/square.ts— REST client with Order → Invoice flow - [ ]
lib/__tests__/square.test.ts— Unit tests (mocked fetch) - [ ]
lib/__tests__/square.live.test.ts(or standalone script) — Real API integration test (temporary, delete after) - [ ]
lib/invoices.ts— Form validation (parseInvoiceForm) - [ ]
components/admin/InvoiceForm.tsx— React form component with $ prefix on amount - [ ]
app/api/admin/invoices/route.ts— POST handler - [ ]
app/admin/invoices/page.tsx— Admin page (wraps form) - [ ]
wrangler.jsonc— Location ID in vars, comment about access token secret - [ ]
.env.local(gitignored) — Local sandbox credentials for dev
Key Differences from QuickBooks¶
| Aspect | QB Online | Square |
|---|---|---|
| Invoice creation | Direct; no order required | Order → Invoice flow |
| Payment method | OAuth + API key | Simple Bearer token |
| Email delivery | App handles sending | Square sends hosted invoice email |
| Status tracking | Complex sync | Straightforward UNPAID / PAID in Square Dashboard |
| Cancellation | App-side delete | Client cancels from Square Dashboard |
References¶
- Square Invoices API: https://developer.squareup.com/reference/square/invoices-api
- Customers API: https://developer.squareup.com/reference/square/customers-api
- Orders API: https://developer.squareup.com/reference/square/orders-api