first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
# SquareProvider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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 <apiKey>` 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<PaymentIntent>`
POSTs `<baseUrl>/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<PaymentIntent>`
No-op for Square (payments are auto-confirmed). Delegates to
`getIntent(intentId)`.
#### `captureIntent(intentId: string): Promise<PaymentIntent>`
POSTs `<baseUrl>/payments/<intentId>/complete` with no body.
#### `cancelIntent(intentId: string): Promise<PaymentIntent>`
POSTs `<baseUrl>/payments/<intentId>/cancel` with no body.
#### `refund(input: RefundInput): Promise<Refund>`
POSTs `<baseUrl>/refunds` with `payment_id`, `idempotency_key`
(falls back to `randomId()`), optional `amount_money`, and optional
`reason`. Returns the mapped `Refund`.
#### `getIntent(intentId: string): Promise<PaymentIntent>`
GETs `<baseUrl>/payments/<intentId>`.
### 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.