@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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/terminal.ts","../src/palette.ts"],"names":[],"mappings":"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","file":"chunk-DXK2QDTX.js","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"]}
|