# 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: ` 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`)