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,126 @@
# Square payment mapping
## Purpose
Translates between Square's `Payment` envelope (as returned by
`POST /v2/payments`, `POST /v2/payments/{id}/complete`,
`POST /v2/payments/{id}/cancel`, and `GET /v2/payments/{id}`) and
`@suaveplan/payment`'s `PaymentIntent` shape. Square's status values
are upper-snake-case (`COMPLETED`, `APPROVED`, ...) while uses
the lower-case `PaymentStatus` union; the mapping pins this contract
in one place so the provider class stays focused on transport.
The mapper also captures the two implicit branches Square encodes via
optional fields rather than dedicated states: `approved_money`
present ↔ manual capture, `card_details` present ↔ card-shaped
`PaymentMethod`.
## Features
- `mapPaymentStatus(squareStatus)` — exhaustive five-status map with a
defensive `"failed"` fallback for forward-rolled API versions.
- `mapPayment(p)` — full envelope-to-`PaymentIntent` shape including
`capturedAmount` derivation (`p.total_money` on `COMPLETED`, `0n`
otherwise), `captureMethod` derivation (`approved_money` ↔
`"manual"`), and optional `PaymentMethod` derivation from
`card_details`.
- `SquareCard` / `SquareMoney` / `SquarePaymentDto` — typed views of
the Square API envelope that downstream code can rely on instead of
threading `unknown` through call sites.
- Card-brand normalisation — Square returns brands upper-case; the
mapper lower-cases and validates against the `CardBrand`
union, falling back to `"unknown"` for non-canonical brands
(Square gift cards, JCB sub-brands, etc.).
## Usage: Basic Example
```ts
import {
mapPayment,
mapPaymentStatus,
} from "@suaveplan/payment-provider-square";
mapPaymentStatus("COMPLETED"); // "captured"
const intent = mapPayment({
id: "sq_pay_123",
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",
});
console.log(intent.status); // "captured"
console.log(intent.capturedAmount.amount); // 5000n
```
## Usage: Advanced Example
Map an authorized-but-not-captured payment with card metadata:
```ts
import { mapPayment } from "@suaveplan/payment-provider-square";
const intent = mapPayment({
id: "sq_pay_456",
status: "APPROVED",
amount_money: { amount: 9999, currency: "EUR" },
total_money: { amount: 9999, currency: "EUR" },
approved_money: { amount: 9999, currency: "EUR" },
customer_id: "cust_42",
note: "Pre-authorisation for hold",
card_details: { card: { card_brand: "MASTERCARD", last_4: "4242" } },
created_at: "2026-02-01T10:00:00Z",
updated_at: "2026-02-01T10:00:00Z",
});
console.log(intent.captureMethod); // "manual"
console.log(intent.status); // "authorized"
console.log(intent.capturedAmount.amount); // 0n
console.log(intent.paymentMethod);
// { id: "sq_card_sq_pay_456", type: "card", brand: "mastercard", last4: "4242" }
```
## API Reference
### `mapPaymentStatus(squareStatus: string): PaymentStatus`
Maps Square status → suaveplan status. Known mappings:
| Square | suaveplan |
|---|---|
| `APPROVED` | `"authorized"` |
| `COMPLETED` | `"captured"` |
| `CANCELED` | `"canceled"` |
| `FAILED` | `"failed"` |
| `PENDING` | `"pending"` |
| _other_ | `"failed"` |
### `mapPayment(p: SquarePaymentDto): PaymentIntent`
Shape the envelope. `capturedAmount.amount` is
`BigInt(p.total_money.amount)` when `p.status === "COMPLETED"`,
otherwise `0n`. `captureMethod` is `"manual"` when `p.approved_money`
is set. `paymentMethod` is set when `p.card_details` is present.
`refundedAmount.amount` is `0n` (refund totals are tracked by
`PaymentEngine`, not derived here). `refunds` is an empty array.
### Types
- `SquareMoney` — `{ amount: number; currency: string }`.
- `SquareCard` — `{ card_brand: string; last_4: string }`.
- `SquarePaymentDto` — the subset of Square's `Payment` envelope this
package consumes.
## Implementation Notes
- The status map is frozen at module init so accidental mutation
throws in strict mode.
- The card-brand normaliser is intentionally conservative: any brand
Square introduces that does not match the `CardBrand` union
falls back to `"unknown"` so downstream code never receives an
invalid discriminant.
- `paymentMethod.id` is synthesised as `sq_card_<payment-id>` because
Square does not expose a stable card-fingerprint on the inline
`card_details.card` object. Consumers needing a true stable card id
should hydrate it from Square's Cards API and overlay it.