solvapay-python 0.8.0__tar.gz → 0.9.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.
- solvapay_python-0.9.0/.github/dependabot.yml +18 -0
- solvapay_python-0.9.0/.github/workflows/ci.yml +53 -0
- solvapay_python-0.9.0/.github/workflows/contract.yml +22 -0
- solvapay_python-0.9.0/.github/workflows/docs.yml +21 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.gitignore +1 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/CHANGELOG.md +28 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/CONTRIBUTING.md +15 -2
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/PKG-INFO +4 -1
- solvapay_python-0.9.0/changelog.d/README.md +35 -0
- solvapay_python-0.9.0/docs/errors.md +50 -0
- solvapay_python-0.9.0/docs/guides/fastapi.md +53 -0
- solvapay_python-0.9.0/docs/guides/langchain.md +45 -0
- solvapay_python-0.9.0/docs/guides/mcp.md +50 -0
- solvapay_python-0.9.0/docs/idempotency.md +83 -0
- solvapay_python-0.9.0/docs/index.md +63 -0
- solvapay_python-0.9.0/docs/migration.md +49 -0
- solvapay_python-0.9.0/docs/reference/client.md +5 -0
- solvapay_python-0.9.0/docs/reference/exceptions.md +3 -0
- solvapay_python-0.9.0/docs/reference/models.md +3 -0
- solvapay_python-0.9.0/docs/reference/paywall.md +9 -0
- solvapay_python-0.9.0/docs/reference/webhooks.md +15 -0
- solvapay_python-0.9.0/docs/rfcs/0002-openapi-investigation.md +35 -0
- solvapay_python-0.9.0/docs/webhooks.md +79 -0
- solvapay_python-0.9.0/mkdocs.yml +44 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/pyproject.toml +54 -1
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/__init__.py +1 -1
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_async_client.py +21 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_transport/httpx_transport.py +5 -1
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_transport/middleware.py +177 -1
- solvapay_python-0.9.0/src/solvapay/adapters/asgi.py +119 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/adapters/mcp.py +1 -1
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/client.py +2 -0
- solvapay_python-0.9.0/src/solvapay/idempotency.py +37 -0
- solvapay_python-0.9.0/src/solvapay/webhooks/__init__.py +43 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/webhooks/pipeline.py +3 -14
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/webhooks/replay.py +32 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/webhooks/rotation.py +8 -4
- solvapay_python-0.9.0/src/solvapay/webhooks/sign.py +37 -0
- solvapay_python-0.9.0/tests/_transport/test_recording_transport.py +68 -0
- solvapay_python-0.9.0/tests/_transport/test_retry_transport.py +78 -0
- solvapay_python-0.9.0/tests/adapters/test_asgi.py +91 -0
- solvapay_python-0.9.0/tests/contract/README.md +38 -0
- solvapay_python-0.9.0/tests/contract/test_contract_ops.py +61 -0
- solvapay_python-0.9.0/tests/paywall/__init__.py +0 -0
- solvapay_python-0.9.0/tests/test_api_version.py +57 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_idempotency.py +33 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_paywall_state.py +42 -39
- solvapay_python-0.9.0/tests/webhooks/__init__.py +0 -0
- solvapay_python-0.9.0/tests/webhooks/test_async_cache.py +48 -0
- solvapay_python-0.9.0/tests/webhooks/test_rotation.py +47 -0
- solvapay_python-0.9.0/tests/webhooks/test_sign.py +37 -0
- solvapay_python-0.9.0/tools/lint_invariants.py +208 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/uv.lock +650 -7
- solvapay_python-0.8.0/.github/workflows/ci.yml +0 -27
- solvapay_python-0.8.0/src/solvapay/idempotency.py +0 -16
- solvapay_python-0.8.0/src/solvapay/webhooks/__init__.py +0 -28
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.github/ISSUE_TEMPLATE/question.yml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.github/workflows/publish.yml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/.python-version +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/CODEOWNERS +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/CODE_OF_CONDUCT.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/LICENSE +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/README.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/SECURITY.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/SUPPORT.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/assets/agent-marketplace.png +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/docs/architecture/layers.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/docs/rfcs/0001-spending-policy.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/.env.example +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/.gitignore +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/README.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/claim.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/pyproject.toml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/server.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/fastmcp-paywall/uv.lock +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/langchain-paywall/.env.example +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/langchain-paywall/.gitignore +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/langchain-paywall/README.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/langchain-paywall/agent.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/langchain-paywall/pyproject.toml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/.env.example +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/.gitignore +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/.streamlit/config.toml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/PLAN.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/README.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/agents.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/app.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/demo_customers.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/requirements.txt +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/sdk_gateway.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/marketplace/ui_components.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/.env.example +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/.gitignore +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/README.md +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/agent_langchain.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/model.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/pyproject.toml +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/script_async.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/server_mcp.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/examples/multi-framework-paywall/tool.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_config.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_http.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_stability.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_transport/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/_transport/_recipe.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/adapters/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/adapters/langchain.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/events.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/exceptions.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/fastapi.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/langchain.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/models.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/_registry.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/checkout.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/customers.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/limits.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/merchant.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/plans.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/products.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/purchases.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/operations/usage.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/core.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/decorators.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/meta.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/policy.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/resolvers.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall/state.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/paywall_state.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/py.typed +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/webhooks/envelope.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/src/solvapay/webhooks/verify.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_stability/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_stability/test_stable_returns_identity.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_transport/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_transport/test_aclose_cascade.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_transport/test_error_wrapping.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_transport/test_headers_case_insensitive.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_transport/test_middleware_composition.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/_transport/test_protocol_conformance.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/adapters/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/adapters/test_langchain_protocol.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/adapters/test_mcp.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/conftest.py +0 -0
- {solvapay_python-0.8.0/tests/operations → solvapay_python-0.9.0/tests/contract}/__init__.py +0 -0
- /solvapay_python-0.8.0/tests/paywall/__init__.py → /solvapay_python-0.9.0/tests/contract/cassettes/.gitkeep +0 -0
- {solvapay_python-0.8.0/tests/webhooks → solvapay_python-0.9.0/tests/operations}/__init__.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/operations/test_namespace_api.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/operations/test_path_interpolation.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/operations/test_retry_safety_enum.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/paywall/test_payable_tool_meta.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/paywall/test_resolvers.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/paywall/test_split_classes.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_admin.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_async_client.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_checkout.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_config.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_customer.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_errors.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_http.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_invariants.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_langchain.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_lifecycle.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_limits.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_packaging.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_paywall.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_redaction.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_webhook_events.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/test_webhooks.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/webhooks/test_seen_cache_atomic.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tests/webhooks/test_webhook_pipeline.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tools/api_baseline.json +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tools/api_diff.py +0 -0
- {solvapay_python-0.8.0 → solvapay_python-0.9.0}/tools/importlinter.cfg +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
open-pull-requests-limit: 5
|
|
8
|
+
labels:
|
|
9
|
+
- dependencies
|
|
10
|
+
|
|
11
|
+
- package-ecosystem: github-actions
|
|
12
|
+
directory: /
|
|
13
|
+
schedule:
|
|
14
|
+
interval: weekly
|
|
15
|
+
open-pull-requests-limit: 5
|
|
16
|
+
labels:
|
|
17
|
+
- dependencies
|
|
18
|
+
- ci
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
include:
|
|
13
|
+
# Linux — full Python matrix
|
|
14
|
+
- os: ubuntu-latest
|
|
15
|
+
python-version: "3.10"
|
|
16
|
+
- os: ubuntu-latest
|
|
17
|
+
python-version: "3.11"
|
|
18
|
+
- os: ubuntu-latest
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
# macOS — 3.12 only
|
|
21
|
+
- os: macos-latest
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
# Windows — 3.12 only
|
|
24
|
+
- os: windows-latest
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
runs-on: ${{ matrix.os }}
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: astral-sh/setup-uv@v3
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
- name: Set Python ${{ matrix.python-version }}
|
|
33
|
+
run: uv python install ${{ matrix.python-version }}
|
|
34
|
+
- run: uv sync --all-extras --dev
|
|
35
|
+
- run: uv run ruff check src tests
|
|
36
|
+
- run: uv run ruff format --check src tests
|
|
37
|
+
- run: uv run mypy src
|
|
38
|
+
- run: uv run pytest -v
|
|
39
|
+
- run: uv run python tools/api_diff.py
|
|
40
|
+
- run: uv run lint-imports --config tools/importlinter.cfg
|
|
41
|
+
- run: uv run python tools/lint_invariants.py
|
|
42
|
+
- name: pip-audit
|
|
43
|
+
run: uv run pip-audit
|
|
44
|
+
- name: Check changelog fragment on PRs touching src/
|
|
45
|
+
if: github.event_name == 'pull_request'
|
|
46
|
+
run: |
|
|
47
|
+
changed=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
|
|
48
|
+
if echo "$changed" | grep -q "^src/"; then
|
|
49
|
+
if ! echo "$changed" | grep -q "^changelog.d/"; then
|
|
50
|
+
echo "ERROR: src/ modified without a changelog.d/ fragment. Add one — see changelog.d/README.md"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: contract
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "0 2 * * *" # nightly at 02:00 UTC
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
contract:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: astral-sh/setup-uv@v3
|
|
14
|
+
with:
|
|
15
|
+
enable-cache: true
|
|
16
|
+
- run: uv sync --all-extras --dev
|
|
17
|
+
- name: Run contract tests
|
|
18
|
+
run: uv run pytest tests/contract/ -m contract -v
|
|
19
|
+
env:
|
|
20
|
+
SOLVAPAY_SANDBOX_KEY: ${{ secrets.SOLVAPAY_SANDBOX_KEY }}
|
|
21
|
+
SOLVAPAY_TEST_CUSTOMER_REF: ${{ secrets.SOLVAPAY_TEST_CUSTOMER_REF }}
|
|
22
|
+
SOLVAPAY_TEST_PRODUCT_REF: ${{ secrets.SOLVAPAY_TEST_PRODUCT_REF }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
deploy:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v3
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
- run: uv sync --group docs
|
|
20
|
+
- name: Deploy to GitHub Pages
|
|
21
|
+
run: uv run mkdocs gh-deploy --force
|
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0 — 2026-05-23
|
|
4
|
+
|
|
5
|
+
Production polish + C1 adversarial-review closure. API-version pinning, idempotency TTL, webhook secret rotation, ASGI adapter, retry/recording transports, contract tests, lint automation, doc site, supply-chain hardening.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **API-version pinning**: `SolvaPay(api_version="2026-05-22")` and `AsyncSolvaPay(api_version=...)` send `Solvapay-Version` header. Default pinned to `"2026-05-22"`. `api_version=None` omits the header. (HLD V1.13)
|
|
9
|
+
- **Idempotency time-bucket encoding**: `from_payload(*parts, time_bucket="day"|"hour"|None)`. Default `"day"` appends UTC date so keys roll at midnight, bounding replay ambiguity past server TTL. (HLD V1.14)
|
|
10
|
+
- **Webhook secret rotation**: `MultiSecretVerifier` full implementation — tries primary then secondary on signature mismatch; both comparisons constant-time. (HLD V1.7)
|
|
11
|
+
- **`AsyncInMemorySeenEventCache`**: async replay-dedup cache using `asyncio.Lock` for async webhook pipelines. (HLD V1.7)
|
|
12
|
+
- **`sign_webhook(body, secret, *, timestamp)`**: public helper to produce a valid `sv-signature` header for testing and outbound webhook fanout.
|
|
13
|
+
- **ASGI webhook adapter** (`solvapay[asgi]`): `webhook_app(pipeline, on_event, path)` returns a raw ASGI app mountable in Starlette, FastAPI, Litestar, or BlackSheep. (HLD V1.10)
|
|
14
|
+
- **`RetryTransport`**: middleware that retries `APIConnectionError`, `APITimeoutError`, `APIServerError`, `RateLimitError` — exponential backoff with jitter, max 3 attempts. Consults `OpSpec.retry_safety`; refuses to retry `NEVER` ops. (HLD V1.4, `solvapay[retry]`)
|
|
15
|
+
- **`RecordingTransport`**: records `(RequestSpec, ResponseSpec)` pairs to JSON cassettes; replays on subsequent runs. Enables offline contract tests. (HLD V1.4)
|
|
16
|
+
- **Sandbox contract tests** (`tests/contract/`): one test per op against real sandbox via `RecordingTransport`. Nightly CI workflow (`contract.yml`).
|
|
17
|
+
- **Lint invariants** (`tools/lint_invariants.py`): AST checks for 10 gotchas (future annotations, `hmac.compare_digest`, no `logging.basicConfig`, no `asyncio.run()`, alias discipline, etc.). Run in CI on every push.
|
|
18
|
+
- **Towncrier changelog automation**: `changelog.d/` fragments, PR gate requiring a fragment when `src/` changes, `towncrier>=23` dev dep.
|
|
19
|
+
- **MkDocs Material doc site**: `mkdocs.yml` + `docs/` skeleton (Quickstart, Reference, Architecture, Guides, Errors, Idempotency, Webhooks, Migration). Deploys to GitHub Pages on tag. (HLD V1.12)
|
|
20
|
+
- **Dependabot**: weekly Python + GitHub Actions dependency updates (`.github/dependabot.yml`).
|
|
21
|
+
- **`pip-audit` in CI**: dependency CVE scan on every push.
|
|
22
|
+
- **CI matrix expanded**: Ubuntu 3.10/3.11/3.12 + macOS 3.12 + Windows 3.12. (HLD V1.15)
|
|
23
|
+
- **`ResourceWarning` on unclosed `AsyncSolvaPay`**: `__del__` emits `ResourceWarning` if client is not closed and event loop is still running.
|
|
24
|
+
- **OpenAPI investigation RFC**: `docs/rfcs/0002-openapi-investigation.md`.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- `pytest.ini_options.filterwarnings`: adds `ignore::DeprecationWarning:solvapay` so tests exercising deprecated flat-shim API do not fail on expected warnings.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
3
31
|
## 0.8.0 — 2026-05-23
|
|
4
32
|
|
|
5
33
|
V1 architecture spine + AI-agent moat. 10 atomic commits establishing locked architecture invariants per HLD §V1.1–V1.19.
|
|
@@ -30,13 +30,26 @@ The SDK has a strict layer hierarchy — see [docs/architecture/layers.md](docs/
|
|
|
30
30
|
|
|
31
31
|
CI fails on violations via `import-linter`.
|
|
32
32
|
|
|
33
|
+
## Changelog fragment (required)
|
|
34
|
+
|
|
35
|
+
Every PR that modifies `src/` must include a towncrier fragment in `changelog.d/`.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Create a fragment (replace 42 with your issue/PR number)
|
|
39
|
+
echo "Add RetryTransport middleware for automatic retry on transient errors." > changelog.d/42.feature
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See `changelog.d/README.md` for naming conventions and types.
|
|
43
|
+
|
|
44
|
+
PRs that touch `src/` without a `changelog.d/` entry will fail CI.
|
|
45
|
+
|
|
33
46
|
## PR checklist
|
|
34
47
|
|
|
35
48
|
- [ ] Tests added for new behavior
|
|
36
|
-
- [ ] `
|
|
49
|
+
- [ ] `changelog.d/<issue>.<type>` fragment added (required for `src/` changes)
|
|
37
50
|
- [ ] `docs/` updated if public API changed
|
|
38
51
|
- [ ] Layer DAG respected (`uv run lint-imports` passes)
|
|
39
|
-
- [ ] Stability manifest updated if new public exports added
|
|
52
|
+
- [ ] Stability manifest updated if new public exports added (`uv run python tools/api_diff.py`)
|
|
40
53
|
|
|
41
54
|
## Commit style
|
|
42
55
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solvapay-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Community Python SDK for SolvaPay (agent-native payment rails)
|
|
5
5
|
Project-URL: Homepage, https://github.com/dhruv-sanan/solvapay-python
|
|
6
6
|
Project-URL: Issues, https://github.com/dhruv-sanan/solvapay-python/issues
|
|
@@ -23,12 +23,15 @@ Classifier: Typing :: Typed
|
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: httpx>=0.27
|
|
25
25
|
Requires-Dist: pydantic<2.13,>=2.6
|
|
26
|
+
Provides-Extra: asgi
|
|
26
27
|
Provides-Extra: fastapi
|
|
27
28
|
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
28
29
|
Provides-Extra: langchain
|
|
29
30
|
Requires-Dist: langchain-core<0.4,>=0.3; extra == 'langchain'
|
|
30
31
|
Provides-Extra: mcp
|
|
31
32
|
Requires-Dist: fastmcp<0.5,>=0.4; extra == 'mcp'
|
|
33
|
+
Provides-Extra: retry
|
|
34
|
+
Requires-Dist: tenacity<10,>=8.2; extra == 'retry'
|
|
32
35
|
Description-Content-Type: text/markdown
|
|
33
36
|
|
|
34
37
|
# solvapay-python
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog Fragments
|
|
2
|
+
|
|
3
|
+
This directory contains towncrier changelog fragments. Each file represents one change entry.
|
|
4
|
+
|
|
5
|
+
## Fragment naming
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<issue_or_pr_number>.<type>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Types:** `feature`, `bugfix`, `doc`, `removal`
|
|
12
|
+
|
|
13
|
+
**Examples:**
|
|
14
|
+
```
|
|
15
|
+
42.feature → "Add RetryTransport middleware"
|
|
16
|
+
55.bugfix → "Fix idempotency key not sent on retry"
|
|
17
|
+
60.doc → "Document api_version pinning"
|
|
18
|
+
99.removal → "Remove deprecated SolvaPayAPIError alias"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Required for PRs
|
|
22
|
+
|
|
23
|
+
Any PR that touches `src/` **must** include at least one fragment. PRs without a fragment will fail CI.
|
|
24
|
+
|
|
25
|
+
Example fragment content:
|
|
26
|
+
```
|
|
27
|
+
Add ``api_version`` kwarg to ``SolvaPay`` and ``AsyncSolvaPay`` for server-side API version pinning.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Building the changelog
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv run towncrier build --version 0.9.0 --draft # preview
|
|
34
|
+
uv run towncrier build --version 0.9.0 # write to CHANGELOG.md
|
|
35
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Errors
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
SolvaPayError
|
|
5
|
+
├── APIError(status_code, body, request_id, error_code, error_message)
|
|
6
|
+
│ ├── AuthenticationError # 401
|
|
7
|
+
│ ├── PermissionError # 403
|
|
8
|
+
│ ├── NotFoundError # 404
|
|
9
|
+
│ ├── RateLimitError(retry_after) # 429
|
|
10
|
+
│ ├── InvalidRequestError # other 4xx
|
|
11
|
+
│ └── APIServerError # 5xx
|
|
12
|
+
├── APIConnectionError # network failure
|
|
13
|
+
├── APITimeoutError # timeout
|
|
14
|
+
└── PaywallRequired # paywall gate hit
|
|
15
|
+
.checkout_url: str | None
|
|
16
|
+
.checkout_mint_error: APIError | None
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Handling errors
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from solvapay import SolvaPay
|
|
23
|
+
from solvapay.exceptions import (
|
|
24
|
+
AuthenticationError,
|
|
25
|
+
RateLimitError,
|
|
26
|
+
APIConnectionError,
|
|
27
|
+
PaywallRequired,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
sv = SolvaPay()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
sv.limits.check(customer_ref="cus_1", product_ref="prd_1")
|
|
34
|
+
except AuthenticationError:
|
|
35
|
+
print("Invalid API key")
|
|
36
|
+
except RateLimitError as e:
|
|
37
|
+
print(f"Rate limited — retry after {e.retry_after}s")
|
|
38
|
+
except APIConnectionError:
|
|
39
|
+
print("Network failure — safe to retry with idempotency key")
|
|
40
|
+
except PaywallRequired as e:
|
|
41
|
+
print(f"Paywall hit. Checkout: {e.checkout_url}")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `request_id`
|
|
45
|
+
|
|
46
|
+
Every `APIError` captures `request_id` from `x-request-id` or `x-correlation-id` response headers. Include this in support tickets.
|
|
47
|
+
|
|
48
|
+
## Legacy alias
|
|
49
|
+
|
|
50
|
+
`SolvaPayAPIError` is an alias for `APIError`. It emits `DeprecationWarning` and will be removed in v2.0. Use `APIError` directly.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# FastAPI Integration
|
|
2
|
+
|
|
3
|
+
## Webhook router
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from solvapay.fastapi import webhook_router
|
|
8
|
+
|
|
9
|
+
app = FastAPI()
|
|
10
|
+
app.include_router(
|
|
11
|
+
webhook_router(
|
|
12
|
+
secret="whsec_...",
|
|
13
|
+
on_event=handle_event,
|
|
14
|
+
path="/webhook",
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## ASGI webhook app (framework-agnostic)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from fastapi import FastAPI
|
|
23
|
+
from solvapay.adapters.asgi import webhook_app
|
|
24
|
+
from solvapay.webhooks import WebhookPipeline
|
|
25
|
+
|
|
26
|
+
pipeline = WebhookPipeline(secrets=["whsec_..."])
|
|
27
|
+
|
|
28
|
+
async def handle(envelope):
|
|
29
|
+
event = envelope.event
|
|
30
|
+
print(f"Received {event['type']} for {event.get('customerId')}")
|
|
31
|
+
|
|
32
|
+
app = FastAPI()
|
|
33
|
+
app.mount("/webhook", webhook_app(pipeline, handle))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Paywall decorator
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from fastapi import FastAPI, Request
|
|
40
|
+
from solvapay.paywall import require
|
|
41
|
+
from solvapay.exceptions import PaywallRequired
|
|
42
|
+
|
|
43
|
+
app = FastAPI()
|
|
44
|
+
|
|
45
|
+
@app.post("/api/search")
|
|
46
|
+
@require(product="prd_0QKI8NHF", plan="pln_pro", customer_ref_arg="customer_id")
|
|
47
|
+
async def search(customer_id: str, query: str):
|
|
48
|
+
return {"results": [...]}
|
|
49
|
+
|
|
50
|
+
@app.exception_handler(PaywallRequired)
|
|
51
|
+
async def paywall_handler(req: Request, exc: PaywallRequired):
|
|
52
|
+
return {"error": "upgrade_required", "checkout_url": exc.checkout_url}
|
|
53
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# LangChain Integration
|
|
2
|
+
|
|
3
|
+
Monetize any LangChain tool with SolvaPay.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install "solvapay-python[langchain]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Monetize a tool
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from solvapay import SolvaPay
|
|
15
|
+
from solvapay.adapters.langchain import monetize_tool
|
|
16
|
+
|
|
17
|
+
sv = SolvaPay()
|
|
18
|
+
|
|
19
|
+
# Any LangChain BaseTool-compatible object
|
|
20
|
+
monetized = monetize_tool(
|
|
21
|
+
my_langchain_tool,
|
|
22
|
+
product="prd_0QKI8NHF",
|
|
23
|
+
customer_ref_arg="customer_ref",
|
|
24
|
+
client=sv,
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## With `@payable_tool`
|
|
29
|
+
|
|
30
|
+
If a tool is already decorated with `@payable_tool`, `monetize_tool` reads `__solvapay_meta__` automatically:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from solvapay.paywall import payable_tool
|
|
34
|
+
from solvapay.adapters.langchain import monetize_tool
|
|
35
|
+
|
|
36
|
+
@payable_tool(product="prd_0QKI8NHF")
|
|
37
|
+
def web_search(*, customer_ref: str, query: str) -> list[str]: ...
|
|
38
|
+
|
|
39
|
+
monetized = monetize_tool(web_search, client=sv)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## See also
|
|
43
|
+
|
|
44
|
+
- `examples/langchain-paywall/` — standalone LangChain agent example
|
|
45
|
+
- `examples/multi-framework-paywall/` — same tool in MCP + LangChain + async
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# MCP Integration
|
|
2
|
+
|
|
3
|
+
Monetize any MCP tool with SolvaPay using `@payable_tool`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install "solvapay-python[mcp]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Stamp a tool
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from solvapay.paywall import payable_tool
|
|
15
|
+
|
|
16
|
+
@payable_tool(product="prd_0QKI8NHF")
|
|
17
|
+
def web_search(*, customer_ref: str, query: str) -> list[str]:
|
|
18
|
+
"""Search the web."""
|
|
19
|
+
return [...]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Register with FastMCP
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from fastmcp import FastMCP
|
|
26
|
+
from solvapay.adapters.mcp import register_payable_tool_fastmcp
|
|
27
|
+
|
|
28
|
+
mcp = FastMCP("my-server")
|
|
29
|
+
register_payable_tool_fastmcp(mcp, web_search)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Schema flavors
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from solvapay.adapters.mcp import (
|
|
36
|
+
payable_tool_mcp_schema,
|
|
37
|
+
payable_tool_anthropic_tool,
|
|
38
|
+
payable_tool_openai_function,
|
|
39
|
+
payable_tool_langchain_args_schema,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
mcp_schema = payable_tool_mcp_schema(web_search)
|
|
43
|
+
anthropic_tool = payable_tool_anthropic_tool(web_search)
|
|
44
|
+
openai_fn = payable_tool_openai_function(web_search)
|
|
45
|
+
langchain_schema = payable_tool_langchain_args_schema(web_search)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Multi-framework example
|
|
49
|
+
|
|
50
|
+
See `examples/multi-framework-paywall/` for a complete example running one tool across FastMCP, LangChain, and raw async.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Idempotency
|
|
2
|
+
|
|
3
|
+
Idempotency keys prevent duplicate operations when a request is retried after a network failure or timeout.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from solvapay import SolvaPay
|
|
9
|
+
from solvapay.idempotency import from_payload
|
|
10
|
+
|
|
11
|
+
sv = SolvaPay()
|
|
12
|
+
|
|
13
|
+
key = from_payload("cus_123", "prd_abc", "checkout")
|
|
14
|
+
session = sv.checkout.create_session(
|
|
15
|
+
customer_ref="cus_123",
|
|
16
|
+
product_ref="prd_abc",
|
|
17
|
+
idempotency_key=key,
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## `from_payload` — key generation
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from solvapay.idempotency import from_payload
|
|
25
|
+
|
|
26
|
+
key = from_payload(*parts, time_bucket="day")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Produces a 32-hex-character SHA-256 key from the given payload parts.
|
|
30
|
+
|
|
31
|
+
### `time_bucket` parameter
|
|
32
|
+
|
|
33
|
+
| Value | Behaviour | Use when |
|
|
34
|
+
|-------|-----------|----------|
|
|
35
|
+
| `"day"` (default) | Appends current UTC date (`2026-05-22`). Key changes at midnight UTC. | Standard checkout / purchase flows |
|
|
36
|
+
| `"hour"` | Appends current UTC hour (`2026-05-22T14`). Key changes every hour. | High-frequency operations where 24 h TTL is too wide |
|
|
37
|
+
| `None` | Pure payload hash — deterministic across time. | Caller manages TTL externally, or operation has no server-side TTL |
|
|
38
|
+
|
|
39
|
+
### Retry contract
|
|
40
|
+
|
|
41
|
+
**Retried POSTs MUST reuse the exact same `idempotency_key` as the original call.**
|
|
42
|
+
|
|
43
|
+
If the key changes between the original request and the retry, the server treats it as a new request, which can cause duplicate charges or records.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
key = from_payload("cus_123", "prd_abc", time_bucket="day")
|
|
47
|
+
|
|
48
|
+
# Original attempt (may fail with network error)
|
|
49
|
+
try:
|
|
50
|
+
session = sv.checkout.create_session(
|
|
51
|
+
customer_ref="cus_123",
|
|
52
|
+
product_ref="prd_abc",
|
|
53
|
+
idempotency_key=key,
|
|
54
|
+
)
|
|
55
|
+
except APIConnectionError:
|
|
56
|
+
# Retry — same key, same day → server deduplicates
|
|
57
|
+
session = sv.checkout.create_session(
|
|
58
|
+
customer_ref="cus_123",
|
|
59
|
+
product_ref="prd_abc",
|
|
60
|
+
idempotency_key=key,
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Bucket roll
|
|
65
|
+
|
|
66
|
+
A bucket roll (midnight UTC for `"day"`, hour boundary for `"hour"`) produces a **different key**. The server will treat a request with the new key as a fresh operation. Do not retry across a bucket boundary unless you intend to create a new operation.
|
|
67
|
+
|
|
68
|
+
## Which operations accept `idempotency_key`?
|
|
69
|
+
|
|
70
|
+
All mutating `POST` operations accept an optional `idempotency_key` kwarg:
|
|
71
|
+
|
|
72
|
+
- `sv.checkout.create_session(..., idempotency_key=key)`
|
|
73
|
+
- `sv.customers.ensure(..., idempotency_key=key)`
|
|
74
|
+
- `sv.usage.track(..., idempotency_key=key)`
|
|
75
|
+
- `sv.purchases.cancel(..., idempotency_key=key)`
|
|
76
|
+
- `sv.purchases.reactivate(..., idempotency_key=key)`
|
|
77
|
+
- `sv.products.create(..., idempotency_key=key)`
|
|
78
|
+
- `sv.products.clone(..., idempotency_key=key)`
|
|
79
|
+
- `sv.plans.create(..., idempotency_key=key)`
|
|
80
|
+
|
|
81
|
+
## Server TTL
|
|
82
|
+
|
|
83
|
+
The SolvaPay server deduplicates idempotency keys within a TTL window. Keys presented after the TTL expires are treated as new requests. The `time_bucket="day"` default is conservative and safe for typical retry windows.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# solvapay-python
|
|
2
|
+
|
|
3
|
+
Community Python SDK for [SolvaPay](https://solvapay.com) — agent-native payment rails.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install solvapay-python
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from solvapay import SolvaPay
|
|
13
|
+
|
|
14
|
+
sv = SolvaPay() # reads SOLVAPAY_SECRET_KEY env var
|
|
15
|
+
|
|
16
|
+
# Ensure a customer exists
|
|
17
|
+
customer_ref = sv.customers.ensure("user_123")
|
|
18
|
+
|
|
19
|
+
# Create a checkout session
|
|
20
|
+
session = sv.checkout.create_session(
|
|
21
|
+
customer_ref=customer_ref,
|
|
22
|
+
product_ref="prd_0QKI8NHF",
|
|
23
|
+
)
|
|
24
|
+
print(session.checkout_url)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Async
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from solvapay import AsyncSolvaPay
|
|
31
|
+
|
|
32
|
+
async with AsyncSolvaPay() as sv:
|
|
33
|
+
session = await sv.checkout.acreate_session(
|
|
34
|
+
customer_ref="cus_123",
|
|
35
|
+
product_ref="prd_0QKI8NHF",
|
|
36
|
+
)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## MCP (payable tools)
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from solvapay.paywall import payable_tool
|
|
43
|
+
|
|
44
|
+
@payable_tool(product="prd_0QKI8NHF")
|
|
45
|
+
def web_search(*, customer_ref: str, query: str) -> list[str]:
|
|
46
|
+
...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API version pinning
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
sv = SolvaPay(api_version="2026-05-22") # pins Solvapay-Version header
|
|
53
|
+
sv = SolvaPay(api_version=None) # omits header
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Install extras
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install "solvapay-python[mcp]" # FastMCP adapter
|
|
60
|
+
pip install "solvapay-python[langchain]" # LangChain adapter
|
|
61
|
+
pip install "solvapay-python[retry]" # RetryTransport (tenacity)
|
|
62
|
+
pip install "solvapay-python[asgi]" # ASGI webhook adapter
|
|
63
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
## v0.7.x → v0.8.x
|
|
4
|
+
|
|
5
|
+
### Flat methods are now deprecated
|
|
6
|
+
|
|
7
|
+
All flat methods on `SolvaPay` / `AsyncSolvaPay` (e.g. `sv.ensure_customer()`) now emit `DeprecationWarning` and will be **removed in v2.0**.
|
|
8
|
+
|
|
9
|
+
Migrate to the resource namespace API:
|
|
10
|
+
|
|
11
|
+
| Old (v0.7) | New (v0.8+) |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `sv.ensure_customer(ref)` | `sv.customers.ensure(ref)` |
|
|
14
|
+
| `sv.get_customer(ref)` | `sv.customers.get(ref)` |
|
|
15
|
+
| `sv.check_limits(...)` | `sv.limits.check(...)` |
|
|
16
|
+
| `sv.create_checkout_session(...)` | `sv.checkout.create_session(...)` |
|
|
17
|
+
| `sv.track_usage(...)` | `sv.usage.track(...)` |
|
|
18
|
+
| `sv.cancel_purchase(ref)` | `sv.purchases.cancel(ref)` |
|
|
19
|
+
| `sv.reactivate_purchase(ref)` | `sv.purchases.reactivate(ref)` |
|
|
20
|
+
|
|
21
|
+
Async variants: prepend `a` to the method name, e.g. `sv.customers.aensure(ref)`.
|
|
22
|
+
|
|
23
|
+
### Mock pattern changed
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
# Old (v0.7) — WRONG for v0.8
|
|
27
|
+
client = MagicMock(spec=SolvaPay)
|
|
28
|
+
client.check_limits.return_value = ...
|
|
29
|
+
|
|
30
|
+
# New (v0.8+) — CORRECT
|
|
31
|
+
client = MagicMock()
|
|
32
|
+
client.limits.check.return_value = ...
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `webhooks.py` and `paywall.py` are now packages
|
|
36
|
+
|
|
37
|
+
Imports from `solvapay.webhooks` and `solvapay.paywall` still work — the packages re-export everything at the same names.
|
|
38
|
+
|
|
39
|
+
## v0.6.x → v0.7.x
|
|
40
|
+
|
|
41
|
+
### Wire format fixes
|
|
42
|
+
|
|
43
|
+
- `Customer.customer_ref` now accepts `reference` (real API) and `customerRef` (legacy) via `AliasChoices`
|
|
44
|
+
- `BalanceResponse.balance` is now major-unit (dollars), not cents
|
|
45
|
+
- Use `paywall_state.gate()` instead of manual enrichment after `check_limits`
|
|
46
|
+
|
|
47
|
+
## SolvaPayAPIError alias
|
|
48
|
+
|
|
49
|
+
`SolvaPayAPIError = APIError` is a deprecated alias. Use `APIError` directly. Will be removed in v2.0.
|