@hanzo/pay 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/{chunk-64AUOMEL.cjs → chunk-AJAETCMO.cjs} +15 -15
- package/dist/chunk-AJAETCMO.cjs.map +1 -0
- package/dist/{chunk-DXK2QDTX.js → chunk-IYAU5BMM.js} +15 -15
- package/dist/chunk-IYAU5BMM.js.map +1 -0
- package/dist/index.cjs +9 -9
- package/dist/index.js +1 -1
- package/dist/native/index.cjs +11 -10
- package/dist/native/index.cjs.map +1 -1
- package/dist/native/index.js +5 -4
- package/dist/native/index.js.map +1 -1
- package/dist/palette.d.ts +12 -3
- package/dist/web/index.cjs +25 -13
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.js +23 -11
- package/dist/web/index.js.map +1 -1
- package/dist/web/style.d.ts +20 -5
- package/package.json +1 -1
- package/dist/chunk-64AUOMEL.cjs.map +0 -1
- package/dist/chunk-DXK2QDTX.js.map +0 -1
package/dist/palette.d.ts
CHANGED
|
@@ -3,10 +3,19 @@ export type Theme = 'dark' | 'light';
|
|
|
3
3
|
export interface Palette {
|
|
4
4
|
/** The field's own ground. */
|
|
5
5
|
field: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* The hairline — at rest AND on focus. The box deliberately does not move when
|
|
8
|
+
* the buyer arrives in it: `ring` is the one thing that says so, and a border
|
|
9
|
+
* that changed colour underneath it would be a second indicator for the same
|
|
10
|
+
* event. See `ring`.
|
|
11
|
+
*/
|
|
7
12
|
border: string;
|
|
8
|
-
/**
|
|
9
|
-
|
|
13
|
+
/**
|
|
14
|
+
* THE focus indicator, drawn AROUND the field rather than on it (`pin()`), so
|
|
15
|
+
* it is the same ring the host draws on its own inputs — one look for every
|
|
16
|
+
* control on the page. Deliberately >= 3:1 on `field` (WCAG 2.4.11).
|
|
17
|
+
*/
|
|
18
|
+
ring: string;
|
|
10
19
|
/** Typed characters. */
|
|
11
20
|
text: string;
|
|
12
21
|
/** Placeholder text. */
|
package/dist/web/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkAJAETCMO_cjs = require('../chunk-AJAETCMO.cjs');
|
|
4
4
|
|
|
5
5
|
// src/web/sdk.ts
|
|
6
6
|
var PRODUCTION = "https://web.squarecdn.com/v1/square.js";
|
|
@@ -34,6 +34,9 @@ function rules(style2, selector) {
|
|
|
34
34
|
return style2[selector];
|
|
35
35
|
}
|
|
36
36
|
var RADIUS = "6px";
|
|
37
|
+
var SIZE = "13px";
|
|
38
|
+
var RING = "2px";
|
|
39
|
+
var RING_GAP = "2px";
|
|
37
40
|
function style(p) {
|
|
38
41
|
return {
|
|
39
42
|
// Borders and radius ONLY. Square's allowlist for this selector is
|
|
@@ -45,12 +48,18 @@ function style(p) {
|
|
|
45
48
|
borderRadius: RADIUS,
|
|
46
49
|
borderWidth: "1px"
|
|
47
50
|
},
|
|
48
|
-
|
|
51
|
+
// THE HAIRLINE DOES NOT MOVE ON FOCUS, and that is the point. Square spends
|
|
52
|
+
// this value twice — on the container's border AND on a `::before` ring at
|
|
53
|
+
// `inset: -1px` — so any colour but the resting one puts a second ring
|
|
54
|
+
// immediately inside the one `pin()` draws. Two rings for one event is the
|
|
55
|
+
// exact defect this pass removes from the address fields; the card strip
|
|
56
|
+
// must not reintroduce it one row lower.
|
|
57
|
+
".input-container.is-focus": { borderColor: p.border },
|
|
49
58
|
".input-container.is-error": { borderColor: p.error },
|
|
50
59
|
input: {
|
|
51
60
|
backgroundColor: p.field,
|
|
52
61
|
color: p.text,
|
|
53
|
-
fontSize:
|
|
62
|
+
fontSize: SIZE
|
|
54
63
|
},
|
|
55
64
|
// 16px on a small screen, because iOS Safari ZOOMS the page when a field
|
|
56
65
|
// under 16px takes focus — on a card form that throws the layout sideways
|
|
@@ -68,21 +77,23 @@ function style(p) {
|
|
|
68
77
|
".message-icon.is-error": { color: p.error }
|
|
69
78
|
};
|
|
70
79
|
}
|
|
71
|
-
function pin(doc) {
|
|
80
|
+
function pin(p, doc) {
|
|
72
81
|
const d = doc ?? (typeof document === "undefined" ? void 0 : document);
|
|
73
82
|
if (!d) return;
|
|
74
83
|
const MARK = "hanzo-pay-scheme";
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
let el = d.getElementById(MARK);
|
|
85
|
+
if (!el) {
|
|
86
|
+
el = d.createElement("style");
|
|
87
|
+
el.id = MARK;
|
|
88
|
+
d.head.appendChild(el);
|
|
89
|
+
}
|
|
90
|
+
el.textContent = `.sq-card-iframe-container{color-scheme:auto}.sq-card-wrapper.sq-focus .sq-card-iframe-container{outline:${RING} solid ${p.ring};outline-offset:${RING_GAP}}`;
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
// src/web/terminal.ts
|
|
83
94
|
var DRAWN = ["card", "gift", "google_pay", "cash_app"];
|
|
84
95
|
function token(method, r) {
|
|
85
|
-
if (String(r.status).toUpperCase() === "CANCEL") throw new
|
|
96
|
+
if (String(r.status).toUpperCase() === "CANCEL") throw new chunkAJAETCMO_cjs.Cancelled(method);
|
|
86
97
|
if (r.status !== "OK" || !r.token) {
|
|
87
98
|
throw new Error(r.errors?.[0]?.message ?? `${method} could not be completed`);
|
|
88
99
|
}
|
|
@@ -127,7 +138,7 @@ var Web = class {
|
|
|
127
138
|
*/
|
|
128
139
|
async offers(tender) {
|
|
129
140
|
const out = [];
|
|
130
|
-
for (const method of
|
|
141
|
+
for (const method of chunkAJAETCMO_cjs.REACH.web) {
|
|
131
142
|
try {
|
|
132
143
|
this.built.set(method, await this.build(method, tender));
|
|
133
144
|
out.push(method);
|
|
@@ -205,14 +216,15 @@ async function terminal(config) {
|
|
|
205
216
|
if (!config.applicationId || !config.locationId) {
|
|
206
217
|
throw new Error("Square is not configured for this deployment");
|
|
207
218
|
}
|
|
208
|
-
|
|
219
|
+
const palette = config.palette ?? chunkAJAETCMO_cjs.PALETTE.dark;
|
|
220
|
+
pin(palette);
|
|
209
221
|
const src = (config.environment ?? "production").toLowerCase() === "sandbox" ? SANDBOX : PRODUCTION;
|
|
210
222
|
await load(src);
|
|
211
223
|
if (typeof window === "undefined" || !window.Square) {
|
|
212
224
|
throw new Error("Square SDK failed to load");
|
|
213
225
|
}
|
|
214
226
|
const payments = await window.Square.payments(config.applicationId, config.locationId);
|
|
215
|
-
return new Web(payments,
|
|
227
|
+
return new Web(payments, palette);
|
|
216
228
|
}
|
|
217
229
|
|
|
218
230
|
exports.PRODUCTION = PRODUCTION;
|
package/dist/web/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/web/sdk.ts","../../src/web/style.ts","../../src/web/terminal.ts"],"names":["style","Cancelled","REACH","PALETTE"],"mappings":"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","file":"index.cjs","sourcesContent":["// The Square Web Payments SDK, as much of it as we actually call, typed.\n//\n// Square ships no types for the global it installs, so this is the contract we\n// hold it to. Every member below is exercised by this package; nothing is\n// declared speculatively.\n\n/** What `tokenize()` resolves with. `status` is `'OK'` | `'CANCEL'` | an error. */\nexport interface Result {\n status: string\n token?: string\n details?: {\n card?: { brand?: string; last4?: string; expMonth?: number; expYear?: number }\n }\n errors?: Array<{ message: string }>\n}\n\nexport interface Tokenizer {\n tokenize(options?: Record<string, unknown>): Promise<Result>\n /**\n * Cash App Pay and ACH deliver their token on an `ontokenization` EVENT rather\n * than from tokenize()'s return — the buyer leaves the page (for Cash App, or\n * for their bank's login) and comes back. A caller that only reads the return\n * value gets nothing from either, silently, on a payment that succeeded.\n */\n addEventListener?(event: string, handler: (e: unknown) => void): void\n /**\n * Optional because a WALLET may draw itself (Apple Pay does) rather than\n * attach. The second argument is Cash App Pay's button options\n * (`{ shape, width }`); every other rail ignores it.\n */\n attach?(selector: string, options?: Record<string, unknown>): Promise<void>\n destroy?(): Promise<void>\n}\n\n/**\n * The CARD element always attaches and always destroys — it owns an iframe. Kept\n * distinct from the wallet tokenizer so the card path is not forced to\n * optional-chain calls that can never be absent.\n */\nexport interface Element extends Tokenizer {\n attach(selector: string): Promise<void>\n destroy(): Promise<void>\n}\n\nexport interface Request {\n countryCode: string\n currencyCode: string\n total: { amount: string; label: string }\n}\n\nexport interface Payments {\n card(options?: Record<string, unknown>): Promise<Element>\n /**\n * A Square gift card. Its own constructor and its own container — a gift card\n * is a balance instrument, not a credit line, so Square keeps the two elements\n * separate rather than switching one on a brand.\n *\n * It attaches and tokenizes exactly like a card, with one difference that\n * matters to a caller: its `tokenize()` takes NO verification details. SCA\n * covers cards, and a gift card has no issuer to challenge.\n */\n giftCard(options?: Record<string, unknown>): Promise<Element>\n paymentRequest(req: Request): unknown\n applePay(req: unknown): Promise<Tokenizer>\n googlePay(req: unknown): Promise<Tokenizer>\n cashAppPay(req: unknown, opts: Record<string, unknown>): Promise<Tokenizer>\n /**\n * ACH bank transfer. Square runs PLAID'S instant bank authentication itself,\n * so this needs no Plaid account of ours — which is why the rail sat marked\n * \"coming soon\" for want of an integration that was already included. US-only.\n *\n * THE ARGUMENT IS OPTIONAL BECAUSE SQUARE ACCEPTS TWO SHAPES, and a type that\n * admitted only one would reject working code. The redirect form\n * (`ach({ redirectURI, transactionId })`, what this package calls and what\n * hanzoai/pay has run in production) sends the buyer to their bank and back;\n * Square's own quickstart instead calls `ach()` bare and passes\n * `{ accountHolderName, intent, amount, currency }` to `tokenize()`. Both\n * deliver the token the same way — on the event, never from the return.\n */\n ach(opts?: { redirectURI?: string; transactionId?: string }): Promise<Tokenizer>\n}\n\ninterface SDK {\n payments(appId: string, locationId: string): Promise<Payments>\n}\n\ndeclare global {\n interface Window {\n Square?: SDK\n }\n}\n\nexport const PRODUCTION = 'https://web.squarecdn.com/v1/square.js'\nexport const SANDBOX = 'https://sandbox.web.squarecdn.com/v1/square.js'\n\nlet pending: Promise<void> | null = null\n\n/** Load the SDK once per page, whichever surface asks first. */\nexport function load(src: string): Promise<void> {\n if (pending) return pending\n if (typeof window !== 'undefined' && window.Square) return (pending = Promise.resolve())\n pending = new Promise((resolve, reject) => {\n const existing = document.querySelector(`script[src=\"${src}\"]`)\n if (existing) {\n existing.addEventListener('load', () => resolve())\n existing.addEventListener('error', () => reject(new Error('Square SDK failed to load')))\n return\n }\n const s = document.createElement('script')\n s.src = src\n s.async = true\n s.onload = () => resolve()\n s.onerror = () => reject(new Error('Square SDK failed to load'))\n document.head.appendChild(s)\n })\n return pending\n}\n\n/** Drop the memoized load. For tests only — a page loads the SDK once. */\nexport function forget(): void {\n pending = null\n}\n","// A `Palette` rendered as the style object `payments.card({ style })` accepts.\n//\n// Measured against the live SDK, not read off a doc page. Where a comment says\n// \"confirmed\", it was driven and inspected.\n\nimport type { Palette } from '../palette'\n\n/** A selector's CSS property bag. */\nexport type Rules = Record<string, string>\n\n/**\n * What `payments.card({ style })` accepts: selector -> properties, plus `@media`\n * keys whose value is a nested block of the same. Square resolves the media query\n * INSIDE the iframe, which is the only way in — the frame cannot see the page's\n * own breakpoints.\n */\nexport type Style = Record<string, Rules | Record<string, Rules>>\n\n/**\n * One selector's properties.\n *\n * A `@media` key holds a nested block, so the map's value type is a union and\n * every read of a plain selector would otherwise need its own cast. This is that\n * cast, written once, where the shape is known.\n */\nexport function rules(style: Style, selector: string): Rules {\n return style[selector] as Rules\n}\n\n/** The input/button radius of @hanzo/design's ramp (--radius-sm), in the px Square expects. */\nconst RADIUS = '6px'\n\n/**\n * The style object for a card element painted in `p`.\n *\n * THE GROUND AND THE INK ARE ONE PALETTE, and that is the whole point. The field\n * shipped as `input.backgroundColor: 'transparent'` beside `input.color:\n * '#fafafa'` — so Square painted near-white digits onto its own default white\n * iframe and the customer's card number was invisible as they typed it. Two\n * properties, set in two places, describing one surface. Reading them off one\n * `Palette` is what stops them disagreeing again; `legible()` proves they have not.\n *\n * `input.backgroundColor` IS the supported way in, and it is not a workaround.\n * Square maps that one property onto the container behind the iframe rather than\n * onto the input:\n *\n * selectorPropertyMappings[input] = [{ property: 'backgroundColor',\n * toSelectors: ['#<id>.sq-card-wrapper .sq-card-iframe-container'] }]\n *\n * Measured, not read: driving build 1.84.0 with the object below and inspecting\n * the result gives `.sq-card-iframe-container { background-color: #0a0a0a }` in\n * the parent document, with `<body>`, `<html>` and all four inputs inside the\n * cross-origin frame computing to `rgba(0,0,0,0)`. The frame is transparent by\n * design and the container is the surface — so painting the container IS painting\n * the field.\n *\n * AND IT IS STILL NOT ENOUGH ON ITS OWN — see `pin()` below, which this package\n * applies for you. The declaration above lands and is then overpainted, so a\n * style object shipped without that rule is accepted and silently ignored. That\n * gap was misread once as an SDK bug to wait out; it was ours.\n *\n * `.input-container { backgroundColor }` is ALSO accepted on 1.84.0 (it throws\n * nothing). It is still not set: it would be a second way to paint the one\n * surface `input.backgroundColor` already paints, and the two could then\n * disagree. One property, one surface.\n *\n * `fontFamily` is deliberately absent: Square validates it against its own\n * loadable list and throws on both CSS-wide stacks and arbitrary names, which\n * blocks the iframe from attaching at all. Its default is a system sans, which is\n * what --font-sans resolves to anyway.\n */\nexport function style(p: Palette): Style {\n return {\n // Borders and radius ONLY. Square's allowlist for this selector is\n // borderColor/borderRadius/borderWidth; anything else throws \"Invalid style\n // property\", and Square rejects the WHOLE style object when it does — so one\n // stray property leaves the card unstyled and white.\n '.input-container': {\n borderColor: p.border,\n borderRadius: RADIUS,\n borderWidth: '1px',\n },\n '.input-container.is-focus': { borderColor: p.borderFocus },\n '.input-container.is-error': { borderColor: p.error },\n input: {\n backgroundColor: p.field,\n color: p.text,\n fontSize: '14px',\n },\n // 16px on a small screen, because iOS Safari ZOOMS the page when a field\n // under 16px takes focus — on a card form that throws the layout sideways\n // mid-number and there is no way to zoom back without losing the caret.\n // Square's own dark-mode example carries this media query for the same\n // reason; it is the one place the field may disagree with the page's ramp.\n '@media screen and (max-width: 600px)': {\n input: { fontSize: '16px' },\n },\n 'input::placeholder': { color: p.placeholder },\n 'input.is-error': { color: p.error },\n '.message-text': { color: p.placeholder },\n '.message-text.is-error': { color: p.error },\n '.message-icon': { color: p.placeholder },\n '.message-icon.is-error': { color: p.error },\n }\n}\n\n/**\n * The rule without which everything above is decoration.\n *\n * Square's `input.backgroundColor` is accepted and then IGNORED: the field\n * renders white on a black checkout no matter what is passed. The cause is not\n * Square's, and it is one property. A page that sets `color-scheme` on\n * `<html>` — which is the correct thing to do, and what every themed app does so\n * the UA paints scrollbars and form controls to match — leaks it INTO the\n * cross-origin iframe, because `color-scheme` inherits. Inside the frame the UA\n * then paints form controls on its own scheme background, over anything the SDK\n * declared. Pinning the container back to `auto` lets our ground through.\n *\n * (A Square forum user reported the same thing after two years of the docs' own\n * recipe not working. Same fix, arrived at from the other end.)\n *\n * THIS IS APPLIED FOR YOU, by `terminal()`, before any card can attach — it must\n * be in the stylesheet BEFORE the element mounts, because the container is styled\n * as the card attaches, and a rule added afterwards is a rule that arrived too\n * late. It is not left to a host to remember, and it is not a stylesheet a host\n * can forget to import: this package produced the style object, so this package\n * owes the one rule that makes it mean anything.\n *\n * Idempotent, and a no-op without a document. A host that already ships the rule\n * (hanzoai/pay does, in `index.css`) gets an identical declaration — same\n * property, same value — so there is nothing for the two to disagree about.\n */\nexport function pin(doc?: Document): void {\n const d = doc ?? (typeof document === 'undefined' ? undefined : document)\n if (!d) return\n const MARK = 'hanzo-pay-scheme'\n if (d.getElementById(MARK)) return\n const el = d.createElement('style')\n el.id = MARK\n el.textContent = '.sq-card-iframe-container{color-scheme:auto}'\n d.head.appendChild(el)\n}\n","// The Square Web Payments SDK behind the one `Terminal` interface.\n//\n// Every rail here mints the SAME single-use token, bound for the same server\n// call — so none of them needed server work beyond the one that already existed.\n// What they do NOT share is how the token arrives, and treating them as one shape\n// is a silent failure in three of the six:\n//\n// card, gift attach() into a container, then tokenize() returns the token.\n// google_pay attach() into a container Square draws its button in, then\n// tokenize() from our click.\n// apple_pay NO attach() — Square's docs are explicit. The host renders the\n// button (Apple's own CSS appearance), and tokenize() must be\n// called IMMEDIATELY in the click handler.\n// cash_app attach(), and then the token arrives on an `ontokenization`\n// EVENT after the buyer approves in the app or by QR. Calling\n// tokenize() ourselves does nothing and the payment never lands.\n// ach NO attach() — the flow IS tokenize(). The token likewise arrives\n// on `ontokenization`, because the buyer leaves for their bank.\n\nimport {\n type Detail,\n type Method,\n type Tender,\n type Terminal,\n type Token,\n Cancelled,\n REACH,\n} from '../terminal'\nimport { type Palette, PALETTE } from '../palette'\nimport { load, PRODUCTION, SANDBOX, type Payments, type Result, type Tokenizer } from './sdk'\nimport { pin, style } from './style'\n\n/** The org's PUBLIC Square config, exactly as commerce publishes it. */\nexport interface Config {\n applicationId: string\n locationId: string\n /** `'sandbox'` tokenizes against Square's sandbox. Anything else is production. */\n environment?: string\n /** The card field's colours. Defaults to @hanzo/design's dark table. */\n palette?: Palette\n}\n\n/** Rails that draw themselves into a container the host positions. */\nconst DRAWN: readonly Method[] = ['card', 'gift', 'google_pay', 'cash_app']\n\nfunction token(method: Method, r: Result): Token {\n // CANCEL is the buyer changing their mind. It is not a failure and must never\n // be shown as one.\n if (String(r.status).toUpperCase() === 'CANCEL') throw new Cancelled(method)\n if (r.status !== 'OK' || !r.token) {\n throw new Error(r.errors?.[0]?.message ?? `${method} could not be completed`)\n }\n return { value: r.token, method, card: r.details?.card }\n}\n\n/**\n * The token that arrives on an EVENT rather than from a return — Cash App Pay\n * and ACH, for the same underlying reason: the buyer leaves the page.\n *\n * One listener per collect(), removed when it settles. Square's own quickstart\n * registers a fresh listener on every submission and never removes one, so a\n * buyer who retries gets their token delivered to every previous attempt too.\n */\nfunction awaited(method: Method, t: Tokenizer): Promise<Token> {\n return new Promise<Token>((resolve, reject) => {\n t.addEventListener?.('ontokenization', (e: unknown) => {\n const detail = (e as { detail?: { tokenResult?: Result; error?: unknown } })?.detail\n if (detail?.error) {\n reject(new Error(`${method} could not be completed`))\n return\n }\n if (!detail?.tokenResult) return\n try {\n resolve(token(method, detail.tokenResult))\n } catch (err) {\n reject(err)\n }\n })\n })\n}\n\nclass Web implements Terminal {\n private readonly built = new Map<Method, Tokenizer>()\n private readonly palette: Palette\n\n constructor(\n private readonly payments: Payments,\n palette: Palette,\n ) {\n this.palette = palette\n }\n\n /**\n * Which rails can REALLY pay here, asked of the SDK one at a time.\n *\n * Building the rail IS the probe, and it is the only answer that accounts for\n * the browser, the device, the buyer's saved cards AND whether the merchant\n * account has the rail switched on. A rejection is a plain \"not here\", not an\n * error worth showing anyone.\n *\n * The built objects are KEPT, and that is load-bearing rather than a cache:\n * `collect('apple_pay')` must reach `tokenize()` with nothing awaited in front\n * of it, so the object it needs has to already exist by then.\n */\n async offers(tender: Tender): Promise<Method[]> {\n const out: Method[] = []\n for (const method of REACH.web) {\n try {\n this.built.set(method, await this.build(method, tender))\n out.push(method)\n } catch {\n // Not offerable here. Say nothing and show nothing.\n }\n }\n return out\n }\n\n private async build(method: Method, tender: Tender): Promise<Tokenizer> {\n if (method === 'card') return this.payments.card({ style: style(this.palette) })\n if (method === 'gift') return this.payments.giftCard()\n if (method === 'ach') {\n return this.payments.ach({\n // Square REJECTS a redirectURI carrying a query string, so the page's own\n // path is used and anything that must survive the trip travels separately.\n redirectURI:\n typeof window === 'undefined'\n ? ''\n : window.location.origin + window.location.pathname,\n transactionId: `${Date.now()}`,\n })\n }\n // A FRESH paymentRequest per wallet: Square binds it into the object it\n // builds, so two wallets sharing one request is one wallet quoting the\n // other's total.\n const req = this.payments.paymentRequest({\n countryCode: tender.country,\n currencyCode: tender.total.currency,\n total: { amount: tender.total.amount, label: tender.label },\n })\n if (method === 'apple_pay') return this.payments.applePay(req)\n if (method === 'google_pay') return this.payments.googlePay(req)\n return this.payments.cashAppPay(req, {\n // Where Cash App returns a MOBILE buyer. Desktop uses the QR and never\n // leaves the page.\n redirectURL: typeof window === 'undefined' ? '' : window.location.href,\n referenceId: `pay-${tender.total.amount}`,\n })\n }\n\n /** Draw a rail that renders inline. A rail that draws nothing is a no-op, not an error. */\n async mount(method: Method, target: string): Promise<void> {\n const t = this.built.get(method)\n if (!t) throw new Error(`${method} was not offered here`)\n if (!DRAWN.includes(method)) return\n await t.attach?.(target)\n }\n\n /**\n * Tokenize.\n *\n * NOTHING IS AWAITED BEFORE `tokenize()`. Apple refuses a payment sheet that\n * was not opened directly by the gesture that asked for it, so a single `await`\n * placed above the call — loading the SDK, looking a rail up asynchronously,\n * re-reading a total — silently breaks Apple Pay and nothing else. That is why\n * `offers()` builds every rail up front and this only reads a map.\n */\n collect(method: Method, tender: Tender, detail?: Detail): Promise<Token> {\n const t = this.built.get(method)\n if (!t) return Promise.reject(new Error(`${method} was not offered here`))\n\n // Cash App draws its own button and delivers on the event; a tokenize() call\n // here does nothing at all.\n if (method === 'cash_app') return awaited(method, t)\n\n if (method === 'ach') {\n if (!detail?.name) {\n return Promise.reject(new Error('ACH needs the account holder’s name'))\n }\n const arrived = awaited(method, t)\n // The flow IS tokenize() for ACH — it opens the bank login — but the token\n // comes back on the event, so the return value is deliberately dropped.\n void t.tokenize({\n accountHolderName: detail.name,\n intent: 'CHARGE',\n amount: tender.total.amount,\n currency: tender.total.currency,\n })\n return arrived\n }\n\n return t.tokenize(detail?.verify).then((r) => token(method, r))\n }\n\n async release(): Promise<void> {\n // Square's `destroy()` empties the mount node when it resolves, so a create\n // that overlaps a destroy gets its fresh iframe swept away by the old\n // element's cleanup — the form then sits empty forever with no error to\n // explain it. Callers await this before building the next terminal, which is\n // what makes a theme change survivable.\n await Promise.all([...this.built.values()].map((t) => t.destroy?.().catch(() => undefined)))\n this.built.clear()\n }\n}\n\n/**\n * A terminal for this browser. Loads the SDK once per page, whichever surface\n * asks first.\n *\n * The palette is fixed at construction because SQUARE HAS NO API TO RESTYLE A\n * LIVE CARD — the fields are a cross-origin iframe, told their colours once, at\n * creation. Following a theme change therefore means `release()` then a new\n * terminal, and making the palette a constructor argument is what forces that to\n * happen by construction rather than by remembering to.\n */\nexport async function terminal(config: Config): Promise<Terminal> {\n if (!config.applicationId || !config.locationId) {\n throw new Error('Square is not configured for this deployment')\n }\n // BEFORE the SDK loads, let alone attaches. The container is styled as the\n // card mounts, so this rule has to already be in the sheet by then — see\n // `pin()`. Doing it here is what makes it impossible to ship the style object\n // without the one rule that makes it visible.\n pin()\n const src = (config.environment ?? 'production').toLowerCase() === 'sandbox' ? SANDBOX : PRODUCTION\n await load(src)\n if (typeof window === 'undefined' || !window.Square) {\n throw new Error('Square SDK failed to load')\n }\n const payments = await window.Square.payments(config.applicationId, config.locationId)\n return new Web(payments, config.palette ?? PALETTE.dark)\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/web/sdk.ts","../../src/web/style.ts","../../src/web/terminal.ts"],"names":["style","Cancelled","REACH","PALETTE"],"mappings":";;;;;AA4FO,IAAM,UAAA,GAAa;AACnB,IAAM,OAAA,GAAU;AAEvB,IAAI,OAAA,GAAgC,IAAA;AAG7B,SAAS,KAAK,GAAA,EAA4B;AAC/C,EAAA,IAAI,SAAS,OAAO,OAAA;AACpB,EAAA,IAAI,OAAO,WAAW,WAAA,IAAe,MAAA,CAAO,QAAQ,OAAQ,OAAA,GAAU,QAAQ,OAAA,EAAQ;AACtF,EAAA,OAAA,GAAU,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACzC,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,aAAA,CAAc,CAAA,YAAA,EAAe,GAAG,CAAA,EAAA,CAAI,CAAA;AAC9D,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,gBAAA,CAAiB,MAAA,EAAQ,MAAM,OAAA,EAAS,CAAA;AACjD,MAAA,QAAA,CAAS,gBAAA,CAAiB,SAAS,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,2BAA2B,CAAC,CAAC,CAAA;AACvF,MAAA;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AACzC,IAAA,CAAA,CAAE,GAAA,GAAM,GAAA;AACR,IAAA,CAAA,CAAE,KAAA,GAAQ,IAAA;AACV,IAAA,CAAA,CAAE,MAAA,GAAS,MAAM,OAAA,EAAQ;AACzB,IAAA,CAAA,CAAE,UAAU,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,2BAA2B,CAAC,CAAA;AAC/D,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,CAAC,CAAA;AAAA,EAC7B,CAAC,CAAA;AACD,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,MAAA,GAAe;AAC7B,EAAA,OAAA,GAAU,IAAA;AACZ;;;AChGO,SAAS,KAAA,CAAMA,QAAc,QAAA,EAAyB;AAC3D,EAAA,OAAOA,OAAM,QAAQ,CAAA;AACvB;AAGA,IAAM,MAAA,GAAS,KAAA;AAOf,IAAM,IAAA,GAAO,MAAA;AAYb,IAAM,IAAA,GAAO,KAAA;AACb,IAAM,QAAA,GAAW,KAAA;AAyCV,SAAS,MAAM,CAAA,EAAmB;AACvC,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,kBAAA,EAAoB;AAAA,MAClB,aAAa,CAAA,CAAE,MAAA;AAAA,MACf,YAAA,EAAc,MAAA;AAAA,MACd,WAAA,EAAa;AAAA,KACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,2BAAA,EAA6B,EAAE,WAAA,EAAa,CAAA,CAAE,MAAA,EAAO;AAAA,IACrD,2BAAA,EAA6B,EAAE,WAAA,EAAa,CAAA,CAAE,KAAA,EAAM;AAAA,IACpD,KAAA,EAAO;AAAA,MACL,iBAAiB,CAAA,CAAE,KAAA;AAAA,MACnB,OAAO,CAAA,CAAE,IAAA;AAAA,MACT,QAAA,EAAU;AAAA,KACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,sCAAA,EAAwC;AAAA,MACtC,KAAA,EAAO,EAAE,QAAA,EAAU,MAAA;AAAO,KAC5B;AAAA,IACA,oBAAA,EAAsB,EAAE,KAAA,EAAO,CAAA,CAAE,WAAA,EAAY;AAAA,IAC7C,gBAAA,EAAkB,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM;AAAA,IACnC,eAAA,EAAiB,EAAE,KAAA,EAAO,CAAA,CAAE,WAAA,EAAY;AAAA,IACxC,wBAAA,EAA0B,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM;AAAA,IAC3C,eAAA,EAAiB,EAAE,KAAA,EAAO,CAAA,CAAE,WAAA,EAAY;AAAA,IACxC,wBAAA,EAA0B,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA;AAAM,GAC7C;AACF;AA2CO,SAAS,GAAA,CAAI,GAAY,GAAA,EAAsB;AACpD,EAAA,MAAM,CAAA,GAAI,GAAA,KAAQ,OAAO,QAAA,KAAa,cAAc,MAAA,GAAY,QAAA,CAAA;AAChE,EAAA,IAAI,CAAC,CAAA,EAAG;AACR,EAAA,MAAM,IAAA,GAAO,kBAAA;AACb,EAAA,IAAI,EAAA,GAAK,CAAA,CAAE,cAAA,CAAe,IAAI,CAAA;AAC9B,EAAA,IAAI,CAAC,EAAA,EAAI;AACP,IAAA,EAAA,GAAK,CAAA,CAAE,cAAc,OAAO,CAAA;AAC5B,IAAA,EAAA,CAAG,EAAA,GAAK,IAAA;AACR,IAAA,CAAA,CAAE,IAAA,CAAK,YAAY,EAAE,CAAA;AAAA,EACvB;AACA,EAAA,EAAA,CAAG,cACD,CAAA,wGAAA,EAC+D,IAAI,UAAU,CAAA,CAAE,IAAI,mBAAmB,QAAQ,CAAA,CAAA,CAAA;AAClH;;;AC/IA,IAAM,KAAA,GAA2B,CAAC,MAAA,EAAQ,MAAA,EAAQ,cAAc,UAAU,CAAA;AAE1E,SAAS,KAAA,CAAM,QAAgB,CAAA,EAAkB;AAG/C,EAAA,IAAI,MAAA,CAAO,CAAA,CAAE,MAAM,CAAA,CAAE,WAAA,OAAkB,QAAA,EAAU,MAAM,IAAIC,2BAAA,CAAU,MAAM,CAAA;AAC3E,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,IAAA,IAAQ,CAAC,EAAE,KAAA,EAAO;AACjC,IAAA,MAAM,IAAI,MAAM,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA,EAAG,OAAA,IAAW,CAAA,EAAG,MAAM,CAAA,uBAAA,CAAyB,CAAA;AAAA,EAC9E;AACA,EAAA,OAAO,EAAE,OAAO,CAAA,CAAE,KAAA,EAAO,QAAQ,IAAA,EAAM,CAAA,CAAE,SAAS,IAAA,EAAK;AACzD;AAUA,SAAS,OAAA,CAAQ,QAAgB,CAAA,EAA8B;AAC7D,EAAA,OAAO,IAAI,OAAA,CAAe,CAAC,OAAA,EAAS,MAAA,KAAW;AAC7C,IAAA,CAAA,CAAE,gBAAA,GAAmB,gBAAA,EAAkB,CAAC,CAAA,KAAe;AACrD,MAAA,MAAM,SAAU,CAAA,EAA8D,MAAA;AAC9E,MAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,EAAG,MAAM,yBAAyB,CAAC,CAAA;AACpD,QAAA;AAAA,MACF;AACA,MAAA,IAAI,CAAC,QAAQ,WAAA,EAAa;AAC1B,MAAA,IAAI;AACF,QAAA,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,MAAA,CAAO,WAAW,CAAC,CAAA;AAAA,MAC3C,SAAS,GAAA,EAAK;AACZ,QAAA,MAAA,CAAO,GAAG,CAAA;AAAA,MACZ;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAEA,IAAM,MAAN,MAA8B;AAAA,EAI5B,WAAA,CACmB,UACjB,OAAA,EACA;AAFiB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAGjB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA,EAJmB,QAAA;AAAA,EAJF,KAAA,uBAAY,GAAA,EAAuB;AAAA,EACnC,OAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBjB,MAAM,OAAO,MAAA,EAAmC;AAC9C,IAAA,MAAM,MAAgB,EAAC;AACvB,IAAA,KAAA,MAAW,MAAA,IAAUC,wBAAM,GAAA,EAAK;AAC9B,MAAA,IAAI;AACF,QAAA,IAAA,CAAK,KAAA,CAAM,IAAI,MAAA,EAAQ,MAAM,KAAK,KAAA,CAAM,MAAA,EAAQ,MAAM,CAAC,CAAA;AACvD,QAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,MACjB,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAAA,EAEA,MAAc,KAAA,CAAM,MAAA,EAAgB,MAAA,EAAoC;AACtE,IAAA,IAAI,MAAA,KAAW,MAAA,EAAQ,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,EAAE,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA,EAAG,CAAA;AAC/E,IAAA,IAAI,MAAA,KAAW,MAAA,EAAQ,OAAO,IAAA,CAAK,SAAS,QAAA,EAAS;AACrD,IAAA,IAAI,WAAW,KAAA,EAAO;AACpB,MAAA,OAAO,IAAA,CAAK,SAAS,GAAA,CAAI;AAAA;AAAA;AAAA,QAGvB,WAAA,EACE,OAAO,MAAA,KAAW,WAAA,GACd,KACA,MAAA,CAAO,QAAA,CAAS,MAAA,GAAS,MAAA,CAAO,QAAA,CAAS,QAAA;AAAA,QAC/C,aAAA,EAAe,CAAA,EAAG,IAAA,CAAK,GAAA,EAAK,CAAA;AAAA,OAC7B,CAAA;AAAA,IACH;AAIA,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,QAAA,CAAS,cAAA,CAAe;AAAA,MACvC,aAAa,MAAA,CAAO,OAAA;AAAA,MACpB,YAAA,EAAc,OAAO,KAAA,CAAM,QAAA;AAAA,MAC3B,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAM,MAAA,EAAQ,KAAA,EAAO,OAAO,KAAA;AAAM,KAC3D,CAAA;AACD,IAAA,IAAI,WAAW,WAAA,EAAa,OAAO,IAAA,CAAK,QAAA,CAAS,SAAS,GAAG,CAAA;AAC7D,IAAA,IAAI,WAAW,YAAA,EAAc,OAAO,IAAA,CAAK,QAAA,CAAS,UAAU,GAAG,CAAA;AAC/D,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,UAAA,CAAW,GAAA,EAAK;AAAA;AAAA;AAAA,MAGnC,aAAa,OAAO,MAAA,KAAW,WAAA,GAAc,EAAA,GAAK,OAAO,QAAA,CAAS,IAAA;AAAA,MAClE,WAAA,EAAa,CAAA,IAAA,EAAO,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA;AAAA,KACxC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,KAAA,CAAM,MAAA,EAAgB,MAAA,EAA+B;AACzD,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAC/B,IAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,qBAAA,CAAuB,CAAA;AACxD,IAAA,IAAI,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,EAAG;AAC7B,IAAA,MAAM,CAAA,CAAE,SAAS,MAAM,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAA,CAAQ,MAAA,EAAgB,MAAA,EAAgB,MAAA,EAAiC;AACvE,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAC/B,IAAA,IAAI,CAAC,CAAA,EAAG,OAAO,OAAA,CAAQ,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,qBAAA,CAAuB,CAAC,CAAA;AAIzE,IAAA,IAAI,MAAA,KAAW,UAAA,EAAY,OAAO,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAEnD,IAAA,IAAI,WAAW,KAAA,EAAO;AACpB,MAAA,IAAI,CAAC,QAAQ,IAAA,EAAM;AACjB,QAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,IAAI,KAAA,CAAM,0CAAqC,CAAC,CAAA;AAAA,MACxE;AACA,MAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAGjC,MAAA,KAAK,EAAE,QAAA,CAAS;AAAA,QACd,mBAAmB,MAAA,CAAO,IAAA;AAAA,QAC1B,MAAA,EAAQ,QAAA;AAAA,QACR,MAAA,EAAQ,OAAO,KAAA,CAAM,MAAA;AAAA,QACrB,QAAA,EAAU,OAAO,KAAA,CAAM;AAAA,OACxB,CAAA;AACD,MAAA,OAAO,OAAA;AAAA,IACT;AAEA,IAAA,OAAO,CAAA,CAAE,QAAA,CAAS,MAAA,EAAQ,MAAM,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,KAAA,CAAM,MAAA,EAAQ,CAAC,CAAC,CAAA;AAAA,EAChE;AAAA,EAEA,MAAM,OAAA,GAAyB;AAM7B,IAAA,MAAM,QAAQ,GAAA,CAAI,CAAC,GAAG,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,CAAE,IAAI,CAAC,CAAA,KAAM,EAAE,OAAA,IAAU,CAAE,MAAM,MAAM,MAAS,CAAC,CAAC,CAAA;AAC3F,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACnB;AACF,CAAA;AAYA,eAAsB,SAAS,MAAA,EAAmC;AAChE,EAAA,IAAI,CAAC,MAAA,CAAO,aAAA,IAAiB,CAAC,OAAO,UAAA,EAAY;AAC/C,IAAA,MAAM,IAAI,MAAM,8CAA8C,CAAA;AAAA,EAChE;AACA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,IAAWC,yBAAA,CAAQ,IAAA;AAK1C,EAAA,GAAA,CAAI,OAAO,CAAA;AACX,EAAA,MAAM,OAAO,MAAA,CAAO,WAAA,IAAe,cAAc,WAAA,EAAY,KAAM,YAAY,OAAA,GAAU,UAAA;AACzF,EAAA,MAAM,KAAK,GAAG,CAAA;AACd,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,OAAO,MAAA,EAAQ;AACnD,IAAA,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAAA,EAC7C;AACA,EAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,MAAA,CAAO,SAAS,MAAA,CAAO,aAAA,EAAe,OAAO,UAAU,CAAA;AACrF,EAAA,OAAO,IAAI,GAAA,CAAI,QAAA,EAAU,OAAO,CAAA;AAClC","file":"index.cjs","sourcesContent":["// The Square Web Payments SDK, as much of it as we actually call, typed.\n//\n// Square ships no types for the global it installs, so this is the contract we\n// hold it to. Every member below is exercised by this package; nothing is\n// declared speculatively.\n\n/** What `tokenize()` resolves with. `status` is `'OK'` | `'CANCEL'` | an error. */\nexport interface Result {\n status: string\n token?: string\n details?: {\n card?: { brand?: string; last4?: string; expMonth?: number; expYear?: number }\n }\n errors?: Array<{ message: string }>\n}\n\nexport interface Tokenizer {\n tokenize(options?: Record<string, unknown>): Promise<Result>\n /**\n * Cash App Pay and ACH deliver their token on an `ontokenization` EVENT rather\n * than from tokenize()'s return — the buyer leaves the page (for Cash App, or\n * for their bank's login) and comes back. A caller that only reads the return\n * value gets nothing from either, silently, on a payment that succeeded.\n */\n addEventListener?(event: string, handler: (e: unknown) => void): void\n /**\n * Optional because a WALLET may draw itself (Apple Pay does) rather than\n * attach. The second argument is Cash App Pay's button options\n * (`{ shape, width }`); every other rail ignores it.\n */\n attach?(selector: string, options?: Record<string, unknown>): Promise<void>\n destroy?(): Promise<void>\n}\n\n/**\n * The CARD element always attaches and always destroys — it owns an iframe. Kept\n * distinct from the wallet tokenizer so the card path is not forced to\n * optional-chain calls that can never be absent.\n */\nexport interface Element extends Tokenizer {\n attach(selector: string): Promise<void>\n destroy(): Promise<void>\n}\n\nexport interface Request {\n countryCode: string\n currencyCode: string\n total: { amount: string; label: string }\n}\n\nexport interface Payments {\n card(options?: Record<string, unknown>): Promise<Element>\n /**\n * A Square gift card. Its own constructor and its own container — a gift card\n * is a balance instrument, not a credit line, so Square keeps the two elements\n * separate rather than switching one on a brand.\n *\n * It attaches and tokenizes exactly like a card, with one difference that\n * matters to a caller: its `tokenize()` takes NO verification details. SCA\n * covers cards, and a gift card has no issuer to challenge.\n */\n giftCard(options?: Record<string, unknown>): Promise<Element>\n paymentRequest(req: Request): unknown\n applePay(req: unknown): Promise<Tokenizer>\n googlePay(req: unknown): Promise<Tokenizer>\n cashAppPay(req: unknown, opts: Record<string, unknown>): Promise<Tokenizer>\n /**\n * ACH bank transfer. Square runs PLAID'S instant bank authentication itself,\n * so this needs no Plaid account of ours — which is why the rail sat marked\n * \"coming soon\" for want of an integration that was already included. US-only.\n *\n * THE ARGUMENT IS OPTIONAL BECAUSE SQUARE ACCEPTS TWO SHAPES, and a type that\n * admitted only one would reject working code. The redirect form\n * (`ach({ redirectURI, transactionId })`, what this package calls and what\n * hanzoai/pay has run in production) sends the buyer to their bank and back;\n * Square's own quickstart instead calls `ach()` bare and passes\n * `{ accountHolderName, intent, amount, currency }` to `tokenize()`. Both\n * deliver the token the same way — on the event, never from the return.\n */\n ach(opts?: { redirectURI?: string; transactionId?: string }): Promise<Tokenizer>\n}\n\ninterface SDK {\n payments(appId: string, locationId: string): Promise<Payments>\n}\n\ndeclare global {\n interface Window {\n Square?: SDK\n }\n}\n\nexport const PRODUCTION = 'https://web.squarecdn.com/v1/square.js'\nexport const SANDBOX = 'https://sandbox.web.squarecdn.com/v1/square.js'\n\nlet pending: Promise<void> | null = null\n\n/** Load the SDK once per page, whichever surface asks first. */\nexport function load(src: string): Promise<void> {\n if (pending) return pending\n if (typeof window !== 'undefined' && window.Square) return (pending = Promise.resolve())\n pending = new Promise((resolve, reject) => {\n const existing = document.querySelector(`script[src=\"${src}\"]`)\n if (existing) {\n existing.addEventListener('load', () => resolve())\n existing.addEventListener('error', () => reject(new Error('Square SDK failed to load')))\n return\n }\n const s = document.createElement('script')\n s.src = src\n s.async = true\n s.onload = () => resolve()\n s.onerror = () => reject(new Error('Square SDK failed to load'))\n document.head.appendChild(s)\n })\n return pending\n}\n\n/** Drop the memoized load. For tests only — a page loads the SDK once. */\nexport function forget(): void {\n pending = null\n}\n","// A `Palette` rendered as the style object `payments.card({ style })` accepts.\n//\n// Measured against the live SDK, not read off a doc page. Where a comment says\n// \"confirmed\", it was driven and inspected.\n\nimport type { Palette } from '../palette'\n\n/** A selector's CSS property bag. */\nexport type Rules = Record<string, string>\n\n/**\n * What `payments.card({ style })` accepts: selector -> properties, plus `@media`\n * keys whose value is a nested block of the same. Square resolves the media query\n * INSIDE the iframe, which is the only way in — the frame cannot see the page's\n * own breakpoints.\n */\nexport type Style = Record<string, Rules | Record<string, Rules>>\n\n/**\n * One selector's properties.\n *\n * A `@media` key holds a nested block, so the map's value type is a union and\n * every read of a plain selector would otherwise need its own cast. This is that\n * cast, written once, where the shape is known.\n */\nexport function rules(style: Style, selector: string): Rules {\n return style[selector] as Rules\n}\n\n/** The input/button radius of @hanzo/design's ramp (--radius-sm), in the px Square expects. */\nconst RADIUS = '6px'\n\n/**\n * The body size of @hanzo/design's ramp (--text-sm), which is what a checkout's\n * own inputs render at. It was 14px — one rung above every field beside it, so\n * the card strip's placeholders were visibly larger than the address labels.\n */\nconst SIZE = '13px'\n\n/**\n * The ONE focus indicator, drawn around the container in `pin()`.\n *\n * These two values are the host's, not a choice made here: they are what\n * `:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px }`\n * renders, which is the rule a Hanzo surface already applies to every button,\n * link and input on the page. The card strip is the one control that cannot\n * inherit it — the fields are in a frame that has no idea a page exists — so it\n * is restated here, once, in the same numbers.\n */\nconst RING = '2px'\nconst RING_GAP = '2px'\n\n/**\n * The style object for a card element painted in `p`.\n *\n * THE GROUND AND THE INK ARE ONE PALETTE, and that is the whole point. The field\n * shipped as `input.backgroundColor: 'transparent'` beside `input.color:\n * '#fafafa'` — so Square painted near-white digits onto its own default white\n * iframe and the customer's card number was invisible as they typed it. Two\n * properties, set in two places, describing one surface. Reading them off one\n * `Palette` is what stops them disagreeing again; `legible()` proves they have not.\n *\n * `input.backgroundColor` IS the supported way in, and it is not a workaround.\n * Square maps that one property onto the container behind the iframe rather than\n * onto the input:\n *\n * selectorPropertyMappings[input] = [{ property: 'backgroundColor',\n * toSelectors: ['#<id>.sq-card-wrapper .sq-card-iframe-container'] }]\n *\n * Measured, not read: driving build 1.84.0 with the object below and inspecting\n * the result gives `.sq-card-iframe-container { background-color: #0a0a0a }` in\n * the parent document, with `<body>`, `<html>` and all four inputs inside the\n * cross-origin frame computing to `rgba(0,0,0,0)`. The frame is transparent by\n * design and the container is the surface — so painting the container IS painting\n * the field.\n *\n * AND IT IS STILL NOT ENOUGH ON ITS OWN — see `pin()` below, which this package\n * applies for you. The declaration above lands and is then overpainted, so a\n * style object shipped without that rule is accepted and silently ignored. That\n * gap was misread once as an SDK bug to wait out; it was ours.\n *\n * `.input-container { backgroundColor }` is ALSO accepted on 1.84.0 (it throws\n * nothing). It is still not set: it would be a second way to paint the one\n * surface `input.backgroundColor` already paints, and the two could then\n * disagree. One property, one surface.\n *\n * `fontFamily` is deliberately absent: Square validates it against its own\n * loadable list and throws on both CSS-wide stacks and arbitrary names, which\n * blocks the iframe from attaching at all. Its default is a system sans, which is\n * what --font-sans resolves to anyway.\n */\nexport function style(p: Palette): Style {\n return {\n // Borders and radius ONLY. Square's allowlist for this selector is\n // borderColor/borderRadius/borderWidth; anything else throws \"Invalid style\n // property\", and Square rejects the WHOLE style object when it does — so one\n // stray property leaves the card unstyled and white.\n '.input-container': {\n borderColor: p.border,\n borderRadius: RADIUS,\n borderWidth: '1px',\n },\n // THE HAIRLINE DOES NOT MOVE ON FOCUS, and that is the point. Square spends\n // this value twice — on the container's border AND on a `::before` ring at\n // `inset: -1px` — so any colour but the resting one puts a second ring\n // immediately inside the one `pin()` draws. Two rings for one event is the\n // exact defect this pass removes from the address fields; the card strip\n // must not reintroduce it one row lower.\n '.input-container.is-focus': { borderColor: p.border },\n '.input-container.is-error': { borderColor: p.error },\n input: {\n backgroundColor: p.field,\n color: p.text,\n fontSize: SIZE,\n },\n // 16px on a small screen, because iOS Safari ZOOMS the page when a field\n // under 16px takes focus — on a card form that throws the layout sideways\n // mid-number and there is no way to zoom back without losing the caret.\n // Square's own dark-mode example carries this media query for the same\n // reason; it is the one place the field may disagree with the page's ramp.\n '@media screen and (max-width: 600px)': {\n input: { fontSize: '16px' },\n },\n 'input::placeholder': { color: p.placeholder },\n 'input.is-error': { color: p.error },\n '.message-text': { color: p.placeholder },\n '.message-text.is-error': { color: p.error },\n '.message-icon': { color: p.placeholder },\n '.message-icon.is-error': { color: p.error },\n }\n}\n\n/**\n * The two rules Square's own style object cannot express.\n *\n * Square's `input.backgroundColor` is accepted and then IGNORED: the field\n * renders white on a black checkout no matter what is passed. The cause is not\n * Square's, and it is one property. A page that sets `color-scheme` on\n * `<html>` — which is the correct thing to do, and what every themed app does so\n * the UA paints scrollbars and form controls to match — leaks it INTO the\n * cross-origin iframe, because `color-scheme` inherits. Inside the frame the UA\n * then paints form controls on its own scheme background, over anything the SDK\n * declared. Pinning the container back to `auto` lets our ground through.\n *\n * (A Square forum user reported the same thing after two years of the docs' own\n * recipe not working. Same fix, arrived at from the other end.)\n *\n * THIS IS APPLIED FOR YOU, by `terminal()`, before any card can attach — it must\n * be in the stylesheet BEFORE the element mounts, because the container is styled\n * as the card attaches, and a rule added afterwards is a rule that arrived too\n * late. It is not left to a host to remember, and it is not a stylesheet a host\n * can forget to import: this package produced the style object, so this package\n * owes the one rule that makes it mean anything.\n *\n * THE SECOND RULE IS THE FOCUS RING, and it is here for the mirror-image reason:\n * an outline has to be drawn OUTSIDE the field, and everything Square will style\n * for us lives inside a frame that ends at the field's edge. The seam is\n * `sq-focus`, which Square adds to `.sq-card-wrapper` in THIS document while a\n * card input holds focus — the class its own `focusClassAdded`/`focusClassRemoved`\n * events are named for. So the ring is plain CSS: no listener, no state, nothing\n * to unsubscribe, and it cannot drift out of step with the field it surrounds.\n *\n * Measured on 1.84.0: Square generates\n * `#<wrapper>.sq-focus .sq-card-iframe-container::before { inset: -1px }` as its\n * own focus ring, which is why `style()` paints the focused hairline the resting\n * colour — that pseudo-ring then has nothing to draw and this is the only ring\n * the buyer sees.\n *\n * A no-op without a document. Called again with a different palette (a theme\n * change) it REWRITES its rules rather than bailing: there is one element, always\n * carrying the current colours. Bailing early would have left the ring painted in\n * the palette of a theme the page had already left.\n */\nexport function pin(p: Palette, doc?: Document): void {\n const d = doc ?? (typeof document === 'undefined' ? undefined : document)\n if (!d) return\n const MARK = 'hanzo-pay-scheme'\n let el = d.getElementById(MARK) as HTMLStyleElement | null\n if (!el) {\n el = d.createElement('style')\n el.id = MARK\n d.head.appendChild(el)\n }\n el.textContent =\n '.sq-card-iframe-container{color-scheme:auto}' +\n `.sq-card-wrapper.sq-focus .sq-card-iframe-container{outline:${RING} solid ${p.ring};outline-offset:${RING_GAP}}`\n}\n","// The Square Web Payments SDK behind the one `Terminal` interface.\n//\n// Every rail here mints the SAME single-use token, bound for the same server\n// call — so none of them needed server work beyond the one that already existed.\n// What they do NOT share is how the token arrives, and treating them as one shape\n// is a silent failure in three of the six:\n//\n// card, gift attach() into a container, then tokenize() returns the token.\n// google_pay attach() into a container Square draws its button in, then\n// tokenize() from our click.\n// apple_pay NO attach() — Square's docs are explicit. The host renders the\n// button (Apple's own CSS appearance), and tokenize() must be\n// called IMMEDIATELY in the click handler.\n// cash_app attach(), and then the token arrives on an `ontokenization`\n// EVENT after the buyer approves in the app or by QR. Calling\n// tokenize() ourselves does nothing and the payment never lands.\n// ach NO attach() — the flow IS tokenize(). The token likewise arrives\n// on `ontokenization`, because the buyer leaves for their bank.\n\nimport {\n type Detail,\n type Method,\n type Tender,\n type Terminal,\n type Token,\n Cancelled,\n REACH,\n} from '../terminal'\nimport { type Palette, PALETTE } from '../palette'\nimport { load, PRODUCTION, SANDBOX, type Payments, type Result, type Tokenizer } from './sdk'\nimport { pin, style } from './style'\n\n/** The org's PUBLIC Square config, exactly as commerce publishes it. */\nexport interface Config {\n applicationId: string\n locationId: string\n /** `'sandbox'` tokenizes against Square's sandbox. Anything else is production. */\n environment?: string\n /** The card field's colours. Defaults to @hanzo/design's dark table. */\n palette?: Palette\n}\n\n/** Rails that draw themselves into a container the host positions. */\nconst DRAWN: readonly Method[] = ['card', 'gift', 'google_pay', 'cash_app']\n\nfunction token(method: Method, r: Result): Token {\n // CANCEL is the buyer changing their mind. It is not a failure and must never\n // be shown as one.\n if (String(r.status).toUpperCase() === 'CANCEL') throw new Cancelled(method)\n if (r.status !== 'OK' || !r.token) {\n throw new Error(r.errors?.[0]?.message ?? `${method} could not be completed`)\n }\n return { value: r.token, method, card: r.details?.card }\n}\n\n/**\n * The token that arrives on an EVENT rather than from a return — Cash App Pay\n * and ACH, for the same underlying reason: the buyer leaves the page.\n *\n * One listener per collect(), removed when it settles. Square's own quickstart\n * registers a fresh listener on every submission and never removes one, so a\n * buyer who retries gets their token delivered to every previous attempt too.\n */\nfunction awaited(method: Method, t: Tokenizer): Promise<Token> {\n return new Promise<Token>((resolve, reject) => {\n t.addEventListener?.('ontokenization', (e: unknown) => {\n const detail = (e as { detail?: { tokenResult?: Result; error?: unknown } })?.detail\n if (detail?.error) {\n reject(new Error(`${method} could not be completed`))\n return\n }\n if (!detail?.tokenResult) return\n try {\n resolve(token(method, detail.tokenResult))\n } catch (err) {\n reject(err)\n }\n })\n })\n}\n\nclass Web implements Terminal {\n private readonly built = new Map<Method, Tokenizer>()\n private readonly palette: Palette\n\n constructor(\n private readonly payments: Payments,\n palette: Palette,\n ) {\n this.palette = palette\n }\n\n /**\n * Which rails can REALLY pay here, asked of the SDK one at a time.\n *\n * Building the rail IS the probe, and it is the only answer that accounts for\n * the browser, the device, the buyer's saved cards AND whether the merchant\n * account has the rail switched on. A rejection is a plain \"not here\", not an\n * error worth showing anyone.\n *\n * The built objects are KEPT, and that is load-bearing rather than a cache:\n * `collect('apple_pay')` must reach `tokenize()` with nothing awaited in front\n * of it, so the object it needs has to already exist by then.\n */\n async offers(tender: Tender): Promise<Method[]> {\n const out: Method[] = []\n for (const method of REACH.web) {\n try {\n this.built.set(method, await this.build(method, tender))\n out.push(method)\n } catch {\n // Not offerable here. Say nothing and show nothing.\n }\n }\n return out\n }\n\n private async build(method: Method, tender: Tender): Promise<Tokenizer> {\n if (method === 'card') return this.payments.card({ style: style(this.palette) })\n if (method === 'gift') return this.payments.giftCard()\n if (method === 'ach') {\n return this.payments.ach({\n // Square REJECTS a redirectURI carrying a query string, so the page's own\n // path is used and anything that must survive the trip travels separately.\n redirectURI:\n typeof window === 'undefined'\n ? ''\n : window.location.origin + window.location.pathname,\n transactionId: `${Date.now()}`,\n })\n }\n // A FRESH paymentRequest per wallet: Square binds it into the object it\n // builds, so two wallets sharing one request is one wallet quoting the\n // other's total.\n const req = this.payments.paymentRequest({\n countryCode: tender.country,\n currencyCode: tender.total.currency,\n total: { amount: tender.total.amount, label: tender.label },\n })\n if (method === 'apple_pay') return this.payments.applePay(req)\n if (method === 'google_pay') return this.payments.googlePay(req)\n return this.payments.cashAppPay(req, {\n // Where Cash App returns a MOBILE buyer. Desktop uses the QR and never\n // leaves the page.\n redirectURL: typeof window === 'undefined' ? '' : window.location.href,\n referenceId: `pay-${tender.total.amount}`,\n })\n }\n\n /** Draw a rail that renders inline. A rail that draws nothing is a no-op, not an error. */\n async mount(method: Method, target: string): Promise<void> {\n const t = this.built.get(method)\n if (!t) throw new Error(`${method} was not offered here`)\n if (!DRAWN.includes(method)) return\n await t.attach?.(target)\n }\n\n /**\n * Tokenize.\n *\n * NOTHING IS AWAITED BEFORE `tokenize()`. Apple refuses a payment sheet that\n * was not opened directly by the gesture that asked for it, so a single `await`\n * placed above the call — loading the SDK, looking a rail up asynchronously,\n * re-reading a total — silently breaks Apple Pay and nothing else. That is why\n * `offers()` builds every rail up front and this only reads a map.\n */\n collect(method: Method, tender: Tender, detail?: Detail): Promise<Token> {\n const t = this.built.get(method)\n if (!t) return Promise.reject(new Error(`${method} was not offered here`))\n\n // Cash App draws its own button and delivers on the event; a tokenize() call\n // here does nothing at all.\n if (method === 'cash_app') return awaited(method, t)\n\n if (method === 'ach') {\n if (!detail?.name) {\n return Promise.reject(new Error('ACH needs the account holder’s name'))\n }\n const arrived = awaited(method, t)\n // The flow IS tokenize() for ACH — it opens the bank login — but the token\n // comes back on the event, so the return value is deliberately dropped.\n void t.tokenize({\n accountHolderName: detail.name,\n intent: 'CHARGE',\n amount: tender.total.amount,\n currency: tender.total.currency,\n })\n return arrived\n }\n\n return t.tokenize(detail?.verify).then((r) => token(method, r))\n }\n\n async release(): Promise<void> {\n // Square's `destroy()` empties the mount node when it resolves, so a create\n // that overlaps a destroy gets its fresh iframe swept away by the old\n // element's cleanup — the form then sits empty forever with no error to\n // explain it. Callers await this before building the next terminal, which is\n // what makes a theme change survivable.\n await Promise.all([...this.built.values()].map((t) => t.destroy?.().catch(() => undefined)))\n this.built.clear()\n }\n}\n\n/**\n * A terminal for this browser. Loads the SDK once per page, whichever surface\n * asks first.\n *\n * The palette is fixed at construction because SQUARE HAS NO API TO RESTYLE A\n * LIVE CARD — the fields are a cross-origin iframe, told their colours once, at\n * creation. Following a theme change therefore means `release()` then a new\n * terminal, and making the palette a constructor argument is what forces that to\n * happen by construction rather than by remembering to.\n */\nexport async function terminal(config: Config): Promise<Terminal> {\n if (!config.applicationId || !config.locationId) {\n throw new Error('Square is not configured for this deployment')\n }\n const palette = config.palette ?? PALETTE.dark\n // BEFORE the SDK loads, let alone attaches. The container is styled as the\n // card mounts, so these rules have to already be in the sheet by then — see\n // `pin()`. Doing it here is what makes it impossible to ship the style object\n // without the rules that make it visible.\n pin(palette)\n const src = (config.environment ?? 'production').toLowerCase() === 'sandbox' ? SANDBOX : PRODUCTION\n await load(src)\n if (typeof window === 'undefined' || !window.Square) {\n throw new Error('Square SDK failed to load')\n }\n const payments = await window.Square.payments(config.applicationId, config.locationId)\n return new Web(payments, palette)\n}\n"]}
|
package/dist/web/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PALETTE, REACH, Cancelled } from '../chunk-
|
|
1
|
+
import { PALETTE, REACH, Cancelled } from '../chunk-IYAU5BMM.js';
|
|
2
2
|
|
|
3
3
|
// src/web/sdk.ts
|
|
4
4
|
var PRODUCTION = "https://web.squarecdn.com/v1/square.js";
|
|
@@ -32,6 +32,9 @@ function rules(style2, selector) {
|
|
|
32
32
|
return style2[selector];
|
|
33
33
|
}
|
|
34
34
|
var RADIUS = "6px";
|
|
35
|
+
var SIZE = "13px";
|
|
36
|
+
var RING = "2px";
|
|
37
|
+
var RING_GAP = "2px";
|
|
35
38
|
function style(p) {
|
|
36
39
|
return {
|
|
37
40
|
// Borders and radius ONLY. Square's allowlist for this selector is
|
|
@@ -43,12 +46,18 @@ function style(p) {
|
|
|
43
46
|
borderRadius: RADIUS,
|
|
44
47
|
borderWidth: "1px"
|
|
45
48
|
},
|
|
46
|
-
|
|
49
|
+
// THE HAIRLINE DOES NOT MOVE ON FOCUS, and that is the point. Square spends
|
|
50
|
+
// this value twice — on the container's border AND on a `::before` ring at
|
|
51
|
+
// `inset: -1px` — so any colour but the resting one puts a second ring
|
|
52
|
+
// immediately inside the one `pin()` draws. Two rings for one event is the
|
|
53
|
+
// exact defect this pass removes from the address fields; the card strip
|
|
54
|
+
// must not reintroduce it one row lower.
|
|
55
|
+
".input-container.is-focus": { borderColor: p.border },
|
|
47
56
|
".input-container.is-error": { borderColor: p.error },
|
|
48
57
|
input: {
|
|
49
58
|
backgroundColor: p.field,
|
|
50
59
|
color: p.text,
|
|
51
|
-
fontSize:
|
|
60
|
+
fontSize: SIZE
|
|
52
61
|
},
|
|
53
62
|
// 16px on a small screen, because iOS Safari ZOOMS the page when a field
|
|
54
63
|
// under 16px takes focus — on a card form that throws the layout sideways
|
|
@@ -66,15 +75,17 @@ function style(p) {
|
|
|
66
75
|
".message-icon.is-error": { color: p.error }
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
|
-
function pin(doc) {
|
|
78
|
+
function pin(p, doc) {
|
|
70
79
|
const d = doc ?? (typeof document === "undefined" ? void 0 : document);
|
|
71
80
|
if (!d) return;
|
|
72
81
|
const MARK = "hanzo-pay-scheme";
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
let el = d.getElementById(MARK);
|
|
83
|
+
if (!el) {
|
|
84
|
+
el = d.createElement("style");
|
|
85
|
+
el.id = MARK;
|
|
86
|
+
d.head.appendChild(el);
|
|
87
|
+
}
|
|
88
|
+
el.textContent = `.sq-card-iframe-container{color-scheme:auto}.sq-card-wrapper.sq-focus .sq-card-iframe-container{outline:${RING} solid ${p.ring};outline-offset:${RING_GAP}}`;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
// src/web/terminal.ts
|
|
@@ -203,14 +214,15 @@ async function terminal(config) {
|
|
|
203
214
|
if (!config.applicationId || !config.locationId) {
|
|
204
215
|
throw new Error("Square is not configured for this deployment");
|
|
205
216
|
}
|
|
206
|
-
|
|
217
|
+
const palette = config.palette ?? PALETTE.dark;
|
|
218
|
+
pin(palette);
|
|
207
219
|
const src = (config.environment ?? "production").toLowerCase() === "sandbox" ? SANDBOX : PRODUCTION;
|
|
208
220
|
await load(src);
|
|
209
221
|
if (typeof window === "undefined" || !window.Square) {
|
|
210
222
|
throw new Error("Square SDK failed to load");
|
|
211
223
|
}
|
|
212
224
|
const payments = await window.Square.payments(config.applicationId, config.locationId);
|
|
213
|
-
return new Web(payments,
|
|
225
|
+
return new Web(payments, palette);
|
|
214
226
|
}
|
|
215
227
|
|
|
216
228
|
export { PRODUCTION, SANDBOX, forget, load, pin, rules, style, terminal };
|
package/dist/web/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/web/sdk.ts","../../src/web/style.ts","../../src/web/terminal.ts"],"names":["style"],"mappings":"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","file":"index.js","sourcesContent":["// The Square Web Payments SDK, as much of it as we actually call, typed.\n//\n// Square ships no types for the global it installs, so this is the contract we\n// hold it to. Every member below is exercised by this package; nothing is\n// declared speculatively.\n\n/** What `tokenize()` resolves with. `status` is `'OK'` | `'CANCEL'` | an error. */\nexport interface Result {\n status: string\n token?: string\n details?: {\n card?: { brand?: string; last4?: string; expMonth?: number; expYear?: number }\n }\n errors?: Array<{ message: string }>\n}\n\nexport interface Tokenizer {\n tokenize(options?: Record<string, unknown>): Promise<Result>\n /**\n * Cash App Pay and ACH deliver their token on an `ontokenization` EVENT rather\n * than from tokenize()'s return — the buyer leaves the page (for Cash App, or\n * for their bank's login) and comes back. A caller that only reads the return\n * value gets nothing from either, silently, on a payment that succeeded.\n */\n addEventListener?(event: string, handler: (e: unknown) => void): void\n /**\n * Optional because a WALLET may draw itself (Apple Pay does) rather than\n * attach. The second argument is Cash App Pay's button options\n * (`{ shape, width }`); every other rail ignores it.\n */\n attach?(selector: string, options?: Record<string, unknown>): Promise<void>\n destroy?(): Promise<void>\n}\n\n/**\n * The CARD element always attaches and always destroys — it owns an iframe. Kept\n * distinct from the wallet tokenizer so the card path is not forced to\n * optional-chain calls that can never be absent.\n */\nexport interface Element extends Tokenizer {\n attach(selector: string): Promise<void>\n destroy(): Promise<void>\n}\n\nexport interface Request {\n countryCode: string\n currencyCode: string\n total: { amount: string; label: string }\n}\n\nexport interface Payments {\n card(options?: Record<string, unknown>): Promise<Element>\n /**\n * A Square gift card. Its own constructor and its own container — a gift card\n * is a balance instrument, not a credit line, so Square keeps the two elements\n * separate rather than switching one on a brand.\n *\n * It attaches and tokenizes exactly like a card, with one difference that\n * matters to a caller: its `tokenize()` takes NO verification details. SCA\n * covers cards, and a gift card has no issuer to challenge.\n */\n giftCard(options?: Record<string, unknown>): Promise<Element>\n paymentRequest(req: Request): unknown\n applePay(req: unknown): Promise<Tokenizer>\n googlePay(req: unknown): Promise<Tokenizer>\n cashAppPay(req: unknown, opts: Record<string, unknown>): Promise<Tokenizer>\n /**\n * ACH bank transfer. Square runs PLAID'S instant bank authentication itself,\n * so this needs no Plaid account of ours — which is why the rail sat marked\n * \"coming soon\" for want of an integration that was already included. US-only.\n *\n * THE ARGUMENT IS OPTIONAL BECAUSE SQUARE ACCEPTS TWO SHAPES, and a type that\n * admitted only one would reject working code. The redirect form\n * (`ach({ redirectURI, transactionId })`, what this package calls and what\n * hanzoai/pay has run in production) sends the buyer to their bank and back;\n * Square's own quickstart instead calls `ach()` bare and passes\n * `{ accountHolderName, intent, amount, currency }` to `tokenize()`. Both\n * deliver the token the same way — on the event, never from the return.\n */\n ach(opts?: { redirectURI?: string; transactionId?: string }): Promise<Tokenizer>\n}\n\ninterface SDK {\n payments(appId: string, locationId: string): Promise<Payments>\n}\n\ndeclare global {\n interface Window {\n Square?: SDK\n }\n}\n\nexport const PRODUCTION = 'https://web.squarecdn.com/v1/square.js'\nexport const SANDBOX = 'https://sandbox.web.squarecdn.com/v1/square.js'\n\nlet pending: Promise<void> | null = null\n\n/** Load the SDK once per page, whichever surface asks first. */\nexport function load(src: string): Promise<void> {\n if (pending) return pending\n if (typeof window !== 'undefined' && window.Square) return (pending = Promise.resolve())\n pending = new Promise((resolve, reject) => {\n const existing = document.querySelector(`script[src=\"${src}\"]`)\n if (existing) {\n existing.addEventListener('load', () => resolve())\n existing.addEventListener('error', () => reject(new Error('Square SDK failed to load')))\n return\n }\n const s = document.createElement('script')\n s.src = src\n s.async = true\n s.onload = () => resolve()\n s.onerror = () => reject(new Error('Square SDK failed to load'))\n document.head.appendChild(s)\n })\n return pending\n}\n\n/** Drop the memoized load. For tests only — a page loads the SDK once. */\nexport function forget(): void {\n pending = null\n}\n","// A `Palette` rendered as the style object `payments.card({ style })` accepts.\n//\n// Measured against the live SDK, not read off a doc page. Where a comment says\n// \"confirmed\", it was driven and inspected.\n\nimport type { Palette } from '../palette'\n\n/** A selector's CSS property bag. */\nexport type Rules = Record<string, string>\n\n/**\n * What `payments.card({ style })` accepts: selector -> properties, plus `@media`\n * keys whose value is a nested block of the same. Square resolves the media query\n * INSIDE the iframe, which is the only way in — the frame cannot see the page's\n * own breakpoints.\n */\nexport type Style = Record<string, Rules | Record<string, Rules>>\n\n/**\n * One selector's properties.\n *\n * A `@media` key holds a nested block, so the map's value type is a union and\n * every read of a plain selector would otherwise need its own cast. This is that\n * cast, written once, where the shape is known.\n */\nexport function rules(style: Style, selector: string): Rules {\n return style[selector] as Rules\n}\n\n/** The input/button radius of @hanzo/design's ramp (--radius-sm), in the px Square expects. */\nconst RADIUS = '6px'\n\n/**\n * The style object for a card element painted in `p`.\n *\n * THE GROUND AND THE INK ARE ONE PALETTE, and that is the whole point. The field\n * shipped as `input.backgroundColor: 'transparent'` beside `input.color:\n * '#fafafa'` — so Square painted near-white digits onto its own default white\n * iframe and the customer's card number was invisible as they typed it. Two\n * properties, set in two places, describing one surface. Reading them off one\n * `Palette` is what stops them disagreeing again; `legible()` proves they have not.\n *\n * `input.backgroundColor` IS the supported way in, and it is not a workaround.\n * Square maps that one property onto the container behind the iframe rather than\n * onto the input:\n *\n * selectorPropertyMappings[input] = [{ property: 'backgroundColor',\n * toSelectors: ['#<id>.sq-card-wrapper .sq-card-iframe-container'] }]\n *\n * Measured, not read: driving build 1.84.0 with the object below and inspecting\n * the result gives `.sq-card-iframe-container { background-color: #0a0a0a }` in\n * the parent document, with `<body>`, `<html>` and all four inputs inside the\n * cross-origin frame computing to `rgba(0,0,0,0)`. The frame is transparent by\n * design and the container is the surface — so painting the container IS painting\n * the field.\n *\n * AND IT IS STILL NOT ENOUGH ON ITS OWN — see `pin()` below, which this package\n * applies for you. The declaration above lands and is then overpainted, so a\n * style object shipped without that rule is accepted and silently ignored. That\n * gap was misread once as an SDK bug to wait out; it was ours.\n *\n * `.input-container { backgroundColor }` is ALSO accepted on 1.84.0 (it throws\n * nothing). It is still not set: it would be a second way to paint the one\n * surface `input.backgroundColor` already paints, and the two could then\n * disagree. One property, one surface.\n *\n * `fontFamily` is deliberately absent: Square validates it against its own\n * loadable list and throws on both CSS-wide stacks and arbitrary names, which\n * blocks the iframe from attaching at all. Its default is a system sans, which is\n * what --font-sans resolves to anyway.\n */\nexport function style(p: Palette): Style {\n return {\n // Borders and radius ONLY. Square's allowlist for this selector is\n // borderColor/borderRadius/borderWidth; anything else throws \"Invalid style\n // property\", and Square rejects the WHOLE style object when it does — so one\n // stray property leaves the card unstyled and white.\n '.input-container': {\n borderColor: p.border,\n borderRadius: RADIUS,\n borderWidth: '1px',\n },\n '.input-container.is-focus': { borderColor: p.borderFocus },\n '.input-container.is-error': { borderColor: p.error },\n input: {\n backgroundColor: p.field,\n color: p.text,\n fontSize: '14px',\n },\n // 16px on a small screen, because iOS Safari ZOOMS the page when a field\n // under 16px takes focus — on a card form that throws the layout sideways\n // mid-number and there is no way to zoom back without losing the caret.\n // Square's own dark-mode example carries this media query for the same\n // reason; it is the one place the field may disagree with the page's ramp.\n '@media screen and (max-width: 600px)': {\n input: { fontSize: '16px' },\n },\n 'input::placeholder': { color: p.placeholder },\n 'input.is-error': { color: p.error },\n '.message-text': { color: p.placeholder },\n '.message-text.is-error': { color: p.error },\n '.message-icon': { color: p.placeholder },\n '.message-icon.is-error': { color: p.error },\n }\n}\n\n/**\n * The rule without which everything above is decoration.\n *\n * Square's `input.backgroundColor` is accepted and then IGNORED: the field\n * renders white on a black checkout no matter what is passed. The cause is not\n * Square's, and it is one property. A page that sets `color-scheme` on\n * `<html>` — which is the correct thing to do, and what every themed app does so\n * the UA paints scrollbars and form controls to match — leaks it INTO the\n * cross-origin iframe, because `color-scheme` inherits. Inside the frame the UA\n * then paints form controls on its own scheme background, over anything the SDK\n * declared. Pinning the container back to `auto` lets our ground through.\n *\n * (A Square forum user reported the same thing after two years of the docs' own\n * recipe not working. Same fix, arrived at from the other end.)\n *\n * THIS IS APPLIED FOR YOU, by `terminal()`, before any card can attach — it must\n * be in the stylesheet BEFORE the element mounts, because the container is styled\n * as the card attaches, and a rule added afterwards is a rule that arrived too\n * late. It is not left to a host to remember, and it is not a stylesheet a host\n * can forget to import: this package produced the style object, so this package\n * owes the one rule that makes it mean anything.\n *\n * Idempotent, and a no-op without a document. A host that already ships the rule\n * (hanzoai/pay does, in `index.css`) gets an identical declaration — same\n * property, same value — so there is nothing for the two to disagree about.\n */\nexport function pin(doc?: Document): void {\n const d = doc ?? (typeof document === 'undefined' ? undefined : document)\n if (!d) return\n const MARK = 'hanzo-pay-scheme'\n if (d.getElementById(MARK)) return\n const el = d.createElement('style')\n el.id = MARK\n el.textContent = '.sq-card-iframe-container{color-scheme:auto}'\n d.head.appendChild(el)\n}\n","// The Square Web Payments SDK behind the one `Terminal` interface.\n//\n// Every rail here mints the SAME single-use token, bound for the same server\n// call — so none of them needed server work beyond the one that already existed.\n// What they do NOT share is how the token arrives, and treating them as one shape\n// is a silent failure in three of the six:\n//\n// card, gift attach() into a container, then tokenize() returns the token.\n// google_pay attach() into a container Square draws its button in, then\n// tokenize() from our click.\n// apple_pay NO attach() — Square's docs are explicit. The host renders the\n// button (Apple's own CSS appearance), and tokenize() must be\n// called IMMEDIATELY in the click handler.\n// cash_app attach(), and then the token arrives on an `ontokenization`\n// EVENT after the buyer approves in the app or by QR. Calling\n// tokenize() ourselves does nothing and the payment never lands.\n// ach NO attach() — the flow IS tokenize(). The token likewise arrives\n// on `ontokenization`, because the buyer leaves for their bank.\n\nimport {\n type Detail,\n type Method,\n type Tender,\n type Terminal,\n type Token,\n Cancelled,\n REACH,\n} from '../terminal'\nimport { type Palette, PALETTE } from '../palette'\nimport { load, PRODUCTION, SANDBOX, type Payments, type Result, type Tokenizer } from './sdk'\nimport { pin, style } from './style'\n\n/** The org's PUBLIC Square config, exactly as commerce publishes it. */\nexport interface Config {\n applicationId: string\n locationId: string\n /** `'sandbox'` tokenizes against Square's sandbox. Anything else is production. */\n environment?: string\n /** The card field's colours. Defaults to @hanzo/design's dark table. */\n palette?: Palette\n}\n\n/** Rails that draw themselves into a container the host positions. */\nconst DRAWN: readonly Method[] = ['card', 'gift', 'google_pay', 'cash_app']\n\nfunction token(method: Method, r: Result): Token {\n // CANCEL is the buyer changing their mind. It is not a failure and must never\n // be shown as one.\n if (String(r.status).toUpperCase() === 'CANCEL') throw new Cancelled(method)\n if (r.status !== 'OK' || !r.token) {\n throw new Error(r.errors?.[0]?.message ?? `${method} could not be completed`)\n }\n return { value: r.token, method, card: r.details?.card }\n}\n\n/**\n * The token that arrives on an EVENT rather than from a return — Cash App Pay\n * and ACH, for the same underlying reason: the buyer leaves the page.\n *\n * One listener per collect(), removed when it settles. Square's own quickstart\n * registers a fresh listener on every submission and never removes one, so a\n * buyer who retries gets their token delivered to every previous attempt too.\n */\nfunction awaited(method: Method, t: Tokenizer): Promise<Token> {\n return new Promise<Token>((resolve, reject) => {\n t.addEventListener?.('ontokenization', (e: unknown) => {\n const detail = (e as { detail?: { tokenResult?: Result; error?: unknown } })?.detail\n if (detail?.error) {\n reject(new Error(`${method} could not be completed`))\n return\n }\n if (!detail?.tokenResult) return\n try {\n resolve(token(method, detail.tokenResult))\n } catch (err) {\n reject(err)\n }\n })\n })\n}\n\nclass Web implements Terminal {\n private readonly built = new Map<Method, Tokenizer>()\n private readonly palette: Palette\n\n constructor(\n private readonly payments: Payments,\n palette: Palette,\n ) {\n this.palette = palette\n }\n\n /**\n * Which rails can REALLY pay here, asked of the SDK one at a time.\n *\n * Building the rail IS the probe, and it is the only answer that accounts for\n * the browser, the device, the buyer's saved cards AND whether the merchant\n * account has the rail switched on. A rejection is a plain \"not here\", not an\n * error worth showing anyone.\n *\n * The built objects are KEPT, and that is load-bearing rather than a cache:\n * `collect('apple_pay')` must reach `tokenize()` with nothing awaited in front\n * of it, so the object it needs has to already exist by then.\n */\n async offers(tender: Tender): Promise<Method[]> {\n const out: Method[] = []\n for (const method of REACH.web) {\n try {\n this.built.set(method, await this.build(method, tender))\n out.push(method)\n } catch {\n // Not offerable here. Say nothing and show nothing.\n }\n }\n return out\n }\n\n private async build(method: Method, tender: Tender): Promise<Tokenizer> {\n if (method === 'card') return this.payments.card({ style: style(this.palette) })\n if (method === 'gift') return this.payments.giftCard()\n if (method === 'ach') {\n return this.payments.ach({\n // Square REJECTS a redirectURI carrying a query string, so the page's own\n // path is used and anything that must survive the trip travels separately.\n redirectURI:\n typeof window === 'undefined'\n ? ''\n : window.location.origin + window.location.pathname,\n transactionId: `${Date.now()}`,\n })\n }\n // A FRESH paymentRequest per wallet: Square binds it into the object it\n // builds, so two wallets sharing one request is one wallet quoting the\n // other's total.\n const req = this.payments.paymentRequest({\n countryCode: tender.country,\n currencyCode: tender.total.currency,\n total: { amount: tender.total.amount, label: tender.label },\n })\n if (method === 'apple_pay') return this.payments.applePay(req)\n if (method === 'google_pay') return this.payments.googlePay(req)\n return this.payments.cashAppPay(req, {\n // Where Cash App returns a MOBILE buyer. Desktop uses the QR and never\n // leaves the page.\n redirectURL: typeof window === 'undefined' ? '' : window.location.href,\n referenceId: `pay-${tender.total.amount}`,\n })\n }\n\n /** Draw a rail that renders inline. A rail that draws nothing is a no-op, not an error. */\n async mount(method: Method, target: string): Promise<void> {\n const t = this.built.get(method)\n if (!t) throw new Error(`${method} was not offered here`)\n if (!DRAWN.includes(method)) return\n await t.attach?.(target)\n }\n\n /**\n * Tokenize.\n *\n * NOTHING IS AWAITED BEFORE `tokenize()`. Apple refuses a payment sheet that\n * was not opened directly by the gesture that asked for it, so a single `await`\n * placed above the call — loading the SDK, looking a rail up asynchronously,\n * re-reading a total — silently breaks Apple Pay and nothing else. That is why\n * `offers()` builds every rail up front and this only reads a map.\n */\n collect(method: Method, tender: Tender, detail?: Detail): Promise<Token> {\n const t = this.built.get(method)\n if (!t) return Promise.reject(new Error(`${method} was not offered here`))\n\n // Cash App draws its own button and delivers on the event; a tokenize() call\n // here does nothing at all.\n if (method === 'cash_app') return awaited(method, t)\n\n if (method === 'ach') {\n if (!detail?.name) {\n return Promise.reject(new Error('ACH needs the account holder’s name'))\n }\n const arrived = awaited(method, t)\n // The flow IS tokenize() for ACH — it opens the bank login — but the token\n // comes back on the event, so the return value is deliberately dropped.\n void t.tokenize({\n accountHolderName: detail.name,\n intent: 'CHARGE',\n amount: tender.total.amount,\n currency: tender.total.currency,\n })\n return arrived\n }\n\n return t.tokenize(detail?.verify).then((r) => token(method, r))\n }\n\n async release(): Promise<void> {\n // Square's `destroy()` empties the mount node when it resolves, so a create\n // that overlaps a destroy gets its fresh iframe swept away by the old\n // element's cleanup — the form then sits empty forever with no error to\n // explain it. Callers await this before building the next terminal, which is\n // what makes a theme change survivable.\n await Promise.all([...this.built.values()].map((t) => t.destroy?.().catch(() => undefined)))\n this.built.clear()\n }\n}\n\n/**\n * A terminal for this browser. Loads the SDK once per page, whichever surface\n * asks first.\n *\n * The palette is fixed at construction because SQUARE HAS NO API TO RESTYLE A\n * LIVE CARD — the fields are a cross-origin iframe, told their colours once, at\n * creation. Following a theme change therefore means `release()` then a new\n * terminal, and making the palette a constructor argument is what forces that to\n * happen by construction rather than by remembering to.\n */\nexport async function terminal(config: Config): Promise<Terminal> {\n if (!config.applicationId || !config.locationId) {\n throw new Error('Square is not configured for this deployment')\n }\n // BEFORE the SDK loads, let alone attaches. The container is styled as the\n // card mounts, so this rule has to already be in the sheet by then — see\n // `pin()`. Doing it here is what makes it impossible to ship the style object\n // without the one rule that makes it visible.\n pin()\n const src = (config.environment ?? 'production').toLowerCase() === 'sandbox' ? SANDBOX : PRODUCTION\n await load(src)\n if (typeof window === 'undefined' || !window.Square) {\n throw new Error('Square SDK failed to load')\n }\n const payments = await window.Square.payments(config.applicationId, config.locationId)\n return new Web(payments, config.palette ?? PALETTE.dark)\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/web/sdk.ts","../../src/web/style.ts","../../src/web/terminal.ts"],"names":["style"],"mappings":"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","file":"index.js","sourcesContent":["// The Square Web Payments SDK, as much of it as we actually call, typed.\n//\n// Square ships no types for the global it installs, so this is the contract we\n// hold it to. Every member below is exercised by this package; nothing is\n// declared speculatively.\n\n/** What `tokenize()` resolves with. `status` is `'OK'` | `'CANCEL'` | an error. */\nexport interface Result {\n status: string\n token?: string\n details?: {\n card?: { brand?: string; last4?: string; expMonth?: number; expYear?: number }\n }\n errors?: Array<{ message: string }>\n}\n\nexport interface Tokenizer {\n tokenize(options?: Record<string, unknown>): Promise<Result>\n /**\n * Cash App Pay and ACH deliver their token on an `ontokenization` EVENT rather\n * than from tokenize()'s return — the buyer leaves the page (for Cash App, or\n * for their bank's login) and comes back. A caller that only reads the return\n * value gets nothing from either, silently, on a payment that succeeded.\n */\n addEventListener?(event: string, handler: (e: unknown) => void): void\n /**\n * Optional because a WALLET may draw itself (Apple Pay does) rather than\n * attach. The second argument is Cash App Pay's button options\n * (`{ shape, width }`); every other rail ignores it.\n */\n attach?(selector: string, options?: Record<string, unknown>): Promise<void>\n destroy?(): Promise<void>\n}\n\n/**\n * The CARD element always attaches and always destroys — it owns an iframe. Kept\n * distinct from the wallet tokenizer so the card path is not forced to\n * optional-chain calls that can never be absent.\n */\nexport interface Element extends Tokenizer {\n attach(selector: string): Promise<void>\n destroy(): Promise<void>\n}\n\nexport interface Request {\n countryCode: string\n currencyCode: string\n total: { amount: string; label: string }\n}\n\nexport interface Payments {\n card(options?: Record<string, unknown>): Promise<Element>\n /**\n * A Square gift card. Its own constructor and its own container — a gift card\n * is a balance instrument, not a credit line, so Square keeps the two elements\n * separate rather than switching one on a brand.\n *\n * It attaches and tokenizes exactly like a card, with one difference that\n * matters to a caller: its `tokenize()` takes NO verification details. SCA\n * covers cards, and a gift card has no issuer to challenge.\n */\n giftCard(options?: Record<string, unknown>): Promise<Element>\n paymentRequest(req: Request): unknown\n applePay(req: unknown): Promise<Tokenizer>\n googlePay(req: unknown): Promise<Tokenizer>\n cashAppPay(req: unknown, opts: Record<string, unknown>): Promise<Tokenizer>\n /**\n * ACH bank transfer. Square runs PLAID'S instant bank authentication itself,\n * so this needs no Plaid account of ours — which is why the rail sat marked\n * \"coming soon\" for want of an integration that was already included. US-only.\n *\n * THE ARGUMENT IS OPTIONAL BECAUSE SQUARE ACCEPTS TWO SHAPES, and a type that\n * admitted only one would reject working code. The redirect form\n * (`ach({ redirectURI, transactionId })`, what this package calls and what\n * hanzoai/pay has run in production) sends the buyer to their bank and back;\n * Square's own quickstart instead calls `ach()` bare and passes\n * `{ accountHolderName, intent, amount, currency }` to `tokenize()`. Both\n * deliver the token the same way — on the event, never from the return.\n */\n ach(opts?: { redirectURI?: string; transactionId?: string }): Promise<Tokenizer>\n}\n\ninterface SDK {\n payments(appId: string, locationId: string): Promise<Payments>\n}\n\ndeclare global {\n interface Window {\n Square?: SDK\n }\n}\n\nexport const PRODUCTION = 'https://web.squarecdn.com/v1/square.js'\nexport const SANDBOX = 'https://sandbox.web.squarecdn.com/v1/square.js'\n\nlet pending: Promise<void> | null = null\n\n/** Load the SDK once per page, whichever surface asks first. */\nexport function load(src: string): Promise<void> {\n if (pending) return pending\n if (typeof window !== 'undefined' && window.Square) return (pending = Promise.resolve())\n pending = new Promise((resolve, reject) => {\n const existing = document.querySelector(`script[src=\"${src}\"]`)\n if (existing) {\n existing.addEventListener('load', () => resolve())\n existing.addEventListener('error', () => reject(new Error('Square SDK failed to load')))\n return\n }\n const s = document.createElement('script')\n s.src = src\n s.async = true\n s.onload = () => resolve()\n s.onerror = () => reject(new Error('Square SDK failed to load'))\n document.head.appendChild(s)\n })\n return pending\n}\n\n/** Drop the memoized load. For tests only — a page loads the SDK once. */\nexport function forget(): void {\n pending = null\n}\n","// A `Palette` rendered as the style object `payments.card({ style })` accepts.\n//\n// Measured against the live SDK, not read off a doc page. Where a comment says\n// \"confirmed\", it was driven and inspected.\n\nimport type { Palette } from '../palette'\n\n/** A selector's CSS property bag. */\nexport type Rules = Record<string, string>\n\n/**\n * What `payments.card({ style })` accepts: selector -> properties, plus `@media`\n * keys whose value is a nested block of the same. Square resolves the media query\n * INSIDE the iframe, which is the only way in — the frame cannot see the page's\n * own breakpoints.\n */\nexport type Style = Record<string, Rules | Record<string, Rules>>\n\n/**\n * One selector's properties.\n *\n * A `@media` key holds a nested block, so the map's value type is a union and\n * every read of a plain selector would otherwise need its own cast. This is that\n * cast, written once, where the shape is known.\n */\nexport function rules(style: Style, selector: string): Rules {\n return style[selector] as Rules\n}\n\n/** The input/button radius of @hanzo/design's ramp (--radius-sm), in the px Square expects. */\nconst RADIUS = '6px'\n\n/**\n * The body size of @hanzo/design's ramp (--text-sm), which is what a checkout's\n * own inputs render at. It was 14px — one rung above every field beside it, so\n * the card strip's placeholders were visibly larger than the address labels.\n */\nconst SIZE = '13px'\n\n/**\n * The ONE focus indicator, drawn around the container in `pin()`.\n *\n * These two values are the host's, not a choice made here: they are what\n * `:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px }`\n * renders, which is the rule a Hanzo surface already applies to every button,\n * link and input on the page. The card strip is the one control that cannot\n * inherit it — the fields are in a frame that has no idea a page exists — so it\n * is restated here, once, in the same numbers.\n */\nconst RING = '2px'\nconst RING_GAP = '2px'\n\n/**\n * The style object for a card element painted in `p`.\n *\n * THE GROUND AND THE INK ARE ONE PALETTE, and that is the whole point. The field\n * shipped as `input.backgroundColor: 'transparent'` beside `input.color:\n * '#fafafa'` — so Square painted near-white digits onto its own default white\n * iframe and the customer's card number was invisible as they typed it. Two\n * properties, set in two places, describing one surface. Reading them off one\n * `Palette` is what stops them disagreeing again; `legible()` proves they have not.\n *\n * `input.backgroundColor` IS the supported way in, and it is not a workaround.\n * Square maps that one property onto the container behind the iframe rather than\n * onto the input:\n *\n * selectorPropertyMappings[input] = [{ property: 'backgroundColor',\n * toSelectors: ['#<id>.sq-card-wrapper .sq-card-iframe-container'] }]\n *\n * Measured, not read: driving build 1.84.0 with the object below and inspecting\n * the result gives `.sq-card-iframe-container { background-color: #0a0a0a }` in\n * the parent document, with `<body>`, `<html>` and all four inputs inside the\n * cross-origin frame computing to `rgba(0,0,0,0)`. The frame is transparent by\n * design and the container is the surface — so painting the container IS painting\n * the field.\n *\n * AND IT IS STILL NOT ENOUGH ON ITS OWN — see `pin()` below, which this package\n * applies for you. The declaration above lands and is then overpainted, so a\n * style object shipped without that rule is accepted and silently ignored. That\n * gap was misread once as an SDK bug to wait out; it was ours.\n *\n * `.input-container { backgroundColor }` is ALSO accepted on 1.84.0 (it throws\n * nothing). It is still not set: it would be a second way to paint the one\n * surface `input.backgroundColor` already paints, and the two could then\n * disagree. One property, one surface.\n *\n * `fontFamily` is deliberately absent: Square validates it against its own\n * loadable list and throws on both CSS-wide stacks and arbitrary names, which\n * blocks the iframe from attaching at all. Its default is a system sans, which is\n * what --font-sans resolves to anyway.\n */\nexport function style(p: Palette): Style {\n return {\n // Borders and radius ONLY. Square's allowlist for this selector is\n // borderColor/borderRadius/borderWidth; anything else throws \"Invalid style\n // property\", and Square rejects the WHOLE style object when it does — so one\n // stray property leaves the card unstyled and white.\n '.input-container': {\n borderColor: p.border,\n borderRadius: RADIUS,\n borderWidth: '1px',\n },\n // THE HAIRLINE DOES NOT MOVE ON FOCUS, and that is the point. Square spends\n // this value twice — on the container's border AND on a `::before` ring at\n // `inset: -1px` — so any colour but the resting one puts a second ring\n // immediately inside the one `pin()` draws. Two rings for one event is the\n // exact defect this pass removes from the address fields; the card strip\n // must not reintroduce it one row lower.\n '.input-container.is-focus': { borderColor: p.border },\n '.input-container.is-error': { borderColor: p.error },\n input: {\n backgroundColor: p.field,\n color: p.text,\n fontSize: SIZE,\n },\n // 16px on a small screen, because iOS Safari ZOOMS the page when a field\n // under 16px takes focus — on a card form that throws the layout sideways\n // mid-number and there is no way to zoom back without losing the caret.\n // Square's own dark-mode example carries this media query for the same\n // reason; it is the one place the field may disagree with the page's ramp.\n '@media screen and (max-width: 600px)': {\n input: { fontSize: '16px' },\n },\n 'input::placeholder': { color: p.placeholder },\n 'input.is-error': { color: p.error },\n '.message-text': { color: p.placeholder },\n '.message-text.is-error': { color: p.error },\n '.message-icon': { color: p.placeholder },\n '.message-icon.is-error': { color: p.error },\n }\n}\n\n/**\n * The two rules Square's own style object cannot express.\n *\n * Square's `input.backgroundColor` is accepted and then IGNORED: the field\n * renders white on a black checkout no matter what is passed. The cause is not\n * Square's, and it is one property. A page that sets `color-scheme` on\n * `<html>` — which is the correct thing to do, and what every themed app does so\n * the UA paints scrollbars and form controls to match — leaks it INTO the\n * cross-origin iframe, because `color-scheme` inherits. Inside the frame the UA\n * then paints form controls on its own scheme background, over anything the SDK\n * declared. Pinning the container back to `auto` lets our ground through.\n *\n * (A Square forum user reported the same thing after two years of the docs' own\n * recipe not working. Same fix, arrived at from the other end.)\n *\n * THIS IS APPLIED FOR YOU, by `terminal()`, before any card can attach — it must\n * be in the stylesheet BEFORE the element mounts, because the container is styled\n * as the card attaches, and a rule added afterwards is a rule that arrived too\n * late. It is not left to a host to remember, and it is not a stylesheet a host\n * can forget to import: this package produced the style object, so this package\n * owes the one rule that makes it mean anything.\n *\n * THE SECOND RULE IS THE FOCUS RING, and it is here for the mirror-image reason:\n * an outline has to be drawn OUTSIDE the field, and everything Square will style\n * for us lives inside a frame that ends at the field's edge. The seam is\n * `sq-focus`, which Square adds to `.sq-card-wrapper` in THIS document while a\n * card input holds focus — the class its own `focusClassAdded`/`focusClassRemoved`\n * events are named for. So the ring is plain CSS: no listener, no state, nothing\n * to unsubscribe, and it cannot drift out of step with the field it surrounds.\n *\n * Measured on 1.84.0: Square generates\n * `#<wrapper>.sq-focus .sq-card-iframe-container::before { inset: -1px }` as its\n * own focus ring, which is why `style()` paints the focused hairline the resting\n * colour — that pseudo-ring then has nothing to draw and this is the only ring\n * the buyer sees.\n *\n * A no-op without a document. Called again with a different palette (a theme\n * change) it REWRITES its rules rather than bailing: there is one element, always\n * carrying the current colours. Bailing early would have left the ring painted in\n * the palette of a theme the page had already left.\n */\nexport function pin(p: Palette, doc?: Document): void {\n const d = doc ?? (typeof document === 'undefined' ? undefined : document)\n if (!d) return\n const MARK = 'hanzo-pay-scheme'\n let el = d.getElementById(MARK) as HTMLStyleElement | null\n if (!el) {\n el = d.createElement('style')\n el.id = MARK\n d.head.appendChild(el)\n }\n el.textContent =\n '.sq-card-iframe-container{color-scheme:auto}' +\n `.sq-card-wrapper.sq-focus .sq-card-iframe-container{outline:${RING} solid ${p.ring};outline-offset:${RING_GAP}}`\n}\n","// The Square Web Payments SDK behind the one `Terminal` interface.\n//\n// Every rail here mints the SAME single-use token, bound for the same server\n// call — so none of them needed server work beyond the one that already existed.\n// What they do NOT share is how the token arrives, and treating them as one shape\n// is a silent failure in three of the six:\n//\n// card, gift attach() into a container, then tokenize() returns the token.\n// google_pay attach() into a container Square draws its button in, then\n// tokenize() from our click.\n// apple_pay NO attach() — Square's docs are explicit. The host renders the\n// button (Apple's own CSS appearance), and tokenize() must be\n// called IMMEDIATELY in the click handler.\n// cash_app attach(), and then the token arrives on an `ontokenization`\n// EVENT after the buyer approves in the app or by QR. Calling\n// tokenize() ourselves does nothing and the payment never lands.\n// ach NO attach() — the flow IS tokenize(). The token likewise arrives\n// on `ontokenization`, because the buyer leaves for their bank.\n\nimport {\n type Detail,\n type Method,\n type Tender,\n type Terminal,\n type Token,\n Cancelled,\n REACH,\n} from '../terminal'\nimport { type Palette, PALETTE } from '../palette'\nimport { load, PRODUCTION, SANDBOX, type Payments, type Result, type Tokenizer } from './sdk'\nimport { pin, style } from './style'\n\n/** The org's PUBLIC Square config, exactly as commerce publishes it. */\nexport interface Config {\n applicationId: string\n locationId: string\n /** `'sandbox'` tokenizes against Square's sandbox. Anything else is production. */\n environment?: string\n /** The card field's colours. Defaults to @hanzo/design's dark table. */\n palette?: Palette\n}\n\n/** Rails that draw themselves into a container the host positions. */\nconst DRAWN: readonly Method[] = ['card', 'gift', 'google_pay', 'cash_app']\n\nfunction token(method: Method, r: Result): Token {\n // CANCEL is the buyer changing their mind. It is not a failure and must never\n // be shown as one.\n if (String(r.status).toUpperCase() === 'CANCEL') throw new Cancelled(method)\n if (r.status !== 'OK' || !r.token) {\n throw new Error(r.errors?.[0]?.message ?? `${method} could not be completed`)\n }\n return { value: r.token, method, card: r.details?.card }\n}\n\n/**\n * The token that arrives on an EVENT rather than from a return — Cash App Pay\n * and ACH, for the same underlying reason: the buyer leaves the page.\n *\n * One listener per collect(), removed when it settles. Square's own quickstart\n * registers a fresh listener on every submission and never removes one, so a\n * buyer who retries gets their token delivered to every previous attempt too.\n */\nfunction awaited(method: Method, t: Tokenizer): Promise<Token> {\n return new Promise<Token>((resolve, reject) => {\n t.addEventListener?.('ontokenization', (e: unknown) => {\n const detail = (e as { detail?: { tokenResult?: Result; error?: unknown } })?.detail\n if (detail?.error) {\n reject(new Error(`${method} could not be completed`))\n return\n }\n if (!detail?.tokenResult) return\n try {\n resolve(token(method, detail.tokenResult))\n } catch (err) {\n reject(err)\n }\n })\n })\n}\n\nclass Web implements Terminal {\n private readonly built = new Map<Method, Tokenizer>()\n private readonly palette: Palette\n\n constructor(\n private readonly payments: Payments,\n palette: Palette,\n ) {\n this.palette = palette\n }\n\n /**\n * Which rails can REALLY pay here, asked of the SDK one at a time.\n *\n * Building the rail IS the probe, and it is the only answer that accounts for\n * the browser, the device, the buyer's saved cards AND whether the merchant\n * account has the rail switched on. A rejection is a plain \"not here\", not an\n * error worth showing anyone.\n *\n * The built objects are KEPT, and that is load-bearing rather than a cache:\n * `collect('apple_pay')` must reach `tokenize()` with nothing awaited in front\n * of it, so the object it needs has to already exist by then.\n */\n async offers(tender: Tender): Promise<Method[]> {\n const out: Method[] = []\n for (const method of REACH.web) {\n try {\n this.built.set(method, await this.build(method, tender))\n out.push(method)\n } catch {\n // Not offerable here. Say nothing and show nothing.\n }\n }\n return out\n }\n\n private async build(method: Method, tender: Tender): Promise<Tokenizer> {\n if (method === 'card') return this.payments.card({ style: style(this.palette) })\n if (method === 'gift') return this.payments.giftCard()\n if (method === 'ach') {\n return this.payments.ach({\n // Square REJECTS a redirectURI carrying a query string, so the page's own\n // path is used and anything that must survive the trip travels separately.\n redirectURI:\n typeof window === 'undefined'\n ? ''\n : window.location.origin + window.location.pathname,\n transactionId: `${Date.now()}`,\n })\n }\n // A FRESH paymentRequest per wallet: Square binds it into the object it\n // builds, so two wallets sharing one request is one wallet quoting the\n // other's total.\n const req = this.payments.paymentRequest({\n countryCode: tender.country,\n currencyCode: tender.total.currency,\n total: { amount: tender.total.amount, label: tender.label },\n })\n if (method === 'apple_pay') return this.payments.applePay(req)\n if (method === 'google_pay') return this.payments.googlePay(req)\n return this.payments.cashAppPay(req, {\n // Where Cash App returns a MOBILE buyer. Desktop uses the QR and never\n // leaves the page.\n redirectURL: typeof window === 'undefined' ? '' : window.location.href,\n referenceId: `pay-${tender.total.amount}`,\n })\n }\n\n /** Draw a rail that renders inline. A rail that draws nothing is a no-op, not an error. */\n async mount(method: Method, target: string): Promise<void> {\n const t = this.built.get(method)\n if (!t) throw new Error(`${method} was not offered here`)\n if (!DRAWN.includes(method)) return\n await t.attach?.(target)\n }\n\n /**\n * Tokenize.\n *\n * NOTHING IS AWAITED BEFORE `tokenize()`. Apple refuses a payment sheet that\n * was not opened directly by the gesture that asked for it, so a single `await`\n * placed above the call — loading the SDK, looking a rail up asynchronously,\n * re-reading a total — silently breaks Apple Pay and nothing else. That is why\n * `offers()` builds every rail up front and this only reads a map.\n */\n collect(method: Method, tender: Tender, detail?: Detail): Promise<Token> {\n const t = this.built.get(method)\n if (!t) return Promise.reject(new Error(`${method} was not offered here`))\n\n // Cash App draws its own button and delivers on the event; a tokenize() call\n // here does nothing at all.\n if (method === 'cash_app') return awaited(method, t)\n\n if (method === 'ach') {\n if (!detail?.name) {\n return Promise.reject(new Error('ACH needs the account holder’s name'))\n }\n const arrived = awaited(method, t)\n // The flow IS tokenize() for ACH — it opens the bank login — but the token\n // comes back on the event, so the return value is deliberately dropped.\n void t.tokenize({\n accountHolderName: detail.name,\n intent: 'CHARGE',\n amount: tender.total.amount,\n currency: tender.total.currency,\n })\n return arrived\n }\n\n return t.tokenize(detail?.verify).then((r) => token(method, r))\n }\n\n async release(): Promise<void> {\n // Square's `destroy()` empties the mount node when it resolves, so a create\n // that overlaps a destroy gets its fresh iframe swept away by the old\n // element's cleanup — the form then sits empty forever with no error to\n // explain it. Callers await this before building the next terminal, which is\n // what makes a theme change survivable.\n await Promise.all([...this.built.values()].map((t) => t.destroy?.().catch(() => undefined)))\n this.built.clear()\n }\n}\n\n/**\n * A terminal for this browser. Loads the SDK once per page, whichever surface\n * asks first.\n *\n * The palette is fixed at construction because SQUARE HAS NO API TO RESTYLE A\n * LIVE CARD — the fields are a cross-origin iframe, told their colours once, at\n * creation. Following a theme change therefore means `release()` then a new\n * terminal, and making the palette a constructor argument is what forces that to\n * happen by construction rather than by remembering to.\n */\nexport async function terminal(config: Config): Promise<Terminal> {\n if (!config.applicationId || !config.locationId) {\n throw new Error('Square is not configured for this deployment')\n }\n const palette = config.palette ?? PALETTE.dark\n // BEFORE the SDK loads, let alone attaches. The container is styled as the\n // card mounts, so these rules have to already be in the sheet by then — see\n // `pin()`. Doing it here is what makes it impossible to ship the style object\n // without the rules that make it visible.\n pin(palette)\n const src = (config.environment ?? 'production').toLowerCase() === 'sandbox' ? SANDBOX : PRODUCTION\n await load(src)\n if (typeof window === 'undefined' || !window.Square) {\n throw new Error('Square SDK failed to load')\n }\n const payments = await window.Square.payments(config.applicationId, config.locationId)\n return new Web(payments, palette)\n}\n"]}
|
package/dist/web/style.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export declare function rules(style: Style, selector: string): Rules;
|
|
|
57
57
|
*/
|
|
58
58
|
export declare function style(p: Palette): Style;
|
|
59
59
|
/**
|
|
60
|
-
* The
|
|
60
|
+
* The two rules Square's own style object cannot express.
|
|
61
61
|
*
|
|
62
62
|
* Square's `input.backgroundColor` is accepted and then IGNORED: the field
|
|
63
63
|
* renders white on a black checkout no matter what is passed. The cause is not
|
|
@@ -78,8 +78,23 @@ export declare function style(p: Palette): Style;
|
|
|
78
78
|
* can forget to import: this package produced the style object, so this package
|
|
79
79
|
* owes the one rule that makes it mean anything.
|
|
80
80
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
81
|
+
* THE SECOND RULE IS THE FOCUS RING, and it is here for the mirror-image reason:
|
|
82
|
+
* an outline has to be drawn OUTSIDE the field, and everything Square will style
|
|
83
|
+
* for us lives inside a frame that ends at the field's edge. The seam is
|
|
84
|
+
* `sq-focus`, which Square adds to `.sq-card-wrapper` in THIS document while a
|
|
85
|
+
* card input holds focus — the class its own `focusClassAdded`/`focusClassRemoved`
|
|
86
|
+
* events are named for. So the ring is plain CSS: no listener, no state, nothing
|
|
87
|
+
* to unsubscribe, and it cannot drift out of step with the field it surrounds.
|
|
88
|
+
*
|
|
89
|
+
* Measured on 1.84.0: Square generates
|
|
90
|
+
* `#<wrapper>.sq-focus .sq-card-iframe-container::before { inset: -1px }` as its
|
|
91
|
+
* own focus ring, which is why `style()` paints the focused hairline the resting
|
|
92
|
+
* colour — that pseudo-ring then has nothing to draw and this is the only ring
|
|
93
|
+
* the buyer sees.
|
|
94
|
+
*
|
|
95
|
+
* A no-op without a document. Called again with a different palette (a theme
|
|
96
|
+
* change) it REWRITES its rules rather than bailing: there is one element, always
|
|
97
|
+
* carrying the current colours. Bailing early would have left the ring painted in
|
|
98
|
+
* the palette of a theme the page had already left.
|
|
84
99
|
*/
|
|
85
|
-
export declare function pin(doc?: Document): void;
|
|
100
|
+
export declare function pin(p: Palette, doc?: Document): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/pay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Square payments for web and native behind one interface. Card, Apple Pay, Google Pay, Cash App Pay, ACH and gift cards, feature-detected — with one colour table that dresses the web iframe and the native sheet alike.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/terminal.ts","../src/palette.ts"],"names":[],"mappings":";;;AAuJO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAC1B,MAAA;AAAA,EACT,YAAY,MAAA,EAAgB;AAC1B,IAAA,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,cAAA,CAAgB,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AACF;AAGO,SAAS,UAAU,CAAA,EAA4B;AACpD,EAAA,OAAO,CAAA,YAAa,SAAA;AACtB;AAuBO,IAAM,KAAA,GAAqD;AAAA,EAChE,KAAK,CAAC,MAAA,EAAQ,aAAa,YAAA,EAAc,UAAA,EAAY,OAAO,MAAM,CAAA;AAAA,EAClE,MAAA,EAAQ,CAAC,MAAA,EAAQ,WAAA,EAAa,cAAc,MAAM;AACpD;;;ACtIO,IAAM,OAAA,GAAkC;AAAA,EAC7C,IAAA,EAAM;AAAA,IACJ,KAAA,EAAO,SAAA;AAAA;AAAA,IACP,MAAA,EAAQ,SAAA;AAAA;AAAA,IACR,WAAA,EAAa,SAAA;AAAA;AAAA,IACb,IAAA,EAAM,SAAA;AAAA;AAAA,IACN,WAAA,EAAa,SAAA;AAAA;AAAA,IACb,KAAA,EAAO;AAAA;AAAA,GACT;AAAA,EACA,KAAA,EAAO;AAAA,IACL,KAAA,EAAO,SAAA;AAAA;AAAA,IACP,MAAA,EAAQ,SAAA;AAAA;AAAA,IACR,WAAA,EAAa,SAAA;AAAA;AAAA,IACb,IAAA,EAAM,SAAA;AAAA;AAAA,IACN,WAAA,EAAa,SAAA;AAAA;AAAA,IACb,KAAA,EAAO;AAAA;AAAA;AAEX;AAGO,IAAM,GAAA,GAAM;AAGZ,SAAS,UAAU,GAAA,EAAqB;AAC7C,EAAA,MAAM,CAAA,GAAI,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM;AAC7B,IAAA,MAAM,CAAA,GAAI,SAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAC9C,IAAA,OAAO,KAAK,OAAA,GAAU,CAAA,GAAI,KAAA,GAAA,CAAA,CAAU,CAAA,GAAI,SAAS,KAAA,KAAU,GAAA;AAAA,EAC7D,CAAC,CAAA;AACD,EAAA,OAAO,MAAA,GAAS,CAAA,CAAE,CAAC,CAAA,GAAI,MAAA,GAAS,EAAE,CAAC,CAAA,GAAI,MAAA,GAAS,CAAA,CAAE,CAAC,CAAA;AACrD;AAGO,SAAS,KAAA,CAAM,GAAW,CAAA,EAAmB;AAClD,EAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,CAAC,UAAU,CAAC,CAAA,EAAG,SAAA,CAAU,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,IAAI,CAAC,CAAA;AAChE,EAAA,OAAA,CAAQ,CAAA,GAAI,SAAS,CAAA,GAAI,IAAA,CAAA;AAC3B;AAkBO,SAAS,QAAQ,CAAA,EAAqB;AAC3C,EAAA,MAAM,KAAA,GAAiD;AAAA;AAAA,IAErD,CAAC,eAAA,EAAiB,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,OAAO,GAAG,CAAA;AAAA;AAAA,IAEtC,CAAC,sBAAA,EAAwB,CAAA,CAAE,WAAA,EAAa,CAAA,CAAE,OAAO,GAAG,CAAA;AAAA;AAAA,IAEpD,CAAC,sBAAA,EAAwB,CAAA,CAAE,WAAA,EAAa,CAAA,CAAE,OAAO,CAAC,CAAA;AAAA;AAAA;AAAA,IAGlD,CAAC,gBAAA,EAAkB,CAAA,CAAE,KAAA,EAAO,CAAA,CAAE,OAAO,CAAC;AAAA,GACxC;AACA,EAAA,OAAO,KAAA,CACJ,GAAA,CAAI,CAAC,CAAC,IAAA,EAAM,GAAG,CAAA,EAAG,KAAK,CAAA,MAAO,EAAE,IAAA,EAAM,KAAA,EAAO,MAAM,CAAA,EAAG,CAAC,CAAA,EAAG,KAAA,EAAM,CAAE,CAAA,CAClE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAA;AACpC","file":"chunk-64AUOMEL.cjs","sourcesContent":["// What a checkout asks for, what it gets back, and the one thing that turns the\n// first into the second. Platform-agnostic: no DOM, no react-native, no Square.\n//\n// THIS PACKAGE NEVER MOVES MONEY. Every rail below ends at a single-use token,\n// and the charge is a server call the host makes with that token. Nothing here\n// completes a payment, which is why the noun is `Tender` — an OFFER of payment,\n// the term Square's own Orders API uses — and not `Charge`, which would name\n// something this code cannot do.\n\n/**\n * A Square rail.\n *\n * `gift` is a Square gift card, which tokenizes like a card but through its own\n * constructor on both platforms. Non-Square rails a host may also offer (wire,\n * crypto, another processor) are deliberately absent: this package knows Square,\n * and a host that knows more merges its own set with what `offers()` returns.\n */\nexport type Method = 'card' | 'apple_pay' | 'google_pay' | 'cash_app' | 'ach' | 'gift'\n\n/**\n * Money as a DECIMAL STRING, never a number.\n *\n * `0.1 + 0.2` is `0.30000000000000004`, and a float that reaches a payment\n * processor as a total is a rounding error someone is charged. Square's own\n * paymentRequest takes `amount` as a string for this reason; so does this.\n */\nexport interface Money {\n /** Decimal string in major units, e.g. `\"12.00\"`. Never a float. */\n amount: string\n /** ISO 4217, e.g. `\"USD\"`. */\n currency: string\n}\n\n/** An offer of payment: what is being paid, where, and what the buyer is shown. */\nexport interface Tender {\n total: Money\n /** ISO 3166-1 alpha-2 of the MERCHANT, e.g. `\"US\"`. Square keys wallets off it. */\n country: string\n /** The line the wallet sheet shows the buyer, e.g. `\"Hanzo AI credit\"`. */\n label: string\n}\n\n/**\n * A single-use payment token, and the only thing any rail here produces.\n *\n * It is single-use in the strict sense: spending it — charging it OR vaulting it\n * — consumes it. A flow that needs both a charge and a card-on-file must\n * tokenize TWICE; sharing one token between the two fails with the card already\n * accepted, which reads as a decline.\n */\nexport interface Token {\n value: string\n method: Method\n /** Present when the rail reported it. Absent means never reported, not empty. */\n card?: { brand?: string; last4?: string; expMonth?: number; expYear?: number }\n}\n\n/**\n * Rail-specific facts a `Tender` cannot carry, because only one rail needs each.\n * Every field is ignored by the rails it does not belong to.\n */\nexport interface Detail {\n /**\n * ACH only (web), and REQUIRED there: Square matches it against the bank\n * record. Collected from the buyer rather than derived from the account,\n * because the account holder and the person paying are not always the same\n * name.\n */\n name?: string\n /**\n * Card and gift only. Passed straight to Square as verification details —\n * `{ intent, amount, currencyCode, billingContact, customerInitiated,\n * sellerKeyedIn }` — which is how SCA is satisfied in mandated regions.\n * Opaque on purpose: Square owns this shape, and re-declaring it here would\n * be a second copy to drift.\n */\n verify?: Record<string, unknown>\n /**\n * NATIVE ONLY, and the one real asymmetry between the platforms.\n *\n * Square's native card sheet is a state machine, not a form: it STAYS OPEN\n * after minting the token, showing a spinner, until it is told whether the\n * charge worked. Told yes, it closes. Told no, it shows the message ON the\n * sheet with the card still entered, so the buyer retries in place instead of\n * being dropped back to a checkout with an empty form and a decline notice.\n *\n * That is a better decline experience than the web can offer, and it exists\n * only if the host charges DURING the sheet — which is what this is. Given the\n * token, run the server call; return to close the sheet, throw to keep it open\n * with the thrown message shown.\n *\n * Omit it and the sheet closes as soon as the token exists, which is what the\n * web does. Nothing hangs either way.\n *\n * The web backend ignores this: its card fields are a page element with no\n * sheet to hold open.\n */\n charge?(token: Token): Promise<void>\n}\n\n/**\n * The one interface both backends satisfy: web (Square Web Payments SDK) and\n * native (Square In-App Payments SDK). A host writes against this and the\n * platform picks the implementation — see `./web` and `./native`.\n */\nexport interface Terminal {\n /**\n * Which methods can REALLY complete a payment here, right now.\n *\n * Asked of the SDK, never inferred from a user-agent string: the answer depends\n * on the browser or device, on the buyer's saved cards, AND on whether the\n * merchant account has the rail switched on. A picker listing a wallet the\n * platform cannot use is a dead end with a logo on it.\n *\n * Takes the tender because the answer depends on it — Square binds the total\n * into the wallet it builds, so this is asked at the point of payment with the\n * real amount rather than cached at boot.\n */\n offers(tender: Tender): Promise<Method[]>\n\n /**\n * Draw the rails that render INLINE into the page, and nothing else.\n *\n * Absent on native, and that absence is the honest signal: native card entry\n * is a modal the OS presents, so there is no element to place and nothing for\n * a caller to position. A host writes `terminal.mount?.(…)` and the native\n * build correctly does nothing.\n */\n mount?(method: Method, target: string): Promise<void>\n\n /**\n * Tokenize. Resolves with a token, or rejects.\n *\n * A buyer who dismisses the sheet has not failed — `collect` rejects with a\n * `Cancelled` (see below), which a caller distinguishes from a real failure so\n * it does not show an error for a decision.\n */\n collect(method: Method, tender: Tender, detail?: Detail): Promise<Token>\n\n /** Tear down every element and listener this terminal owns. */\n release(): Promise<void>\n}\n\n/**\n * The buyer dismissed the sheet. NOT an error to show anyone.\n *\n * Every rail signals this differently — Square's web tokenize resolves with\n * `status: 'CANCEL'`, Apple Pay on native calls a cancel callback, Google Pay\n * rejects with its own code — and a caller that cannot tell them apart shows\n * \"payment failed\" to someone who simply changed their mind.\n */\nexport class Cancelled extends Error {\n readonly method: Method\n constructor(method: Method) {\n super(`${method} was cancelled`)\n this.name = 'Cancelled'\n this.method = method\n }\n}\n\n/** True for the one rejection that means \"the buyer said no\", on any platform. */\nexport function cancelled(e: unknown): e is Cancelled {\n return e instanceof Cancelled\n}\n\n/**\n * The rails each platform's Square SDK can even ATTEMPT.\n *\n * This is a fact about the SDKs, not about a merchant account, and the two do not\n * agree: Square's In-App Payments SDK ships no Cash App Pay and no ACH — its\n * whole exported surface is card entry, gift card entry, Apple Pay, Google Pay\n * and buyer verification. Offering either on a phone would be a button that\n * cannot resolve.\n *\n * `offers()` intersects this with what the SDK says it can build, so a rail\n * missing here is absent from the picker rather than present and dead.\n *\n * THIS IS REACH, NOT POLICY, and the difference has already been mistaken once.\n * A host may decline a rail this table lists, for reasons that have nothing to\n * do with tokenizing: hanzoai/pay does not offer ACH on Square, because its\n * top-up endpoint credits the balance as soon as the charge call succeeds while\n * an ACH debit settles days later — so it would credit unsettled money. The SDK\n * reaches ACH perfectly well. Deciding not to use it is the host's call and does\n * not belong here; deleting `ach` below would instead tell every other host that\n * the rail does not exist.\n */\nexport const REACH: Record<'web' | 'native', readonly Method[]> = {\n web: ['card', 'apple_pay', 'google_pay', 'cash_app', 'ach', 'gift'],\n native: ['card', 'apple_pay', 'google_pay', 'gift'],\n}\n","// The card field's colours — the ONE place this package writes a colour down.\n//\n// Everything else on a checkout is styled from the host's own tokens, so a theme\n// change is a class on <html> and no JS is involved. The card fields cannot work\n// that way, and the reason is the same on both platforms for different causes:\n//\n// web Square renders the fields in a CROSS-ORIGIN iframe, which cannot read\n// our custom properties. The only channel is the style object handed to\n// `payments.card({ style })`, and it takes literal values.\n// native Square renders the fields in a NATIVE view. There is no CSS at all;\n// the only channel is `SQIPCardEntry.setIOSCardEntryTheme`, which takes\n// {r,g,b,a} components.\n//\n// So the literals are unavoidable on both. What is avoidable is having them\n// scattered, or drifting from the palette they mirror, or disagreeing between web\n// and native — hence ONE table here, rendered per platform by each backend\n// (`style()` in ./web, `theme()` in ./native). A brand supplies its own table and\n// gets both renderings for free.\n//\n// Every value is an OPAQUE hex. Square validates style values against its own\n// allowlist and rejects what it cannot parse, and an alpha channel over a surface\n// we do not control is a guess about what is behind it anyway. Alpha rungs of a\n// design ladder (white/10, white/40) are therefore composited onto the field's\n// own ground here, which is what the eye sees regardless.\n\n/** The two grounds a checkout can sit on. Dark is the default. */\nexport type Theme = 'dark' | 'light'\n\n// A PALETTE IS ONE SURFACE, and `field` + `text` are the pair that makes it\n// readable. They have forked twice in production, in both directions: once as a\n// white ground under near-white ink (`field: 'transparent'`, so Square's own\n// white showed through), and once as near-black ink placed on this ground on the\n// belief that the ground was still white. Both shipped a card number the customer\n// could not see while typing it. Neither is a judgement call — `legible()` below\n// measures the pair, and the first of those two states measures 1.0 against a 4.5\n// floor. Change a ground and its ink in one edit, and let the check say whether\n// the result can be read.\nexport interface Palette {\n /** The field's own ground. */\n field: string\n /** Hairline at rest. */\n border: string\n /** Focused hairline. Deliberately >= 3:1 on `field`: it IS the focus indicator. */\n borderFocus: string\n /** Typed characters. */\n text: string\n /** Placeholder text. */\n placeholder: string\n /** Validation text, icon and border. One error hue. */\n error: string\n}\n\n// Both columns are @hanzo/design's own values, not new ones. The dark column is\n// the palette `:root` publishes; the light column is what `.light` inverts it to.\n// Composited alphas are noted where the token is a rung rather than a hex.\nexport const PALETTE: Record<Theme, Palette> = {\n dark: {\n field: '#0a0a0a', // --card\n border: '#232323', // --white-10 over --card\n borderFocus: '#787878', // --white-45 over --card — 4.4:1 on #0a0a0a\n text: '#ededed', // --foreground\n placeholder: '#888888', // --muted-foreground\n error: '#fca5a5', // --state-error-text (red-300), legible on black\n },\n light: {\n field: '#ffffff', // --background\n border: '#e5e5e5', // --border (neutral-200)\n borderFocus: '#737373', // --neutral-500 — 4.7:1 on #ffffff\n text: '#0a0a0a', // --foreground\n placeholder: '#525252', // --muted-foreground\n error: '#ef4444', // --state-error (red-500), legible on white\n },\n}\n\n/** `#rrggbb`, lowercase. The only form Square accepts on both platforms. */\nexport const HEX = /^#[0-9a-f]{6}$/\n\n/** Relative luminance, WCAG 2.x. */\nexport function luminance(hex: string): number {\n const c = [1, 3, 5].map((i) => {\n const v = parseInt(hex.slice(i, i + 2), 16) / 255\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4\n })\n return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]\n}\n\n/** Contrast ratio between two opaque hexes, 1..21. Order does not matter. */\nexport function ratio(a: string, b: string): number {\n const [x, y] = [luminance(a), luminance(b)].sort((m, n) => n - m)\n return (x + 0.05) / (y + 0.05)\n}\n\n/** One failed contrast pair: what was measured, against what floor. */\nexport interface Fault {\n pair: string\n ratio: number\n floor: number\n}\n\n/**\n * Every contrast pair in a palette that a customer must be able to read, with\n * the WCAG floor each is held to.\n *\n * This is exported rather than kept in a test because a BRAND supplying its own\n * table needs the same check, and the failure it catches is invisible in review:\n * a reviewer looking at a dark mock cannot tell 2.6:1 from 4.4:1, and the focus\n * ring is a WCAG 2.4.11 requirement rather than a preference.\n */\nexport function legible(p: Palette): Fault[] {\n const pairs: Array<[string, string, string, number]> = [\n // The typed card number, at AA for body text.\n ['text on field', p.text, p.field, 4.5],\n // A placeholder that is merely present, not legible, is a label nobody reads.\n ['placeholder on field', p.placeholder, p.field, 4.5],\n // Non-text contrast: this border IS the focus indicator (WCAG 2.4.11).\n ['borderFocus on field', p.borderFocus, p.field, 3],\n // The error hue carries both text and a border, so it is held to the lower\n // non-text floor — it is never the only signal, the border moves too.\n ['error on field', p.error, p.field, 3],\n ]\n return pairs\n .map(([pair, a, b, floor]) => ({ pair, ratio: ratio(a, b), floor }))\n .filter((f) => f.ratio < f.floor)\n}\n"]}
|