openreceive-rails 0.3.0 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 146123aec012ee584a8b76fd2a0377abb9e972fafd56f5332e67deee15fc7ad3
4
- data.tar.gz: d38a035c403bffa06b058ed1030a7bb55170cce1e9853c26d8ff71d4698ec6be
3
+ metadata.gz: e7ef5df95a3cc1af66502a65a1b130aad1a88ab8ff93dcd26b4e93a1d732dca2
4
+ data.tar.gz: 291e45e4efa083eee2345490086e97e757ae3009aa0f79ba3a7ef3ba55a04bb5
5
5
  SHA512:
6
- metadata.gz: 4703f944a363b826d607c7e63a3e918fb76465e5e260f54bc59a00e2e84b316ebdfd5909a91943a1bf3e7ce92051216b0c7f35b61a2e6b17a66cf20b81f3e92a
7
- data.tar.gz: 9b13db6baea87e02685030ef09ec88f741c3bd18b13b30a54189d43e2c3ecb9ba2b7e656c93b1aff2c80bd321d99bd96aebfc99eae0046b8ab0873e914fa0126
6
+ metadata.gz: 36a33f19c0791f0e127c7cb4bc6b746aa377a124d5e1c9cef9c553826416f6fb51383029c0b8ba1b0ad11e33e4b069e3156bff2d751adbafe0718825c99a9105
7
+ data.tar.gz: 18e1177d9a7974614eb8db63fe27c5414b21a0758c7e2f4f02d4e955970403217ebf6bbf3c94e1d7d840219ba99cdaf944f4bc0e64ad395fc7318937fef5e85f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 - 2026-08-29
4
+
5
+ No changes to this gem in 0.3.2. The release is the overpaid-deposit refund
6
+ mapping in `openreceive-server` and the browser packages' `resumePaymentHash`
7
+ resume path; the engine, its generators and the doctor task are byte-identical
8
+ to 0.3.1 and ship to keep the one-version-for-everything rule.
9
+
10
+ The Rails demo at `examples/buttons/server/rails` gained a testkit mode and a
11
+ `/checkout/:reference` route in this release. Neither is part of this gem.
12
+
13
+ ## 0.3.1 - 2026-08-28
14
+
15
+ ### The generated allow-all `config.authorize` is no longer silent
16
+
17
+ `openreceive:install` writes a `config.authorize` that allows every request,
18
+ treating possession of the reference as the authorization. It is now the named
19
+ constant `OpenReceive::ALLOW_ALL_AUTHORIZE`, for the same reason
20
+ `LOGGING_ON_PAID` is a constant: the engine detects it at boot by identity and
21
+ warns that anyone holding an order id can mint invoices, poll status and request
22
+ refunds for it. Safe only while references are unguessable. The five-minute demo
23
+ still works; it just stops being quiet about what it is.
24
+
25
+ ### `bin/rails openreceive:doctor`
26
+
27
+ Step 0 of the agent directions as one command. It reports credential PRESENCE
28
+ only — every line is `set` or `unset`, and no secret is printed, echoed or
29
+ partially shown, which is what makes it safe to run in a shared terminal or
30
+ paste into an issue. Alongside that: whether `OpenReceive.configure` ran, which
31
+ hooks are missing or still the generated placeholders, where the engine is
32
+ mounted, and a wallet preflight.
33
+
34
+ The wallet line is last and best-effort. It builds the service — the same eager
35
+ preflight production boot runs, so a missing `NWC_URI`, a dead relay or a
36
+ SPEND-CAPABLE connection all answer here rather than on a payer's first checkout
37
+ — and reports the failure sanitized rather than raising it, because a doctor
38
+ that dies at line six tells an operator less than one that finishes.
39
+
40
+ "Look for `NWC_URI` in this app's server environment" has a different answer on
41
+ every host shape, and in a containerised app grepping the repo finds the name in
42
+ a compose file and proves nothing about the value. This asks the process, which
43
+ is the only place that always knows.
44
+
3
45
  ## 0.3.0 - 2026-08-26
4
46
 
5
47
  ### `config.amount_for` may return a `:description` beside the price
@@ -21,8 +21,9 @@ OpenReceive.configure do |config|
21
21
  # prove this caller owns it.
22
22
  # Return true to allow the request, false for a 403.
23
23
  #
24
- # The default below allows every request, treating possession of the
25
- # reference as the authorization (an unknown reference is still a 404,
24
+ # The default below is a NAMED PLACEHOLDER, and the engine warns at boot for
25
+ # as long as it is still here. It allows every request, treating possession of
26
+ # the reference as the authorization (an unknown reference is still a 404,
26
27
  # because amount_for returns nil for it). That is only safe while your
27
28
  # references are unguessable (UUIDs, not sequential integers). If they are
28
29
  # enumerable, or an order should only be visible to the customer who placed
@@ -32,7 +33,7 @@ OpenReceive.configure do |config|
32
33
  # order = Order.find_by(id: context[:resource][:reference])
33
34
  # order && order.user_id == context[:request].session[:user_id]
34
35
  # end
35
- config.authorize = ->(_context) { true }
36
+ config.authorize = OpenReceive::ALLOW_ALL_AUTHORIZE
36
37
 
37
38
  # The price for a reference — the string your checkout passes, typically
38
39
  # your order id. Your application is the only price authority; payer input
@@ -3,6 +3,14 @@
3
3
  require "openreceive/server"
4
4
 
5
5
  module OpenReceive
6
+ # The generated initializer's placeholder `config.authorize`: it allows every
7
+ # request, treating possession of the reference as the authorization. Kept as
8
+ # a named constant for the same reason LOGGING_ON_PAID is — the engine detects
9
+ # it at boot and says out loud that anyone holding an order id can mint
10
+ # invoices, poll status and request refunds for it. The five-minute demo still
11
+ # works; it just stops being silent.
12
+ ALLOW_ALL_AUTHORIZE = ->(_context) { true }
13
+
6
14
  class ConfigurationError < StandardError; end
7
15
 
8
16
  # Passed to the quickstart `config.on_paid` inside the settlement transaction,
@@ -415,6 +423,24 @@ module OpenReceive
415
423
  preflight_skip_reason.nil?
416
424
  end
417
425
 
426
+ # One line for `bin/rails openreceive:doctor`: did the wallet check pass?
427
+ #
428
+ # It builds the service, which is the same eager preflight the engine runs
429
+ # at production boot — a missing NWC_URI, a dead relay, or a SPEND-CAPABLE
430
+ # connection all answer here rather than on a payer's first checkout. The
431
+ # failure is reported, never raised: an operator running a doctor wants
432
+ # every other line too, and a doctor that dies at line six tells them less
433
+ # than one that finishes. Sanitized for the same reason the reconcile logs
434
+ # are — a connect failure can quote the NWC URI, secret and all.
435
+ def doctor_wallet_report
436
+ return "skipped — OpenReceive.configure has not run" unless configured?
437
+
438
+ config.service
439
+ "ok — the wallet answered and is receive-only"
440
+ rescue StandardError => error
441
+ "FAILED — #{sanitize_failure_message(error)}"
442
+ end
443
+
418
444
  # nil when the boot preflight should run; otherwise the short reason the
419
445
  # engine logs, so an operator who expected a fail-closed boot and did not
420
446
  # get one can see why in the same log line.
@@ -33,6 +33,14 @@ module OpenReceive
33
33
  "config/initializers/openreceive.rb."
34
34
  )
35
35
  end
36
+ if OpenReceive.config.authorize.equal?(OpenReceive::ALLOW_ALL_AUTHORIZE)
37
+ ::Rails.logger&.warn(
38
+ "[openreceive] config.authorize is still the generated allow-all placeholder — " \
39
+ "anyone holding an order id can mint invoices, poll status, and request refunds " \
40
+ "for it. Safe only while your references are unguessable. Replace it in " \
41
+ "config/initializers/openreceive.rb."
42
+ )
43
+ end
36
44
  if ::Rails.env.production?
37
45
  skipped = OpenReceive.preflight_skip_reason
38
46
  if skipped.nil?
@@ -5,6 +5,6 @@ module OpenReceive
5
5
  # top-level `::Rails` framework constant — engine code always references the framework as
6
6
  # `::Rails` to avoid shadowing.
7
7
  module Rails
8
- VERSION = "0.3.0"
8
+ VERSION = "0.3.2"
9
9
  end
10
10
  end
@@ -1,6 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :openreceive do
4
+ # Step 0 of the agent directions, as one command.
5
+ #
6
+ # "Look for NWC_URI in this app's server environment" is a SEARCH, and it has
7
+ # a different answer on every host shape: a .env file, Rails credentials, a
8
+ # deploy config, or — in a containerised app — a value that exists only in the
9
+ # running process, fetched by a launcher script, where grepping the repo finds
10
+ # the NAME in a compose file and proves nothing about the value. This asks the
11
+ # process, which is the only place that always knows.
12
+ #
13
+ # PRESENCE ONLY. No secret is ever printed, echoed, or partially shown; every
14
+ # line is "set" or "unset". That rule is what makes this safe to run in a
15
+ # shared terminal, paste into an issue, or hand to a coding agent.
16
+ desc "Report OpenReceive's install state: credentials (set/unset only), engine mount, hooks"
17
+ task doctor: :environment do
18
+ set = ->(name) { ENV[name].to_s.strip.empty? ? "unset" : "set" }
19
+ lines = [
20
+ "openreceive:doctor",
21
+ " NWC_URI: #{set.call('NWC_URI')}",
22
+ " LSC_URI_PRIMARY: #{set.call('LSC_URI_PRIMARY')}",
23
+ " LSC_URI_BACKUP: #{set.call('LSC_URI_BACKUP')}"
24
+ ]
25
+
26
+ if OpenReceive.configured?
27
+ config = OpenReceive.config
28
+ hook = lambda do |value, placeholder, name|
29
+ return "MISSING — the engine refuses to serve checkouts without it" if value.nil?
30
+ return "the generated placeholder (#{name}) — replace it" if value.equal?(placeholder)
31
+
32
+ "set"
33
+ end
34
+ lines << " configure: run"
35
+ lines << " authorize: #{hook.call(config.authorize, OpenReceive::ALLOW_ALL_AUTHORIZE,
36
+ 'allow-all')}"
37
+ lines << " amount_for: #{config.amount_for.nil? ? 'MISSING — the engine refuses to serve checkouts without it' : 'set'}"
38
+ lines << " on_paid: #{hook.call(config.on_paid, OpenReceive::LOGGING_ON_PAID,
39
+ 'logging-only')}"
40
+ else
41
+ lines << " configure: NOT RUN — run `bin/rails generate openreceive:install`"
42
+ end
43
+
44
+ mount = Rails.application.routes.routes.find do |route|
45
+ route.app.respond_to?(:app) && route.app.app == OpenReceive::Engine
46
+ end
47
+ lines << " engine mounted: #{mount.nil? ? 'no — add `mount OpenReceive::Engine` to config/routes.rb' : "at #{mount.path.spec}"}"
48
+
49
+ # The wallet check, last and best-effort: it is the only line that talks to
50
+ # a relay, and a doctor that raises tells an operator less than one that
51
+ # reports. The receive-only verdict is the whole point — a spend-capable
52
+ # connection is refused, not warned about.
53
+ lines << " wallet preflight: #{OpenReceive.doctor_wallet_report}"
54
+
55
+ puts lines.join("\n")
56
+ end
57
+
4
58
  desc "Run one OpenReceive reconciliation pass over pending payment attempts"
5
59
  task reconcile: :environment do
6
60
  checks = OpenReceive.reconcile!
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.3.0
4
+ version: 0.3.2
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.3.0
18
+ version: 0.3.2
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.3.0
25
+ version: 0.3.2
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.3.0
32
+ version: 0.3.2
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.3.0
39
+ version: 0.3.2
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rails
42
42
  requirement: !ruby/object:Gem::Requirement