solvapay-python 0.7.1__tar.gz → 0.8.0__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 (153) hide show
  1. solvapay_python-0.8.0/.github/ISSUE_TEMPLATE/bug.yml +27 -0
  2. solvapay_python-0.8.0/.github/ISSUE_TEMPLATE/feature.yml +23 -0
  3. solvapay_python-0.8.0/.github/ISSUE_TEMPLATE/question.yml +14 -0
  4. solvapay_python-0.8.0/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  5. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/.github/workflows/ci.yml +3 -0
  6. solvapay_python-0.8.0/CHANGELOG.md +93 -0
  7. solvapay_python-0.8.0/CODEOWNERS +1 -0
  8. solvapay_python-0.8.0/CODE_OF_CONDUCT.md +27 -0
  9. solvapay_python-0.8.0/CONTRIBUTING.md +58 -0
  10. solvapay_python-0.8.0/PKG-INFO +315 -0
  11. solvapay_python-0.8.0/README.md +282 -0
  12. solvapay_python-0.8.0/SECURITY.md +36 -0
  13. solvapay_python-0.8.0/SUPPORT.md +10 -0
  14. solvapay_python-0.8.0/assets/agent-marketplace.png +0 -0
  15. solvapay_python-0.8.0/docs/architecture/layers.md +46 -0
  16. solvapay_python-0.8.0/docs/rfcs/0001-spending-policy.md +60 -0
  17. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/pyproject.toml +1 -1
  18. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/langchain-paywall/pyproject.toml +1 -1
  19. solvapay_python-0.8.0/examples/marketplace/.gitignore +4 -0
  20. solvapay_python-0.8.0/examples/multi-framework-paywall/.env.example +3 -0
  21. solvapay_python-0.8.0/examples/multi-framework-paywall/.gitignore +4 -0
  22. solvapay_python-0.8.0/examples/multi-framework-paywall/README.md +52 -0
  23. solvapay_python-0.8.0/examples/multi-framework-paywall/agent_langchain.py +43 -0
  24. solvapay_python-0.8.0/examples/multi-framework-paywall/model.py +13 -0
  25. solvapay_python-0.8.0/examples/multi-framework-paywall/pyproject.toml +9 -0
  26. solvapay_python-0.8.0/examples/multi-framework-paywall/script_async.py +43 -0
  27. solvapay_python-0.8.0/examples/multi-framework-paywall/server_mcp.py +28 -0
  28. solvapay_python-0.8.0/examples/multi-framework-paywall/tool.py +36 -0
  29. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/pyproject.toml +4 -2
  30. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/__init__.py +31 -1
  31. solvapay_python-0.8.0/src/solvapay/_async_client.py +308 -0
  32. solvapay_python-0.8.0/src/solvapay/_http.py +21 -0
  33. solvapay_python-0.8.0/src/solvapay/_stability.py +60 -0
  34. solvapay_python-0.8.0/src/solvapay/_transport/__init__.py +106 -0
  35. solvapay_python-0.8.0/src/solvapay/_transport/_recipe.py +63 -0
  36. solvapay_python-0.8.0/src/solvapay/_transport/httpx_transport.py +397 -0
  37. solvapay_python-0.8.0/src/solvapay/_transport/middleware.py +233 -0
  38. solvapay_python-0.8.0/src/solvapay/adapters/__init__.py +3 -0
  39. solvapay_python-0.8.0/src/solvapay/adapters/langchain.py +88 -0
  40. solvapay_python-0.8.0/src/solvapay/adapters/mcp.py +158 -0
  41. solvapay_python-0.8.0/src/solvapay/client.py +303 -0
  42. solvapay_python-0.8.0/src/solvapay/langchain.py +15 -0
  43. solvapay_python-0.8.0/src/solvapay/operations/__init__.py +7 -0
  44. solvapay_python-0.8.0/src/solvapay/operations/_registry.py +117 -0
  45. solvapay_python-0.8.0/src/solvapay/operations/checkout.py +75 -0
  46. solvapay_python-0.8.0/src/solvapay/operations/customers.py +256 -0
  47. solvapay_python-0.8.0/src/solvapay/operations/limits.py +77 -0
  48. solvapay_python-0.8.0/src/solvapay/operations/merchant.py +71 -0
  49. solvapay_python-0.8.0/src/solvapay/operations/plans.py +167 -0
  50. solvapay_python-0.8.0/src/solvapay/operations/products.py +141 -0
  51. solvapay_python-0.8.0/src/solvapay/operations/purchases.py +103 -0
  52. solvapay_python-0.8.0/src/solvapay/operations/usage.py +75 -0
  53. solvapay_python-0.8.0/src/solvapay/paywall/__init__.py +32 -0
  54. solvapay_python-0.8.0/src/solvapay/paywall/core.py +152 -0
  55. solvapay_python-0.8.0/src/solvapay/paywall/decorators.py +126 -0
  56. solvapay_python-0.8.0/src/solvapay/paywall/meta.py +18 -0
  57. solvapay_python-0.8.0/src/solvapay/paywall/policy.py +14 -0
  58. solvapay_python-0.8.0/src/solvapay/paywall/resolvers.py +60 -0
  59. solvapay_python-0.8.0/src/solvapay/paywall/state.py +23 -0
  60. solvapay_python-0.8.0/src/solvapay/webhooks/__init__.py +28 -0
  61. solvapay_python-0.8.0/src/solvapay/webhooks/envelope.py +16 -0
  62. solvapay_python-0.8.0/src/solvapay/webhooks/pipeline.py +88 -0
  63. solvapay_python-0.8.0/src/solvapay/webhooks/replay.py +42 -0
  64. solvapay_python-0.8.0/src/solvapay/webhooks/rotation.py +32 -0
  65. solvapay_python-0.7.1/src/solvapay/webhooks.py → solvapay_python-0.8.0/src/solvapay/webhooks/verify.py +4 -22
  66. solvapay_python-0.8.0/tests/_stability/__init__.py +0 -0
  67. solvapay_python-0.8.0/tests/_stability/test_stable_returns_identity.py +75 -0
  68. solvapay_python-0.8.0/tests/_transport/__init__.py +0 -0
  69. solvapay_python-0.8.0/tests/_transport/test_aclose_cascade.py +100 -0
  70. solvapay_python-0.8.0/tests/_transport/test_error_wrapping.py +66 -0
  71. solvapay_python-0.8.0/tests/_transport/test_headers_case_insensitive.py +52 -0
  72. solvapay_python-0.8.0/tests/_transport/test_middleware_composition.py +51 -0
  73. solvapay_python-0.8.0/tests/_transport/test_protocol_conformance.py +46 -0
  74. solvapay_python-0.8.0/tests/adapters/__init__.py +0 -0
  75. solvapay_python-0.8.0/tests/adapters/test_langchain_protocol.py +99 -0
  76. solvapay_python-0.8.0/tests/adapters/test_mcp.py +165 -0
  77. solvapay_python-0.8.0/tests/operations/__init__.py +0 -0
  78. solvapay_python-0.8.0/tests/operations/test_namespace_api.py +59 -0
  79. solvapay_python-0.8.0/tests/operations/test_path_interpolation.py +54 -0
  80. solvapay_python-0.8.0/tests/operations/test_retry_safety_enum.py +57 -0
  81. solvapay_python-0.8.0/tests/paywall/__init__.py +0 -0
  82. solvapay_python-0.8.0/tests/paywall/test_checkout_mint_error_surfaces.py +52 -0
  83. solvapay_python-0.8.0/tests/paywall/test_payable_tool_meta.py +59 -0
  84. solvapay_python-0.8.0/tests/paywall/test_resolvers.py +47 -0
  85. solvapay_python-0.8.0/tests/paywall/test_split_classes.py +34 -0
  86. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_invariants.py +2 -2
  87. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_langchain.py +21 -16
  88. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_lifecycle.py +3 -1
  89. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_paywall.py +5 -5
  90. solvapay_python-0.8.0/tests/webhooks/__init__.py +0 -0
  91. solvapay_python-0.8.0/tests/webhooks/test_clock_skew_vs_replay_ttl.py +58 -0
  92. solvapay_python-0.8.0/tests/webhooks/test_seen_cache_atomic.py +54 -0
  93. solvapay_python-0.8.0/tests/webhooks/test_webhook_pipeline.py +66 -0
  94. solvapay_python-0.8.0/tools/api_baseline.json +82 -0
  95. solvapay_python-0.8.0/tools/api_diff.py +60 -0
  96. solvapay_python-0.8.0/tools/importlinter.cfg +42 -0
  97. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/uv.lock +795 -114
  98. solvapay_python-0.7.1/CHANGELOG.md +0 -53
  99. solvapay_python-0.7.1/PKG-INFO +0 -278
  100. solvapay_python-0.7.1/README.md +0 -247
  101. solvapay_python-0.7.1/src/solvapay/_async_client.py +0 -387
  102. solvapay_python-0.7.1/src/solvapay/_http.py +0 -220
  103. solvapay_python-0.7.1/src/solvapay/client.py +0 -380
  104. solvapay_python-0.7.1/src/solvapay/langchain.py +0 -67
  105. solvapay_python-0.7.1/src/solvapay/paywall.py +0 -148
  106. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/.github/workflows/publish.yml +0 -0
  107. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/.gitignore +0 -0
  108. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/.python-version +0 -0
  109. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/LICENSE +0 -0
  110. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/.env.example +0 -0
  111. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/.gitignore +0 -0
  112. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/README.md +0 -0
  113. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/claim.py +0 -0
  114. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/server.py +0 -0
  115. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/fastmcp-paywall/uv.lock +0 -0
  116. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/langchain-paywall/.env.example +0 -0
  117. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/langchain-paywall/.gitignore +0 -0
  118. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/langchain-paywall/README.md +0 -0
  119. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/langchain-paywall/agent.py +0 -0
  120. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/.env.example +0 -0
  121. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/.streamlit/config.toml +0 -0
  122. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/PLAN.md +0 -0
  123. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/README.md +0 -0
  124. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/agents.py +0 -0
  125. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/app.py +0 -0
  126. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/demo_customers.py +0 -0
  127. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/requirements.txt +0 -0
  128. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/sdk_gateway.py +0 -0
  129. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/examples/marketplace/ui_components.py +0 -0
  130. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/_config.py +0 -0
  131. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/events.py +0 -0
  132. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/exceptions.py +0 -0
  133. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/fastapi.py +0 -0
  134. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/idempotency.py +0 -0
  135. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/models.py +0 -0
  136. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/paywall_state.py +0 -0
  137. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/src/solvapay/py.typed +0 -0
  138. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/__init__.py +0 -0
  139. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/conftest.py +0 -0
  140. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_admin.py +0 -0
  141. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_async_client.py +0 -0
  142. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_checkout.py +0 -0
  143. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_config.py +0 -0
  144. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_customer.py +0 -0
  145. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_errors.py +0 -0
  146. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_http.py +0 -0
  147. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_idempotency.py +0 -0
  148. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_limits.py +0 -0
  149. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_packaging.py +0 -0
  150. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_paywall_state.py +0 -0
  151. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_redaction.py +0 -0
  152. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_webhook_events.py +0 -0
  153. {solvapay_python-0.7.1 → solvapay_python-0.8.0}/tests/test_webhooks.py +0 -0
@@ -0,0 +1,27 @@
1
+ name: Bug Report
2
+ description: Something broken in the SDK
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: Please fill in the details below.
8
+ - type: input
9
+ id: version
10
+ attributes:
11
+ label: SDK version
12
+ placeholder: "0.8.0"
13
+ validations:
14
+ required: true
15
+ - type: textarea
16
+ id: description
17
+ attributes:
18
+ label: What happened?
19
+ description: What did you expect vs what actually happened?
20
+ validations:
21
+ required: true
22
+ - type: textarea
23
+ id: repro
24
+ attributes:
25
+ label: Minimal repro
26
+ description: Minimal Python snippet to reproduce
27
+ render: python
@@ -0,0 +1,23 @@
1
+ name: Feature Request
2
+ description: Suggest a new capability
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem does this solve?
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: proposal
13
+ attributes:
14
+ label: Proposed solution
15
+ validations:
16
+ required: true
17
+ - type: dropdown
18
+ id: priority
19
+ attributes:
20
+ label: Priority
21
+ options:
22
+ - Nice to have
23
+ - Blocking a use-case
@@ -0,0 +1,14 @@
1
+ name: Question
2
+ description: How do I...?
3
+ labels: [question]
4
+ body:
5
+ - type: textarea
6
+ id: question
7
+ attributes:
8
+ label: What are you trying to do?
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: tried
13
+ attributes:
14
+ label: What have you tried?
@@ -0,0 +1,15 @@
1
+ ## What
2
+
3
+ <!-- Brief description of the change -->
4
+
5
+ ## Why
6
+
7
+ <!-- Problem it solves or feature it adds -->
8
+
9
+ ## Checklist
10
+
11
+ - [ ] Tests added / updated
12
+ - [ ] `CHANGELOG.md` entry added
13
+ - [ ] Layer DAG respected (`uv run lint-imports` passes)
14
+ - [ ] If new public exports: `tools/api_baseline.json` updated
15
+ - [ ] Docs updated (if public API changed)
@@ -20,5 +20,8 @@ jobs:
20
20
  run: uv python install ${{ matrix.python-version }}
21
21
  - run: uv sync --all-extras --dev
22
22
  - run: uv run ruff check src tests
23
+ - run: uv run ruff format --check src tests
23
24
  - run: uv run mypy src
24
25
  - run: uv run pytest -v
26
+ - run: uv run python tools/api_diff.py
27
+ - run: uv run lint-imports --config tools/importlinter.cfg
@@ -0,0 +1,93 @@
1
+ # Changelog
2
+
3
+ ## 0.8.0 — 2026-05-23
4
+
5
+ V1 architecture spine + AI-agent moat. 10 atomic commits establishing locked architecture invariants per HLD §V1.1–V1.19.
6
+
7
+ ### Added
8
+ - **Transport Protocol kernel** (`solvapay._transport`): `Transport`/`AsyncTransport` Protocol shapes, `RequestSpec`/`ResponseSpec` frozen dataclasses, case-insensitive `Headers`, canonical middleware stack (`Redacting` → `Logging` → `IdempotencyHeader` → `ContextPropagating`), `default_stack()` recipe.
9
+ - **Operations registry + resource-namespace API**: `OpSpec` + `RetrySafety` enum, `REGISTRY`, path interpolation with `urllib.parse.quote`. All clients gain eager namespace attrs: `sv.customers`, `sv.checkout`, `sv.limits`, `sv.purchases`, `sv.usage`, `sv.products`, `sv.plans`, `sv.merchant`.
10
+ - **Paywall package** (`solvapay.paywall`): `Paywall`/`AsyncPaywall` split with type-narrowed construction, `PaywallRequired.checkout_mint_error`, `PayableToolMeta(_meta_version=1)`, `KwargsResolver`/`PositionalResolver`/`PydanticBodyResolver`, `@require`/`@require_async`/`@payable_tool` decorators.
11
+ - **Webhook pipeline** (`solvapay.webhooks`): `WebhookPipeline` with two-knob config (`max_clock_skew_seconds`, `replay_ttl_seconds`), atomic `SeenEventCache.try_claim` (`threading.Lock`), `MultiSecretVerifier` interface, `WebhookEnvelope` dataclass.
12
+ - **`solvapay.adapters.mcp`** (optional `solvapay[mcp]`): `@payable_tool` decorator + four schema flavors (`payable_tool_mcp_schema`, `payable_tool_openai_function`, `payable_tool_anthropic_tool`, `payable_tool_langchain_args_schema`), `register_payable_tool_fastmcp`.
13
+ - **Stability manifest** (`solvapay._stability`): `stable(X)→X` identity decorator registers in `MANIFEST`; `experimental` emits once-per-process `RuntimeWarning`; `deprecated(removed_in=)`. CI gate: `tools/api_diff.py` fails on `@stable` symbol removal without prior `@deprecated`.
14
+ - **Import-linter DAG gate**: `tools/importlinter.cfg` (3 contracts), `docs/architecture/layers.md`. CI fails on layer violation.
15
+ - **LangChain adapter refactor** (`solvapay.adapters.langchain`): Protocol duck-typing — no hard `langchain_core` import at module level; absorbs 0.3→0.4 churn.
16
+ - **Multi-framework demo** (`examples/multi-framework-paywall/`): one `@payable_tool`-decorated function runs across FastMCP, LangChain, and raw async — "one tool, three runtimes, one paywall."
17
+ - **Governance scaffold**: `CODEOWNERS`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `SUPPORT.md`, `CONTRIBUTING.md`, GitHub issue templates, PR template, `docs/rfcs/0001-spending-policy.md`.
18
+
19
+ ### Changed
20
+ - **Flat methods deprecated**: `sv.ensure_customer()`, `sv.check_limits()`, etc. now emit `DeprecationWarning(stacklevel=2)` per call. Use `sv.customers.ensure()`, `sv.limits.check()`, etc. Flat shims removed in v2.0.
21
+ - **`langchain-core` upper bound**: `langchain-core>=0.3,<0.4` now enforced in both dev and optional deps.
22
+ - **`solvapay.langchain`** replaced by `solvapay.adapters.langchain`; old path is a deprecated shim.
23
+
24
+ ### Deprecated
25
+ - `SolvaPayAPIError` — use `APIError`. Alias fires `DeprecationWarning`; removed in v2.0.
26
+ - All flat client methods (`sv.ensure_customer`, `sv.check_limits`, etc.) — use resource-namespace API.
27
+
28
+ ## 0.7.2 — 2026-05-18
29
+
30
+ Bug fixes and documentation update.
31
+
32
+ ### Fixed
33
+ - **`paywall.require_async`**: `AsyncSolvaPay()` instantiated without a caller-supplied `client=` was not closed after the decorated function returned, leaking the underlying `httpx.AsyncClient` connection pool. Wrapped in `try/finally`; `await sv.aclose()` called when the decorator owns the client.
34
+ - **`examples/fastmcp-paywall/pyproject.toml`**: dependency used wrong PyPI dist name (`solvapay`) and a stale `@v0.3.0` pin. Updated to `solvapay-python>=0.7.2`.
35
+ - **`examples/langchain-paywall/pyproject.toml`**: same wrong dist name and stale `@v0.5.0` pin. Updated to `solvapay-python[langchain]>=0.7.2`.
36
+
37
+ ### Docs
38
+ - **README** updated to v0.7.1 surface: `paywall_state.gate()`, error hierarchy, idempotency keys, admin ops table, marketplace example, roadmap entries for v0.7.0 and v0.7.1.
39
+
40
+ ### Internal
41
+ - `tests/test_lifecycle.py` reformatted (ruff format compliance)
42
+
43
+ ## 0.7.1 — 2026-05-17
44
+
45
+ Payments-grade hardening: structured errors, idempotency keys, py.typed, structured logging.
46
+
47
+ ### Added
48
+ - **Structured error hierarchy** (`exceptions.py`): `APIError` base with `status_code`, `request_id`, `error_code`, `error_message`. Subclasses: `AuthenticationError` (401), `PermissionError` (403), `NotFoundError` (404), `RateLimitError` (429, adds `.retry_after`), `InvalidRequestError` (4xx), `APIServerError` (5xx), `APIConnectionError`, `APITimeoutError`. `SolvaPayAPIError` aliased to `APIError` for back-compat.
49
+ - **Idempotency keys** on all mutating ops: `create_checkout_session`, `ensure_customer`, `track_usage`, `cancel_purchase`, `reactivate_purchase`, `create_product`, `clone_product`, `create_plan` all accept `idempotency_key: str | None = None`. Header casing: `Idempotency-Key` (matches TS SDK).
50
+ - **`solvapay.idempotency.from_payload(*parts)`** — SHA256 of stable-serialized payload parts → 32-hex-char deterministic key.
51
+ - **PEP 561 `py.typed` marker** — downstream mypy users no longer see `Any` on `solvapay.*` imports.
52
+ - **Structured logging** at `solvapay.http` logger: INFO on success (method, path, status, request_id, duration_ms), WARNING on 4xx/5xx (adds body_excerpt ≤200 chars). Never calls `logging.basicConfig`. Optional `logger=` injection on `SolvaPay`/`AsyncSolvaPay` constructors for `loguru`/`structlog`.
53
+ - **Secret redaction**: `Authorization` header never appears in logs. Verified by `tests/test_redaction.py`.
54
+ - **pyproject classifiers**: `Development Status :: 4 - Beta`, `Framework :: AsyncIO`, `Topic :: Office/Business :: Financial`, `Typing :: Typed`.
55
+
56
+ ### Internal
57
+ - User-Agent bumped to `solvapay-python/0.7.1`
58
+ - 142 tests (up from 125), `mypy --strict` clean, `ruff` clean
59
+
60
+ ## 0.7.0 — 2026-05-17
61
+
62
+ Real-API alignment after testing against the SolvaPay sandbox revealed wire-format mismatches.
63
+
64
+ ### Fixed
65
+ - **`Customer.customer_ref`** now accepts `reference` (real API) in addition to `customerRef` via `validation_alias=AliasChoices(...)`.
66
+ - **`ensure_customer()`** reads `reference` from API response; falls back to `customerRef`. Raises if neither present.
67
+ - **`BalanceResponse`** rewritten: `credits`, `display_currency`, `credits_per_minor_unit`, `display_exchange_rate`; `balance` and `currency` kept as computed properties.
68
+
69
+ ### Added
70
+ - **`paywall_state.gate()`** — one-call enrichment helper (limits + checkout URL + plan via `get_customer`).
71
+ - **`paywall.require` / `require_async`**: auto-mints checkout URL when `LimitResponse.checkout_url is None`.
72
+ - **`examples/marketplace/`** — Streamlit demo with 4 paywalled AI agents (Google Gemini) against real SolvaPay sandbox.
73
+
74
+ ### Internal
75
+ - User-Agent `solvapay-python/0.7.0`. 125 tests (up from 121). `mypy --strict` clean.
76
+
77
+ ## 0.6.0
78
+ - Admin endpoints (products, plans, merchant config). GitHub Actions trusted-publish to PyPI.
79
+
80
+ ## 0.5.0
81
+ - `paywall_state` classifier (ACTIVATION_REQUIRED / TOPUP_REQUIRED / UPGRADE_REQUIRED). LangChain `monetize_tool`.
82
+
83
+ ## 0.4.0
84
+ - Full async client. 5 lifecycle operations. 13 typed webhook event classes.
85
+
86
+ ## 0.3.0
87
+ - FastMCP example: AI agent with two paywalled tools.
88
+
89
+ ## 0.2.0
90
+ - `@paywall.require` decorator. FastAPI webhook router.
91
+
92
+ ## 0.1.0
93
+ - Sync client, HMAC-SHA256 webhook verification, Pydantic v2 models, CI on 3 Python versions.
@@ -0,0 +1 @@
1
+ * @dhruv-sanan
@@ -0,0 +1,27 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We pledge to make participation in our community a harassment-free experience for everyone.
6
+
7
+ ## Our Standards
8
+
9
+ **Positive behavior:**
10
+ - Using welcoming and inclusive language
11
+ - Being respectful of differing viewpoints
12
+ - Gracefully accepting constructive criticism
13
+ - Focusing on what is best for the community
14
+
15
+ **Unacceptable behavior:**
16
+ - Harassment, trolling, or personal attacks
17
+ - Publishing others' private information without permission
18
+ - Other conduct which could reasonably be considered inappropriate
19
+
20
+ ## Enforcement
21
+
22
+ Community leaders may remove, edit, or reject contributions that violate this Code of Conduct.
23
+ Report issues to `dhruv.sanan@greyorange.com`.
24
+
25
+ ## Attribution
26
+
27
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,58 @@
1
+ # Contributing
2
+
3
+ ## Local development
4
+
5
+ ```bash
6
+ git clone https://github.com/dhruv-sanan/solvapay-python
7
+ cd solvapay-python
8
+ uv sync --all-extras --dev
9
+ ```
10
+
11
+ ## Running checks
12
+
13
+ ```bash
14
+ uv run ruff check src tests # lint
15
+ uv run ruff format --check src tests # format gate
16
+ uv run mypy src # type check
17
+ uv run pytest -v # tests
18
+ uv run python tools/api_diff.py # stability manifest gate
19
+ uv run lint-imports --config tools/importlinter.cfg # layer DAG gate
20
+ ```
21
+
22
+ ## Layer DAG rules
23
+
24
+ The SDK has a strict layer hierarchy — see [docs/architecture/layers.md](docs/architecture/layers.md).
25
+
26
+ **Hard rules:**
27
+ - `_transport` must not import `client`, `paywall`, `adapters`, or `operations`
28
+ - `operations` must not import `paywall` or `adapters`
29
+ - `client` must not import `adapters` or `experimental`
30
+
31
+ CI fails on violations via `import-linter`.
32
+
33
+ ## PR checklist
34
+
35
+ - [ ] Tests added for new behavior
36
+ - [ ] `CHANGELOG.md` updated
37
+ - [ ] `docs/` updated if public API changed
38
+ - [ ] Layer DAG respected (`uv run lint-imports` passes)
39
+ - [ ] Stability manifest updated if new public exports added
40
+
41
+ ## Commit style
42
+
43
+ [Conventional Commits](https://www.conventionalcommits.org/):
44
+
45
+ ```
46
+ feat: add AsyncPaywall class
47
+ fix(webhooks): set replay_ttl_seconds default correctly
48
+ refactor: extract _transport package
49
+ docs: update README with MCP quickstart
50
+ ```
51
+
52
+ ## Adding a new resource namespace
53
+
54
+ 1. Add `OpSpec` entries to `src/solvapay/operations/_registry.py`
55
+ 2. Create `src/solvapay/operations/<resource>.py` with sync + async methods
56
+ 3. Wire namespace attr in `SolvaPay.__init__` and `AsyncSolvaPay.__init__`
57
+ 4. Add deprecated flat shim in `client.py` / `_async_client.py`
58
+ 5. Tests in `tests/operations/`
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: solvapay-python
3
+ Version: 0.8.0
4
+ Summary: Community Python SDK for SolvaPay (agent-native payment rails)
5
+ Project-URL: Homepage, https://github.com/dhruv-sanan/solvapay-python
6
+ Project-URL: Issues, https://github.com/dhruv-sanan/solvapay-python/issues
7
+ Project-URL: Official TS SDK, https://github.com/solvapay/solvapay-sdk
8
+ Author: Dhruv Sanan
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,fintech,mcp,payments,solvapay
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Office/Business :: Financial
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: pydantic<2.13,>=2.6
26
+ Provides-Extra: fastapi
27
+ Requires-Dist: fastapi>=0.110; extra == 'fastapi'
28
+ Provides-Extra: langchain
29
+ Requires-Dist: langchain-core<0.4,>=0.3; extra == 'langchain'
30
+ Provides-Extra: mcp
31
+ Requires-Dist: fastmcp<0.5,>=0.4; extra == 'mcp'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # solvapay-python
35
+
36
+ Community Python SDK for [SolvaPay](https://solvapay.com) — agentic payment rails.
37
+ `pip install solvapay-python` · Python 3.10+ · Fully typed (py.typed) · [PyPI](https://pypi.org/project/solvapay-python/)
38
+
39
+ [![CI](https://github.com/dhruv-sanan/solvapay-python/actions/workflows/ci.yml/badge.svg)](https://github.com/dhruv-sanan/solvapay-python/actions/workflows/ci.yml)
40
+ [![PyPI](https://img.shields.io/pypi/v/solvapay-python.svg)](https://pypi.org/project/solvapay-python/)
41
+
42
+ ---
43
+
44
+ ## One tool. Three runtimes. One paywall.
45
+
46
+ `@payable_tool` stamps a function with payment metadata once. Any framework reads it — no per-framework wiring.
47
+
48
+ ```python
49
+ # Define once
50
+ from solvapay.adapters.mcp import payable_tool
51
+
52
+ @payable_tool(product="prd_search")
53
+ def web_search(*, customer_ref: str, query: str) -> list[str]:
54
+ """Search the web."""
55
+ return do_real_search(query)
56
+ ```
57
+
58
+ ```python
59
+ # Run on MCP (Claude Desktop / Claude Code)
60
+ from solvapay.adapters.mcp import register_payable_tool_fastmcp
61
+ from fastmcp import FastMCP
62
+
63
+ mcp = FastMCP("My App")
64
+ register_payable_tool_fastmcp(mcp, web_search)
65
+ mcp.run()
66
+ ```
67
+
68
+ ```python
69
+ # Run on LangChain
70
+ from solvapay.adapters.langchain import monetize_tool
71
+
72
+ paid = monetize_tool(web_search, product="prd_search")
73
+ # Blocked callers get {"paywall_required": True, "checkout_url": "..."}
74
+ ```
75
+
76
+ ```python
77
+ # Run raw async
78
+ async with AsyncSolvaPay() as sv:
79
+ limits = await sv.limits.acheck(customer_ref="cus_123", product_ref="prd_search")
80
+ if limits.within_limits:
81
+ result = await web_search(customer_ref="cus_123", query="hello")
82
+ ```
83
+
84
+ `pip install 'solvapay-python[mcp]'` · `pip install 'solvapay-python[langchain]'`
85
+
86
+ ---
87
+
88
+ ## Quickstart
89
+
90
+ ```bash
91
+ pip install solvapay-python
92
+ export SOLVAPAY_SECRET_KEY=sk_...
93
+ ```
94
+
95
+ ```python
96
+ from solvapay import SolvaPay
97
+
98
+ sv = SolvaPay()
99
+
100
+ # Ensure customer exists
101
+ customer_ref = sv.customers.ensure("user_alice")
102
+
103
+ # Create checkout session
104
+ session = sv.checkout.create_session(customer_ref=customer_ref, product_ref="prd_0QKI8NHF")
105
+ print(session.checkout_url)
106
+
107
+ # Check limits and track usage
108
+ limits = sv.limits.check(customer_ref=customer_ref, product_ref="prd_0QKI8NHF")
109
+ if limits.within_limits:
110
+ sv.usage.track(
111
+ customer_ref=customer_ref,
112
+ product_ref="prd_0QKI8NHF",
113
+ meter_name="requests",
114
+ units=1.0,
115
+ idempotency_key="req_abc123", # idempotent — safe to retry
116
+ )
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Stable API
122
+
123
+ | Namespace | Sync | Async |
124
+ |-----------|------|-------|
125
+ | `sv.customers` | `ensure`, `get`, `update`, `balance` | `aensure`, `aget`, `aupdate`, `abalance` |
126
+ | `sv.checkout` | `create_session` | `acreate_session` |
127
+ | `sv.limits` | `check` | `acheck` |
128
+ | `sv.purchases` | `cancel`, `reactivate` | `acancel`, `areactivate` |
129
+ | `sv.usage` | `track` | `atrack` |
130
+ | `sv.products` | `list`, `get`, `create`, `delete`, `clone` | `a` prefix on each |
131
+ | `sv.plans` | `list`, `create`, `update`, `delete` | `a` prefix on each |
132
+ | `sv.merchant` | `get`, `get_platform_config` | `aget`, `aget_platform_config` |
133
+
134
+ ---
135
+
136
+ ## Idempotency
137
+
138
+ All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys from request content:
139
+
140
+ ```python
141
+ from solvapay.idempotency import from_payload
142
+
143
+ key = from_payload("track_usage", customer_ref, product_ref, "requests", units)
144
+ sv.usage.track(..., idempotency_key=key) # retry-safe
145
+ ```
146
+
147
+ Retried POSTs **must reuse the same key**. Key should change only when the logical request changes.
148
+
149
+ ---
150
+
151
+ ## Errors and retries
152
+
153
+ ```python
154
+ from solvapay import (
155
+ AuthenticationError, PermissionError, NotFoundError,
156
+ RateLimitError, InvalidRequestError, APIServerError,
157
+ APIConnectionError, APITimeoutError, SolvaPayError,
158
+ )
159
+
160
+ try:
161
+ sv.customers.ensure("cus_123")
162
+ except AuthenticationError:
163
+ ... # 401 — bad key
164
+ except RateLimitError as e:
165
+ ... # 429 — retry after e.retry_after seconds
166
+ except APIConnectionError:
167
+ ... # network failure
168
+ except APITimeoutError:
169
+ ... # request timed out (default 30s)
170
+ except SolvaPayError as e:
171
+ ... # catch-all
172
+ ```
173
+
174
+ No built-in retries by design. Layer `tenacity` manually. `solvapay[retry]` RetryTransport ships in v0.9.
175
+
176
+ ---
177
+
178
+ ## Webhooks
179
+
180
+ ```python
181
+ import os
182
+ from solvapay.webhooks import WebhookPipeline
183
+
184
+ pipeline = WebhookPipeline(
185
+ [os.environ["SOLVAPAY_WEBHOOK_SECRET"]],
186
+ max_clock_skew_seconds=300,
187
+ replay_ttl_seconds=600,
188
+ )
189
+
190
+ envelope = pipeline.process(body=request.body, signature=request.headers["sv-signature"])
191
+ ```
192
+
193
+ **Typed events** — discriminated union over 13 event types:
194
+
195
+ ```python
196
+ from solvapay import WebhookEvent, PurchaseCreated, PaymentSucceeded
197
+ from pydantic import TypeAdapter
198
+
199
+ event = TypeAdapter(WebhookEvent).validate_python(envelope.event)
200
+
201
+ match event:
202
+ case PurchaseCreated():
203
+ print(f"New purchase: {event.data['purchaseRef']}")
204
+ case PaymentSucceeded():
205
+ print(f"Payment: {event.data['amount']}")
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Paywall decorator
211
+
212
+ ```python
213
+ from solvapay.paywall import require, PaywallRequired
214
+
215
+ @require(product="prd_0QKI8NHF", client=sv)
216
+ def run_query(*, customer_ref: str, query: str) -> str:
217
+ return expensive_query(query)
218
+
219
+ try:
220
+ result = run_query(customer_ref="cus_123", query="hello")
221
+ except PaywallRequired as e:
222
+ print(f"Upgrade at: {e.checkout_url}")
223
+ if e.checkout_mint_error:
224
+ print(f"Could not auto-mint checkout URL: {e.checkout_mint_error}")
225
+ ```
226
+
227
+ Async version: `@require_async`. For MCP/LangChain: `@payable_tool` (see above).
228
+
229
+ ---
230
+
231
+ ## Async
232
+
233
+ `AsyncSolvaPay` is the supported async pattern. Always use `async with` — it guarantees `aclose()`:
234
+
235
+ ```python
236
+ from solvapay import AsyncSolvaPay
237
+
238
+ async with AsyncSolvaPay() as sv:
239
+ customer_ref = await sv.customers.aensure("user_alice")
240
+ limits = await sv.limits.acheck(customer_ref=customer_ref, product_ref="prd_0QKI8NHF")
241
+ if limits.within_limits:
242
+ await sv.usage.atrack(
243
+ customer_ref=customer_ref,
244
+ product_ref="prd_0QKI8NHF",
245
+ meter_name="requests",
246
+ units=1.0,
247
+ )
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Migrating from v0.7.x
253
+
254
+ Flat methods still work but emit `DeprecationWarning`. Removed in v2.0.
255
+
256
+ | v0.7.x | v0.8+ |
257
+ |--------|-------|
258
+ | `sv.ensure_customer(ref)` | `sv.customers.ensure(ref)` |
259
+ | `sv.get_customer(ref)` | `sv.customers.get(ref)` |
260
+ | `sv.check_limits(...)` | `sv.limits.check(...)` |
261
+ | `sv.track_usage(...)` | `sv.usage.track(...)` |
262
+ | `sv.create_checkout_session(...)` | `sv.checkout.create_session(...)` |
263
+ | `sv.cancel_purchase(ref)` | `sv.purchases.cancel(ref)` |
264
+ | `sv.reactivate_purchase(ref)` | `sv.purchases.reactivate(ref)` |
265
+ | `sv.get_customer_balance(ref)` | `sv.customers.balance(ref)` |
266
+ | `sv.list_products()` | `sv.products.list()` |
267
+ | `sv.create_plan(prd, ...)` | `sv.plans.create(prd, ...)` |
268
+ | `sv.get_merchant()` | `sv.merchant.get()` |
269
+
270
+ ---
271
+
272
+ ## Installation
273
+
274
+ ```bash
275
+ pip install solvapay-python # core
276
+ pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
277
+ pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
278
+ pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
279
+ ```
280
+
281
+ ## Environment variables
282
+
283
+ | Variable | Required | Default |
284
+ |----------|----------|---------|
285
+ | `SOLVAPAY_SECRET_KEY` | Yes | — |
286
+ | `SOLVAPAY_API_BASE_URL` | No | `https://api.solvapay.com` |
287
+ | `SOLVAPAY_WEBHOOK_SECRET` | For webhooks | — |
288
+
289
+ ---
290
+
291
+ ## Examples
292
+
293
+ | Path | What |
294
+ |------|------|
295
+ | `examples/multi-framework-paywall/` | One `@payable_tool` → FastMCP + LangChain + raw async |
296
+ | `examples/marketplace/` | Streamlit AI-agent marketplace — real SolvaPay sandbox + Gemini LLM |
297
+ | `examples/fastmcp-paywall/` | FastMCP server gated by `@paywall.require` |
298
+ | `examples/langchain-paywall/` | LangChain agent with `monetize_tool` |
299
+
300
+ ---
301
+
302
+ ## Roadmap
303
+
304
+ | Version | Theme |
305
+ |---------|-------|
306
+ | v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
307
+ | v0.9 | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, contract tests, lint automation, doc site (MkDocs Material), supply-chain hygiene |
308
+ | v1.0 | Gated on founder signal — OpenAPI-generated models, full secret rotation, production hardening |
309
+
310
+ ---
311
+
312
+ ## Status
313
+
314
+ **v0.8.0** — V1 architecture spine + AI-agent moat. `mypy --strict` clean (43 files). 261 tests. 89% line coverage.
315
+ Community SDK, not official. Proposal: [solvapay/solvapay-sdk#187](https://github.com/solvapay/solvapay-sdk/issues/187).