# SquareProvider _(no submodules)_ ## Purpose `SquareProvider` is the Square Payments API adapter for `@suaveplan/payment`. It implements the structural `PaymentProvider` contract with `name: "square"` (narrowed at the type level) and routes every outbound HTTP call through the `@suaveplan/payment-providers` foundation's `httpRequest`, which handles JSON serialisation, the per-call timeout signal, external `AbortSignal` composition, and typed `PaymentProviderError` raising. The provider preserves the upstream `square.ts` surface verbatim — the six lifecycle methods (`createIntent`, `confirmIntent`, `captureIntent`, `cancelIntent`, `refund`, `getIntent`), the canonical request bodies, and the status mapping — while wiring the previously-hard-coded `globalThis.crypto.randomUUID()` call onto the foundation's injectable `RandomIdFn` seam. Tests pass a deterministic counter instead of monkey-patching `globalThis.fetch`. ## Features - Implements `PaymentProvider` with literal `name: "square"`. - Sends `Authorization: Bearer ` and `Square-Version` on every request (`Square-Version` defaults to `2024-01-18`; override via `SquareConfig.apiVersion`). - Sandbox toggle (`sandbox: true`) flips the default base URL to `https://connect.squareupsandbox.com/v2`; an explicit `baseUrl` always wins. - Idempotency keys filled from the injected `randomId` seam when the caller omits one — deterministic in tests, `crypto.randomUUID()` by default in production. - Multi-location merchants supported via the required `SquareConfig.locationId` field, sent as `location_id` on every `CreatePayment` body. - All errors raised as `PaymentProviderError` (foundation contract); the package itself never throws `Error` instances directly. - Universal — no `node:`, `bun:`, `fs`, `path`, `crypto` imports. ## Usage: Basic Example ```ts import { SquareProvider } from "@suaveplan/payment-provider-square"; const square = new SquareProvider({ apiKey: process.env.SQUARE_ACCESS_TOKEN ?? "", locationId: process.env.SQUARE_LOCATION_ID ?? "", }); const intent = await square.createIntent({ amount: { amount: 5000n, currency: "USD" }, description: "Order #123", }); const captured = await square.captureIntent(intent.id); console.log(captured.status); // "captured" ``` ## Usage: Advanced Example Inject deterministic seams (FetchLike + RandomIdFn) for testing, target the sandbox, pin the API version, and compose with an external `AbortSignal`: ```ts import type { FetchLike } from "@suaveplan/payment-providers"; import { SquareProvider } from "@suaveplan/payment-provider-square"; const controller = new AbortController(); const fakeFetch: FetchLike = async (url, init) => { return new Response( JSON.stringify({ payment: { id: "sq_pay_1", status: "COMPLETED", amount_money: { amount: 5000, currency: "USD" }, total_money: { amount: 5000, currency: "USD" }, created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-01T00:00:00Z", }, }), { status: 200 } ); }; const square = new SquareProvider({ apiKey: "sandbox-token", locationId: "L1", sandbox: true, apiVersion: "2024-09-19", fetch: fakeFetch, signal: controller.signal, randomId: () => "deterministic-idempotency-key", timeoutMs: 5_000, }); const intent = await square.createIntent({ amount: { amount: 5000n, currency: "USD" }, }); ``` ## API Reference ### `class SquareProvider implements PaymentProvider` #### `constructor(config: SquareConfig)` Accepts a `SquareConfig` (see `../types/config.md`). Required: `apiKey`, `locationId`. Optional: `baseUrl`, `sandbox`, `apiVersion`, `randomId`, `fetch`, `signal`, `timeoutMs`, `webhookSecret`. #### `readonly name: "square"` Literal — narrows at the type level for discriminated unions. #### `createIntent(input: CreatePaymentIntentInput): Promise` POSTs `/payments` with `amount_money`, `idempotency_key` (falls back to `randomId()`), `autocomplete` (`true` unless `captureMethod === "manual"`), `location_id`, and the optional `customer_id`, `note`, `source_id` fields. Returns the mapped `PaymentIntent`. #### `confirmIntent(intentId: string): Promise` No-op for Square (payments are auto-confirmed). Delegates to `getIntent(intentId)`. #### `captureIntent(intentId: string): Promise` POSTs `/payments//complete` with no body. #### `cancelIntent(intentId: string): Promise` POSTs `/payments//cancel` with no body. #### `refund(input: RefundInput): Promise` POSTs `/refunds` with `payment_id`, `idempotency_key` (falls back to `randomId()`), optional `amount_money`, and optional `reason`. Returns the mapped `Refund`. #### `getIntent(intentId: string): Promise` GETs `/payments/`. ### Throws Every method throws `PaymentProviderError` on HTTP failure, timeout, or JSON parse failure (raised by the foundation's `httpRequest`). `err.provider` is always `"square"`; `err.httpStatus` is set when the failure originated from a non-ok response. ## Implementation Notes - The provider stores its `FetchLike` / `AbortSignal` / `timeoutMs` / `randomId` once at construction and passes them through to every `httpRequest` call so behaviour is deterministic across method invocations. - `location_id` is sent on `CreatePayment` only — Square does not require it on `CompletePayment` / `CancelPayment` / `Refund` calls. - The default `randomId` is a closure that calls `crypto.randomUUID()` at call time. Runtimes without a global `crypto` MUST inject their own `RandomIdFn`. - The class does not duplicate webhook verification logic; Square webhook signatures are verified by `@suaveplan/payment`'s `WebhookVerifier`, which is provider-agnostic. - All formatting flows through the foundation: caller-supplied `Content-Type` / `Accept` are preserved by `httpRequest`'s shallow header merge, but the Square-specific `Authorization` and `Square-Version` headers always win on collision.