Skip to content

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

  1. Navigate to /admin on the live site
  2. Log in
  3. Go to Invoices
  4. Send a test invoice to your email
  5. Verify:
  6. Email arrives from Square
  7. Invoice shows in client's Square Dashboard
  8. Invoice has correct amount, description, due date
  9. 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