openreceive-rails 0.4.14 → 0.4.16
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -1
- data/README.md +10 -0
- data/lib/openreceive/rails/version.rb +1 -1
- data/lib/rails/commands/openreceive/openreceive_command.rb +18 -0
- data/lib/tasks/openreceive.rake +7 -0
- metadata +8 -25
- data/skills/debug-openreceive-payment/SKILL.md +0 -97
- data/skills/integrate-openreceive/SKILL.md +0 -139
- data/skills/integrate-openreceive/references/btcpay.md +0 -224
- data/skills/integrate-openreceive/references/django.md +0 -745
- data/skills/integrate-openreceive/references/fastapi.md +0 -619
- data/skills/integrate-openreceive/references/fastify.md +0 -615
- data/skills/integrate-openreceive/references/laravel.md +0 -749
- data/skills/integrate-openreceive/references/next.md +0 -660
- data/skills/integrate-openreceive/references/node.md +0 -588
- data/skills/integrate-openreceive/references/php.md +0 -682
- data/skills/integrate-openreceive/references/rails.md +0 -698
- data/skills/integrate-openreceive/references/woocommerce.md +0 -193
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f3b64d28550bbe44f3ca4b0b6fcc4e94430a415f168b8249f2513e7c85c56a46
|
|
4
|
+
data.tar.gz: 60dd73cfd9f11b23b99c34e64640c541aaf5404ba01980b35a31d23e7bd9eeb6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68dac8c68722b354533736a51fe7a870df757ac5ca17d76fe1e301cee750ed195a40403620bd7d1d34334adc95c8143418ca1bb6582d731c5b0c99da7087edcc
|
|
7
|
+
data.tar.gz: ab2e30e2e102aafa38f4b9c8e0f55bec5dcc18b9919d5f329c3b5e6f71b3ba80c59abef064afdb8d5ac7a43d7133a25f7e19b28e88221d06e1cb02d005ba856e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.16 - 2026-10-06
|
|
4
|
+
|
|
5
|
+
Release with the complete 0.4.16 package family and corrected shared agent
|
|
6
|
+
directions. No Ruby runtime changes from 0.4.15.
|
|
7
|
+
|
|
8
|
+
## 0.4.15 - 2026-10-06
|
|
9
|
+
|
|
10
|
+
- Install agent skills with `bin/rails openreceive:skills` (optional
|
|
11
|
+
`--dir .claude/skills`). The core `openreceive` gem owns the one offline
|
|
12
|
+
bundle; Rails/server gems no longer duplicate it. Other Ruby hosts can use
|
|
13
|
+
`npx skills add OpenReceive/openreceive`.
|
|
14
|
+
|
|
15
|
+
Requires `nwc-ruby ~> 0.3`. On 0.2.x a silent relay, or an offline wallet
|
|
16
|
+
behind a live relay, could block a checkout's `make_invoice` and the boot
|
|
17
|
+
preflight indefinitely, holding a web thread. 0.3 bounds every call by
|
|
18
|
+
`request_timeout`, retries only failures from before the request is written,
|
|
19
|
+
and tries each relay in the connection string. Relay failures now answer 503
|
|
20
|
+
`WALLET_UNAVAILABLE` (retryable) instead of 502 `OTHER`.
|
|
21
|
+
|
|
3
22
|
## 0.4.14 - 2026-10-04
|
|
4
23
|
|
|
5
24
|
Fixes two 0.4.13 reconciliation regressions. A host-clock attempt could be
|
|
@@ -162,7 +181,8 @@ warning links the rate-limiting guide.
|
|
|
162
181
|
|
|
163
182
|
### The gem carries the agent skills
|
|
164
183
|
|
|
165
|
-
`skills/`
|
|
184
|
+
At this release, `skills/` shipped in this gem (now supplied by the core
|
|
185
|
+
`openreceive` dependency; see Unreleased) — the integrate and debug playbooks for coding
|
|
166
186
|
agents, kept byte-identical to the repository tree by
|
|
167
187
|
`npm run generate:skills`.
|
|
168
188
|
|
data/README.md
CHANGED
|
@@ -131,3 +131,13 @@ Storage-free `openreceive-server` handlers can call `on_paid` on every settled
|
|
|
131
131
|
poll, so advanced hosts own the conditional write/outbox. A raw create hook
|
|
132
132
|
refusal returns 409 with instructions withheld; repository infrastructure failures
|
|
133
133
|
remain retryable 503.
|
|
134
|
+
|
|
135
|
+
## Agent skills
|
|
136
|
+
|
|
137
|
+
In Rails, run `bin/rails openreceive:skills` from your application. The offline
|
|
138
|
+
skills ship once in the core `openreceive` gem. Non-Rails projects can use
|
|
139
|
+
`npx skills add OpenReceive/openreceive`.
|
|
140
|
+
See [agent setup](https://openreceive.org/agents). The bundled installers write
|
|
141
|
+
to `.agents/skills/`; use `--dir .claude/skills` for Claude Code. They replace
|
|
142
|
+
only `integrate-openreceive` and `debug-openreceive-payment`, preserving
|
|
143
|
+
unrelated skills.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/command"
|
|
4
|
+
require "openreceive/skills"
|
|
5
|
+
|
|
6
|
+
module Rails
|
|
7
|
+
module Command
|
|
8
|
+
# A Rails command accepts --dir before the Rake fallback parses arguments.
|
|
9
|
+
# It does not boot the application or require wallet credentials.
|
|
10
|
+
class OpenreceiveCommand < Base
|
|
11
|
+
desc "skills", "Install the bundled OpenReceive agent skills into this project"
|
|
12
|
+
method_option :dir, type: :string, default: ".agents/skills"
|
|
13
|
+
def skills
|
|
14
|
+
OpenReceive::Skills.install(directory: options[:dir])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/tasks/openreceive.rake
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
namespace :openreceive do
|
|
4
|
+
desc "Install agent skills (optional directory: openreceive:skills[path])"
|
|
5
|
+
task :skills, [:dir] do |_task, args|
|
|
6
|
+
require "openreceive/skills"
|
|
7
|
+
OpenReceive::Skills.install(directory: args[:dir] || ".agents/skills")
|
|
8
|
+
end
|
|
9
|
+
|
|
4
10
|
# Step 0 of the agent directions, as one command.
|
|
5
11
|
#
|
|
6
12
|
# "Look for NWC_URI in this app's server environment" is a SEARCH, and it has
|
|
@@ -18,6 +24,7 @@ namespace :openreceive do
|
|
|
18
24
|
set = ->(name) { ENV[name].to_s.strip.empty? ? "unset" : "set" }
|
|
19
25
|
lines = [
|
|
20
26
|
"openreceive:doctor",
|
|
27
|
+
"Agent skills: run `bin/rails openreceive:skills`",
|
|
21
28
|
" NWC_URI: #{set.call('NWC_URI')}",
|
|
22
29
|
" LSC_URI_PRIMARY: #{set.call('LSC_URI_PRIMARY')}",
|
|
23
30
|
" LSC_URI_BACKUP: #{set.call('LSC_URI_BACKUP')}"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openreceive-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OpenReceive
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.4.
|
|
18
|
+
version: 0.4.16
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.4.
|
|
25
|
+
version: 0.4.16
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: openreceive-server
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.4.
|
|
32
|
+
version: 0.4.16
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.4.
|
|
39
|
+
version: 0.4.16
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: rails
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,20 +57,14 @@ dependencies:
|
|
|
57
57
|
requirements:
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
61
|
-
- - ">="
|
|
62
|
-
- !ruby/object:Gem::Version
|
|
63
|
-
version: 0.2.4
|
|
60
|
+
version: '0.3'
|
|
64
61
|
type: :runtime
|
|
65
62
|
prerelease: false
|
|
66
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
67
64
|
requirements:
|
|
68
65
|
- - "~>"
|
|
69
66
|
- !ruby/object:Gem::Version
|
|
70
|
-
version: '0.
|
|
71
|
-
- - ">="
|
|
72
|
-
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.2.4
|
|
67
|
+
version: '0.3'
|
|
74
68
|
- !ruby/object:Gem::Dependency
|
|
75
69
|
name: sqlite3
|
|
76
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -130,19 +124,8 @@ files:
|
|
|
130
124
|
- lib/openreceive/rails/version.rb
|
|
131
125
|
- lib/openreceive/reconcile.rb
|
|
132
126
|
- lib/openreceive/reconcile_scan.rb
|
|
127
|
+
- lib/rails/commands/openreceive/openreceive_command.rb
|
|
133
128
|
- lib/tasks/openreceive.rake
|
|
134
|
-
- skills/debug-openreceive-payment/SKILL.md
|
|
135
|
-
- skills/integrate-openreceive/SKILL.md
|
|
136
|
-
- skills/integrate-openreceive/references/btcpay.md
|
|
137
|
-
- skills/integrate-openreceive/references/django.md
|
|
138
|
-
- skills/integrate-openreceive/references/fastapi.md
|
|
139
|
-
- skills/integrate-openreceive/references/fastify.md
|
|
140
|
-
- skills/integrate-openreceive/references/laravel.md
|
|
141
|
-
- skills/integrate-openreceive/references/next.md
|
|
142
|
-
- skills/integrate-openreceive/references/node.md
|
|
143
|
-
- skills/integrate-openreceive/references/php.md
|
|
144
|
-
- skills/integrate-openreceive/references/rails.md
|
|
145
|
-
- skills/integrate-openreceive/references/woocommerce.md
|
|
146
129
|
homepage: https://openreceive.org
|
|
147
130
|
licenses:
|
|
148
131
|
- MIT
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: debug-openreceive-payment
|
|
3
|
-
description: >
|
|
4
|
-
Diagnose a failing OpenReceive integration. Use when an OpenReceive-powered
|
|
5
|
-
checkout misbehaves: the server refuses to boot, checkout routes return 403,
|
|
6
|
-
404, 409, or 5xx, a paid invoice never settles, a swap refund seems
|
|
7
|
-
unreachable, or the checkout UI renders nothing.
|
|
8
|
-
license: MIT
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Debug an OpenReceive payment
|
|
12
|
-
|
|
13
|
-
Work top-down: configuration, then the request, then settlement. Every guide
|
|
14
|
-
URL below is raw markdown — fetch it when the step needs it.
|
|
15
|
-
|
|
16
|
-
## 1. Run the doctor first
|
|
17
|
-
|
|
18
|
-
```sh
|
|
19
|
-
npx openreceive doctor # Node version, NWC_URI, swap config, wallet probe
|
|
20
|
-
npx openreceive doctor --db <db> # + are openreceive_payments/openreceive_meta migrated?
|
|
21
|
-
npx openreceive doctor --url http://localhost:3000 # + are the routes actually mounted?
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Each failing line states its own fix. `npx openreceive debug-report` prints the
|
|
25
|
-
same diagnostics redacted, always exit 0 — safe to share.
|
|
26
|
-
|
|
27
|
-
## 2. Boot failures
|
|
28
|
-
|
|
29
|
-
| Symptom | Cause and fix |
|
|
30
|
-
| --- | --- |
|
|
31
|
-
| `MISSING_NWC` / "needs a receive-only NWC code" | `NWC_URI` is not in the server process env. A `.env` file alone is not enough — something must load it (`dotenv/config`, Next auto-load). Get a code: https://openreceive.org/get_a_nwc_code_to_receive_payments |
|
|
32
|
-
| `INVALID_NWC` / "not a valid NWC code" | The value is malformed (must be `nostr+walletconnect://` with 64-hex pubkey and secret, ≥1 `wss` relay). Re-copy it from the wallet. |
|
|
33
|
-
| "NOT receive-only" / spend methods advertised | The wallet minted a spend-capable code; OpenReceive fails closed because a leak would drain the wallet. Mint a receive-only code. Overriding (`allowSpendCapableWallet` / `OPENRECEIVE_ALLOW_SPEND_CAPABLE_NWC`) is a last resort. |
|
|
34
|
-
| Wallet preflight failed (methods/encryption) | The wallet must advertise `make_invoice` + `list_transactions` and NIP-04 or NIP-44 v2. Use a compatible wallet. |
|
|
35
|
-
| "The openreceive_meta table does not exist" / raw `no such table: openreceive_payments` | The migration was never applied. Node: `npx openreceive scaffold payments --orm <yours>`, then run the emitted migration through the app's normal workflow. Rails: `bin/rails generate openreceive:install`, then `bin/rails db:migrate`. https://openreceive.org/guides/storage.md |
|
|
36
|
-
| "requires amountFor / onPaid / authorize / host" | The factory is missing a required hook — see the host contract in https://openreceive.org/guides/api-reference.md |
|
|
37
|
-
|
|
38
|
-
## 3. Request-time errors from the routes
|
|
39
|
-
|
|
40
|
-
| Status | Meaning | Where to look |
|
|
41
|
-
| --- | --- | --- |
|
|
42
|
-
| 403 FORBIDDEN | Your own `authorize` hook denied it, or the request looked cross-site. Check the session/cookie actually reaches the checkout routes. https://openreceive.org/guides/authorization.md |
|
|
43
|
-
| Never 403s — any visitor can mint, poll, or refund for any reference | The opposite failure: on Rails the generated `config.authorize = OpenReceive::ALLOW_ALL_AUTHORIZE` placeholder is still installed (the engine warns at boot; `bin/rails openreceive:doctor` reports it). Replace it with the app's real ownership check. https://openreceive.org/guides/authorization.md |
|
|
44
|
-
| 404 NOT_FOUND | `amountFor` returned `null` (unknown reference), or the `payment_hash` does not belong to that reference. |
|
|
45
|
-
| 409 CONFLICT | **Normal state, not a bug**: the reference already settled, or an unpaid checkout for that method is already live. Show it as order state; never retry-loop. |
|
|
46
|
-
| 503 retryable | The host hook failed while persisting the attempt (instructions withheld), or the wallet is unavailable. Read the server log for the underlying error. |
|
|
47
|
-
| Framework 404 / HTML error page | The router is not mounted, or mounted at a different prefix than the UI's `prefix` prop. `doctor --url` distinguishes these. |
|
|
48
|
-
|
|
49
|
-
## 4. Paid but never settles
|
|
50
|
-
|
|
51
|
-
- Settlement is opportunistic: any OpenReceive request runs one reconcile pass
|
|
52
|
-
through a durable gate (min 3s between wallet scans, stretched by invoice
|
|
53
|
-
age). A quiet server settles on the next request — or run the optional
|
|
54
|
-
notification worker. No timer is missing; that is the design.
|
|
55
|
-
- An unpaid attempt closes only after a successful wallet scan at/after expiry
|
|
56
|
-
plus a 900s grace constant — a local clock alone never closes one. `expired`
|
|
57
|
-
arriving "late" is correct.
|
|
58
|
-
- `onPaid` runs once per reference, first settled attempt only, inside the
|
|
59
|
-
settlement transaction. If your fulfillment did not run, check whether the
|
|
60
|
-
guarded `UPDATE … WHERE` matched zero rows (already transitioned).
|
|
61
|
-
https://openreceive.org/guides/storage.md
|
|
62
|
-
|
|
63
|
-
## 5. Swaps and refunds
|
|
64
|
-
|
|
65
|
-
- A deposit that arrives short or late becomes `refund_required`; the payer
|
|
66
|
-
claims it on a second visit. That needs a per-order URL you serve
|
|
67
|
-
(`/checkout/:reference`, `syncUrl` on the drop-ins). Keep the
|
|
68
|
-
`payment_hash`: `POST /swaps/status` reopens the attempt with no expiry
|
|
69
|
-
window, while re-picking the coin mints a new deposit after ~30 minutes.
|
|
70
|
-
- Refunds exist only for swap deposits from `refund_required`. There is **no
|
|
71
|
-
Lightning refund** — the wallet cannot spend. Do not chase one.
|
|
72
|
-
https://openreceive.org/guides/swap-refunds.md
|
|
73
|
-
- "Payer reports two different amounts on a stablecoin checkout" (50.05 or
|
|
74
|
-
50.03?): the deposit amount is a token quantity, `fee.pay_in_fiat` is its
|
|
75
|
-
fiat valuation. Only `swap.deposit_amount` is an instruction. From 0.4.10 the
|
|
76
|
-
packaged checkout renders a USD stablecoin's breakdown in the token and never
|
|
77
|
-
shows `pay_in_fiat`; on an older bundle, upgrade `@openreceive/*`. To verify,
|
|
78
|
-
read the row's `deposit_amount` and `fee` and confirm the UI shows only the
|
|
79
|
-
deposit amount. A custom UI must call `createSwapFeeBreakdown(fee, swap)`
|
|
80
|
-
with the swap, not the fee alone.
|
|
81
|
-
|
|
82
|
-
## 6. Checkout UI shows nothing
|
|
83
|
-
|
|
84
|
-
- The components require `prefix` — the exact base path the routes are mounted
|
|
85
|
-
at (`"/openreceive"` unless you changed it).
|
|
86
|
-
- Import the stylesheet (`@openreceive/react/styles.css` or the elements
|
|
87
|
-
sheet).
|
|
88
|
-
- "invoice must not be an NWC connection string" means a server secret leaked
|
|
89
|
-
into a browser payload — stop and fix the server response; never render it.
|
|
90
|
-
https://openreceive.org/guides/frontend-checkout.md
|
|
91
|
-
|
|
92
|
-
## Still stuck
|
|
93
|
-
|
|
94
|
-
The full route/option/error reference:
|
|
95
|
-
https://openreceive.org/guides/api-reference.md · machine-readable contract:
|
|
96
|
-
https://openreceive.org/openapi.yaml · library bug reports:
|
|
97
|
-
https://openreceive.org/contact
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: integrate-openreceive
|
|
3
|
-
description: >
|
|
4
|
-
Integrate OpenReceive inbound Bitcoin Lightning payments into an application.
|
|
5
|
-
Use when adding Bitcoin, Lightning, or crypto checkout to a Node.js, Express,
|
|
6
|
-
Fastify, Next.js, Rails, React, Vue, Svelte, Angular, or plain-HTML
|
|
7
|
-
application with OpenReceive (the @openreceive/* npm packages or the
|
|
8
|
-
openreceive-rails gem), or when connecting a BTCPay Server store to a
|
|
9
|
-
receive-only NWC wallet with the OpenReceive plugin.
|
|
10
|
-
license: MIT
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Integrate OpenReceive
|
|
14
|
-
|
|
15
|
-
OpenReceive is a payment library that runs inside the application you are
|
|
16
|
-
editing. It mounts HTTP routes there, issues Lightning invoices against a
|
|
17
|
-
wallet the merchant already controls, and calls back into your code when one
|
|
18
|
-
settles. There is no OpenReceive account and no API key; funds land directly in
|
|
19
|
-
the merchant's wallet. The one required credential is a **receive-only NWC
|
|
20
|
-
code** (`NWC_URI`).
|
|
21
|
-
|
|
22
|
-
## Pick the stack, then follow its directions
|
|
23
|
-
|
|
24
|
-
1. Identify the server stack of the application you are in.
|
|
25
|
-
2. Open the matching reference — it is complete (quickstart inlined) and needs
|
|
26
|
-
no network access:
|
|
27
|
-
- Node, Express: [references/node.md](references/node.md)
|
|
28
|
-
- Node, Fastify: [references/fastify.md](references/fastify.md)
|
|
29
|
-
- Node, Next.js App Router: [references/next.md](references/next.md)
|
|
30
|
-
- Rails: [references/rails.md](references/rails.md)
|
|
31
|
-
- Django: [references/django.md](references/django.md)
|
|
32
|
-
- Laravel: [references/laravel.md](references/laravel.md)
|
|
33
|
-
- WordPress + WooCommerce: [references/woocommerce.md](references/woocommerce.md) — the packaged gateway and merchant settings.
|
|
34
|
-
- BTCPay Server: [references/btcpay.md](references/btcpay.md) — a plugin,
|
|
35
|
-
configured in BTCPay's store UI or Greenfield API; no application code,
|
|
36
|
-
no npm packages, no gem. The rest of this file is about the library.
|
|
37
|
-
3. Follow its **Step 0** first: before writing code or searching the machine,
|
|
38
|
-
ask the user for the receive-only NWC code (then the swap URI), one question
|
|
39
|
-
per message, and store each pasted code in the project's env file yourself.
|
|
40
|
-
Never print the value; never invent a placeholder.
|
|
41
|
-
|
|
42
|
-
Install, per adapter — Express: `npm install @openreceive/express @openreceive/react`;
|
|
43
|
-
Fastify: `npm install @openreceive/fastify @openreceive/react`; Next.js:
|
|
44
|
-
`npm install @openreceive/next @openreceive/react`. Swap the UI package (`vue`,
|
|
45
|
-
`svelte`, `angular`, `elements`) for the frontend the app already has. Install
|
|
46
|
-
(Rails): `bundle add openreceive-rails`.
|
|
47
|
-
|
|
48
|
-
## The three server objects
|
|
49
|
-
|
|
50
|
-
| Object | Built with | Talks to |
|
|
51
|
-
| --- | --- | --- |
|
|
52
|
-
| Wallet client | `createOpenReceive()` | the merchant's wallet — mints invoices, reads settlement, holds the NWC code |
|
|
53
|
-
| Host | `createHost()` | your database — your hooks plus the `openreceive_payments` table |
|
|
54
|
-
| HTTP routes | `openReceiveExpress()` / `openReceiveFastify()` / `openReceiveNext()` / the Rails engine | the browser — mounted at `/openreceive` by default |
|
|
55
|
-
|
|
56
|
-
The quickstart's one-factory form (`openReceiveExpress({ wallet, storage,
|
|
57
|
-
amountFor, authorize })`) builds all three; compose them separately only for a
|
|
58
|
-
shared wallet client or a custom repository. The checkout UI
|
|
59
|
-
(`<Checkout reference={...} prefix="/openreceive" />`) is the optional fourth
|
|
60
|
-
piece.
|
|
61
|
-
|
|
62
|
-
## The host contract: authorize, amountFor, onPaid
|
|
63
|
-
|
|
64
|
-
Your application keeps orders, users, prices, and fulfillment. Three hooks are
|
|
65
|
-
the entire bridge — wire them to the models this app already has, never to
|
|
66
|
-
copied demo models:
|
|
67
|
-
|
|
68
|
-
- `amountFor(reference)` — the authoritative price, read from your own data.
|
|
69
|
-
Return `{ currency, value, description }` with `value` a **decimal string**
|
|
70
|
-
(never a float, never payer input), or `null` when there is nothing to pay
|
|
71
|
-
for. The `reference` is your order id: one per thing you fulfill, created
|
|
72
|
-
before checkout, kept across retries, never reused.
|
|
73
|
-
- `authorize({ action, request, resource })` — your own access check, run on
|
|
74
|
-
every request. `resource.reference` is a claim the payer made, not proof;
|
|
75
|
-
read a real session.
|
|
76
|
-
- `onPaid({ reference, paidAt, query })` — fulfillment, run once per reference
|
|
77
|
-
inside the settlement transaction, only for the first settled attempt. Use
|
|
78
|
-
the provided `query`, not your ORM's other connection, and guard the
|
|
79
|
-
transition (`UPDATE … WHERE state = 'awaiting_payment'`).
|
|
80
|
-
|
|
81
|
-
## 409 is a state, not a failure
|
|
82
|
-
|
|
83
|
-
The library serializes attempts per reference. A create that returns **409
|
|
84
|
-
CONFLICT** is normal checkout flow: the reference already settled, or an unpaid
|
|
85
|
-
checkout for that payment method is already in progress. Surface it as order
|
|
86
|
-
state; do not retry-loop it, and do not build an idempotency store around it —
|
|
87
|
-
that serialization is the library's job. (A hook failure while persisting an
|
|
88
|
-
attempt is a **503 retryable**, deliberately distinct.)
|
|
89
|
-
|
|
90
|
-
## Amounts on the deposit panel
|
|
91
|
-
|
|
92
|
-
`swap.deposit_amount` is the ONLY amount a payer is ever told to send, in the
|
|
93
|
-
pay-in token. `swap.fee.pay_in_fiat` / `payout_fiat` are fiat valuations that
|
|
94
|
-
explain the spread (why the deposit exceeds the cart total); they are not
|
|
95
|
-
instructions. For a stablecoin pegged to the fee currency (USDT, USDC) the
|
|
96
|
-
packaged checkout expresses the breakdown in the token and never renders
|
|
97
|
-
`pay_in_fiat` — "$50.03" under "50.05 USDC" reads as the same number with a
|
|
98
|
-
typo. A custom UI gets the same rule from `createSwapFeeBreakdown(fee, swap)`;
|
|
99
|
-
pass the swap, not just the fee.
|
|
100
|
-
|
|
101
|
-
## Secrets
|
|
102
|
-
|
|
103
|
-
`NWC_URI` and `LSC_URI_*` are server-only. Never put them in browser code,
|
|
104
|
-
logs, assets, or tests. Boot fails closed if the NWC code advertises spend
|
|
105
|
-
methods such as `pay_invoice` — mint a receive-only code
|
|
106
|
-
(https://openreceive.org/get_a_nwc_code_to_receive_payments) instead of
|
|
107
|
-
overriding.
|
|
108
|
-
|
|
109
|
-
## Database tables
|
|
110
|
-
|
|
111
|
-
```sh
|
|
112
|
-
npx openreceive scaffold payments --orm prisma # or drizzle | typeorm | sequelize | knex
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
emits the `openreceive_payments` + `openreceive_meta` migration for THIS app's
|
|
116
|
-
database (Rails: `bin/rails generate openreceive:install`); run it through the
|
|
117
|
-
app's normal migration workflow. The tables sit beside your models — no
|
|
118
|
-
relations to them, no separate database, no Redis.
|
|
119
|
-
|
|
120
|
-
## Verify, and test without a real wallet
|
|
121
|
-
|
|
122
|
-
`npx openreceive doctor` checks the configuration and says what to fix.
|
|
123
|
-
|
|
124
|
-
For tests, inject a fake wallet at the stable seams — `client` on
|
|
125
|
-
`createOpenReceive` (any object with `preflight`, `makeInvoice`,
|
|
126
|
-
`listTransactions`) or `config.nwc_client` in Rails — plus
|
|
127
|
-
`StaticPriceProvider` for fiat pricing without a network. Your routes,
|
|
128
|
-
persistence, reconcile, and `onPaid` then run the production code paths.
|
|
129
|
-
Details: https://openreceive.org/guides/host-testing.md
|
|
130
|
-
|
|
131
|
-
## Deeper documentation
|
|
132
|
-
|
|
133
|
-
Fetch on demand — each URL is raw markdown:
|
|
134
|
-
https://openreceive.org/guides/authorization.md ·
|
|
135
|
-
https://openreceive.org/guides/storage.md ·
|
|
136
|
-
https://openreceive.org/guides/api-reference.md ·
|
|
137
|
-
https://openreceive.org/guides/security.md ·
|
|
138
|
-
https://openreceive.org/openapi.yaml (the normative HTTP contract) ·
|
|
139
|
-
https://openreceive.org/llms.txt (the full index)
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
# OpenReceive agent directions (BTCPay Server)
|
|
2
|
-
|
|
3
|
-
These directions describe OpenReceive 0.4.14.
|
|
4
|
-
|
|
5
|
-
Connect a BTCPay Server store to a receive-only NWC wallet with the OpenReceive
|
|
6
|
-
plugin, and optionally let payers pay BTCPay invoices with USDT, USDC, ETH or
|
|
7
|
-
SOL. You do not need a copy of the OpenReceive source, and there is no
|
|
8
|
-
application code to write: the plugin is configured through BTCPay's store UI
|
|
9
|
-
or its Greenfield API, and the quickstart is appended to this file in full.
|
|
10
|
-
|
|
11
|
-
This is the BTCPay plugin, not the Node or Rails library. Do not install
|
|
12
|
-
`@openreceive/*` packages or the `openreceive-rails` gem into a BTCPay
|
|
13
|
-
deployment, do not add `openreceive_payments` tables, and do not mount
|
|
14
|
-
OpenReceive HTTP routes. BTCPay's invoices, checkout, webhooks and Greenfield
|
|
15
|
-
API are the host; the plugin only supplies the Lightning backend and the swap
|
|
16
|
-
rail.
|
|
17
|
-
|
|
18
|
-
## What the plugin is
|
|
19
|
-
|
|
20
|
-
A BTCPay Server plugin (`BTCPayServer.Plugins.OpenReceive`) that registers a
|
|
21
|
-
Lightning connection-string handler for `type=openreceive;nwc=<NWC URI>`.
|
|
22
|
-
Saving that string makes the NWC wallet the store's Lightning node: BTCPay
|
|
23
|
-
mints every Lightning invoice in that wallet and its own `LightningListener`
|
|
24
|
-
records the payments. The plugin never calls a NIP-47 `pay_*` method, so
|
|
25
|
-
every send-side BTCPay feature (Lightning payouts, pull-payment refunds over
|
|
26
|
-
Lightning, the send tab) is unavailable by design.
|
|
27
|
-
|
|
28
|
-
The one required credential is a receive-only NWC code. A Lightning Swap
|
|
29
|
-
Connect (LSC) code optionally adds server-side swaps: a provider order aimed at
|
|
30
|
-
the invoice's existing BOLT11, tracked in the plugin's own table, with the
|
|
31
|
-
refund path on the same checkout screen.
|
|
32
|
-
|
|
33
|
-
## Step 0 — check the deployment before you change anything
|
|
34
|
-
|
|
35
|
-
1. Confirm the BTCPay Server version is 2.4.4 or later (Server Settings →
|
|
36
|
-
About, or `GET /api/v1/server/info`). The plugin declares that minimum and
|
|
37
|
-
BTCPay refuses to load it below.
|
|
38
|
-
2. Check whether the plugin is installed (the Plugins menu — the plug icon in
|
|
39
|
-
the top-right corner — under Installed Plugins, or the store navigation
|
|
40
|
-
shows an "OpenReceive" entry). If not, install it from the BTCPay plugin
|
|
41
|
-
directory (the same Plugins menu → Plugin Directory, search "openreceive",
|
|
42
|
-
then Install and Restart now), as the quickstart says; do not invent an
|
|
43
|
-
installer command.
|
|
44
|
-
3. Check whether the store already has an OpenReceive connection:
|
|
45
|
-
`GET /api/v1/stores/{storeId}/openreceive/settings` returns
|
|
46
|
-
`lightningNodeIsOpenReceive`. If true, the wallet step is done — go to
|
|
47
|
-
swaps only if the user wants them.
|
|
48
|
-
4. If no receive-only NWC code is available, stop and tell the user exactly
|
|
49
|
-
what to create:
|
|
50
|
-
|
|
51
|
-
> OpenReceive cannot mint an invoice without a receive-only NWC code. Get
|
|
52
|
-
> one at https://openreceive.org/get_a_nwc_code_to_receive_payments and
|
|
53
|
-
> paste it into Store → OpenReceive → Test connection, or hand it to me and
|
|
54
|
-
> I will set it through the Greenfield API.
|
|
55
|
-
|
|
56
|
-
Never print, log or echo the code; report only whether it is set. Never
|
|
57
|
-
paste a bare `nostr+walletconnect://` string into BTCPay's Lightning node
|
|
58
|
-
screen — that form is claimed by the Nostr plugin, without the receive-only
|
|
59
|
-
guard.
|
|
60
|
-
5. If the user wants altcoin payments, ask for an LSC code from
|
|
61
|
-
https://openreceive.org/set_up_swap_provider. Do not wait for it: the
|
|
62
|
-
wallet works without it, and swaps switch on later with one settings change.
|
|
63
|
-
|
|
64
|
-
Only then start the quickstart.
|
|
65
|
-
|
|
66
|
-
## Non-negotiables
|
|
67
|
-
|
|
68
|
-
- The connection string is `type=openreceive;nwc=<NWC URI>[;allow-spend=true]`
|
|
69
|
-
and nothing else. Set it through the setup page or
|
|
70
|
-
`PUT /api/v1/stores/{storeId}/openreceive/settings` with `nwcUri`, never by
|
|
71
|
-
editing BTCPay's Lightning node screen by hand.
|
|
72
|
-
- Receive-only is required. A code that advertises `pay_invoice` or another
|
|
73
|
-
spend method is refused on save. The override (`allowSpendCapableWallet`,
|
|
74
|
-
the checkbox on the setup page) is the user's explicit choice; never tick it
|
|
75
|
-
to make a save succeed.
|
|
76
|
-
- The wallet's network must match BTCPay's. A mismatch is a refusal, not a
|
|
77
|
-
warning.
|
|
78
|
-
- The wallet must grant `make_invoice` and `list_transactions`.
|
|
79
|
-
`lookup_invoice` is optional; do not ask the user for a code that grants it.
|
|
80
|
-
- Swaps require the store's Lightning node to be the OpenReceive connection.
|
|
81
|
-
Enabling swaps on a store using the internal node is refused
|
|
82
|
-
(`wallet_required`).
|
|
83
|
-
- Swaps set the store's invoice expiration to 60 minutes when it is shorter,
|
|
84
|
-
and the plugin refuses to create a swap on an invoice with less than the
|
|
85
|
-
provider's window left. Do not lower the expiration below 45 minutes on a
|
|
86
|
-
swap-enabled store.
|
|
87
|
-
- Top-up (amountless) invoices are unsupported on this backend. Do not
|
|
88
|
-
configure a point of sale or payment link that relies on them with this
|
|
89
|
-
wallet.
|
|
90
|
-
- Secrets stay server-side. The NWC code and LSC code live in BTCPay's
|
|
91
|
-
database like every other BTCPay credential; never copy them into
|
|
92
|
-
screenshots, tickets, browser code or logs. The provider's order token never
|
|
93
|
-
leaves the server.
|
|
94
|
-
- BTCPay's `LightningListener` is the settlement authority. Provider
|
|
95
|
-
`completed` is not payment; only the wallet reporting the Lightning invoice
|
|
96
|
-
settled is. Do not build anything that fulfils on a provider state.
|
|
97
|
-
- There is no merchant-initiated refund of a settled Lightning payment. A swap
|
|
98
|
-
refund is a payer reclaiming a deposit that never converted, and only from
|
|
99
|
-
the `refund_required` provider state.
|
|
100
|
-
|
|
101
|
-
## Verifying
|
|
102
|
-
|
|
103
|
-
Store → OpenReceive → **Run a health check** (the doctor page) runs every probe now: connection, preflight,
|
|
104
|
-
notifications, last scan, provider reachability, invoice expiration, swaps
|
|
105
|
-
needing attention. On a regtest machine, `packages/dotnet/docker/up.sh` then
|
|
106
|
-
`e2e.sh` in the OpenReceive repository proves the whole path end to end, and
|
|
107
|
-
that is the only situation where cloning the repository is the right move.
|
|
108
|
-
|
|
109
|
-
## More documentation
|
|
110
|
-
|
|
111
|
-
Fetch one when the moment comes. Each is raw markdown, so a plain GET is
|
|
112
|
-
enough; drop the `.md` for the same page a person would read.
|
|
113
|
-
|
|
114
|
-
- https://openreceive.org/guides/btcpay-reference.md — every setting, route, swap state, doctor probe and log event of the plugin
|
|
115
|
-
- https://openreceive.org/guides/security.md — why receive-only is the only wallet credential
|
|
116
|
-
- https://openreceive.org/guides/lightning-swap-connect.md — what an LSC code actually is
|
|
117
|
-
- https://openreceive.org/guides/automated-swaps.md — provider states, and what turning swaps on commits a merchant to
|
|
118
|
-
- https://openreceive.org/guides/swap-refunds.md — the refund states; the route back is BTCPay's own invoice checkout page here
|
|
119
|
-
- https://openreceive.org/guides.md — the index, if what you need is not above
|
|
120
|
-
|
|
121
|
-
Questions, or a problem with the plugin itself:
|
|
122
|
-
https://openreceive.org/contact
|
|
123
|
-
|
|
124
|
-
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## The quickstart, in full
|
|
129
|
-
|
|
130
|
-
Inlined verbatim so this file needs no network access — follow it once Step 0
|
|
131
|
-
passes. The page it comes from is https://openreceive.org/guides/quickstart-btcpay.
|
|
132
|
-
|
|
133
|
-
## BTCPay Server quickstart
|
|
134
|
-
|
|
135
|
-
Requires BTCPay Server ≥ 2.4.4.
|
|
136
|
-
|
|
137
|
-
The OpenReceive plugin makes a receive-only NWC wallet the Lightning node of a
|
|
138
|
-
BTCPay store. BTCPay creates every Lightning invoice in that wallet. It records
|
|
139
|
-
payments the same way it records any other payment. You can also let payers pay
|
|
140
|
-
a BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
|
|
141
|
-
provider. The swap pays into the same wallet. The store's internal node is
|
|
142
|
-
never used.
|
|
143
|
-
|
|
144
|
-
This is not the Node or Rails library. There are no hooks, no
|
|
145
|
-
`openreceive_payments` table and no OpenReceive HTTP routes. BTCPay's own
|
|
146
|
-
invoices, checkout, webhooks and Greenfield API do that work.
|
|
147
|
-
|
|
148
|
-
### 1. Prerequisites
|
|
149
|
-
|
|
150
|
-
- A BTCPay Server, version 2.4.4 or later, on any network (mainnet, testnet,
|
|
151
|
-
signet, regtest). The wallet must be on the same network.
|
|
152
|
-
- A receive-only NWC code for the wallet you want to receive into
|
|
153
|
-
([get one here](https://openreceive.org/get_a_nwc_code_to_receive_payments)).
|
|
154
|
-
The code must grant `make_invoice` and `list_transactions` and must not
|
|
155
|
-
advertise any spend method. `lookup_invoice` is optional.
|
|
156
|
-
- Optionally, a Lightning Swap Connect (LSC) code from a
|
|
157
|
-
[swap provider](https://openreceive.org/set_up_swap_provider), if payers
|
|
158
|
-
should be able to pay with USDT, USDC, ETH or SOL.
|
|
159
|
-
|
|
160
|
-
### 2. Install the plugin
|
|
161
|
-
|
|
162
|
-
Sign in as a **server administrator**. If someone else hosts your server, ask
|
|
163
|
-
them to install the plugin for you.
|
|
164
|
-
|
|
165
|
-
**1. Open the Plugins menu.** It is the plug icon in the top-right corner.
|
|
166
|
-
|
|
167
|
-
**2. Click Plugin Directory.**
|
|
168
|
-
|
|
169
|
-
**3. Search for `openreceive`** and click the **OpenReceive** result.
|
|
170
|
-
|
|
171
|
-
**4. Click Install in BTCPay Server.** Confirm when prompted, then click
|
|
172
|
-
**Restart now** and wait for BTCPay to come back.
|
|
173
|
-
|
|
174
|
-
At startup, BTCPay creates the plugin's two tables in its own Postgres
|
|
175
|
-
database: `openreceive_invoices` and `openreceive_swaps`, in the schema
|
|
176
|
-
`BTCPayServer.Plugins.OpenReceive`. Nothing else is created.
|
|
177
|
-
|
|
178
|
-
To build the plugin from source instead, follow
|
|
179
|
-
[the .NET workspace README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/README.md).
|
|
180
|
-
|
|
181
|
-
### 3. Connect the wallet
|
|
182
|
-
|
|
183
|
-
1. Select your store and open **OpenReceive** in its sidebar, under Wallets.
|
|
184
|
-
2. Paste your receive-only NWC code. To see what the wallet supports first,
|
|
185
|
-
click **Test connection**.
|
|
186
|
-
3. Click **Save NWC Code**.
|
|
187
|
-
4. To turn swaps on, paste a Lightning Swap Connect code and click **Save swap
|
|
188
|
-
settings**.
|
|
189
|
-
|
|
190
|
-
The page then shows **Wallet connected**. If you set up a provider, it also
|
|
191
|
-
shows **Swaps on**. There is nothing else to configure. You never open BTCPay's
|
|
192
|
-
Lightning node screen, and the plugin never reads the internal node.
|
|
193
|
-
|
|
194
|
-
Screenshots for each of those steps, and for creating a first test invoice,
|
|
195
|
-
are in the plugin's
|
|
196
|
-
[README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/BTCPayServer.Plugins.OpenReceive/README.md).
|
|
197
|
-
|
|
198
|
-
The plugin refuses to save a code whose wallet advertises a spend method such
|
|
199
|
-
as `pay_invoice`. Create a receive-only code instead. If your wallet cannot
|
|
200
|
-
make one, there is an override, but using it is a deliberate choice and the
|
|
201
|
-
plugin logs it.
|
|
202
|
-
|
|
203
|
-
Turning swaps on raises the store's invoice expiration to 60 minutes if it is
|
|
204
|
-
shorter. A swap needs the invoice to stay open for at least 45 minutes.
|
|
205
|
-
|
|
206
|
-
### 4. Check it
|
|
207
|
-
|
|
208
|
-
Click **Run a health check** on the OpenReceive page. It runs every check
|
|
209
|
-
right there:
|
|
210
|
-
|
|
211
|
-
- the connection
|
|
212
|
-
- the wallet preflight
|
|
213
|
-
- payment notifications
|
|
214
|
-
- the last wallet scan
|
|
215
|
-
- the swap provider and its assets
|
|
216
|
-
- the invoice expiration
|
|
217
|
-
- swaps that need a human
|
|
218
|
-
|
|
219
|
-
Each failing check comes with a link to the fix.
|
|
220
|
-
|
|
221
|
-
The [BTCPay plugin reference](https://openreceive.org/guides/btcpay-reference.md) lists every setting,
|
|
222
|
-
Greenfield route, swap state, log event and check. It also lists what the
|
|
223
|
-
plugin does not support by design: every send-side feature, top-up invoices,
|
|
224
|
-
and a bare `nostr+walletconnect://` string in BTCPay's Lightning node screen.
|