4.5 KiB
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-PaymentIntentshape includingcapturedAmountderivation (p.total_moneyonCOMPLETED,0notherwise),captureMethodderivation (approved_money↔"manual"), and optionalPaymentMethodderivation fromcard_details.SquareCard/SquareMoney/SquarePaymentDto— typed views of the Square API envelope that downstream code can rely on instead of threadingunknownthrough call sites.- Card-brand normalisation — Square returns brands upper-case; the
mapper lower-cases and validates against the
CardBrandunion, falling back to"unknown"for non-canonical brands (Square gift cards, JCB sub-brands, etc.).
Usage: Basic Example
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:
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'sPaymentenvelope 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
CardBrandunion falls back to"unknown"so downstream code never receives an invalid discriminant. paymentMethod.idis synthesised assq_card_<payment-id>because Square does not expose a stable card-fingerprint on the inlinecard_details.cardobject. Consumers needing a true stable card id should hydrate it from Square's Cards API and overlay it.