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.
- openreceive-0.4.4/.gitignore +82 -0
- openreceive-0.4.4/LICENSE +21 -0
- openreceive-0.4.4/PKG-INFO +77 -0
- openreceive-0.4.4/README.md +36 -0
- openreceive-0.4.4/hatch_build.py +46 -0
- openreceive-0.4.4/pyproject.toml +121 -0
- openreceive-0.4.4/src/openreceive/__init__.py +27 -0
- openreceive-0.4.4/src/openreceive/_generated/fulfillment_note.py +59 -0
- openreceive-0.4.4/src/openreceive/_generated/tables.py +426 -0
- openreceive-0.4.4/src/openreceive/_version.py +4 -0
- openreceive-0.4.4/src/openreceive/cli.py +598 -0
- openreceive-0.4.4/src/openreceive/django/__init__.py +13 -0
- openreceive-0.4.4/src/openreceive/django/admin.py +59 -0
- openreceive-0.4.4/src/openreceive/django/apps.py +21 -0
- openreceive-0.4.4/src/openreceive/django/checks.py +75 -0
- openreceive-0.4.4/src/openreceive/django/conf.py +203 -0
- openreceive-0.4.4/src/openreceive/django/management/__init__.py +0 -0
- openreceive-0.4.4/src/openreceive/django/management/commands/__init__.py +0 -0
- openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_doctor.py +70 -0
- openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_install.py +211 -0
- openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_notifications.py +62 -0
- openreceive-0.4.4/src/openreceive/django/management/commands/openreceive_reconcile.py +20 -0
- openreceive-0.4.4/src/openreceive/django/migrations/0001_initial.py +91 -0
- openreceive-0.4.4/src/openreceive/django/migrations/__init__.py +0 -0
- openreceive-0.4.4/src/openreceive/django/models.py +111 -0
- openreceive-0.4.4/src/openreceive/django/repository.py +404 -0
- openreceive-0.4.4/src/openreceive/django/static/openreceive/MANIFEST.json +20 -0
- openreceive-0.4.4/src/openreceive/django/static/openreceive/openreceive-checkout.css +4 -0
- openreceive-0.4.4/src/openreceive/django/static/openreceive/openreceive-checkout.js +628 -0
- openreceive-0.4.4/src/openreceive/django/static/openreceive/openreceive-checkout.js.map +7 -0
- openreceive-0.4.4/src/openreceive/django/urls.py +23 -0
- openreceive-0.4.4/src/openreceive/django/views.py +129 -0
- openreceive-0.4.4/src/openreceive/fastapi/__init__.py +10 -0
- openreceive-0.4.4/src/openreceive/fastapi/binding.py +216 -0
- openreceive-0.4.4/src/openreceive/fastapi/lifespan.py +69 -0
- openreceive-0.4.4/src/openreceive/fastapi/requests.py +71 -0
- openreceive-0.4.4/src/openreceive/fastapi/router.py +127 -0
- openreceive-0.4.4/src/openreceive/money.py +76 -0
- openreceive-0.4.4/src/openreceive/nwc/__init__.py +39 -0
- openreceive-0.4.4/src/openreceive/nwc/client.py +39 -0
- openreceive-0.4.4/src/openreceive/nwc/errors.py +196 -0
- openreceive-0.4.4/src/openreceive/nwc/info.py +77 -0
- openreceive-0.4.4/src/openreceive/nwc/receive_client.py +103 -0
- openreceive-0.4.4/src/openreceive/nwc/requests.py +169 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/__init__.py +6 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/errors.py +25 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/nip01.py +86 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/nip04.py +34 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/nip44.py +94 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/receive_client.py +271 -0
- openreceive-0.4.4/src/openreceive/nwc/transport/relay.py +90 -0
- openreceive-0.4.4/src/openreceive/nwc/uri.py +105 -0
- openreceive-0.4.4/src/openreceive/payments/__init__.py +10 -0
- openreceive-0.4.4/src/openreceive/payments/reconciliation.py +49 -0
- openreceive-0.4.4/src/openreceive/payments/scan.py +105 -0
- openreceive-0.4.4/src/openreceive/rates/__init__.py +194 -0
- openreceive-0.4.4/src/openreceive/rates/cached_feed.py +204 -0
- openreceive-0.4.4/src/openreceive/rates/simple_price.py +69 -0
- openreceive-0.4.4/src/openreceive/rates/static.py +26 -0
- openreceive-0.4.4/src/openreceive/server/__init__.py +31 -0
- openreceive-0.4.4/src/openreceive/server/app.py +208 -0
- openreceive-0.4.4/src/openreceive/server/client_ip.py +67 -0
- openreceive-0.4.4/src/openreceive/server/doctor.py +150 -0
- openreceive-0.4.4/src/openreceive/server/errors.py +184 -0
- openreceive-0.4.4/src/openreceive/server/handler.py +827 -0
- openreceive-0.4.4/src/openreceive/server/hosts.py +84 -0
- openreceive-0.4.4/src/openreceive/server/notifications.py +100 -0
- openreceive-0.4.4/src/openreceive/server/rate_limit.py +76 -0
- openreceive-0.4.4/src/openreceive/server/reconcile.py +351 -0
- openreceive-0.4.4/src/openreceive/server/service.py +795 -0
- openreceive-0.4.4/src/openreceive/settlement.py +48 -0
- openreceive-0.4.4/src/openreceive/storage/__init__.py +49 -0
- openreceive-0.4.4/src/openreceive/storage/repository.py +310 -0
- openreceive-0.4.4/src/openreceive/storage/sql/__init__.py +16 -0
- openreceive-0.4.4/src/openreceive/storage/sql/ddl.py +68 -0
- openreceive-0.4.4/src/openreceive/storage/sql/repository.py +471 -0
- openreceive-0.4.4/src/openreceive/storage/sql/tables.py +134 -0
- openreceive-0.4.4/src/openreceive/swap/__init__.py +55 -0
- openreceive-0.4.4/src/openreceive/swap/address.py +112 -0
- openreceive-0.4.4/src/openreceive/swap/assets.py +46 -0
- openreceive-0.4.4/src/openreceive/swap/base58.py +23 -0
- openreceive-0.4.4/src/openreceive/swap/budget.py +94 -0
- openreceive-0.4.4/src/openreceive/swap/cache.py +148 -0
- openreceive-0.4.4/src/openreceive/swap/fixedfloat.py +873 -0
- openreceive-0.4.4/src/openreceive/swap/http.py +53 -0
- openreceive-0.4.4/src/openreceive/swap/keccak.py +81 -0
- openreceive-0.4.4/src/openreceive/swap/lsc_uri.py +105 -0
- openreceive-0.4.4/src/openreceive/swap/rates_feed.py +271 -0
- openreceive-0.4.4/src/openreceive/swap/state.py +79 -0
- openreceive-0.4.4/src/openreceive/testing/__init__.py +9 -0
- openreceive-0.4.4/src/openreceive/testing/fake_swap_provider.py +251 -0
- openreceive-0.4.4/src/openreceive/testing/fake_wallet.py +221 -0
- openreceive-0.4.4/src/openreceive/testing/fixtures.py +42 -0
- 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
|
+
)
|