openreceive-rails 0.4.11 → 0.4.13
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 +19 -0
- data/lib/generators/openreceive/install/templates/initializer.rb +1 -1
- data/lib/openreceive/configuration.rb +1 -1
- data/lib/openreceive/rails/version.rb +1 -1
- data/lib/openreceive/reconcile.rb +11 -6
- data/lib/openreceive/reconcile_scan.rb +15 -2
- data/skills/debug-openreceive-payment/SKILL.md +1 -1
- data/skills/integrate-openreceive/SKILL.md +4 -3
- data/skills/integrate-openreceive/references/btcpay.md +65 -36
- data/skills/integrate-openreceive/references/django.md +288 -243
- data/skills/integrate-openreceive/references/fastapi.md +203 -154
- data/skills/integrate-openreceive/references/fastify.md +197 -146
- data/skills/integrate-openreceive/references/laravel.md +291 -233
- data/skills/integrate-openreceive/references/next.md +207 -161
- data/skills/integrate-openreceive/references/node.md +185 -137
- data/skills/integrate-openreceive/references/php.md +245 -192
- data/skills/integrate-openreceive/references/rails.md +259 -217
- data/skills/integrate-openreceive/references/woocommerce.md +66 -51
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c17784f21721177e139d27349d7fa0367f2e6f90cce005e5dd61906ac5fae96a
|
|
4
|
+
data.tar.gz: f285c9fb57805628956740699f078115672f1485916784fbc2e4b6d8738b1578
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16634ea35e356230c34b4da9ecf58bc8c089835d1b919692e41ddda40ef127fbddc23e5aa3090ba62835f90ba23b393d562a9006df015cbb5a84c2c16ec0ee35
|
|
7
|
+
data.tar.gz: 5d70f67f044bfa1003fb963da3e3e8ffe620f27517662d8fd1199ae0972906c7fcc1172196b8b8704d9c4354485700e2732dcf7d99ea818772cb19999a045296
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.13 - 2026-10-01
|
|
4
|
+
|
|
5
|
+
Reconciliation no longer livelocks on a slow wallet. A wallet-history page
|
|
6
|
+
still in flight at the scan deadline (`Timeout` in the nwc-ruby adapter) failed
|
|
7
|
+
the whole pass and dropped its resume offset, so a long walk restarted at
|
|
8
|
+
offset 0 on every pass and logged `reconciliation failed (will retry):
|
|
9
|
+
OpenReceive::Server::WalletFailureError: execution expired` forever. A cut page
|
|
10
|
+
now ends the slice and keeps the completed pages' progress. A first page cut
|
|
11
|
+
before the wallet answers still fails the pass. Host-clock attempts (legacy
|
|
12
|
+
rows without `created_at_source`) get their own full-history cohort instead of
|
|
13
|
+
widening the window of every wallet-timed attempt pending with them. The gate
|
|
14
|
+
floor rises from 2 s to 3 s (`OpenReceive::MIN_RECONCILE_INTERVAL_SECONDS`).
|
|
15
|
+
|
|
16
|
+
## 0.4.12 - 2026-10-01
|
|
17
|
+
|
|
18
|
+
Release in lockstep with the 0.4.12 checkout fixes. The Rails Buy a Button
|
|
19
|
+
demo's checkout now returns to the method grid on every "Switch payment
|
|
20
|
+
method" click. No Rails engine API change.
|
|
21
|
+
|
|
3
22
|
## 0.4.11 - 2026-09-21
|
|
4
23
|
|
|
5
24
|
Reconciliation progress is durable and bounded. A capped wallet walk now
|
|
@@ -90,7 +90,7 @@ OpenReceive.configure do |config|
|
|
|
90
90
|
|
|
91
91
|
# Settlement discovery is opportunistic by default: every engine request
|
|
92
92
|
# first runs one reconcile pass through the durable openreceive_meta gate
|
|
93
|
-
# (shared by all Puma workers; min
|
|
93
|
+
# (shared by all Puma workers; min 3s between real wallet scans), so pending
|
|
94
94
|
# attempts settle or close on any later OpenReceive call — no scheduled job
|
|
95
95
|
# required. Set false only if a dedicated worker owns scanning.
|
|
96
96
|
# config.opportunistic_reconcile = false
|
|
@@ -81,7 +81,7 @@ module OpenReceive
|
|
|
81
81
|
# durably gated reconcile pass when attempts are pending, so abandoned
|
|
82
82
|
# checkouts settle on any later OpenReceive call with no scheduled job.
|
|
83
83
|
# The openreceive_meta gate row is shared by every Puma worker/process on
|
|
84
|
-
# the host database (min
|
|
84
|
+
# the host database (min 3s between real wallet scans, stretched by
|
|
85
85
|
# invoice age). Set false to disable (e.g. when the optional
|
|
86
86
|
# `bin/rails openreceive:notifications` worker owns scanning), or a Hash
|
|
87
87
|
# with min_interval_seconds to tune.
|
|
@@ -6,9 +6,9 @@ require "openreceive/reconcile_scan"
|
|
|
6
6
|
|
|
7
7
|
module OpenReceive
|
|
8
8
|
# Floor for the durable reconcile-gate interval (seconds); stretched by
|
|
9
|
-
# invoice age (
|
|
9
|
+
# invoice age (3s while any pending invoice is under 2 minutes old, 6s under
|
|
10
10
|
# 5 minutes, else 12s). Mirrors the JS OPENRECEIVE_MIN_RECONCILE_INTERVAL_SECONDS.
|
|
11
|
-
MIN_RECONCILE_INTERVAL_SECONDS =
|
|
11
|
+
MIN_RECONCILE_INTERVAL_SECONDS = 3
|
|
12
12
|
# The deadline reaches the wallet adapter, which bounds only network I/O.
|
|
13
13
|
# Never interrupt the whole pass: it also runs host/database transactions.
|
|
14
14
|
RECONCILE_SCAN_TIMEOUT_SECONDS = 9
|
|
@@ -86,7 +86,12 @@ module OpenReceive
|
|
|
86
86
|
scheduler["cursor"] = candidates.length < Server::RECONCILE_BATCH_SIZE ? nil : last.slice("created_at", "payment_hash")
|
|
87
87
|
queued = windows.flat_map { |w| w.fetch("attempts").map { |a| a.fetch("payment_hash") } }
|
|
88
88
|
cohort = candidates.reject { |a| queued.include?(a.fetch("payment_hash")) }
|
|
89
|
-
|
|
89
|
+
# A host-clock attempt's window spans the whole wallet history. It
|
|
90
|
+
# gets its own window, so it never drags wallet-timed attempts into
|
|
91
|
+
# that walk; one the cap leaves out returns on cursor wrap.
|
|
92
|
+
cohort.partition { |a| a["created_at_source"] == "wallet" }.each do |group|
|
|
93
|
+
windows << ReconcileScan.new_window(group, observed_at, overlap_seconds) unless group.empty? || windows.length >= 2
|
|
94
|
+
end
|
|
90
95
|
end
|
|
91
96
|
end
|
|
92
97
|
window = windows.shift
|
|
@@ -266,7 +271,7 @@ module OpenReceive
|
|
|
266
271
|
)
|
|
267
272
|
end
|
|
268
273
|
|
|
269
|
-
# Info, not debug: passes are durably gated (min
|
|
274
|
+
# Info, not debug: passes are durably gated (min 3s apart, and only while
|
|
270
275
|
# attempts are pending), so operators can watch settlement discovery and
|
|
271
276
|
# the batched list_transactions window without raising the log level. All
|
|
272
277
|
# pending attempts share one creation-time window walked at most twice —
|
|
@@ -296,7 +301,7 @@ module OpenReceive
|
|
|
296
301
|
|
|
297
302
|
# The gate interval for the current pending set: the configured floor
|
|
298
303
|
# (config.opportunistic_reconcile min_interval_seconds), stretched by
|
|
299
|
-
# invoice age —
|
|
304
|
+
# invoice age — 3s while any pending invoice is under 2 minutes old, 6s
|
|
300
305
|
# under 5 minutes, else 12s. Mirrors the JS reconcile gate.
|
|
301
306
|
def reconcile_gate_interval_seconds(attempts, now, setting)
|
|
302
307
|
floor = MIN_RECONCILE_INTERVAL_SECONDS
|
|
@@ -307,7 +312,7 @@ module OpenReceive
|
|
|
307
312
|
age_stretch = attempts.map do |attempt|
|
|
308
313
|
elapsed = [now - Integer(attempt.fetch("created_at")), 0].max
|
|
309
314
|
if elapsed < 120
|
|
310
|
-
|
|
315
|
+
3
|
|
311
316
|
elsif elapsed < 300
|
|
312
317
|
6
|
|
313
318
|
else
|
|
@@ -27,7 +27,7 @@ module OpenReceive
|
|
|
27
27
|
anchor = resumed ? window["anchor_offset"] : nil
|
|
28
28
|
replaying = !anchor.nil?
|
|
29
29
|
previous = window["fingerprint"]
|
|
30
|
-
max_pages.times do
|
|
30
|
+
max_pages.times do |page_number|
|
|
31
31
|
break if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
32
32
|
|
|
33
33
|
offset = replaying ? anchor : window.fetch("offset")
|
|
@@ -35,7 +35,20 @@ module OpenReceive
|
|
|
35
35
|
request["until"] = window["until"] unless window["until"].nil?
|
|
36
36
|
request["unpaid"] = true if window.fetch("view") == "inclusive"
|
|
37
37
|
request["_deadline"] = deadline
|
|
38
|
-
page =
|
|
38
|
+
page = begin
|
|
39
|
+
OpenReceive.normalize_list_transactions_response(service.send(:call_nwc, :list_transactions, request))
|
|
40
|
+
rescue Server::WalletFailureError
|
|
41
|
+
# The client cuts the in-flight page at this deadline. After at least
|
|
42
|
+
# one answered page that ends the slice like a page answered late:
|
|
43
|
+
# the completed pages keep their progress. Failing the pass would drop
|
|
44
|
+
# the resume offset, and a wallet too slow to finish the walk in one
|
|
45
|
+
# slice would then re-walk the same first pages on every pass. A cut
|
|
46
|
+
# first page made no progress and still fails the pass, so a wallet
|
|
47
|
+
# that never answers stays visible.
|
|
48
|
+
raise if page_number.zero? || Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
49
|
+
|
|
50
|
+
return [results.values, false, false]
|
|
51
|
+
end
|
|
39
52
|
return [results.values, false, false] if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
40
53
|
|
|
41
54
|
rows = page.fetch("transactions")
|
|
@@ -49,7 +49,7 @@ same diagnostics redacted, always exit 0 — safe to share.
|
|
|
49
49
|
## 4. Paid but never settles
|
|
50
50
|
|
|
51
51
|
- Settlement is opportunistic: any OpenReceive request runs one reconcile pass
|
|
52
|
-
through a durable gate (min
|
|
52
|
+
through a durable gate (min 3s between wallet scans, stretched by invoice
|
|
53
53
|
age). A quiet server settles on the next request — or run the optional
|
|
54
54
|
notification worker. No timer is missing; that is the design.
|
|
55
55
|
- An unpaid attempt closes only after a successful wallet scan at/after expiry
|
|
@@ -34,9 +34,10 @@ code** (`NWC_URI`).
|
|
|
34
34
|
- BTCPay Server: [references/btcpay.md](references/btcpay.md) — a plugin,
|
|
35
35
|
configured in BTCPay's store UI or Greenfield API; no application code,
|
|
36
36
|
no npm packages, no gem. The rest of this file is about the library.
|
|
37
|
-
3. Follow its **Step 0** first:
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
40
41
|
|
|
41
42
|
Install, per adapter — Express: `npm install @openreceive/express @openreceive/react`;
|
|
42
43
|
Fastify: `npm install @openreceive/fastify @openreceive/react`; Next.js:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (BTCPay Server)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.13.
|
|
4
4
|
|
|
5
5
|
Connect a BTCPay Server store to a receive-only NWC wallet with the OpenReceive
|
|
6
6
|
plugin, and optionally let payers pay BTCPay invoices with USDT, USDC, ETH or
|
|
@@ -35,10 +35,12 @@ refund path on the same checkout screen.
|
|
|
35
35
|
1. Confirm the BTCPay Server version is 2.4.4 or later (Server Settings →
|
|
36
36
|
About, or `GET /api/v1/server/info`). The plugin declares that minimum and
|
|
37
37
|
BTCPay refuses to load it below.
|
|
38
|
-
2. Check whether the plugin is installed (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
42
44
|
3. Check whether the store already has an OpenReceive connection:
|
|
43
45
|
`GET /api/v1/stores/{storeId}/openreceive/settings` returns
|
|
44
46
|
`lightningNodeIsOpenReceive`. If true, the wallet step is done — go to
|
|
@@ -133,15 +135,15 @@ passes. The page it comes from is https://openreceive.org/guides/quickstart-btcp
|
|
|
133
135
|
Requires BTCPay Server ≥ 2.4.4.
|
|
134
136
|
|
|
135
137
|
The OpenReceive plugin makes a receive-only NWC wallet the Lightning node of a
|
|
136
|
-
BTCPay store. BTCPay
|
|
137
|
-
payments
|
|
138
|
-
BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
|
|
139
|
-
provider
|
|
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
|
|
140
142
|
never used.
|
|
141
143
|
|
|
142
144
|
This is not the Node or Rails library. There are no hooks, no
|
|
143
|
-
`openreceive_payments` table and no OpenReceive HTTP routes
|
|
144
|
-
invoices, checkout, webhooks and Greenfield API
|
|
145
|
+
`openreceive_payments` table and no OpenReceive HTTP routes. BTCPay's own
|
|
146
|
+
invoices, checkout, webhooks and Greenfield API do that work.
|
|
145
147
|
|
|
146
148
|
### 1. Prerequisites
|
|
147
149
|
|
|
@@ -157,39 +159,66 @@ invoices, checkout, webhooks and Greenfield API are the host.
|
|
|
157
159
|
|
|
158
160
|
### 2. Install the plugin
|
|
159
161
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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.
|
|
165
177
|
|
|
166
178
|
To build the plugin from source instead, follow
|
|
167
179
|
[the .NET workspace README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/README.md).
|
|
168
180
|
|
|
169
181
|
### 3. Connect the wallet
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
node.
|
|
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**.
|
|
178
189
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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.
|
|
184
205
|
|
|
185
206
|
### 4. Check it
|
|
186
207
|
|
|
187
|
-
**Run a health check** on the OpenReceive page runs every
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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.
|
|
191
220
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`nostr+walletconnect://` string in BTCPay's Lightning node screen.
|
|
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.
|