solvapay 0.1.0.dev4__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 (64) hide show
  1. solvapay-0.1.0.dev4/.gitignore +50 -0
  2. solvapay-0.1.0.dev4/AGENTS.md +22 -0
  3. solvapay-0.1.0.dev4/PKG-INFO +183 -0
  4. solvapay-0.1.0.dev4/README.md +155 -0
  5. solvapay-0.1.0.dev4/conformance.lock +278 -0
  6. solvapay-0.1.0.dev4/conformance_runner/_agent_identity.py +242 -0
  7. solvapay-0.1.0.dev4/conformance_runner/_api_answer.py +23 -0
  8. solvapay-0.1.0.dev4/conformance_runner/agent_calls_runner.py +206 -0
  9. solvapay-0.1.0.dev4/conformance_runner/conformance_agent_mcp.py +427 -0
  10. solvapay-0.1.0.dev4/conformance_runner/conformance_merchant_sign_in.py +280 -0
  11. solvapay-0.1.0.dev4/conformance_runner/test_conformance.py +751 -0
  12. solvapay-0.1.0.dev4/package.json +6 -0
  13. solvapay-0.1.0.dev4/pyproject.toml +79 -0
  14. solvapay-0.1.0.dev4/scripts/check_version.py +41 -0
  15. solvapay-0.1.0.dev4/scripts/currency_prefixes.mjs +42 -0
  16. solvapay-0.1.0.dev4/scripts/generate_client.py +324 -0
  17. solvapay-0.1.0.dev4/scripts/pin_conformance.py +41 -0
  18. solvapay-0.1.0.dev4/scripts/preview_version.py +68 -0
  19. solvapay-0.1.0.dev4/scripts/sync_widget.py +53 -0
  20. solvapay-0.1.0.dev4/scripts/tax_ids.mjs +94 -0
  21. solvapay-0.1.0.dev4/src/solvapay/__init__.py +26 -0
  22. solvapay-0.1.0.dev4/src/solvapay/_currencies.py +170 -0
  23. solvapay-0.1.0.dev4/src/solvapay/_generated/__init__.py +1 -0
  24. solvapay-0.1.0.dev4/src/solvapay/_generated/models.py +3643 -0
  25. solvapay-0.1.0.dev4/src/solvapay/_generated/operations.py +1894 -0
  26. solvapay-0.1.0.dev4/src/solvapay/_http.py +243 -0
  27. solvapay-0.1.0.dev4/src/solvapay/_js.py +114 -0
  28. solvapay-0.1.0.dev4/src/solvapay/_tax_ids.py +442 -0
  29. solvapay-0.1.0.dev4/src/solvapay/agents/__init__.py +67 -0
  30. solvapay-0.1.0.dev4/src/solvapay/agents/calls.py +302 -0
  31. solvapay-0.1.0.dev4/src/solvapay/agents/identity.py +293 -0
  32. solvapay-0.1.0.dev4/src/solvapay/agents/mcp.py +614 -0
  33. solvapay-0.1.0.dev4/src/solvapay/agents/oauth.py +105 -0
  34. solvapay-0.1.0.dev4/src/solvapay/business_details.py +124 -0
  35. solvapay-0.1.0.dev4/src/solvapay/client.py +107 -0
  36. solvapay-0.1.0.dev4/src/solvapay/errors.py +33 -0
  37. solvapay-0.1.0.dev4/src/solvapay/mcp/__init__.py +90 -0
  38. solvapay-0.1.0.dev4/src/solvapay/mcp/app.py +243 -0
  39. solvapay-0.1.0.dev4/src/solvapay/mcp/auth.py +604 -0
  40. solvapay-0.1.0.dev4/src/solvapay/mcp/bootstrap.py +494 -0
  41. solvapay-0.1.0.dev4/src/solvapay/mcp/intent.py +474 -0
  42. solvapay-0.1.0.dev4/src/solvapay/mcp/mcp-app.html +119 -0
  43. solvapay-0.1.0.dev4/src/solvapay/mcp/narrate.py +1019 -0
  44. solvapay-0.1.0.dev4/src/solvapay/mcp/response.py +220 -0
  45. solvapay-0.1.0.dev4/src/solvapay/mcp/server.py +343 -0
  46. solvapay-0.1.0.dev4/src/solvapay/mcp/tools.py +273 -0
  47. solvapay-0.1.0.dev4/src/solvapay/mcp/transport.py +550 -0
  48. solvapay-0.1.0.dev4/src/solvapay/money.py +61 -0
  49. solvapay-0.1.0.dev4/src/solvapay/paywall/__init__.py +25 -0
  50. solvapay-0.1.0.dev4/src/solvapay/paywall/decide.py +450 -0
  51. solvapay-0.1.0.dev4/src/solvapay/paywall/gate.py +176 -0
  52. solvapay-0.1.0.dev4/src/solvapay/paywall/state.py +371 -0
  53. solvapay-0.1.0.dev4/src/solvapay/pricing.py +281 -0
  54. solvapay-0.1.0.dev4/src/solvapay/py.typed +0 -0
  55. solvapay-0.1.0.dev4/src/solvapay/solvapay.py +173 -0
  56. solvapay-0.1.0.dev4/src/solvapay/webhooks.py +93 -0
  57. solvapay-0.1.0.dev4/tests/test_agents.py +314 -0
  58. solvapay-0.1.0.dev4/tests/test_agents_mcp.py +348 -0
  59. solvapay-0.1.0.dev4/tests/test_client.py +185 -0
  60. solvapay-0.1.0.dev4/tests/test_mcp.py +327 -0
  61. solvapay-0.1.0.dev4/tests/test_mcp_app.py +514 -0
  62. solvapay-0.1.0.dev4/tests/test_mcp_intent.py +384 -0
  63. solvapay-0.1.0.dev4/tests/test_paywall.py +311 -0
  64. solvapay-0.1.0.dev4/uv.lock +2069 -0
@@ -0,0 +1,50 @@
1
+ node_modules
2
+ dist
3
+ .cache
4
+ .next
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ !.env.example.*
9
+ !.env.platform-local.example
10
+ .npmrc
11
+ .pnpm-store
12
+ .turbo
13
+ .cursor/*
14
+ !.cursor/rules/
15
+ !.cursor/plans/
16
+ !.cursor/skills/
17
+ .DS_Store
18
+ *.tsbuildinfo
19
+ **/.demo-data/*
20
+ **/*.local.sh
21
+
22
+ # Deno — written by `deno check` during the CI validate gate. We pin
23
+ # workspace imports to `@preview` dist-tags on purpose (mutable; each
24
+ # gate run should pick up the latest preview), so committing a lockfile
25
+ # here would defeat the regression-detection window.
26
+ deno.lock
27
+
28
+ # Code coverage
29
+ coverage/
30
+ *.lcov
31
+ .nyc_output
32
+
33
+ # Generated documentation (TypeDoc output)
34
+ docs/api/
35
+ docs/api.json
36
+
37
+ # Scratch research notes — internal, not for the public repo
38
+ research-tmp/
39
+
40
+ # Scratch design explorations — internal, not for the public repo
41
+ tmp-designs/
42
+
43
+ # Python
44
+ __pycache__/
45
+ *.py[cod]
46
+ .venv/
47
+ .pytest_cache/
48
+ .mypy_cache/
49
+ .ruff_cache/
50
+ python/dist/
@@ -0,0 +1,22 @@
1
+ # AGENTS.md — python/
2
+
3
+ The `solvapay` Python SDK. It must behave like the TypeScript SDK; the
4
+ conformance cases in `../conformance` say how.
5
+
6
+ - **Never edit `../conformance` or `../openapi` from here.** Those belong to the
7
+ TypeScript SDK. If a case looks wrong, raise it; don't change it to pass.
8
+ - **Adopting new cases:** make them pass (add a mapping in
9
+ `conformance_runner/` for a new `fn`), then run
10
+ `uv run python scripts/pin_conformance.py`. Cases newer than the pin are
11
+ skipped as "not adopted"; publishing fails until the pin catches up.
12
+ - **Generated files, never by hand:** `src/solvapay/_generated/`
13
+ (`scripts/generate_client.py`), `src/solvapay/mcp/mcp-app.html`
14
+ (`scripts/sync_widget.py`), the currency and tax tables (`scripts/tax_ids.mjs`),
15
+ and `conformance.lock`.
16
+ - Check before committing: `uv run pytest`, `uv run ruff check .`,
17
+ `uv run ruff format --check .`, `uv run mypy`.
18
+ - **Versions:** `python/package.json` (private, never on npm) is the version
19
+ source. Bump it with a changeset naming `@solvapay/python`;
20
+ `pnpm changeset:version` copies the version into `pyproject.toml` and the
21
+ `uv.lock` files. Never edit the version in `pyproject.toml` by hand. It does
22
+ not follow `@solvapay/server`.
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.5
2
+ Name: solvapay
3
+ Version: 0.1.0.dev4
4
+ Summary: SolvaPay for Python: paywalls, usage billing, webhooks and paid MCP tools.
5
+ Project-URL: Homepage, https://solvapay.com
6
+ Project-URL: Documentation, https://docs.solvapay.com
7
+ Project-URL: Source, https://github.com/solvapay/solvapay-sdk/tree/main/python
8
+ Author: SolvaPay
9
+ License-Expression: MIT
10
+ Keywords: agents,billing,mcp,payments,paywall,solvapay
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx<1,>=0.27
21
+ Requires-Dist: pydantic<3,>=2.7
22
+ Provides-Extra: agents
23
+ Requires-Dist: pyjwt[crypto]<3,>=2.9; extra == 'agents'
24
+ Provides-Extra: mcp
25
+ Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
26
+ Requires-Dist: starlette>=0.40; extra == 'mcp'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # solvapay
30
+
31
+ SolvaPay for Python: paywalls, usage billing, webhooks and paid MCP tools.
32
+
33
+ ```bash
34
+ pip install solvapay # API client, paywall, webhooks
35
+ pip install 'solvapay[mcp]' # paid tools for the official MCP Python SDK
36
+ ```
37
+
38
+ Python 3.10+. Typed (`py.typed`), async-first, with blocking twins for the API client.
39
+
40
+ ## Configure
41
+
42
+ ```bash
43
+ export SOLVAPAY_SECRET_KEY=sk_sandbox_... # required
44
+ export SOLVAPAY_PRODUCT_REF=prd_... # the product paid calls bill against
45
+ export SOLVAPAY_API_BASE_URL=... # optional; defaults to https://api.solvapay.com
46
+ ```
47
+
48
+ Run `npx solvapay init` to sign in and write these to `.env`.
49
+
50
+ ## Make a function paid
51
+
52
+ ```python
53
+ from solvapay import PaywallError, create_solvapay
54
+
55
+ solvapay = create_solvapay()
56
+
57
+
58
+ @solvapay.payable()
59
+ async def summarise(text: str, *, customer_ref: str) -> str:
60
+ return text[:100]
61
+
62
+
63
+ try:
64
+ summary = await summarise("...", customer_ref="user_123")
65
+ except PaywallError as error:
66
+ print(error.gate["message"]) # what the customer should see, with a checkout link
67
+ ```
68
+
69
+ `customer_ref` is your own user id: SolvaPay creates the customer on first use.
70
+ Each call is checked against the customer's plan, and billed when it succeeds.
71
+
72
+ ## Paid MCP tools
73
+
74
+ ```python
75
+ from solvapay import create_solvapay
76
+ from solvapay.mcp import ResponseContext, create_solvapay_mcp_server
77
+
78
+ mcp = create_solvapay_mcp_server(
79
+ solvapay=create_solvapay(),
80
+ public_base_url="https://research.example.com",
81
+ )
82
+
83
+
84
+ async def quote(symbol: str, ctx: ResponseContext):
85
+ return ctx.respond({"symbol": symbol, "price": 101.5})
86
+
87
+
88
+ mcp.register_payable("quote", handler=quote, description="Latest price for a symbol.")
89
+
90
+ app = mcp.http_app() # serve with uvicorn; the MCP endpoint is /mcp
91
+ ```
92
+
93
+ Customers sign in with SolvaPay over OAuth, and every bearer token is verified
94
+ with SolvaPay. A call over the customer's limit returns the paywall: a message
95
+ naming the next step, with a checkout link. See
96
+ [`examples/python-stock-research-mcp`](../examples/python-stock-research-mcp).
97
+
98
+ The server also comes with SolvaPay's built-in tools, as the TypeScript server
99
+ does: `account` shows the customer's plan, balance and usage with a checkout,
100
+ top-up or account link (`view`: `checkout`, `account` or `topup`), and
101
+ `activate_plan` activates a plan by `planRef`. The `/upgrade`,
102
+ `/manage_account`, `/topup` and `/activate_plan` prompts and the
103
+ `docs://solvapay/overview.md` resource come with them. Pass `views=[...]` to
104
+ choose the views, or `views=[]` to leave `account` out.
105
+
106
+ ### The checkout widget
107
+
108
+ On hosts that support MCP Apps (ChatGPT, Claude), `account` also opens
109
+ SolvaPay's checkout and account widget in the chat, the same one the TypeScript
110
+ SDK serves. It ships in this package (`solvapay/mcp/mcp-app.html`) and is
111
+ served at `ui://<name>/mcp-app.html` (`resource_uri=` to change it). Text-only
112
+ hosts get the same text as before; `mode: "text"` asks for text on any host.
113
+
114
+ The widget pays, tops up, cancels and reactivates renewals through six tools
115
+ only it calls: `create_payment_intent`, `process_payment`,
116
+ `create_hosted_session`, `set_renewal`, `attach_business_details` and
117
+ `get_history`. They are marked for the app (`_meta.ui.visibility: ["app"]`), so
118
+ MCP Apps hosts keep them from the model. For a host that lists them anyway,
119
+ `hide_tools_by_audience=["ui"]` leaves them out of `tools/list`; ChatGPT still
120
+ gets them, since its widget can only call listed tools.
121
+
122
+ Its Content Security Policy allows Stripe and the SolvaPay API. Add domains with
123
+ `csp={"connectDomains": [...], "resourceDomains": [...], "frameDomains": [...]}`.
124
+
125
+ ### Paid by agents
126
+
127
+ `solvapay.agents.mcp.create_agent_mcp_route` serves an MCP server that SolvaPay
128
+ agents pay for per call: each paid tool call is authorized with SolvaPay before
129
+ it runs and completed at the cost the upstream reports. Install
130
+ `solvapay[agents,mcp]`.
131
+
132
+ ## Verify webhooks
133
+
134
+ ```python
135
+ from solvapay import SolvaPayError, verify_webhook
136
+
137
+ try:
138
+ event = verify_webhook(
139
+ body=raw_body, # bytes or str, exactly as received
140
+ signature=request.headers["sv-signature"],
141
+ secret=os.environ["SOLVAPAY_WEBHOOK_SECRET"],
142
+ )
143
+ except SolvaPayError as error:
144
+ ... # error.code: webhook_signature_invalid, webhook_timestamp_out_of_tolerance, ...
145
+ ```
146
+
147
+ ## The API
148
+
149
+ Every SolvaPay API operation, generated from the API spec:
150
+
151
+ ```python
152
+ from solvapay import AsyncApiClient
153
+
154
+ async with AsyncApiClient(api_key="sk_sandbox_...") as api:
155
+ customer = await api.get_customer(reference="cus_123")
156
+ balance = await api.get_customer_balance(reference="cus_123")
157
+ ```
158
+
159
+ `ApiClient` is the blocking twin. Errors are `SolvaPayError` with a stable `code`
160
+ and the HTTP `status`; branch on those, not on the message.
161
+
162
+ ## Same behaviour as the TypeScript SDK
163
+
164
+ The paywall, its messages, the `account` and `activate_plan` narration, what
165
+ the widget's tools answer and webhook verification are checked against the
166
+ TypeScript SDK on every change by a shared conformance suite
167
+ ([`conformance/`](../conformance)). The API client is generated from the same
168
+ spec as the TypeScript types ([`openapi/`](../openapi)).
169
+
170
+ ## Versions
171
+
172
+ `solvapay` has its own version line, starting at 0.1.0. Its numbers don't
173
+ follow the TypeScript packages: Python 0.3.0 and `@solvapay/server` 2.12.0 can
174
+ behave the same. What the two share is the conformance suite (the version this
175
+ package passes is pinned in [`conformance.lock`](conformance.lock)) and the API
176
+ spec.
177
+
178
+ Until 1.0.0, a minor release (0.x → 0.y) may change the API. Previews of the
179
+ next release are published from the `dev` branch as `<next>.devN`:
180
+
181
+ ```bash
182
+ pip install --pre solvapay
183
+ ```
@@ -0,0 +1,155 @@
1
+ # solvapay
2
+
3
+ SolvaPay for Python: paywalls, usage billing, webhooks and paid MCP tools.
4
+
5
+ ```bash
6
+ pip install solvapay # API client, paywall, webhooks
7
+ pip install 'solvapay[mcp]' # paid tools for the official MCP Python SDK
8
+ ```
9
+
10
+ Python 3.10+. Typed (`py.typed`), async-first, with blocking twins for the API client.
11
+
12
+ ## Configure
13
+
14
+ ```bash
15
+ export SOLVAPAY_SECRET_KEY=sk_sandbox_... # required
16
+ export SOLVAPAY_PRODUCT_REF=prd_... # the product paid calls bill against
17
+ export SOLVAPAY_API_BASE_URL=... # optional; defaults to https://api.solvapay.com
18
+ ```
19
+
20
+ Run `npx solvapay init` to sign in and write these to `.env`.
21
+
22
+ ## Make a function paid
23
+
24
+ ```python
25
+ from solvapay import PaywallError, create_solvapay
26
+
27
+ solvapay = create_solvapay()
28
+
29
+
30
+ @solvapay.payable()
31
+ async def summarise(text: str, *, customer_ref: str) -> str:
32
+ return text[:100]
33
+
34
+
35
+ try:
36
+ summary = await summarise("...", customer_ref="user_123")
37
+ except PaywallError as error:
38
+ print(error.gate["message"]) # what the customer should see, with a checkout link
39
+ ```
40
+
41
+ `customer_ref` is your own user id: SolvaPay creates the customer on first use.
42
+ Each call is checked against the customer's plan, and billed when it succeeds.
43
+
44
+ ## Paid MCP tools
45
+
46
+ ```python
47
+ from solvapay import create_solvapay
48
+ from solvapay.mcp import ResponseContext, create_solvapay_mcp_server
49
+
50
+ mcp = create_solvapay_mcp_server(
51
+ solvapay=create_solvapay(),
52
+ public_base_url="https://research.example.com",
53
+ )
54
+
55
+
56
+ async def quote(symbol: str, ctx: ResponseContext):
57
+ return ctx.respond({"symbol": symbol, "price": 101.5})
58
+
59
+
60
+ mcp.register_payable("quote", handler=quote, description="Latest price for a symbol.")
61
+
62
+ app = mcp.http_app() # serve with uvicorn; the MCP endpoint is /mcp
63
+ ```
64
+
65
+ Customers sign in with SolvaPay over OAuth, and every bearer token is verified
66
+ with SolvaPay. A call over the customer's limit returns the paywall: a message
67
+ naming the next step, with a checkout link. See
68
+ [`examples/python-stock-research-mcp`](../examples/python-stock-research-mcp).
69
+
70
+ The server also comes with SolvaPay's built-in tools, as the TypeScript server
71
+ does: `account` shows the customer's plan, balance and usage with a checkout,
72
+ top-up or account link (`view`: `checkout`, `account` or `topup`), and
73
+ `activate_plan` activates a plan by `planRef`. The `/upgrade`,
74
+ `/manage_account`, `/topup` and `/activate_plan` prompts and the
75
+ `docs://solvapay/overview.md` resource come with them. Pass `views=[...]` to
76
+ choose the views, or `views=[]` to leave `account` out.
77
+
78
+ ### The checkout widget
79
+
80
+ On hosts that support MCP Apps (ChatGPT, Claude), `account` also opens
81
+ SolvaPay's checkout and account widget in the chat, the same one the TypeScript
82
+ SDK serves. It ships in this package (`solvapay/mcp/mcp-app.html`) and is
83
+ served at `ui://<name>/mcp-app.html` (`resource_uri=` to change it). Text-only
84
+ hosts get the same text as before; `mode: "text"` asks for text on any host.
85
+
86
+ The widget pays, tops up, cancels and reactivates renewals through six tools
87
+ only it calls: `create_payment_intent`, `process_payment`,
88
+ `create_hosted_session`, `set_renewal`, `attach_business_details` and
89
+ `get_history`. They are marked for the app (`_meta.ui.visibility: ["app"]`), so
90
+ MCP Apps hosts keep them from the model. For a host that lists them anyway,
91
+ `hide_tools_by_audience=["ui"]` leaves them out of `tools/list`; ChatGPT still
92
+ gets them, since its widget can only call listed tools.
93
+
94
+ Its Content Security Policy allows Stripe and the SolvaPay API. Add domains with
95
+ `csp={"connectDomains": [...], "resourceDomains": [...], "frameDomains": [...]}`.
96
+
97
+ ### Paid by agents
98
+
99
+ `solvapay.agents.mcp.create_agent_mcp_route` serves an MCP server that SolvaPay
100
+ agents pay for per call: each paid tool call is authorized with SolvaPay before
101
+ it runs and completed at the cost the upstream reports. Install
102
+ `solvapay[agents,mcp]`.
103
+
104
+ ## Verify webhooks
105
+
106
+ ```python
107
+ from solvapay import SolvaPayError, verify_webhook
108
+
109
+ try:
110
+ event = verify_webhook(
111
+ body=raw_body, # bytes or str, exactly as received
112
+ signature=request.headers["sv-signature"],
113
+ secret=os.environ["SOLVAPAY_WEBHOOK_SECRET"],
114
+ )
115
+ except SolvaPayError as error:
116
+ ... # error.code: webhook_signature_invalid, webhook_timestamp_out_of_tolerance, ...
117
+ ```
118
+
119
+ ## The API
120
+
121
+ Every SolvaPay API operation, generated from the API spec:
122
+
123
+ ```python
124
+ from solvapay import AsyncApiClient
125
+
126
+ async with AsyncApiClient(api_key="sk_sandbox_...") as api:
127
+ customer = await api.get_customer(reference="cus_123")
128
+ balance = await api.get_customer_balance(reference="cus_123")
129
+ ```
130
+
131
+ `ApiClient` is the blocking twin. Errors are `SolvaPayError` with a stable `code`
132
+ and the HTTP `status`; branch on those, not on the message.
133
+
134
+ ## Same behaviour as the TypeScript SDK
135
+
136
+ The paywall, its messages, the `account` and `activate_plan` narration, what
137
+ the widget's tools answer and webhook verification are checked against the
138
+ TypeScript SDK on every change by a shared conformance suite
139
+ ([`conformance/`](../conformance)). The API client is generated from the same
140
+ spec as the TypeScript types ([`openapi/`](../openapi)).
141
+
142
+ ## Versions
143
+
144
+ `solvapay` has its own version line, starting at 0.1.0. Its numbers don't
145
+ follow the TypeScript packages: Python 0.3.0 and `@solvapay/server` 2.12.0 can
146
+ behave the same. What the two share is the conformance suite (the version this
147
+ package passes is pinned in [`conformance.lock`](conformance.lock)) and the API
148
+ spec.
149
+
150
+ Until 1.0.0, a minor release (0.x → 0.y) may change the API. Previews of the
151
+ next release are published from the `dev` branch as `<next>.devN`:
152
+
153
+ ```bash
154
+ pip install --pre solvapay
155
+ ```