npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hanzo/pay

v0.1.2

Published

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.

Readme

@hanzo/pay

Square payments for web and native, behind one interface.

pnpm add @hanzo/pay

The shape

Three entries. The root is platform-agnostic and dependency-free — contracts, the colour table, the idempotency-key rule — so a server, a shared module and a test can all hold it. The adapters are explicit:

| Entry | Needs | Gives you | |---|---|---| | @hanzo/pay | nothing | Terminal, Method, Tender, Token, PALETTE, legible, attempt | | @hanzo/pay/web | a browser | terminal() over the Web Payments SDK, style(), pin() | | @hanzo/pay/native | react-native-square-in-app-payments | terminal() over the In-App Payments SDK, appearance() |

Both adapters export terminal(config) and both return the same Terminal. Picking one by filename magic works right up until a bundler picks the other, and then it fails at runtime, in a payment, on someone else's device — so the host says which platform it is, in the import.

Taking a payment

import { cancelled, attempt } from '@hanzo/pay'
import { terminal } from '@hanzo/pay/web'

const pay = await terminal({ applicationId, locationId, environment })
const tender = { total: { amount: '12.00', currency: 'USD' }, country: 'US', label: 'Credit' }

const rails = await pay.offers(tender)     // only what can really pay, here, now
await pay.mount?.('card', '#card')          // absent on native: there is no element

const key = attempt()                       // hold this across retries
try {
  const token = await pay.collect('card', tender)
  // charge server-side with token.value and `key` as the idempotency key
} catch (e) {
  if (!cancelled(e)) throw e                // a dismissed sheet is not a failure
}

This package never moves money. Every rail ends at a single-use token; the charge is a server call you make with it.

What it knows that you would otherwise learn the hard way

  • offers() asks the SDK, one rail at a time. Availability depends on the browser, the device, the buyer's saved cards and whether the merchant account has the rail switched on. A picker listing a wallet the platform cannot use is a dead end with a logo on it.
  • REACH.native has no Cash App Pay and no ACH. Square's native SDK ships neither. That is reach, not policy — a host may decline a rail this table lists, and hanzoai/pay declines ACH on Square because its top-up endpoint credits on charge success while an ACH debit settles days later.
  • Nothing is awaited before tokenize(). Apple refuses a sheet not opened by the gesture that asked for it, so offers() builds every rail up front and collect() only reads a map. One stray await above that call breaks Apple Pay and nothing else.
  • Cash App Pay and ACH deliver on an event, not a return value. A caller reading only the return gets nothing from either, silently, on a payment that succeeded.
  • pin() is applied for you. input.backgroundColor is accepted and then ignored: color-scheme inherits into the cross-origin iframe and the UA paints form controls over whatever the SDK declared. One rule fixes it, and it has to be in the sheet before the card attaches — so terminal() puts it there rather than leaving a stylesheet you can forget to import. It carries the FOCUS RING too, for the mirror-image reason: a ring is drawn outside the field, and everything Square will style for you stops at the field's edge. Call pin() yourself only if you drive payments.card() directly instead of taking a Terminal.
  • attempt() fits Square's ruler. An idempotency_key over 45 characters is rejected before authorization. A 36-char UUID under a topup:<org>: wrapper is 49, which failed every card charge for one org and read as "charge failed".
  • Detail.charge holds the native sheet open. Square's native card sheet stays up after minting the token and is told afterwards whether the charge worked — so a decline retries in place instead of dumping the buyer back to an empty form. The web has no equivalent and ignores it.

Theming

One Palette dresses both platforms: style() renders it as the CSS literals the web iframe takes, appearance() as the {r,g,b,a} components the native sheet takes. Neither surface can read your CSS variables — one is cross-origin, the other is not the web — which is why the table holds literals.

import { legible, PALETTE } from '@hanzo/pay'

legible(PALETTE.dark)                        // [] — every contrast floor met
legible({ ...PALETTE.dark, field: '#fafafa' }) // names the pairs that fail

Run legible() over a brand palette before shipping it. A reviewer looking at a dark mock cannot tell 2.6:1 from 4.4:1, and the focus ring is a WCAG 2.4.11 requirement rather than a preference.

Set field and border to what your own inputs COMPOSITE to, measured in a browser. The card strip lands in a column of your fields and is read as one of them or as a foreign object; there is no third outcome. Ours shipped on --card and its hairline — real tokens, for a card — so the strip rendered a lighter ground behind a fainter edge than every field above it.

ring is the one focus indicator, and the box does not move. pin() draws it around the container as an outline, so it is the same ring your page already puts on its own controls; style() therefore paints the FOCUSED hairline the resting colour, because Square spends that value twice (the border and a ::before ring one pixel outside it) and any other value is a second ring inside the first.

Native setup

@hanzo/pay/native needs react-native-square-in-app-payments and the native build steps that plugin documents (Apple Pay entitlement and merchant id, Google Pay via Play Services). appearance() is iOS only — Android's card entry is themed through native XML styles at build time, with no runtime channel.

License

Apache-2.0