openreceive 0.4.4__tar.gz

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.
Files changed (94) hide show
  1. openreceive-0.4.4/.gitignore +82 -0
  2. openreceive-0.4.4/LICENSE +21 -0
  3. openreceive-0.4.4/PKG-INFO +77 -0
  4. openreceive-0.4.4/README.md +36 -0
  5. openreceive-0.4.4/hatch_build.py +46 -0
  6. openreceive-0.4.4/pyproject.toml +121 -0
  7. openreceive-0.4.4/src/openreceive/__init__.py +27 -0
  8. openreceive-0.4.4/src/openreceive/_generated/fulfillment_note.py +59 -0
  9. openreceive-0.4.4/src/openreceive/_generated/tables.py +426 -0
  10. openreceive-0.4.4/src/openreceive/_version.py +4 -0
  11. openreceive-0.4.4/src/openreceive/cli.py +598 -0
  12. openreceive-0.4.4/src/openreceive/django/__init__.py +13 -0
  13. openreceive-0.4.4/src/openreceive/django/admin.py +59 -0
  14. openreceive-0.4.4/src/openreceive/django/apps.py +21 -0
  15. openreceive-0.4.4/src/openreceive/django/checks.py +75 -0
  16. openreceive-0.4.4/src/openreceive/django/conf.py +203 -0
  17. openreceive-0.4.4/src/openreceive/django/management/__init__.py +0 -0
  18. openreceive-0.4.4/src/openreceive/django/management/commands/__init__.py +0 -0
  19. openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_doctor.py +70 -0
  20. openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_install.py +211 -0
  21. openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_notifications.py +62 -0
  22. openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_reconcile.py +20 -0
  23. openreceive-0.4.4/src/openreceive/django/migrations/0001_initial.py +91 -0
  24. openreceive-0.4.4/src/openreceive/django/migrations/__init__.py +0 -0
  25. openreceive-0.4.4/src/openreceive/django/models.py +111 -0
  26. openreceive-0.4.4/src/openreceive/django/repository.py +404 -0
  27. openreceive-0.4.4/src/openreceive/django/static/openreceive/MANIFEST.json +20 -0
  28. openreceive-0.4.4/src/openreceive/django/static/openreceive/openreceive-checkout.css +4 -0
  29. openreceive-0.4.4/src/openreceive/django/static/openreceive/openreceive-checkout.js +628 -0
  30. openreceive-0.4.4/src/openreceive/django/static/openreceive/openreceive-checkout.js.map +7 -0
  31. openreceive-0.4.4/src/openreceive/django/urls.py +23 -0
  32. openreceive-0.4.4/src/openreceive/django/views.py +129 -0
  33. openreceive-0.4.4/src/openreceive/fastapi/__init__.py +10 -0
  34. openreceive-0.4.4/src/openreceive/fastapi/binding.py +216 -0
  35. openreceive-0.4.4/src/openreceive/fastapi/lifespan.py +69 -0
  36. openreceive-0.4.4/src/openreceive/fastapi/requests.py +71 -0
  37. openreceive-0.4.4/src/openreceive/fastapi/router.py +127 -0
  38. openreceive-0.4.4/src/openreceive/money.py +76 -0
  39. openreceive-0.4.4/src/openreceive/nwc/__init__.py +39 -0
  40. openreceive-0.4.4/src/openreceive/nwc/client.py +39 -0
  41. openreceive-0.4.4/src/openreceive/nwc/errors.py +196 -0
  42. openreceive-0.4.4/src/openreceive/nwc/info.py +77 -0
  43. openreceive-0.4.4/src/openreceive/nwc/receive_client.py +103 -0
  44. openreceive-0.4.4/src/openreceive/nwc/requests.py +169 -0
  45. openreceive-0.4.4/src/openreceive/nwc/transport/__init__.py +6 -0
  46. openreceive-0.4.4/src/openreceive/nwc/transport/errors.py +25 -0
  47. openreceive-0.4.4/src/openreceive/nwc/transport/nip01.py +86 -0
  48. openreceive-0.4.4/src/openreceive/nwc/transport/nip04.py +34 -0
  49. openreceive-0.4.4/src/openreceive/nwc/transport/nip44.py +94 -0
  50. openreceive-0.4.4/src/openreceive/nwc/transport/receive_client.py +271 -0
  51. openreceive-0.4.4/src/openreceive/nwc/transport/relay.py +90 -0
  52. openreceive-0.4.4/src/openreceive/nwc/uri.py +105 -0
  53. openreceive-0.4.4/src/openreceive/payments/__init__.py +10 -0
  54. openreceive-0.4.4/src/openreceive/payments/reconciliation.py +49 -0
  55. openreceive-0.4.4/src/openreceive/payments/scan.py +105 -0
  56. openreceive-0.4.4/src/openreceive/rates/__init__.py +194 -0
  57. openreceive-0.4.4/src/openreceive/rates/cached_feed.py +204 -0
  58. openreceive-0.4.4/src/openreceive/rates/simple_price.py +69 -0
  59. openreceive-0.4.4/src/openreceive/rates/static.py +26 -0
  60. openreceive-0.4.4/src/openreceive/server/__init__.py +31 -0
  61. openreceive-0.4.4/src/openreceive/server/app.py +208 -0
  62. openreceive-0.4.4/src/openreceive/server/client_ip.py +67 -0
  63. openreceive-0.4.4/src/openreceive/server/doctor.py +150 -0
  64. openreceive-0.4.4/src/openreceive/server/errors.py +184 -0
  65. openreceive-0.4.4/src/openreceive/server/handler.py +827 -0
  66. openreceive-0.4.4/src/openreceive/server/hosts.py +84 -0
  67. openreceive-0.4.4/src/openreceive/server/notifications.py +100 -0
  68. openreceive-0.4.4/src/openreceive/server/rate_limit.py +76 -0
  69. openreceive-0.4.4/src/openreceive/server/reconcile.py +351 -0
  70. openreceive-0.4.4/src/openreceive/server/service.py +795 -0
  71. openreceive-0.4.4/src/openreceive/settlement.py +48 -0
  72. openreceive-0.4.4/src/openreceive/storage/__init__.py +49 -0
  73. openreceive-0.4.4/src/openreceive/storage/repository.py +310 -0
  74. openreceive-0.4.4/src/openreceive/storage/sql/__init__.py +16 -0
  75. openreceive-0.4.4/src/openreceive/storage/sql/ddl.py +68 -0
  76. openreceive-0.4.4/src/openreceive/storage/sql/repository.py +471 -0
  77. openreceive-0.4.4/src/openreceive/storage/sql/tables.py +134 -0
  78. openreceive-0.4.4/src/openreceive/swap/__init__.py +55 -0
  79. openreceive-0.4.4/src/openreceive/swap/address.py +112 -0
  80. openreceive-0.4.4/src/openreceive/swap/assets.py +46 -0
  81. openreceive-0.4.4/src/openreceive/swap/base58.py +23 -0
  82. openreceive-0.4.4/src/openreceive/swap/budget.py +94 -0
  83. openreceive-0.4.4/src/openreceive/swap/cache.py +148 -0
  84. openreceive-0.4.4/src/openreceive/swap/fixedfloat.py +873 -0
  85. openreceive-0.4.4/src/openreceive/swap/http.py +53 -0
  86. openreceive-0.4.4/src/openreceive/swap/keccak.py +81 -0
  87. openreceive-0.4.4/src/openreceive/swap/lsc_uri.py +105 -0
  88. openreceive-0.4.4/src/openreceive/swap/rates_feed.py +271 -0
  89. openreceive-0.4.4/src/openreceive/swap/state.py +79 -0
  90. openreceive-0.4.4/src/openreceive/testing/__init__.py +9 -0
  91. openreceive-0.4.4/src/openreceive/testing/fake_swap_provider.py +251 -0
  92. openreceive-0.4.4/src/openreceive/testing/fake_wallet.py +221 -0
  93. openreceive-0.4.4/src/openreceive/testing/fixtures.py +42 -0
  94. openreceive-0.4.4/src/openreceive/values.py +65 -0
@@ -0,0 +1,82 @@
1
+ /building/*
2
+
3
+ # Local environment and deployment secrets.
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+ .openreceive/
8
+
9
+ private_private_infra_details.txt
10
+
11
+ # Local planning files and the private scratch directory. Never committed;
12
+ # these names have existed locally and must not reach the public repo.
13
+ or-master-plan.txt
14
+ or-to-do.txt
15
+ /private/
16
+
17
+ .DS_Store
18
+
19
+ # Local Claude Code project memory (path-leaking, machine-specific).
20
+ .claude/projects/
21
+
22
+ # Tooling output.
23
+ *.gem
24
+ packages/ruby/*/Gemfile.lock
25
+ node_modules/
26
+ coverage/
27
+ dist/
28
+ .turbo/
29
+ .vite/
30
+ .next/
31
+ .cache/
32
+ .release/
33
+ *.tsbuildinfo
34
+
35
+ # PHP and Python engine toolchain output (packages/php, packages/python, the
36
+ # Laravel/Django/FastAPI demos). Composer's vendor/ and uv's .venv/ are
37
+ # rebuilt from composer.lock / uv.lock; never committed.
38
+ vendor/
39
+ .venv/
40
+ __pycache__/
41
+ *.pyc
42
+ .pytest_cache/
43
+ .mypy_cache/
44
+ .ruff_cache/
45
+ *.egg-info/
46
+ .phpunit.cache/
47
+ .phpunit.result.cache
48
+ tmp/*
49
+
50
+ /logs/
51
+ # The demos' file logger writes cwd-relative when enabled.
52
+ examples/*/server/*/logs/
53
+
54
+ # Local audit scratch lists and working files (any extension: .txt lists, the
55
+ # .mts/.xt one-off harnesses used while chasing a bug). Never committed.
56
+ zz-*
57
+ **/zz*.test.mjs
58
+
59
+ # Local live-test override; expected_capabilities.example.json is the tracked template.
60
+ tools/live-nwc-test/expected_capabilities.json
61
+
62
+ # Playwright artifacts
63
+ tests/e2e/test-results/
64
+
65
+ # Prisma client for the test:orms lane; `npm run test:orms` regenerates it.
66
+ tests/orms/generated/
67
+
68
+ # The Node stacks' SQLite databases and cookie secrets. The shop SURVIVES a
69
+ # restart, so unlike a disposable checkout demo these are real local state.
70
+ examples/buttons/.data/
71
+
72
+ # .NET build output (BTCPay plugin workspace)
73
+ packages/dotnet/**/bin/
74
+ packages/dotnet/**/obj/
75
+ packages/dotnet/**/bin-docker/
76
+ packages/dotnet/**/obj-docker/
77
+ packages/dotnet/**/TestResults/
78
+ packages/dotnet/docker/.state/
79
+ tests/e2e-btcpay/test-results/
80
+
81
+ # The standalone checkout build copied in by hatch_build.py at wheel/sdist build time.
82
+ packages/python/openreceive/src/openreceive/django/static/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenReceive contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.5
2
+ Name: openreceive
3
+ Version: 0.4.4
4
+ Summary: Receive-only Lightning checkout for Python hosts: NWC wallet kernel, settlement engine, framework-free HTTP handler, Django and FastAPI adapters.
5
+ Project-URL: Homepage, https://openreceive.org
6
+ Project-URL: Documentation, https://openreceive.org/guides
7
+ Project-URL: Source, https://github.com/OpenReceive/openreceive
8
+ Project-URL: Changelog, https://github.com/OpenReceive/openreceive/blob/master/CHANGELOG.md
9
+ Author-email: OpenReceive <info@openreceive.org>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: bitcoin,django,fastapi,lightning,nostr,nwc,payments
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Office/Business :: Financial
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: coincurve>=20
26
+ Requires-Dist: cryptography>=42
27
+ Requires-Dist: httpx>=0.27
28
+ Requires-Dist: websockets<16,>=13
29
+ Provides-Extra: django
30
+ Requires-Dist: django>=5.2; extra == 'django'
31
+ Provides-Extra: fastapi
32
+ Requires-Dist: fastapi>=0.115; extra == 'fastapi'
33
+ Requires-Dist: sqlalchemy>=2; extra == 'fastapi'
34
+ Requires-Dist: starlette>=0.40; extra == 'fastapi'
35
+ Provides-Extra: flask
36
+ Requires-Dist: flask>=3; extra == 'flask'
37
+ Requires-Dist: sqlalchemy>=2; extra == 'flask'
38
+ Provides-Extra: sqlalchemy
39
+ Requires-Dist: sqlalchemy>=2; extra == 'sqlalchemy'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # openreceive
43
+
44
+ Receive-only Lightning checkout for Python hosts, over Nostr Wallet Connect.
45
+ One distribution with framework extras:
46
+
47
+ ```sh
48
+ pip install "openreceive[django]" # Django >= 5.2
49
+ pip install "openreceive[fastapi]" # FastAPI >= 0.115 (sync SQLAlchemy engine for the two tables)
50
+ pip install "openreceive[sqlalchemy]" # plain hosts: the handler + the SQL repository
51
+ ```
52
+
53
+ The package is engine #5 of the OpenReceive monorepo and reproduces the shared
54
+ money, settlement, NIP-47 paging, swap-provider and HTTP behavior pinned by
55
+ `spec/test-vectors/` (`tools/conformance/python-crosslang.py`). The host owns
56
+ orders, prices and fulfillment; OpenReceive owns the `openreceive_payments` /
57
+ `openreceive_meta` rows inside the host's database, the per-reference lock,
58
+ write-once settlement and the reconciliation state machine.
59
+
60
+ Layout:
61
+
62
+ - `openreceive` — kernel: `money`, `settlement`, `nwc` (URI, info, requests,
63
+ errors, the `ReceiveNwcClient` protocol and the production client),
64
+ `payments` (wallet walk, closure decision), `swap`, `rates`.
65
+ - `openreceive.storage` — the `PaymentRepository` protocol, the SQLAlchemy
66
+ Core repository (`SqlPaymentRepository`, `payments_schema_sql(dialect)`).
67
+ - `openreceive.server` — `Service`, the framework-free `RequestHandler`
68
+ (request → status/body/headers), `OpenReceiveApp` (handler + repository +
69
+ the gated opportunistic reconcile), notifications worker, doctor, `Host`.
70
+ - `openreceive.testing` — `FakeWallet`, `FakeSwapProvider` and the testkit
71
+ fixtures every engine shares (`docs/internal/testkit-contract.md`).
72
+
73
+ Documentation: https://openreceive.org/guides — quickstarts for Django and
74
+ FastAPI, the storage guide, and the host testing guide.
75
+
76
+ Receive-only NWC codes must never reach browsers, logs or tests. `NWC_URI`,
77
+ `LSC_URI_PRIMARY` and `LSC_URI_BACKUP` are read from the process environment.
@@ -0,0 +1,36 @@
1
+ # openreceive
2
+
3
+ Receive-only Lightning checkout for Python hosts, over Nostr Wallet Connect.
4
+ One distribution with framework extras:
5
+
6
+ ```sh
7
+ pip install "openreceive[django]" # Django >= 5.2
8
+ pip install "openreceive[fastapi]" # FastAPI >= 0.115 (sync SQLAlchemy engine for the two tables)
9
+ pip install "openreceive[sqlalchemy]" # plain hosts: the handler + the SQL repository
10
+ ```
11
+
12
+ The package is engine #5 of the OpenReceive monorepo and reproduces the shared
13
+ money, settlement, NIP-47 paging, swap-provider and HTTP behavior pinned by
14
+ `spec/test-vectors/` (`tools/conformance/python-crosslang.py`). The host owns
15
+ orders, prices and fulfillment; OpenReceive owns the `openreceive_payments` /
16
+ `openreceive_meta` rows inside the host's database, the per-reference lock,
17
+ write-once settlement and the reconciliation state machine.
18
+
19
+ Layout:
20
+
21
+ - `openreceive` — kernel: `money`, `settlement`, `nwc` (URI, info, requests,
22
+ errors, the `ReceiveNwcClient` protocol and the production client),
23
+ `payments` (wallet walk, closure decision), `swap`, `rates`.
24
+ - `openreceive.storage` — the `PaymentRepository` protocol, the SQLAlchemy
25
+ Core repository (`SqlPaymentRepository`, `payments_schema_sql(dialect)`).
26
+ - `openreceive.server` — `Service`, the framework-free `RequestHandler`
27
+ (request → status/body/headers), `OpenReceiveApp` (handler + repository +
28
+ the gated opportunistic reconcile), notifications worker, doctor, `Host`.
29
+ - `openreceive.testing` — `FakeWallet`, `FakeSwapProvider` and the testkit
30
+ fixtures every engine shares (`docs/internal/testkit-contract.md`).
31
+
32
+ Documentation: https://openreceive.org/guides — quickstarts for Django and
33
+ FastAPI, the storage guide, and the host testing guide.
34
+
35
+ Receive-only NWC codes must never reach browsers, logs or tests. `NWC_URI`,
36
+ `LSC_URI_PRIMARY` and `LSC_URI_BACKUP` are read from the process environment.
@@ -0,0 +1,46 @@
1
+ """Hatchling build hook: carry the standalone checkout build into the wheel.
2
+
3
+ `openreceive.django` templates load `{% static "openreceive/openreceive-checkout.js" %}`
4
+ and its stylesheet; those files are the output of `npm run build:packages`
5
+ (tools/package/build-standalone-elements.mjs → packages/js/elements/dist/standalone/),
6
+ not Python source, so they are NOT committed under src/. This hook copies that
7
+ tree into `src/openreceive/django/static/openreceive/` whenever it exists next
8
+ to this package in the monorepo — for `uv build` (sdist and wheel) and for the
9
+ editable install `uv sync` performs — so the wheel carries the assets and their
10
+ MANIFEST.json. Outside the monorepo (a wheel built from the sdist) the copy is
11
+ already inside the sdist and nothing happens. With neither present the build
12
+ still succeeds and warns: the engine works without the static files; only the
13
+ packaged-static render path in the quickstart needs them.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import shutil
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
23
+
24
+ STANDALONE_SOURCE = Path("..", "..", "js", "elements", "dist", "standalone")
25
+ STATIC_TARGET = Path("src", "openreceive", "django", "static", "openreceive")
26
+
27
+
28
+ class StandaloneCheckoutHook(BuildHookInterface): # type: ignore[type-arg]
29
+ PLUGIN_NAME = "standalone-checkout"
30
+
31
+ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
32
+ root = Path(self.root)
33
+ source = (root / STANDALONE_SOURCE).resolve()
34
+ target = root / STATIC_TARGET
35
+ if (source / "MANIFEST.json").is_file():
36
+ if target.exists():
37
+ shutil.rmtree(target)
38
+ shutil.copytree(source, target)
39
+ return
40
+ if (target / "MANIFEST.json").is_file():
41
+ return
42
+ self.app.display_warning(
43
+ f"openreceive: {STATIC_TARGET} is empty — the standalone checkout build was not found at "
44
+ f"{STANDALONE_SOURCE} (run `npm run build:packages` in the monorepo first). The wheel "
45
+ "will ship without the packaged static checkout files."
46
+ )
@@ -0,0 +1,121 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openreceive"
7
+ dynamic = ["version"]
8
+ description = "Receive-only Lightning checkout for Python hosts: NWC wallet kernel, settlement engine, framework-free HTTP handler, Django and FastAPI adapters."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "OpenReceive", email = "info@openreceive.org" }]
13
+ keywords = ["lightning", "bitcoin", "nwc", "nostr", "payments", "django", "fastapi"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Framework :: Django",
24
+ "Framework :: FastAPI",
25
+ "Topic :: Office/Business :: Financial",
26
+ ]
27
+ dependencies = [
28
+ "coincurve>=20",
29
+ "cryptography>=42",
30
+ "websockets>=13,<16",
31
+ "httpx>=0.27",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ django = ["Django>=5.2"]
36
+ fastapi = ["fastapi>=0.115", "starlette>=0.40", "sqlalchemy>=2"]
37
+ flask = ["flask>=3", "sqlalchemy>=2"]
38
+ sqlalchemy = ["sqlalchemy>=2"]
39
+
40
+ [project.urls]
41
+ Homepage = "https://openreceive.org"
42
+ Documentation = "https://openreceive.org/guides"
43
+ Source = "https://github.com/OpenReceive/openreceive"
44
+ Changelog = "https://github.com/OpenReceive/openreceive/blob/master/CHANGELOG.md"
45
+
46
+ [project.scripts]
47
+ openreceive = "openreceive.cli:main"
48
+
49
+ [dependency-groups]
50
+ dev = [
51
+ "pytest>=8",
52
+ "pytest-django>=4.9",
53
+ "ruff>=0.6",
54
+ "mypy>=1.11",
55
+ "sqlalchemy>=2",
56
+ "alembic>=1.13",
57
+ "fastapi>=0.115",
58
+ "starlette>=0.40",
59
+ "psycopg[binary]>=3.2",
60
+ "pymysql>=1.1",
61
+ "Django>=5.2",
62
+ "django-stubs>=5.2",
63
+ ]
64
+
65
+ [tool.hatch.version]
66
+ path = "src/openreceive/_version.py"
67
+
68
+ # Copies packages/js/elements/dist/standalone into django/static/openreceive at
69
+ # build time (hatch_build.py); the copy is gitignored, the wheel carries it.
70
+ [tool.hatch.build.hooks.custom]
71
+ path = "hatch_build.py"
72
+
73
+ [tool.hatch.build.targets.wheel]
74
+ packages = ["src/openreceive"]
75
+ # The Django app's static checkout assets and migrations are package data
76
+ # (hatchling needs the explicit include; verify with `unzip -l` in CI).
77
+ include = [
78
+ "src/openreceive/**/*.py",
79
+ "src/openreceive/django/static/**",
80
+ "src/openreceive/django/migrations/**",
81
+ ]
82
+
83
+ [tool.hatch.build.targets.sdist]
84
+ include = ["src/openreceive", "README.md", "pyproject.toml", "hatch_build.py"]
85
+ exclude = ["tests/fixtures/**"]
86
+
87
+ [tool.pytest.ini_options]
88
+ testpaths = ["tests"]
89
+ pythonpath = ["src", "."]
90
+ addopts = "-q -p no:cacheprovider"
91
+ # The Django adapter's tests (tests/django) run through pytest-django against
92
+ # the package's own sqlite test settings; the rest of the suite ignores them.
93
+ DJANGO_SETTINGS_MODULE = "tests.django.settings"
94
+ filterwarnings = ["error::DeprecationWarning:openreceive.*"]
95
+
96
+ [tool.ruff]
97
+ line-length = 100
98
+ target-version = "py310"
99
+ src = ["src", "tests"]
100
+ # Generated renderings are never hand-edited (npm run generate:models owns them).
101
+ extend-exclude = ["src/openreceive/_generated"]
102
+
103
+ [tool.ruff.lint]
104
+ select = ["E", "F", "I", "UP", "B", "W"]
105
+ ignore = ["B904", "E501"]
106
+
107
+ [tool.ruff.lint.isort]
108
+ # tests/fastapi and tests/django sit under a `src` root, so isort would
109
+ # otherwise file the frameworks themselves as first-party.
110
+ known-third-party = ["fastapi", "starlette", "django"]
111
+
112
+ [tool.mypy]
113
+ python_version = "3.10"
114
+ strict = true
115
+ mypy_path = "src"
116
+ packages = ["openreceive"]
117
+ warn_unreachable = true
118
+
119
+ [[tool.mypy.overrides]]
120
+ module = ["coincurve", "coincurve.*"]
121
+ ignore_missing_imports = true
@@ -0,0 +1,27 @@
1
+ """OpenReceive for Python: receive-only Lightning checkout over NWC.
2
+
3
+ The kernel (money, settlement, NIP-47 normalization, the wallet walk, the
4
+ closure decision, swaps) is pure functions over dicts; the server
5
+ (`openreceive.server`) binds it to a wallet client, a price feed and a payment
6
+ repository, and `openreceive.django` / `openreceive.fastapi` mount the
7
+ framework-free handler. This module re-exports only the kernel entry points
8
+ hosts reach for directly.
9
+ """
10
+
11
+ from openreceive._version import __version__
12
+ from openreceive.money import quote_fiat_to_msats
13
+ from openreceive.nwc.uri import NWC_CODE_HELP_URL, NwcUriParseError, parse_uri, redact_uri
14
+ from openreceive.settlement import is_settled
15
+
16
+ parse_nwc_uri = parse_uri
17
+ redact_nwc_uri = redact_uri
18
+
19
+ __all__ = [
20
+ "NWC_CODE_HELP_URL",
21
+ "NwcUriParseError",
22
+ "__version__",
23
+ "is_settled",
24
+ "parse_nwc_uri",
25
+ "quote_fiat_to_msats",
26
+ "redact_nwc_uri",
27
+ ]
@@ -0,0 +1,59 @@
1
+ """GENERATED FILE — DO NOT EDIT.
2
+
3
+ Source: spec/data/fulfillment-note.txt (npm run generate:models).
4
+ Twins: packages/js/core/src/generated/fulfillment-note-text.ts, packages/ruby/openreceive-rails/lib/openreceive/generated/fulfillment_note.rb,
5
+ packages/php/openreceive/src/Generated/FulfillmentNote.php; all render the same text, so no host scaffold can
6
+ give different advice.
7
+ """
8
+
9
+ from typing import Final
10
+
11
+ # The note's lines, with "{{table}}" awaiting the caller's table name.
12
+ FULFILLMENT_NOTE_TEMPLATE: Final = (
13
+ "Fulfilling exactly once",
14
+ "",
15
+ "WHAT OPENRECEIVE GUARANTEES",
16
+ "",
17
+ "Across every settlement path OpenReceive itself owns (wallet notifications,",
18
+ "the opportunistic reconcile pass, an explicit reconcile job), the settlement",
19
+ "hook runs AT MOST ONCE per reference. The library serializes on its own",
20
+ "`{{table}}` rows, decides the winner there, and runs your hook",
21
+ "inside that same transaction. A second payment to a second invoice for the",
22
+ "same order is still recorded - with `status_reason = 'duplicate_settlement'`",
23
+ "- but never fulfills a second time. You do not need to add a lock for this.",
24
+ "",
25
+ "That makes the reference the unit of fulfillment: give every payable order",
26
+ "its own reference, created before checkout, kept across retries, and never",
27
+ "reused. A new checkout under a reference that has already settled is refused",
28
+ "with a 409 rather than fulfilled again; a fresh reference per page load",
29
+ "leaves one order payable twice.",
30
+ "",
31
+ "WHAT YOU MUST GUARANTEE",
32
+ "",
33
+ "OpenReceive cannot see fulfillment that happens outside it. If ANY other",
34
+ "path can also mark this order fulfilled - an admin action, a second payment",
35
+ "processor, a support tool, a replayed webhook, a retried background job -",
36
+ "then those paths race each other, not OpenReceive, and you must make",
37
+ "fulfillment idempotent yourself.",
38
+ "",
39
+ "The usual way is to make the transition itself the lock: guard it with a",
40
+ "conditional write that only one transaction can win.",
41
+ "",
42
+ " -- Idempotent by construction: the WHERE clause is the guard. Whoever",
43
+ " -- flips 'awaiting_payment' -> 'paid' first is the only one who fulfills;",
44
+ " -- every later attempt updates 0 rows and must do nothing.",
45
+ " UPDATE orders",
46
+ " SET state = 'paid', paid_at = :paid_at",
47
+ " WHERE id = :reference",
48
+ " AND state = 'awaiting_payment';",
49
+ " -- then: if 0 rows were affected, return without shipping anything.",
50
+ "",
51
+ "If your fulfillment is a read-modify-write that cannot be expressed as one",
52
+ "conditional UPDATE, take a row lock for the duration instead:",
53
+ "",
54
+ " SELECT * FROM orders WHERE id = :reference FOR UPDATE; -- postgres/mysql",
55
+ " -- ...check state, ship, write the new state, all before COMMIT.",
56
+ "",
57
+ "Run either one inside the transaction OpenReceive hands your settlement",
58
+ "hook, so the order transition and the payment record commit together.",
59
+ )