Files
suaveplan/openspec/archive/2026-05-17-add-payment-provider-square/proposal.md
2026-05-27 23:29:53 +01:00

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-square at packages/universal/payment-provider-square/ (version 0.1.0).
  • ADDED: SquareProvider implements PaymentProvider (class) with literal name: "square" — full Square Payments API lifecycle (createIntent, confirmIntent, captureIntent, cancelIntent, refund, getIntent).
  • ADDED: SquareConfig extends ApiKeyConfig config interface — adds required locationId: string, optional sandbox?: boolean, optional randomId?: RandomIdFn, optional apiVersion?: string.
  • ADDED: helpers mapPayment, mapRefund, mapPaymentStatus, mapRefundStatus covering every Square status value the API documents.
  • ADDED: Square-Version: <apiVersion> header on every request (defaults to 2024-01-18 — the value the upstream package hard-coded).
  • ADDED: idempotency-key generation via injectable randomId() for CreatePayment + RefundPayment calls.
  • 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 PaymentProvider contract.
  • 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 — mapPaymentStatus exhaustive coverage and mapPayment shape mapping including missing-card-details and approved-money branches.
  • src/mapping/refund.test.ts — mapRefundStatus exhaustive coverage and mapRefund reason fallback.
  • src/types/config.test.ts — type-only assertions pinning SquareConfig extends ApiKeyConfig and the locationId required-ness.
  • tests/dep-graph.test.ts — universal-tier purity (no node: / bun: / fs / path / crypto imports in src/).

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)