8.0 KiB
Add @suaveplan/payment-provider-square
Package: @suaveplan/payment-provider-square
Status: PROPOSED
Type: new-package
Why
The 2026-05-17-slim-payment-providers change removed the five concrete provider classes from @suaveplan/payment-providers and committed to re-landing each in a dedicated sibling package. Square is the first of those siblings. Shipping it restores the Square Payments API surface that consumers depend on (SquareProvider, SquareConfig) without re-bundling the unrelated Stripe / PayPal / Mollie / Adyen adapters, and proves the foundation's httpRequest + composeAbortSignal + PAYMENT_PROVIDER_CODES contract works end-to-end for a sibling.
The upstream implementation (../suaveplan/packages/universal/payment-providers/src/square.ts) reaches for globalThis.crypto.randomUUID() directly and is exercised by tests that monkey-patch globalThis.fetch. Both anti-patterns are banned in genesis (universal tier purity, no mocks). This change ports the surface verbatim while wiring it onto the genesis foundation's injectable FetchLike + RandomIdFn seams.
What changes
- ADDED: package
@suaveplan/payment-provider-squareatpackages/universal/payment-provider-square/(version0.1.0). - ADDED:
SquareProvider implements PaymentProvider(class) with literalname: "square"— full Square Payments API lifecycle (createIntent,confirmIntent,captureIntent,cancelIntent,refund,getIntent). - ADDED:
SquareConfig extends ApiKeyConfigconfig interface — adds requiredlocationId: string, optionalsandbox?: boolean, optionalrandomId?: RandomIdFn, optionalapiVersion?: string. - ADDED: helpers
mapPayment,mapRefund,mapPaymentStatus,mapRefundStatuscovering every Squarestatusvalue the API documents. - ADDED:
Square-Version: <apiVersion>header on every request (defaults to2024-01-18— the value the upstream package hard-coded). - ADDED: idempotency-key generation via injectable
randomId()forCreatePayment+RefundPaymentcalls. - ADDED: 5 subpath exports (
.,./square/square,./types/config,./mapping/payment,./mapping/refund).
Out of scope
- The Square Webhooks API (HMAC signature verification) — webhook verification lives in
@suaveplan/payment(WebhookVerifier) and is provider-agnostic. The sibling package does not duplicate it. - The Square Catalog, Customers, or Orders APIs — they are not part of the
PaymentProvidercontract. - The OAuth merchant-onboarding flow — Square's OAuth flow is for delegated multi-merchant scenarios; the sibling assumes the caller already holds a tenant access token (matches the upstream implementation).
- Stripe, PayPal, Mollie, Adyen sibling packages — each ships in its own follow-up change.
Source mapping
This change derives from ../suaveplan/enterprise/payment-providers.md (the family-wide enterprise plan; Square-specific behavior is captured in the upstream square.ts + square.md + square.test.ts triple).
| Requirement | Source lines | Notes |
|---|---|---|
SquareProvider implements PaymentProvider with literal name: "square" |
src/square.ts L97-L108 |
verbatim |
| Authorization + Square-Version headers on every request | src/square.ts L192-L197 |
verbatim, version pinned to 2024-01-18 upstream — exposed via apiVersion option in genesis |
| createIntent maps amount/currency, customer, description, source, autocomplete flag | src/square.ts L110-L133 |
verbatim |
createIntent supplies an idempotency key (default = crypto.randomUUID()) |
src/square.ts L113-L118 |
extended in genesis — randomId is injectable instead of reaching for globalThis.crypto |
confirmIntent is a no-op that returns getIntent (Square auto-confirms) |
src/square.ts L135-L137 |
verbatim |
captureIntent posts to /payments/{id}/complete |
src/square.ts L139-L147 |
verbatim |
cancelIntent posts to /payments/{id}/cancel |
src/square.ts L149-L157 |
verbatim |
refund posts to /refunds with optional amount_money and reason |
src/square.ts L159-L180 |
verbatim — refund also supplies an idempotency key via randomId |
getIntent fetches /payments/{id} |
src/square.ts L182-L190 |
verbatim |
| Status mapping: APPROVED→authorized, COMPLETED→captured, CANCELED→canceled, FAILED→failed, PENDING→pending, fallback→failed | src/square.ts L38-L47 |
verbatim |
| Refund status mapping: COMPLETED→succeeded, PENDING→pending, fallback→failed | src/square.ts L49-L53 |
verbatim |
| Payment mapping handles missing card details + missing approved_money | src/square.ts L55-L81 |
verbatim |
Refund mapping reads reason → requested_by_customer when present, otherwise other |
src/square.ts L83-L95 |
verbatim |
| Sandbox base URL fallback | — | extended in genesis (see "no source line" table) |
locationId is a required Square-specific config field |
— | extended in genesis (see "no source line" table) |
Only PaymentProviderError is raised |
src/square.ts (implicit via httpRequest) |
inherited from the genesis foundation contract |
Requirements with no source line
| Requirement | Justification |
|---|---|
SquareConfig extends ApiKeyConfig adds locationId: string |
Square's CreatePayment requires a location_id for multi-location merchants. The upstream package omitted it (single-location bias); genesis adds it as a required field because shipping without it forces every consumer to hand-roll a wrapper. |
sandbox?: boolean flips the default base URL to https://connect.squareupsandbox.com/v2 |
Square's sandbox is a first-class environment with its own host. Exposing a boolean is friendlier than asking callers to flip baseUrl themselves. |
apiVersion?: string (default 2024-01-18) |
Square requires a Square-Version header on every call. The upstream package hard-coded the value; genesis exposes it so consumers can pin a newer version without forking. |
randomId?: RandomIdFn injectable seam |
Universal-tier purity (CLAUDE.md). Direct crypto.randomUUID() calls fail on runtimes without a global crypto, and they prevent deterministic tests. |
Universal-tier purity (no node:crypto, node:fs, etc.) |
Tier requirement from CLAUDE.md — enforced by sibling tests/dep-graph.test.ts. |
100% test coverage (istanbul) + sibling .md per implementation file |
Genesis-wide policy (CLAUDE.md). |
Verification approach
Every requirement in specs/payment-provider-square/spec.md carries a **Verified by:** line pointing at concrete it(...) titles in:
src/square/square.test.ts— full provider lifecycle (createIntent / confirmIntent / captureIntent / cancelIntent / refund / getIntent), header pinning, idempotency-key injection, sandbox URL flip.src/mapping/payment.test.ts—mapPaymentStatusexhaustive coverage andmapPaymentshape mapping including missing-card-details and approved-money branches.src/mapping/refund.test.ts—mapRefundStatusexhaustive coverage andmapRefundreason fallback.src/types/config.test.ts— type-only assertions pinningSquareConfig extends ApiKeyConfigand thelocationIdrequired-ness.tests/dep-graph.test.ts— universal-tier purity (nonode:/bun:/fs/path/cryptoimports insrc/).
All tests use real FetchLike recorders (closures over a calls array returning real Response objects) and a deterministic injected randomId — no vi.mock, no globalThis.fetch patching, no monkey-patching.
Migration impact
None — additive only. The package is new; no consumer in the genesis monorepo currently imports SquareProvider. Upstream consumers were already directed to switch their import path by 2026-05-17-slim-payment-providers (see that change's ## Migration section and the foundation README.md).
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/payment-provider-square to enumerate. No dependents at landing time — the package is the first in the payment-provider-* sibling family.
- No dependents (verified by
bunx turbo ls --filter=...@suaveplan/payment-provider-square)