openreceive 0.4.10 → 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 +30 -0
- data/lib/openreceive/core.rb +30 -5
- data/lib/openreceive/nwc_ruby.rb +13 -1
- data/lib/openreceive/version.rb +1 -1
- data/skills/debug-openreceive-payment/SKILL.md +1 -1
- data/skills/integrate-openreceive/SKILL.md +4 -3
- data/skills/integrate-openreceive/references/btcpay.md +67 -36
- data/skills/integrate-openreceive/references/django.md +291 -244
- data/skills/integrate-openreceive/references/fastapi.md +206 -155
- data/skills/integrate-openreceive/references/fastify.md +200 -147
- data/skills/integrate-openreceive/references/laravel.md +294 -234
- data/skills/integrate-openreceive/references/next.md +210 -162
- data/skills/integrate-openreceive/references/node.md +188 -138
- data/skills/integrate-openreceive/references/php.md +248 -193
- data/skills/integrate-openreceive/references/rails.md +262 -218
- data/skills/integrate-openreceive/references/woocommerce.md +68 -51
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 01e1bb860a7a293671cf05f57aab92369123dcade28a58bdb21c7c0917d3e53c
|
|
4
|
+
data.tar.gz: ccdce9021e041361aa8340972fea01cdf4e762bcf4798903900185ec9535f63c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72862dc8a7059c13ff37db4261c4c6f37daaf2eba0e7bceebaf6cc2eeae8754938bb2aa853790856a0429922d6eff465d65f8e293759d9757c998b7b7060b107
|
|
7
|
+
data.tar.gz: c3f5e0fc7c645e3a298579f29f926e1c33ee7bfa9fc5f764ef5e826d0905f34705439adde32156eb3758944dd14346ef18a50a4b315901683a3a512a056509e0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.13 - 2026-10-01
|
|
4
|
+
|
|
5
|
+
Release in lockstep with the 0.4.13 reconciliation fixes (in
|
|
6
|
+
`openreceive-rails`: a slow wallet no longer livelocks the reconcile pass,
|
|
7
|
+
host-clock attempts get their own scan window, the gate floor is 3 s). No
|
|
8
|
+
change to this gem's API.
|
|
9
|
+
|
|
10
|
+
## 0.4.12 - 2026-10-01
|
|
11
|
+
|
|
12
|
+
Release in lockstep with the 0.4.12 checkout fixes ("Switch payment method"
|
|
13
|
+
always returns to the method grid; the swap deposit warning drops its "pay
|
|
14
|
+
with one method only" sentence). No Ruby core changes.
|
|
15
|
+
|
|
16
|
+
## 0.4.11 - 2026-09-21
|
|
17
|
+
|
|
18
|
+
Wallet history scans stop trusting a page's usable length. A page the wallet
|
|
19
|
+
sent full but that lost a malformed row to normalization no longer reads as
|
|
20
|
+
the end of the history, the skipped-row count survives a second normalization
|
|
21
|
+
(the walk normalizes a page the client already normalized), and the offset
|
|
22
|
+
advances by the rows the wallet actually sent. A non-object transaction row is
|
|
23
|
+
now a skipped row rather than a normalization crash. Two new cases in
|
|
24
|
+
`spec/test-vectors/wallet-scan-truncation.json` cover both shapes.
|
|
25
|
+
|
|
26
|
+
Normalized wallet errors are redacted: `nostr+walletconnect:` and
|
|
27
|
+
`lightning+swapconnect:` URIs and `key=`/`token=`/`preimage=`-style parameters
|
|
28
|
+
never survive into a message, and `redact_secrets` is available for diagnostic
|
|
29
|
+
payloads. The nwc-ruby adapter enforces a scan deadline inside the wallet
|
|
30
|
+
request itself (`_deadline`), bounding only wallet I/O — no database
|
|
31
|
+
transaction or fulfillment callback is interrupted.
|
|
32
|
+
|
|
3
33
|
## 0.4.10 - 2026-09-16
|
|
4
34
|
|
|
5
35
|
Release in lockstep with the 0.4.10 stablecoin checkout fix. The shared
|
data/lib/openreceive/core.rb
CHANGED
|
@@ -185,9 +185,13 @@ module OpenReceive
|
|
|
185
185
|
# settle nor close pending attempts (a permanent livelock while the bad
|
|
186
186
|
# row stays inside the scan window). Bad rows are skipped and counted.
|
|
187
187
|
# Mirrors the JS normalizeListTransactionsResult policy.
|
|
188
|
+
# A page a client already normalized arrives with its count: the wallet
|
|
189
|
+
# walk normalizes again, and must still see how many rows the wallet sent.
|
|
188
190
|
transactions = []
|
|
189
|
-
skipped_rows = 0
|
|
191
|
+
skipped_rows = data["skipped_rows"].is_a?(Integer) ? data["skipped_rows"] : 0
|
|
190
192
|
rows.each do |row|
|
|
193
|
+
raise ArgumentError, "non-object transaction row" unless row.respond_to?(:each_pair)
|
|
194
|
+
|
|
191
195
|
transactions << normalize_transaction(row)
|
|
192
196
|
rescue StandardError
|
|
193
197
|
skipped_rows += 1
|
|
@@ -357,6 +361,23 @@ module OpenReceive
|
|
|
357
361
|
# Normalize any wallet/library failure into the canonical error body shape
|
|
358
362
|
# shared with JS (spec/test-vectors/error-normalization.json):
|
|
359
363
|
# { "code", "message", "retryable", "request_id"?, "details"? }.
|
|
364
|
+
def redact_error_text(value)
|
|
365
|
+
value.to_s.gsub(/nostr\+walletconnect:[^\s"'`<>]+/i, "[REDACTED_NWC]")
|
|
366
|
+
.gsub(/lightning\+swapconnect:[^\s"'`<>]+/i, "[REDACTED_LSC]")
|
|
367
|
+
.gsub(/((?:key|secret|client_secret|provider_token|api_key|apikey|token|preimage)=)[^&\s"'<>]+/i, '\\1[REDACTED]')
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def redact_secrets(value)
|
|
371
|
+
case value
|
|
372
|
+
when String then redact_error_text(value)
|
|
373
|
+
when Array then value.map { |item| redact_secrets(item) }
|
|
374
|
+
when Hash
|
|
375
|
+
sensitive = %w[secret clientsecret providertoken apikey key token preimage invoice bolt11 swapdata authorization password nwc nwcuri lscuri]
|
|
376
|
+
value.to_h { |key, item| [key, sensitive.include?(key.to_s.downcase.gsub(/[^a-z0-9]/, "")) ? "[REDACTED]" : redact_secrets(item)] }
|
|
377
|
+
else value
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
360
381
|
def normalize_wallet_error(raw)
|
|
361
382
|
records = collect_error_records(raw)
|
|
362
383
|
code = error_code_from_records(records) ||
|
|
@@ -364,7 +385,7 @@ module OpenReceive
|
|
|
364
385
|
"OTHER"
|
|
365
386
|
{
|
|
366
387
|
"code" => code,
|
|
367
|
-
"message" => error_message_from(records, raw, code),
|
|
388
|
+
"message" => redact_error_text(error_message_from(records, raw, code)),
|
|
368
389
|
"retryable" => first_boolean(records, "retryable") { RETRYABLE_ERROR_CODES.include?(code) },
|
|
369
390
|
"request_id" => first_string(records, %w[request_id requestId]),
|
|
370
391
|
"details" => records.filter_map { |record| record["details"] if record["details"].is_a?(Hash) }.first
|
|
@@ -496,7 +517,8 @@ module OpenReceive
|
|
|
496
517
|
request["unpaid"] = true if include_unpaid
|
|
497
518
|
request["from"] = scan_from unless scan_from.nil?
|
|
498
519
|
request["until"] = scan_until unless scan_until.nil?
|
|
499
|
-
|
|
520
|
+
response = Nwc.normalize_list_transactions_response(client.list_transactions(request))
|
|
521
|
+
page = response.fetch("transactions")
|
|
500
522
|
page.each do |row|
|
|
501
523
|
next unless row["type"].nil? || row["type"] == "incoming"
|
|
502
524
|
payment_hash = row_payment_hash(row)
|
|
@@ -504,7 +526,10 @@ module OpenReceive
|
|
|
504
526
|
rows[payment_hash] = row
|
|
505
527
|
outstanding.delete(payment_hash)
|
|
506
528
|
end
|
|
507
|
-
|
|
529
|
+
# The wallet ran out of rows only when the page IT sent was short: a
|
|
530
|
+
# row the normalizer dropped was still a row, and a full page with one
|
|
531
|
+
# of them dropped must not read as the end of the history.
|
|
532
|
+
if outstanding.empty? || page.length + response.fetch("skipped_rows", 0) == 0
|
|
508
533
|
truncated = false
|
|
509
534
|
break
|
|
510
535
|
end
|
|
@@ -513,7 +538,7 @@ module OpenReceive
|
|
|
513
538
|
page_key = page.map { |row| row["payment_hash"].to_s }.join(",")
|
|
514
539
|
break if page_key == previous_page
|
|
515
540
|
previous_page = page_key
|
|
516
|
-
offset +=
|
|
541
|
+
offset += page.length + response.fetch("skipped_rows", 0)
|
|
517
542
|
end
|
|
518
543
|
{ rows: rows, truncated: truncated }
|
|
519
544
|
end
|
data/lib/openreceive/nwc_ruby.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# `openreceive` umbrella, which loads this adapter — carries everything the
|
|
7
7
|
# adapter calls, so requiring this file directly keeps working.
|
|
8
8
|
require_relative "core"
|
|
9
|
+
require "timeout"
|
|
9
10
|
|
|
10
11
|
module OpenReceive
|
|
11
12
|
# Thin adapter binding the engine to the nwc-ruby gem (NwcRuby::Client).
|
|
@@ -29,7 +30,18 @@ module OpenReceive
|
|
|
29
30
|
def list_transactions(request)
|
|
30
31
|
params = symbolize_keys(OpenReceive.list_transactions_nip47_request(request))
|
|
31
32
|
params[:until_ts] = params.delete(:until) if params.key?(:until)
|
|
32
|
-
|
|
33
|
+
deadline = request["_deadline"]
|
|
34
|
+
response = if deadline.nil?
|
|
35
|
+
@client.list_transactions(**params)
|
|
36
|
+
else
|
|
37
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
38
|
+
raise Timeout::Error, "Wallet history scan deadline exceeded." unless remaining.positive?
|
|
39
|
+
|
|
40
|
+
# Bound only wallet I/O. The gem closes its per-call socket in
|
|
41
|
+
# ensure; no database transaction or fulfillment is interrupted.
|
|
42
|
+
Timeout.timeout(remaining) { @client.list_transactions(**params) }
|
|
43
|
+
end
|
|
44
|
+
OpenReceive.normalize_list_transactions_response(response)
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
def preflight
|
data/lib/openreceive/version.rb
CHANGED
|
@@ -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
|
|
@@ -119,6 +121,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
119
121
|
Questions, or a problem with the plugin itself:
|
|
120
122
|
https://openreceive.org/contact
|
|
121
123
|
|
|
124
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
125
|
+
|
|
122
126
|
---
|
|
123
127
|
|
|
124
128
|
## The quickstart, in full
|
|
@@ -131,15 +135,15 @@ passes. The page it comes from is https://openreceive.org/guides/quickstart-btcp
|
|
|
131
135
|
Requires BTCPay Server ≥ 2.4.4.
|
|
132
136
|
|
|
133
137
|
The OpenReceive plugin makes a receive-only NWC wallet the Lightning node of a
|
|
134
|
-
BTCPay store. BTCPay
|
|
135
|
-
payments
|
|
136
|
-
BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
|
|
137
|
-
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
|
|
138
142
|
never used.
|
|
139
143
|
|
|
140
144
|
This is not the Node or Rails library. There are no hooks, no
|
|
141
|
-
`openreceive_payments` table and no OpenReceive HTTP routes
|
|
142
|
-
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.
|
|
143
147
|
|
|
144
148
|
### 1. Prerequisites
|
|
145
149
|
|
|
@@ -155,39 +159,66 @@ invoices, checkout, webhooks and Greenfield API are the host.
|
|
|
155
159
|
|
|
156
160
|
### 2. Install the plugin
|
|
157
161
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
163
177
|
|
|
164
178
|
To build the plugin from source instead, follow
|
|
165
179
|
[the .NET workspace README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/README.md).
|
|
166
180
|
|
|
167
181
|
### 3. Connect the wallet
|
|
168
182
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
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**.
|
|
176
189
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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.
|
|
182
205
|
|
|
183
206
|
### 4. Check it
|
|
184
207
|
|
|
185
|
-
**Run a health check** on the OpenReceive page runs every
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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.
|
|
189
220
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`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.
|