85 lines
8.0 KiB
Markdown
85 lines
8.0 KiB
Markdown
# 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`)
|