Skip to content

OpenReceive v0.3.3

Choose a tag to compare

@OpenReceive OpenReceive released this 02 Sep 00:35
· 62 commits to master since this release

OpenReceive ships as agent skills

Two skills in the open SKILL.md format now travel with the library:
integrate-openreceive (stack detection, the three server objects, the
authorize/amountFor/onPaid contract, 409 semantics, the secret boundary,
scaffolding, and testing against a fake wallet — with the full per-stack agent
directions as references) and debug-openreceive-payment (doctor-first
triage: boot failures, request-time 403/404/409, settlement timing, swap
refunds, each symptom with its fix). They install from the repo
(npx skills add OpenReceive/openreceive, or Claude Code
/plugin marketplace add OpenReceive/openreceive via the new
.claude-plugin/marketplace.json), are discovered from the .agents/skills/
mirror, and ship inside every npm package and gem, so an agent working in an
app that already installed OpenReceive finds them with no network.
npm run generate:skills keeps the mirrors byte-identical (checked in CI),
and the release checklist now requires the skills to describe the released
API.

The site contract moves to v3 for the same audience: openreceive.org now
serves /llms.txt (generated here from the docs manifest), the normative
OpenAPI file verbatim at /openapi.yaml, an /agents page, and llms.txt v2
discovery tags (rel="describedby", markdown alternates) — so a browsing
agent can find the whole documentation surface from any page.

Error messages state the fix and link the doc

Every integrator-facing setup error now says what to do about itself and
links the guide that owns the answer — the boot/mount "requires amountFor /
onPaid / authorize / host" family, the composed-options error, the
rate-limiting conflicts, and the reconcile-gate requirement, in both engines.
The 403 for a denied reference now names the authorize hook and links the
authorization guide. Ruby reaches parity with the JS NWC messages: missing,
invalid, and spend-capable NWC_URI failures carry the same receive-only
framing and the get-a-code URL (the long-dormant NWC_CODE_HELP_URL constant
finally earns its keep), and the spend override is spelled =true in both
engines.

A missing migration also stops surfacing as a raw driver error. When the
openreceive_meta probe finds the table absent, both engines now raise a
configuration error naming the exact fix — npx openreceive scaffold payments --orm <yours> on Node, bin/rails generate openreceive:install
then db:migrate on Rails — instead of leaking no such table from the
first query. Any other probe failure (connection refused, permissions) keeps
the old silent tolerance, and a healed database is retried, not remembered
as broken.

openreceive doctor proves the integration, not just the env

The doctor used to stop at parsing NWC_URI. It now probes the wallet over
the relay — the same preflight boot runs — and reports receive-only or the
exact refusal; --db <sqlite file | postgres:// | mysql://> confirms the
openreceive_payments + openreceive_meta migration actually ran, and
--url http://localhost:3000 confirms the routes answer on the running app
(recognized by the router's own JSON 404, which no framework fallback
produces). Every failing line states its own fix and links the guide that
owns it. The default run still touches no database, and --offline keeps the
old fully-offline behavior; debug-report stays exit-0 and redacted.

Checkout children compose instead of replacing the payment UI

Passing anything as <Checkout> children — even the one-line order summary
the docs recommend — silently replaced the QR, the method tiles, and the
whole payment flow, so a first store's checkout could ship as a description
with no way to pay. Children now render above the shipped payment UI, in the
same position as the custom element's order slot, exactly as the docs
always said they did. A host that wants its own checkout builds on
useCheckout or @openreceive/browser/headless, not on children.

The host can lock the checkout theme

<Checkout theme="dark" | "light" | "system"> locks the theme: it wins over
the payer's stored preference (localStorage["openreceive.theme"]) and any
ancestor ThemeScope, and hides the toggle, so a checkout embedded in a page
that is always dark can never come up as a white card. ThemeScope and
useTheme accept the same theme option. The custom element's theme
attribute already behaved this way; the element wrappers do not forward it
yet (docs/internal/wrapper-parity.md tracks the gap).

themeToggle={false} now hides the control but still stamps data-theme
hiding the toggle no longer renders an unstyled checkout.

The theme toggle is labeled by the action ("switch to dark mode"), not the
current state: a checkout stuck light on a dark page used to announce
"light mode" at the payer.

Release notes

All four Buy a Button examples (examples/buttons/server/node-express,
static-html-small-api, nextjs-fullstack, rails) were rebuilt against the
0.3.3 workspace packages through npm run build:demo in the release gate.

The live wallet smoke (npm run test:live) was NOT run for this release: it
needs a funded NWC connection.

This is a PATCH bump: nothing left the public API. The doctor flags, the
DoctorWalletClient seam, and the skills shipped inside every package are
additive; the checkout-children change makes the component do what its docs
always said; error-message rewrites keep every pinned wire message and code.