@hanzo/pay 0.1.0

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.
@@ -0,0 +1,166 @@
1
+ /**
2
+ * A Square rail.
3
+ *
4
+ * `gift` is a Square gift card, which tokenizes like a card but through its own
5
+ * constructor on both platforms. Non-Square rails a host may also offer (wire,
6
+ * crypto, another processor) are deliberately absent: this package knows Square,
7
+ * and a host that knows more merges its own set with what `offers()` returns.
8
+ */
9
+ export type Method = 'card' | 'apple_pay' | 'google_pay' | 'cash_app' | 'ach' | 'gift';
10
+ /**
11
+ * Money as a DECIMAL STRING, never a number.
12
+ *
13
+ * `0.1 + 0.2` is `0.30000000000000004`, and a float that reaches a payment
14
+ * processor as a total is a rounding error someone is charged. Square's own
15
+ * paymentRequest takes `amount` as a string for this reason; so does this.
16
+ */
17
+ export interface Money {
18
+ /** Decimal string in major units, e.g. `"12.00"`. Never a float. */
19
+ amount: string;
20
+ /** ISO 4217, e.g. `"USD"`. */
21
+ currency: string;
22
+ }
23
+ /** An offer of payment: what is being paid, where, and what the buyer is shown. */
24
+ export interface Tender {
25
+ total: Money;
26
+ /** ISO 3166-1 alpha-2 of the MERCHANT, e.g. `"US"`. Square keys wallets off it. */
27
+ country: string;
28
+ /** The line the wallet sheet shows the buyer, e.g. `"Hanzo AI credit"`. */
29
+ label: string;
30
+ }
31
+ /**
32
+ * A single-use payment token, and the only thing any rail here produces.
33
+ *
34
+ * It is single-use in the strict sense: spending it — charging it OR vaulting it
35
+ * — consumes it. A flow that needs both a charge and a card-on-file must
36
+ * tokenize TWICE; sharing one token between the two fails with the card already
37
+ * accepted, which reads as a decline.
38
+ */
39
+ export interface Token {
40
+ value: string;
41
+ method: Method;
42
+ /** Present when the rail reported it. Absent means never reported, not empty. */
43
+ card?: {
44
+ brand?: string;
45
+ last4?: string;
46
+ expMonth?: number;
47
+ expYear?: number;
48
+ };
49
+ }
50
+ /**
51
+ * Rail-specific facts a `Tender` cannot carry, because only one rail needs each.
52
+ * Every field is ignored by the rails it does not belong to.
53
+ */
54
+ export interface Detail {
55
+ /**
56
+ * ACH only (web), and REQUIRED there: Square matches it against the bank
57
+ * record. Collected from the buyer rather than derived from the account,
58
+ * because the account holder and the person paying are not always the same
59
+ * name.
60
+ */
61
+ name?: string;
62
+ /**
63
+ * Card and gift only. Passed straight to Square as verification details —
64
+ * `{ intent, amount, currencyCode, billingContact, customerInitiated,
65
+ * sellerKeyedIn }` — which is how SCA is satisfied in mandated regions.
66
+ * Opaque on purpose: Square owns this shape, and re-declaring it here would
67
+ * be a second copy to drift.
68
+ */
69
+ verify?: Record<string, unknown>;
70
+ /**
71
+ * NATIVE ONLY, and the one real asymmetry between the platforms.
72
+ *
73
+ * Square's native card sheet is a state machine, not a form: it STAYS OPEN
74
+ * after minting the token, showing a spinner, until it is told whether the
75
+ * charge worked. Told yes, it closes. Told no, it shows the message ON the
76
+ * sheet with the card still entered, so the buyer retries in place instead of
77
+ * being dropped back to a checkout with an empty form and a decline notice.
78
+ *
79
+ * That is a better decline experience than the web can offer, and it exists
80
+ * only if the host charges DURING the sheet — which is what this is. Given the
81
+ * token, run the server call; return to close the sheet, throw to keep it open
82
+ * with the thrown message shown.
83
+ *
84
+ * Omit it and the sheet closes as soon as the token exists, which is what the
85
+ * web does. Nothing hangs either way.
86
+ *
87
+ * The web backend ignores this: its card fields are a page element with no
88
+ * sheet to hold open.
89
+ */
90
+ charge?(token: Token): Promise<void>;
91
+ }
92
+ /**
93
+ * The one interface both backends satisfy: web (Square Web Payments SDK) and
94
+ * native (Square In-App Payments SDK). A host writes against this and the
95
+ * platform picks the implementation — see `./web` and `./native`.
96
+ */
97
+ export interface Terminal {
98
+ /**
99
+ * Which methods can REALLY complete a payment here, right now.
100
+ *
101
+ * Asked of the SDK, never inferred from a user-agent string: the answer depends
102
+ * on the browser or device, on the buyer's saved cards, AND on whether the
103
+ * merchant account has the rail switched on. A picker listing a wallet the
104
+ * platform cannot use is a dead end with a logo on it.
105
+ *
106
+ * Takes the tender because the answer depends on it — Square binds the total
107
+ * into the wallet it builds, so this is asked at the point of payment with the
108
+ * real amount rather than cached at boot.
109
+ */
110
+ offers(tender: Tender): Promise<Method[]>;
111
+ /**
112
+ * Draw the rails that render INLINE into the page, and nothing else.
113
+ *
114
+ * Absent on native, and that absence is the honest signal: native card entry
115
+ * is a modal the OS presents, so there is no element to place and nothing for
116
+ * a caller to position. A host writes `terminal.mount?.(…)` and the native
117
+ * build correctly does nothing.
118
+ */
119
+ mount?(method: Method, target: string): Promise<void>;
120
+ /**
121
+ * Tokenize. Resolves with a token, or rejects.
122
+ *
123
+ * A buyer who dismisses the sheet has not failed — `collect` rejects with a
124
+ * `Cancelled` (see below), which a caller distinguishes from a real failure so
125
+ * it does not show an error for a decision.
126
+ */
127
+ collect(method: Method, tender: Tender, detail?: Detail): Promise<Token>;
128
+ /** Tear down every element and listener this terminal owns. */
129
+ release(): Promise<void>;
130
+ }
131
+ /**
132
+ * The buyer dismissed the sheet. NOT an error to show anyone.
133
+ *
134
+ * Every rail signals this differently — Square's web tokenize resolves with
135
+ * `status: 'CANCEL'`, Apple Pay on native calls a cancel callback, Google Pay
136
+ * rejects with its own code — and a caller that cannot tell them apart shows
137
+ * "payment failed" to someone who simply changed their mind.
138
+ */
139
+ export declare class Cancelled extends Error {
140
+ readonly method: Method;
141
+ constructor(method: Method);
142
+ }
143
+ /** True for the one rejection that means "the buyer said no", on any platform. */
144
+ export declare function cancelled(e: unknown): e is Cancelled;
145
+ /**
146
+ * The rails each platform's Square SDK can even ATTEMPT.
147
+ *
148
+ * This is a fact about the SDKs, not about a merchant account, and the two do not
149
+ * agree: Square's In-App Payments SDK ships no Cash App Pay and no ACH — its
150
+ * whole exported surface is card entry, gift card entry, Apple Pay, Google Pay
151
+ * and buyer verification. Offering either on a phone would be a button that
152
+ * cannot resolve.
153
+ *
154
+ * `offers()` intersects this with what the SDK says it can build, so a rail
155
+ * missing here is absent from the picker rather than present and dead.
156
+ *
157
+ * THIS IS REACH, NOT POLICY, and the difference has already been mistaken once.
158
+ * A host may decline a rail this table lists, for reasons that have nothing to
159
+ * do with tokenizing: hanzoai/pay does not offer ACH on Square, because its
160
+ * top-up endpoint credits the balance as soon as the charge call succeeds while
161
+ * an ACH debit settles days later — so it would credit unsettled money. The SDK
162
+ * reaches ACH perfectly well. Deciding not to use it is the host's call and does
163
+ * not belong here; deleting `ach` below would instead tell every other host that
164
+ * the rail does not exist.
165
+ */
166
+ export declare const REACH: Record<'web' | 'native', readonly Method[]>;
@@ -0,0 +1,227 @@
1
+ 'use strict';
2
+
3
+ var chunk64AUOMEL_cjs = require('../chunk-64AUOMEL.cjs');
4
+
5
+ // src/web/sdk.ts
6
+ var PRODUCTION = "https://web.squarecdn.com/v1/square.js";
7
+ var SANDBOX = "https://sandbox.web.squarecdn.com/v1/square.js";
8
+ var pending = null;
9
+ function load(src) {
10
+ if (pending) return pending;
11
+ if (typeof window !== "undefined" && window.Square) return pending = Promise.resolve();
12
+ pending = new Promise((resolve, reject) => {
13
+ const existing = document.querySelector(`script[src="${src}"]`);
14
+ if (existing) {
15
+ existing.addEventListener("load", () => resolve());
16
+ existing.addEventListener("error", () => reject(new Error("Square SDK failed to load")));
17
+ return;
18
+ }
19
+ const s = document.createElement("script");
20
+ s.src = src;
21
+ s.async = true;
22
+ s.onload = () => resolve();
23
+ s.onerror = () => reject(new Error("Square SDK failed to load"));
24
+ document.head.appendChild(s);
25
+ });
26
+ return pending;
27
+ }
28
+ function forget() {
29
+ pending = null;
30
+ }
31
+
32
+ // src/web/style.ts
33
+ function rules(style2, selector) {
34
+ return style2[selector];
35
+ }
36
+ var RADIUS = "6px";
37
+ function style(p) {
38
+ return {
39
+ // Borders and radius ONLY. Square's allowlist for this selector is
40
+ // borderColor/borderRadius/borderWidth; anything else throws "Invalid style
41
+ // property", and Square rejects the WHOLE style object when it does — so one
42
+ // stray property leaves the card unstyled and white.
43
+ ".input-container": {
44
+ borderColor: p.border,
45
+ borderRadius: RADIUS,
46
+ borderWidth: "1px"
47
+ },
48
+ ".input-container.is-focus": { borderColor: p.borderFocus },
49
+ ".input-container.is-error": { borderColor: p.error },
50
+ input: {
51
+ backgroundColor: p.field,
52
+ color: p.text,
53
+ fontSize: "14px"
54
+ },
55
+ // 16px on a small screen, because iOS Safari ZOOMS the page when a field
56
+ // under 16px takes focus — on a card form that throws the layout sideways
57
+ // mid-number and there is no way to zoom back without losing the caret.
58
+ // Square's own dark-mode example carries this media query for the same
59
+ // reason; it is the one place the field may disagree with the page's ramp.
60
+ "@media screen and (max-width: 600px)": {
61
+ input: { fontSize: "16px" }
62
+ },
63
+ "input::placeholder": { color: p.placeholder },
64
+ "input.is-error": { color: p.error },
65
+ ".message-text": { color: p.placeholder },
66
+ ".message-text.is-error": { color: p.error },
67
+ ".message-icon": { color: p.placeholder },
68
+ ".message-icon.is-error": { color: p.error }
69
+ };
70
+ }
71
+ function pin(doc) {
72
+ const d = doc ?? (typeof document === "undefined" ? void 0 : document);
73
+ if (!d) return;
74
+ const MARK = "hanzo-pay-scheme";
75
+ if (d.getElementById(MARK)) return;
76
+ const el = d.createElement("style");
77
+ el.id = MARK;
78
+ el.textContent = ".sq-card-iframe-container{color-scheme:auto}";
79
+ d.head.appendChild(el);
80
+ }
81
+
82
+ // src/web/terminal.ts
83
+ var DRAWN = ["card", "gift", "google_pay", "cash_app"];
84
+ function token(method, r) {
85
+ if (String(r.status).toUpperCase() === "CANCEL") throw new chunk64AUOMEL_cjs.Cancelled(method);
86
+ if (r.status !== "OK" || !r.token) {
87
+ throw new Error(r.errors?.[0]?.message ?? `${method} could not be completed`);
88
+ }
89
+ return { value: r.token, method, card: r.details?.card };
90
+ }
91
+ function awaited(method, t) {
92
+ return new Promise((resolve, reject) => {
93
+ t.addEventListener?.("ontokenization", (e) => {
94
+ const detail = e?.detail;
95
+ if (detail?.error) {
96
+ reject(new Error(`${method} could not be completed`));
97
+ return;
98
+ }
99
+ if (!detail?.tokenResult) return;
100
+ try {
101
+ resolve(token(method, detail.tokenResult));
102
+ } catch (err) {
103
+ reject(err);
104
+ }
105
+ });
106
+ });
107
+ }
108
+ var Web = class {
109
+ constructor(payments, palette) {
110
+ this.payments = payments;
111
+ this.palette = palette;
112
+ }
113
+ payments;
114
+ built = /* @__PURE__ */ new Map();
115
+ palette;
116
+ /**
117
+ * Which rails can REALLY pay here, asked of the SDK one at a time.
118
+ *
119
+ * Building the rail IS the probe, and it is the only answer that accounts for
120
+ * the browser, the device, the buyer's saved cards AND whether the merchant
121
+ * account has the rail switched on. A rejection is a plain "not here", not an
122
+ * error worth showing anyone.
123
+ *
124
+ * The built objects are KEPT, and that is load-bearing rather than a cache:
125
+ * `collect('apple_pay')` must reach `tokenize()` with nothing awaited in front
126
+ * of it, so the object it needs has to already exist by then.
127
+ */
128
+ async offers(tender) {
129
+ const out = [];
130
+ for (const method of chunk64AUOMEL_cjs.REACH.web) {
131
+ try {
132
+ this.built.set(method, await this.build(method, tender));
133
+ out.push(method);
134
+ } catch {
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+ async build(method, tender) {
140
+ if (method === "card") return this.payments.card({ style: style(this.palette) });
141
+ if (method === "gift") return this.payments.giftCard();
142
+ if (method === "ach") {
143
+ return this.payments.ach({
144
+ // Square REJECTS a redirectURI carrying a query string, so the page's own
145
+ // path is used and anything that must survive the trip travels separately.
146
+ redirectURI: typeof window === "undefined" ? "" : window.location.origin + window.location.pathname,
147
+ transactionId: `${Date.now()}`
148
+ });
149
+ }
150
+ const req = this.payments.paymentRequest({
151
+ countryCode: tender.country,
152
+ currencyCode: tender.total.currency,
153
+ total: { amount: tender.total.amount, label: tender.label }
154
+ });
155
+ if (method === "apple_pay") return this.payments.applePay(req);
156
+ if (method === "google_pay") return this.payments.googlePay(req);
157
+ return this.payments.cashAppPay(req, {
158
+ // Where Cash App returns a MOBILE buyer. Desktop uses the QR and never
159
+ // leaves the page.
160
+ redirectURL: typeof window === "undefined" ? "" : window.location.href,
161
+ referenceId: `pay-${tender.total.amount}`
162
+ });
163
+ }
164
+ /** Draw a rail that renders inline. A rail that draws nothing is a no-op, not an error. */
165
+ async mount(method, target) {
166
+ const t = this.built.get(method);
167
+ if (!t) throw new Error(`${method} was not offered here`);
168
+ if (!DRAWN.includes(method)) return;
169
+ await t.attach?.(target);
170
+ }
171
+ /**
172
+ * Tokenize.
173
+ *
174
+ * NOTHING IS AWAITED BEFORE `tokenize()`. Apple refuses a payment sheet that
175
+ * was not opened directly by the gesture that asked for it, so a single `await`
176
+ * placed above the call — loading the SDK, looking a rail up asynchronously,
177
+ * re-reading a total — silently breaks Apple Pay and nothing else. That is why
178
+ * `offers()` builds every rail up front and this only reads a map.
179
+ */
180
+ collect(method, tender, detail) {
181
+ const t = this.built.get(method);
182
+ if (!t) return Promise.reject(new Error(`${method} was not offered here`));
183
+ if (method === "cash_app") return awaited(method, t);
184
+ if (method === "ach") {
185
+ if (!detail?.name) {
186
+ return Promise.reject(new Error("ACH needs the account holder\u2019s name"));
187
+ }
188
+ const arrived = awaited(method, t);
189
+ void t.tokenize({
190
+ accountHolderName: detail.name,
191
+ intent: "CHARGE",
192
+ amount: tender.total.amount,
193
+ currency: tender.total.currency
194
+ });
195
+ return arrived;
196
+ }
197
+ return t.tokenize(detail?.verify).then((r) => token(method, r));
198
+ }
199
+ async release() {
200
+ await Promise.all([...this.built.values()].map((t) => t.destroy?.().catch(() => void 0)));
201
+ this.built.clear();
202
+ }
203
+ };
204
+ async function terminal(config) {
205
+ if (!config.applicationId || !config.locationId) {
206
+ throw new Error("Square is not configured for this deployment");
207
+ }
208
+ pin();
209
+ const src = (config.environment ?? "production").toLowerCase() === "sandbox" ? SANDBOX : PRODUCTION;
210
+ await load(src);
211
+ if (typeof window === "undefined" || !window.Square) {
212
+ throw new Error("Square SDK failed to load");
213
+ }
214
+ const payments = await window.Square.payments(config.applicationId, config.locationId);
215
+ return new Web(payments, config.palette ?? chunk64AUOMEL_cjs.PALETTE.dark);
216
+ }
217
+
218
+ exports.PRODUCTION = PRODUCTION;
219
+ exports.SANDBOX = SANDBOX;
220
+ exports.forget = forget;
221
+ exports.load = load;
222
+ exports.pin = pin;
223
+ exports.rules = rules;
224
+ exports.style = style;
225
+ exports.terminal = terminal;
226
+ //# sourceMappingURL=index.cjs.map
227
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,3 @@
1
+ export { type Config, terminal } from './terminal';
2
+ export { type Rules, type Style, pin, rules, style } from './style';
3
+ export { type Element, type Payments, type Request, type Result, type Tokenizer, forget, load, PRODUCTION, SANDBOX, } from './sdk';