waffo 0.1.0b0__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 (78) hide show
  1. waffo-0.1.0b0/.gitignore +14 -0
  2. waffo-0.1.0b0/ADR.md +138 -0
  3. waffo-0.1.0b0/CHANGELOG.md +5 -0
  4. waffo-0.1.0b0/CLAUDE.md +154 -0
  5. waffo-0.1.0b0/LICENSE +21 -0
  6. waffo-0.1.0b0/MANIFEST.in +4 -0
  7. waffo-0.1.0b0/PKG-INFO +55 -0
  8. waffo-0.1.0b0/README.md +17 -0
  9. waffo-0.1.0b0/README_CN.md +17 -0
  10. waffo-0.1.0b0/docs/python-sdk-integration-acceptance-report-2026-05-09.md +208 -0
  11. waffo-0.1.0b0/examples/__init__.py +1 -0
  12. waffo-0.1.0b0/examples/fastapi_integration/README.md +27 -0
  13. waffo-0.1.0b0/examples/fastapi_integration/__init__.py +1 -0
  14. waffo-0.1.0b0/examples/fastapi_integration/app.py +301 -0
  15. waffo-0.1.0b0/examples/fastapi_integration/service.py +607 -0
  16. waffo-0.1.0b0/examples/fastapi_integration/store.py +563 -0
  17. waffo-0.1.0b0/pyproject.toml +77 -0
  18. waffo-0.1.0b0/src/waffo/__init__.py +17 -0
  19. waffo-0.1.0b0/src/waffo/_version.py +1 -0
  20. waffo-0.1.0b0/src/waffo/client.py +46 -0
  21. waffo-0.1.0b0/src/waffo/core/__init__.py +1 -0
  22. waffo-0.1.0b0/src/waffo/core/http_client.py +243 -0
  23. waffo-0.1.0b0/src/waffo/core/webhook_handler.py +172 -0
  24. waffo-0.1.0b0/src/waffo/exceptions.py +21 -0
  25. waffo-0.1.0b0/src/waffo/net/__init__.py +6 -0
  26. waffo-0.1.0b0/src/waffo/net/default_http_transport.py +65 -0
  27. waffo-0.1.0b0/src/waffo/net/http_request.py +14 -0
  28. waffo-0.1.0b0/src/waffo/net/http_response.py +17 -0
  29. waffo-0.1.0b0/src/waffo/net/http_transport.py +11 -0
  30. waffo-0.1.0b0/src/waffo/py.typed +1 -0
  31. waffo-0.1.0b0/src/waffo/resources/__init__.py +13 -0
  32. waffo-0.1.0b0/src/waffo/resources/base.py +70 -0
  33. waffo-0.1.0b0/src/waffo/resources/merchant_config.py +27 -0
  34. waffo-0.1.0b0/src/waffo/resources/order.py +60 -0
  35. waffo-0.1.0b0/src/waffo/resources/pay_method_config.py +27 -0
  36. waffo-0.1.0b0/src/waffo/resources/refund.py +20 -0
  37. waffo-0.1.0b0/src/waffo/resources/subscription.py +98 -0
  38. waffo-0.1.0b0/src/waffo/types/__init__.py +4 -0
  39. waffo-0.1.0b0/src/waffo/types/api_response.py +52 -0
  40. waffo-0.1.0b0/src/waffo/types/base.py +15 -0
  41. waffo-0.1.0b0/src/waffo/types/common/__init__.py +33 -0
  42. waffo-0.1.0b0/src/waffo/types/config.py +259 -0
  43. waffo-0.1.0b0/src/waffo/types/generated/__init__.py +157 -0
  44. waffo-0.1.0b0/src/waffo/types/generated/models.py +839 -0
  45. waffo-0.1.0b0/src/waffo/types/merchant/__init__.py +6 -0
  46. waffo-0.1.0b0/src/waffo/types/order/__init__.py +36 -0
  47. waffo-0.1.0b0/src/waffo/types/payment/__init__.py +16 -0
  48. waffo-0.1.0b0/src/waffo/types/refund/__init__.py +6 -0
  49. waffo-0.1.0b0/src/waffo/types/subscription/__init__.py +50 -0
  50. waffo-0.1.0b0/src/waffo/utils/__init__.py +23 -0
  51. waffo-0.1.0b0/src/waffo/utils/action_utils.py +22 -0
  52. waffo-0.1.0b0/src/waffo/utils/amount_validator.py +80 -0
  53. waffo-0.1.0b0/src/waffo/utils/rsa_utils.py +107 -0
  54. waffo-0.1.0b0/src/waffo/utils/time_utils.py +27 -0
  55. waffo-0.1.0b0/tests/__init__.py +1 -0
  56. waffo-0.1.0b0/tests/conftest.py +23 -0
  57. waffo-0.1.0b0/tests/e2e/__init__.py +1 -0
  58. waffo-0.1.0b0/tests/e2e/application-test.yml.example +9 -0
  59. waffo-0.1.0b0/tests/e2e/conftest.py +118 -0
  60. waffo-0.1.0b0/tests/e2e/helpers.py +571 -0
  61. waffo-0.1.0b0/tests/e2e/test_merchant_config_e2e.py +22 -0
  62. waffo-0.1.0b0/tests/e2e/test_payment_flow_e2e.py +68 -0
  63. waffo-0.1.0b0/tests/e2e/test_refund_flow_e2e.py +64 -0
  64. waffo-0.1.0b0/tests/e2e/test_subscription_flow_e2e.py +75 -0
  65. waffo-0.1.0b0/tests/e2e/test_webhook_flow_e2e.py +106 -0
  66. waffo-0.1.0b0/tests/integration/__init__.py +1 -0
  67. waffo-0.1.0b0/tests/integration/conftest.py +183 -0
  68. waffo-0.1.0b0/tests/integration/test_fastapi_integration_acceptance.py +671 -0
  69. waffo-0.1.0b0/tests/unit/test_amount_validator.py +62 -0
  70. waffo-0.1.0b0/tests/unit/test_api_response.py +66 -0
  71. waffo-0.1.0b0/tests/unit/test_config.py +143 -0
  72. waffo-0.1.0b0/tests/unit/test_http_client.py +184 -0
  73. waffo-0.1.0b0/tests/unit/test_public_types.py +35 -0
  74. waffo-0.1.0b0/tests/unit/test_resources.py +110 -0
  75. waffo-0.1.0b0/tests/unit/test_rsa_utils.py +63 -0
  76. waffo-0.1.0b0/tests/unit/test_schema_fields.py +20 -0
  77. waffo-0.1.0b0/tests/unit/test_time_utils.py +35 -0
  78. waffo-0.1.0b0/tests/unit/test_webhook_handler.py +117 -0
@@ -0,0 +1,14 @@
1
+ /.venv/
2
+ /dist/
3
+ /build/
4
+ /*.egg-info/
5
+ /uv.lock
6
+ /.pytest_cache/
7
+ /.ruff_cache/
8
+ /.mypy_cache/
9
+ /htmlcov/
10
+ /.coverage
11
+ /tests/e2e/application-test.yml
12
+ /target/
13
+ __pycache__/
14
+ *.py[cod]
waffo-0.1.0b0/ADR.md ADDED
@@ -0,0 +1,138 @@
1
+ # Python SDK Architecture Decision Record
2
+
3
+ This document records the technical decisions for the Waffo Python SDK.
4
+
5
+ ## Summary Table
6
+
7
+ | Decision Area | Choice | Rationale |
8
+ |---|---|---|
9
+ | Runtime Version | Python 3.9+ | Broad production support while keeping modern typing features available |
10
+ | Dependency Strategy | Minimal runtime dependencies | Payment SDK should stay small, but Python benefits from well-maintained crypto, HTTP, and model libraries |
11
+ | HTTP Client | httpx sync client | Modern API, timeout controls, TLS verification, and transport testability |
12
+ | JSON Framework | Pydantic v2 + stdlib json | Generated models, alias support, validation, and stable JSON serialization |
13
+ | Build Tool | pyproject.toml + hatchling | Current Python packaging standard with simple wheel/sdist output |
14
+ | Test Framework | pytest | Standard Python test runner with fixtures, coverage, and vector-driven tests |
15
+ | E2E Test Framework | pytest + playwright-python | Matches sandbox browser flows while keeping E2E independent from app frameworks |
16
+ | Type System | Python type hints + Pydantic v2 models + PEP 561 | IDE support, runtime validation, and typed package consumers |
17
+ | Code Style | ruff + mypy | Fast linting plus static checks |
18
+ | Framework Adaptation | FastAPI first; Flask and Django examples | FastAPI is the primary modern Python web stack; Flask/Django remain common merchant stacks |
19
+ | Package and Release Workflow | PyPI package `waffo`, import module `waffo`, `.github/workflows/publish-python.yml` | Release workflow validates version, tests, wheel/sdist, metadata, clean install, then publishes through PyPI trusted publishing |
20
+
21
+ ## Detailed Decisions
22
+
23
+ ### 1. Runtime Version
24
+
25
+ **Decision:** Python 3.9+.
26
+
27
+ **Rationale:** Python 3.9 is still common in production and supports the typing features needed by this SDK. CI will cover Python 3.9 through 3.13. Local development may use newer interpreters, but release support is 3.9-3.13 until the CI matrix is updated.
28
+
29
+ **Alternatives Considered:** Python 3.8 would broaden support but is past normal support windows. Python 3.10+ would simplify typing syntax but exclude older merchant environments.
30
+
31
+ ### 2. Dependency Strategy
32
+
33
+ **Decision:** Minimal runtime dependencies: `pydantic`, `httpx`, `cryptography`, `PyYAML`, and conditional `eval-type-backport` for Python 3.9.
34
+
35
+ **Rationale:** RSA and TLS-sensitive code should use maintained libraries rather than hand-rolled implementations. Pydantic v2 lets generated models preserve OpenAPI field aliases and required/optional semantics.
36
+
37
+ Python 3.9 needs `eval-type-backport` because generated Pydantic models use postponed annotations containing PEP 604 union syntax such as `str | None`; Pydantic evaluates those annotations at model construction time.
38
+
39
+ **Alternatives Considered:** A zero-dependency SDK would reduce supply-chain surface but would push too much crypto, HTTP, and validation responsibility into this repo.
40
+
41
+ ### 3. HTTP Client / Network Layer
42
+
43
+ **Decision:** Use a synchronous `httpx.Client` behind a `HttpTransport` protocol.
44
+
45
+ **Rationale:** The first Python SDK is sync-only, matching the current plan and simplifying merchant integration. A transport protocol keeps tests deterministic and allows future async support without changing resource APIs.
46
+
47
+ **Spec Notes:** `sdk-spec/HTTP_CLIENT.md` is authoritative for base URLs, headers, timeout defaults, response signature verification, and unknown-status errors. Python sends `X-API-VERSION=1.0.0`; the current Java SDK uses `v1`, which is tracked as cross-SDK drift.
48
+
49
+ ### 4. JSON Framework
50
+
51
+ **Decision:** Use Pydantic v2 models with camelCase aliases and stdlib JSON for lower-level parsing when exact body bytes are needed.
52
+
53
+ **Rationale:** API fields must come from `openapi.json`. Generated Pydantic models can preserve aliases, support extra service fields, and serialize with `exclude_none=True`.
54
+
55
+ **Spec Notes:** Webhook response body bytes follow `sdk-spec/WEBHOOK_HANDLER.md` and `sdk-spec/test-vectors/webhook-response.json`.
56
+
57
+ ### 5. Build Tool
58
+
59
+ **Decision:** Use `pyproject.toml` with hatchling.
60
+
61
+ **Rationale:** This is the modern Python packaging path and works with PyPI trusted publishing.
62
+
63
+ ### 6. Test Framework
64
+
65
+ **Decision:** Use pytest, pytest-cov, ruff, and mypy.
66
+
67
+ **Rationale:** Pytest supports vector-driven tests cleanly. Coverage gates will start at 80% for core modules.
68
+
69
+ ### 7. E2E Test Framework
70
+
71
+ **Decision:** Use pytest with playwright-python and optional pyngrok for webhook callbacks.
72
+
73
+ **Rationale:** The SDK E2E surface requires real browser payment flows plus webhook delivery. Playwright gives stable browser automation while pytest keeps test orchestration simple.
74
+
75
+ ### 8. Type System
76
+
77
+ **Decision:** Ship typed Python code (`py.typed`) and generated Pydantic v2 API models.
78
+
79
+ **Rationale:** `openapi.json` is the only authority for API fields. Java/Node/Go are behavior references and known to have schema drift in `sdk-spec/SCHEMA_ALIGNMENT_CHECKLIST.md`.
80
+
81
+ ### 9. Code Style
82
+
83
+ **Decision:** Use ruff for linting and formatting checks, and mypy for type checks.
84
+
85
+ **Rationale:** Ruff is fast enough for local and CI use. Mypy catches API surface and generic response mistakes before release.
86
+
87
+ ### 10. Framework Adaptation
88
+
89
+ **Decision:** Provide FastAPI examples first, plus Flask and Django webhook examples.
90
+
91
+ **Rationale:** The SDK core stays framework-agnostic. Documentation and `waffo-integrate` templates will show raw-body webhook handling for common Python frameworks.
92
+
93
+ ### 11. Package and Release Workflow
94
+
95
+ **Decision:** Publish the distribution name `waffo` to PyPI while preserving the import module `waffo`.
96
+
97
+ **Workflow:** `.github/workflows/publish-python.yml`.
98
+
99
+ **Tag Pattern:** `python-sdk-v*` (for example `python-sdk-v0.1.0b0`).
100
+
101
+ **Version Source:** `pyproject.toml` `[project].version` and `src/waffo/_version.py` `__version__` must both equal the release version resolved from the tag or manual workflow input.
102
+
103
+ **Build Artifact:** wheel + source distribution from `python -m build`.
104
+
105
+ **Publish Auth:** PyPI trusted publishing/OIDC only. The workflow does not accept `PYPI_API_TOKEN`; PyPI must be configured with a trusted publisher before the release tag is pushed.
106
+
107
+ **Artifact Verification:** The workflow runs ruff, mypy, unit tests/test vectors, `twine check`, and a clean temporary venv install/import smoke test before publishing.
108
+
109
+ **Rationale:** Publishing is part of the SDK delivery contract. A release workflow that proves the built artifact can be installed catches packaging drift that source-tree tests miss.
110
+
111
+ ## Spec Gate
112
+
113
+ The Python SDK implementation follows these current source-of-truth decisions:
114
+
115
+ - API request/response fields are generated from `openapi.json`; public type packages may re-export or thinly wrap generated models but must not handwrite API fields.
116
+ - RSA uses SHA256withRSA / RSASSA-PKCS1-v1_5, Base64 PKCS#8 private keys, and Base64 X.509 SubjectPublicKeyInfo public keys per `sdk-spec/RSA_SIGNING.md`.
117
+ - Time formatting is UTC `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` per `sdk-spec/TIME_FORMAT.md`.
118
+ - HTTP headers include `Content-Type`, `X-API-KEY`, `X-SIGNATURE`, `X-API-VERSION=1.0.0`, and `X-SDK-VERSION=waffo-python/{version}` per `sdk-spec/HTTP_CLIENT.md`.
119
+ - Error classes are `WaffoError` and `WaffoUnknownStatusError` per `sdk-spec/ERROR_HANDLING.md`.
120
+ - Webhook events are the five events in `sdk-spec/WEBHOOK_HANDLER.md`, including `SUBSCRIPTION_CHANGE_NOTIFICATION`. There is no standalone `SUBSCRIPTION_PAYMENT_NOTIFICATION`; `on_subscription_payment` is a fallback for `SUBSCRIPTION_STATUS_NOTIFICATION`.
121
+ - Webhook success/failure response bodies are exactly `{"message":"success"}` and `{"message":"failed"}` per `sdk-spec/WEBHOOK_HANDLER.md` and `sdk-spec/test-vectors/webhook-response.json`.
122
+
123
+ ## Current Spec Drift Recorded Before Implementation
124
+
125
+ | Area | Current Drift | Python Decision |
126
+ |---|---|---|
127
+ | Webhook response test rule | `sdk-spec/test-rules/04-webhook.md` still mentions `{"code":"SUCCESS"}` / `{"code":"FAILED"}` | Follow `WEBHOOK_HANDLER.md` + `webhook-response.json`; update stale rule when touching webhook spec |
128
+ | Time vector | `sdk-spec/TIME_FORMAT.md` references missing `test-vectors/time-format.json` | Cover equivalent tests in `test_time_utils.py`; add vector if shared runners need it |
129
+ | Config vector | `sdk-spec/TEST_SPECIFICATION.md` references missing `config-validation.json` | Treat actual 8 files in `sdk-spec/test-vectors/` as current vector set until spec doc is corrected |
130
+ | API version header | Java has `ApiVersion.CURRENT = "v1"` while `HTTP_CLIENT.md` says `1.0.0` | Python uses `1.0.0` and records Java drift |
131
+ | Schema fields | `SCHEMA_ALIGNMENT_CHECKLIST.md` lists Java/Node/Go field mismatches | Python generated models follow `openapi.json`, not existing SDK mismatches |
132
+
133
+ ## Decision Log
134
+
135
+ | Date | Decision | Change | Author |
136
+ |---|---|---|---|
137
+ | 2026-05-08 | Initial Python SDK ADR | Created before implementation, with spec drift audit | Codex |
138
+ | 2026-05-09 | Python release workflow | Added PyPI package/release workflow decision and artifact verification gates | Codex |
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0b0
4
+
5
+ - Initial Python SDK development scaffold.
@@ -0,0 +1,154 @@
1
+ # CLAUDE.md for waffo-python
2
+
3
+ This file provides guidance for Claude Code when working with the Waffo Python SDK.
4
+
5
+ ## Project Overview
6
+
7
+ Official Python SDK for Waffo Payment Services. This package is part of the Waffo SDK monorepo.
8
+
9
+ ## Project Rules
10
+
11
+ 1. This project is part of the Waffo SDK monorepo.
12
+ 2. API type fields are generated from `../../openapi.json`; do not handwrite API request/response fields.
13
+ 3. Core logic follows `../../sdk-spec/`.
14
+ 4. All shared test vectors in `../../sdk-spec/test-vectors/` must pass.
15
+ 5. README changes must start from the monorepo `../../README.md` and `../../README_CN.md`, then run `../../scripts/sync-readme.sh`.
16
+ 6. Technical decisions are documented in `ADR.md`; update ADR when changing runtime, dependencies, build/test tools, framework support, package name, or release workflow.
17
+
18
+ ## Architecture Decision Record
19
+
20
+ See `ADR.md` for all technical decisions.
21
+
22
+ | Decision | Choice |
23
+ |---|---|
24
+ | Runtime Version | Python 3.9+ |
25
+ | Dependency Strategy | Minimal runtime dependencies |
26
+ | HTTP Client | httpx sync client |
27
+ | JSON Framework | Pydantic v2 + stdlib json |
28
+ | Build Tool | pyproject.toml + hatchling |
29
+ | Test Framework | pytest |
30
+ | E2E Framework | pytest + playwright-python |
31
+ | Framework Integrations | FastAPI, Flask, Django |
32
+ | Package and Release Workflow | PyPI package `waffo`; workflow `.github/workflows/publish-python.yml`; tag pattern `python-sdk-v*` |
33
+
34
+ ## README Synchronization Rules
35
+
36
+ The monorepo README files are the single source of truth for shared documentation:
37
+
38
+ 1. Update `../../README.md` and `../../README_CN.md` first.
39
+ 2. Update `../../scripts/sync-readme.js` when adding or renaming synced sections.
40
+ 3. Run `../../scripts/sync-readme.sh --verbose`.
41
+ 4. Add only Python-specific details in this package README.
42
+
43
+ ## Build & Test Commands
44
+
45
+ ```bash
46
+ # From packages/waffo-python
47
+ python -m pip install -e ".[dev]"
48
+ ruff check src tests
49
+ mypy src/waffo
50
+ pytest tests/unit -v
51
+
52
+ # From monorepo root
53
+ ./scripts/generate-types.sh python
54
+ ./scripts/sync-readme.sh --check
55
+ ```
56
+
57
+ ## Architecture
58
+
59
+ ```text
60
+ src/waffo/
61
+ ├── __init__.py
62
+ ├── client.py
63
+ ├── core/
64
+ │ ├── http_client.py
65
+ │ └── webhook_handler.py
66
+ ├── resources/
67
+ ├── net/
68
+ ├── types/
69
+ │ ├── api_response.py
70
+ │ ├── config.py
71
+ │ └── generated/
72
+ ├── utils/
73
+ └── exceptions.py
74
+ ```
75
+
76
+ ## Key Implementation Notes
77
+
78
+ ### API Fields
79
+
80
+ - `openapi.json` is authoritative.
81
+ - Generated models live under `src/waffo/types/generated/`.
82
+ - Public type packages may re-export generated models or add behavior-only thin wrappers.
83
+ - Do not copy Java/Node/Go field mismatches into Python.
84
+
85
+ ### RSA Signing
86
+
87
+ - Algorithm: SHA256withRSA / RSASSA-PKCS1-v1_5.
88
+ - Private key: Base64 PKCS#8 DER.
89
+ - Public key: Base64 X.509 SubjectPublicKeyInfo DER.
90
+ - Validate keys during config initialization and fail fast with `S0007` / `S0002`.
91
+
92
+ ### HTTP Client
93
+
94
+ - Use `httpx` behind a transport protocol.
95
+ - Enforce TLS 1.2+.
96
+ - Send `X-API-VERSION=1.0.0` and `X-SDK-VERSION=waffo-python/{version}`.
97
+ - Query operations catch `WaffoUnknownStatusError` and return `ApiResponse.error("E0001", ...)`.
98
+
99
+ ### Webhook
100
+
101
+ - Recognize the five event types in `../../sdk-spec/WEBHOOK_HANDLER.md`.
102
+ - `SUBSCRIPTION_STATUS_NOTIFICATION` routes to `on_subscription_status` first, then falls back to `on_subscription_payment`.
103
+ - There is no standalone `SUBSCRIPTION_PAYMENT_NOTIFICATION`.
104
+ - Success response body is exactly `{"message":"success"}`; failure is exactly `{"message":"failed"}`.
105
+
106
+ ## E2E Test Error Logging
107
+
108
+ Every assertion that checks an API response must include request identifiers and the full response body in the failure message. Do not use bare `assert response.is_success()`.
109
+
110
+ ## Release Checklist
111
+
112
+ Before releasing:
113
+
114
+ 1. All unit tests and shared test vectors pass.
115
+ 2. Ruff and mypy pass.
116
+ 3. README sync passes.
117
+ 4. Full Python E2E matrix passes.
118
+ 5. Cross-repo `waffo-sdk-test`, `waffo-integrate`, and `waffo-integrate-test` validation passes.
119
+ 6. CHANGELOG is updated.
120
+ 7. `pyproject.toml` version and `src/waffo/_version.py` `__version__` match the release tag/input.
121
+
122
+ ## Release Workflow
123
+
124
+ The Python SDK is published by `.github/workflows/publish-python.yml`.
125
+
126
+ ### Automatic release
127
+
128
+ 1. Complete the release checklist above.
129
+ 2. Make sure the release commit is on `main`.
130
+ 3. Create and push a tag matching `python-sdk-v{version}`, for example:
131
+
132
+ ```bash
133
+ git tag python-sdk-v0.1.0b0
134
+ git push origin python-sdk-v0.1.0b0
135
+ ```
136
+
137
+ The workflow resolves `0.1.0b0`, verifies that the tag points to a commit on `origin/main`, checks package/runtime version consistency, runs ruff, mypy, unit tests/test vectors, builds wheel + sdist, runs `twine check`, performs a clean venv install/import smoke test, then publishes to PyPI.
138
+
139
+ ### Manual release
140
+
141
+ Use GitHub Actions `Publish Python SDK to PyPI` -> `Run workflow` from `main`, with `version` set to the exact package version such as `0.1.0b0`.
142
+
143
+ ### PyPI trusted publishing
144
+
145
+ Configure PyPI trusted publishing for this repository/workflow before pushing the release tag:
146
+
147
+ | Field | Value |
148
+ |---|---|
149
+ | Repository owner | `waffo-com` |
150
+ | Repository name | `waffo-sdk` |
151
+ | Workflow filename | `publish-python.yml` |
152
+ | Environment name | leave empty |
153
+
154
+ The workflow publishes with GitHub OIDC only. Do not configure `PYPI_API_TOKEN` for this workflow.
waffo-0.1.0b0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Waffo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include README_CN.md
3
+ include LICENSE
4
+ recursive-include src/waffo py.typed
waffo-0.1.0b0/PKG-INFO ADDED
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: waffo
3
+ Version: 0.1.0b0
4
+ Summary: Official Waffo Payment Platform Python SDK
5
+ Project-URL: Homepage, https://github.com/waffo-com/waffo-sdk/tree/main/packages/waffo-python
6
+ Project-URL: Repository, https://github.com/waffo-com/waffo-sdk
7
+ Project-URL: Issues, https://github.com/waffo-com/waffo-sdk/issues
8
+ Author-email: Waffo <merchant.support@waffo.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: payment,payment-gateway,psp,sdk,subscription,waffo
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: cryptography<47,>=42
24
+ Requires-Dist: eval-type-backport<1,>=0.2; python_version < '3.10'
25
+ Requires-Dist: httpx<1,>=0.27
26
+ Requires-Dist: pydantic<3,>=2.6
27
+ Requires-Dist: pyyaml<7,>=6
28
+ Provides-Extra: dev
29
+ Requires-Dist: mypy<2.0,>=1.8; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Provides-Extra: e2e
34
+ Requires-Dist: playwright>=1.40; extra == 'e2e'
35
+ Requires-Dist: pyngrok>=7; extra == 'e2e'
36
+ Requires-Dist: pytest-playwright>=0.5; extra == 'e2e'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # Waffo Python SDK
40
+
41
+ Official Python SDK for Waffo Payment Platform.
42
+
43
+ This package is currently under active development. Shared documentation is maintained in the monorepo root `README.md` and will be synchronized here through `scripts/sync-readme.sh`.
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.9+
48
+ - Runtime dependencies are declared in `pyproject.toml`.
49
+
50
+ ## Development
51
+
52
+ ```bash
53
+ python -m pip install -e ".[dev]"
54
+ pytest tests/unit -v
55
+ ```
@@ -0,0 +1,17 @@
1
+ # Waffo Python SDK
2
+
3
+ Official Python SDK for Waffo Payment Platform.
4
+
5
+ This package is currently under active development. Shared documentation is maintained in the monorepo root `README.md` and will be synchronized here through `scripts/sync-readme.sh`.
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.9+
10
+ - Runtime dependencies are declared in `pyproject.toml`.
11
+
12
+ ## Development
13
+
14
+ ```bash
15
+ python -m pip install -e ".[dev]"
16
+ pytest tests/unit -v
17
+ ```
@@ -0,0 +1,17 @@
1
+ # Waffo Python SDK
2
+
3
+ Waffo Payment Platform 官方 Python SDK。
4
+
5
+ 该包正在开发中。共享文档以仓库根目录 `README_CN.md` 为准,并通过 `scripts/sync-readme.sh` 同步到本目录。
6
+
7
+ ## 环境要求
8
+
9
+ - Python 3.9+
10
+ - 运行时依赖见 `pyproject.toml`。
11
+
12
+ ## 开发
13
+
14
+ ```bash
15
+ python -m pip install -e ".[dev]"
16
+ pytest tests/unit -v
17
+ ```
@@ -0,0 +1,208 @@
1
+ # Python SDK 集成验收报告
2
+
3
+ ## 概览
4
+
5
+ | 字段 | 值 |
6
+ |------|----|
7
+ | 项目 | Waffo Python SDK FastAPI reference integration |
8
+ | 日期 | 2026-05-09 |
9
+ | SDK 版本 | `0.1.0b0` |
10
+ | 环境 | Sandbox |
11
+ | MID | `1200000256` |
12
+ | 验收范围 | 支付、退款、订阅、订阅升降级、Webhook、商户配置、支付方式配置 |
13
+ | 项目接口 | `packages/waffo-python/examples/fastapi_integration` |
14
+ | Webhook Endpoint | `POST /api/waffo/webhook` |
15
+ | 支付方式配置 | `payMethodConfig().inquiry()` 返回 268 条 active rows |
16
+ | 自动化支付方式结果 | `CC_VISA`、`DANA`、`PIX`、`PERMATA`、`7ELEVEN` 已通过;`APPLEPAY`/`GOOGLEPAY` 需要设备钱包手工验收 |
17
+
18
+ ## 本轮补充修正
19
+
20
+ | 问题 | 修正 |
21
+ |------|------|
22
+ | 订阅通知没有单独拆项 | 新增“订阅通知专项验收”,按 4 个通知类型分别列出结果和对应 ID |
23
+ | `SUBSCRIPTION_CHANGE_NOTIFICATION` 未测 | 已补订阅升降级真实 Sandbox 验收,收到 `SUBSCRIPTION_CHANGE_NOTIFICATION` |
24
+ | `/api/v1/subscription/change` 未测 | FastAPI 集成新增项目侧 `POST /api/subscriptions/{subscriptionRequest}/change`,底层调用 SDK `subscription().change()`,已通过 |
25
+ | `/api/v1/subscription/change/inquiry` 未测 | FastAPI 集成新增项目侧 `GET /api/subscriptions/{originSubscriptionRequest}/changes/{subscriptionRequest}`,底层调用 SDK `subscription().change_inquiry()`,已通过 |
26
+ | `Order ID / Key` 混用多种 ID | 所有主结果表拆为 `Request ID`、`Merchant ID`、`Acquiring ID`、`Subscription ID`、`Refund ID`、`Change Request ID` 等独立列 |
27
+ | 报告中文可读性不足 | 本报告改为中文补充版,保留接口名、事件名、ID 字段英文原名 |
28
+
29
+ ## 集成配置
30
+
31
+ | 参数 | 值 |
32
+ |------|----|
33
+ | 语言 / 框架 | Python / FastAPI |
34
+ | SDK 初始化 | 单例 `Waffo` 注入 service layer |
35
+ | `userTerminal` | `WEB` |
36
+ | Checkout 模式 | 项目接口支持动态 `payMethodType`、`payMethodName`、`payMethodProperties` |
37
+ | 订阅模式 | Payment-first subscription |
38
+ | 订阅相关事件 | `PAYMENT_NOTIFICATION`、`SUBSCRIPTION_STATUS_NOTIFICATION`、`SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION`、`SUBSCRIPTION_CHANGE_NOTIFICATION` |
39
+ | Webhook 业务逻辑 | 签名校验、幂等 key、store transaction、状态落库、履约/撤销/激活计数只执行一次 |
40
+ | 凭据 | 从本地 Sandbox `application-test.yml` 查找路径读取,不提交 Git |
41
+
42
+ ## 项目接口覆盖
43
+
44
+ | 模块 | 项目接口 | Waffo SDK/API 覆盖 |
45
+ |------|----------|--------------------|
46
+ | 支付 | `POST /api/payments` | `order().create()` / `/api/v1/order/create` |
47
+ | 支付查询 | `GET /api/payments/{paymentRequestId}` | `order().inquiry()` / `/api/v1/order/inquiry` |
48
+ | 取消订单 | `POST /api/payments/{paymentRequestId}/cancel` | `order().cancel()` / `/api/v1/order/cancel` |
49
+ | 退款 | `POST /api/refunds` | `order().refund()` / `/api/v1/order/refund` |
50
+ | 退款查询 | `GET /api/refunds/{refundRequestId}` | `refund().inquiry()` / `/api/v1/refund/inquiry` |
51
+ | 订阅创建 | `POST /api/subscriptions` | `subscription().create()` / `/api/v1/subscription/create` |
52
+ | 订阅查询 | `GET /api/subscriptions/{subscriptionRequest}` | `subscription().inquiry()` / `/api/v1/subscription/inquiry` |
53
+ | 订阅管理 | `POST /api/subscriptions/{subscriptionRequest}/manage` | `subscription().manage()` / `/api/v1/subscription/manage` |
54
+ | 订阅取消 | `POST /api/subscriptions/{subscriptionRequest}/cancel` | `subscription().cancel()` / `/api/v1/subscription/cancel` |
55
+ | 订阅升降级 | `POST /api/subscriptions/{subscriptionRequest}/change` | `subscription().change()` / `/api/v1/subscription/change` |
56
+ | 订阅升降级查询 | `GET /api/subscriptions/{originSubscriptionRequest}/changes/{subscriptionRequest}` | `subscription().change_inquiry()` / `/api/v1/subscription/change/inquiry` |
57
+ | 商户配置 | `GET /api/merchant-config` | `merchantConfig().inquiry()` |
58
+ | 支付方式配置 | `GET /api/payment-methods` | `payMethodConfig().inquiry()` |
59
+ | Webhook | `POST /api/waffo/webhook` | `webhook().handle_webhook()` |
60
+
61
+ ## 主动验收结果
62
+
63
+ | 验收项 | 结果 | Request ID | Merchant ID | Acquiring ID | Subscription ID | Refund ID | Change Request ID | 说明 |
64
+ |--------|------|------------|-------------|--------------|-----------------|-----------|-------------------|------|
65
+ | merchant-config | PASS | - | `1200000256` | - | - | - | - | 通过项目接口查询商户配置 |
66
+ | payment-method-config | PASS | - | `1200000256` | - | - | - | - | 查询到 268 条 active rows |
67
+ | order-create | PASS | `pay_...` | `ord_...` | `A202605090723532146976` | - | - | - | 项目接口返回 checkout URL 并落库 |
68
+ | payment-success (`CC_VISA`) | PASS | `pay_...` | `ord_...` | `A202605090723532146976` | - | - | - | Playwright 完成 checkout,查询状态 `PAY_SUCCESS` |
69
+ | payment-failure (`CC_VISA`) | PASS | `pay_...` | `ord_...` | `A202605090724013246977` | - | - | - | 失败卡关闭订单,`fulfillmentCount=0` |
70
+ | cancel-unpaid-order | PASS | `pay_...` | `ord_...` | `A202605090724098676978` | - | - | - | 项目取消接口返回 `ORDER_CLOSE` |
71
+ | order-create-error | PASS | `pay_4b8a3343192c4aec822d934e587a` | - | - | - | - | - | 非法金额返回用户友好 `400`,本地订单标记 `FAILED` |
72
+ | pay-method-coverage | PASS WITH MANUAL ITEMS | 多个 | 多个 | 见“支付方式覆盖” | - | - | - | 覆盖 card、e-wallet、special params、VA/bank、OTC;设备钱包手工 |
73
+ | refund-success | PASS | `pay_...` | - | `A202605090725023336982` | - | `ref_3ecf891874634297a0984f697e6e` | - | DANA 支付源触发退款 webhook |
74
+ | refund-inquiry | PASS | `pay_...` | - | `A202605090725023336982` | - | `ref_3ecf891874634297a0984f697e6e` | - | 项目查询返回 `ORDER_PARTIALLY_REFUNDED` |
75
+ | refund-webhook | PASS | `pay_...` | - | `A202605090725023336982` | - | `ref_3ecf891874634297a0984f697e6e` | - | `REFUND_NOTIFICATION` 后 `revokeCount=1` |
76
+ | webhook-idempotency | PASS | `pay_...` | - | `A202605090725023336982` | - | - | - | 重放 DANA payment webhook,`fulfillmentCount` 保持 `1` |
77
+ | subscription-create | PASS | `sub_80d68d452cd0486c841520b07080` | `msub_...` | - | `SC202605091445554676139` | - | - | 项目接口返回订阅 checkout URL 并落库 |
78
+ | subscription-activation | PASS | `sub_80d68d452cd0486c841520b07080` | `msub_...` | - | `SC202605091445554676139` | - | - | 完成订阅 checkout,`SUBSCRIPTION_STATUS_NOTIFICATION` 激活本地状态 |
79
+ | subscription-inquiry | PASS | `sub_80d68d452cd0486c841520b07080` | `msub_...` | - | `SC202605091445554676139` | - | - | 项目查询接口返回 `ACTIVE` |
80
+ | subscription-manage | PASS | `sub_80d68d452cd0486c841520b07080` | `msub_...` | - | `SC202605091445554676139` | - | - | 项目 manage 接口返回 management URL |
81
+ | subscription-renewal | PASS | `sub_80d68d452cd0486c841520b07080` | `msub_...` | - | `SC202605091445554676139` | - | - | 管理页 mock 触发续期通知,`renewalCount=1` |
82
+ | subscription-change | PASS | `sub_80d68d452cd0486c841520b07080` | `msubchg_...` | - | `SC202605091446207776140` | - | `subchg_1d4ee1ea844f4461851400260` | 调用 `/api/v1/subscription/change`,完成升降级 checkout,状态 `SUCCESS` |
83
+ | subscription-change-inquiry | PASS | `sub_80d68d452cd0486c841520b07080` | `msubchg_...` | - | `SC202605091446207776140` | - | `subchg_1d4ee1ea844f4461851400260` | 调用 `/api/v1/subscription/change/inquiry`,返回 `SUCCESS` |
84
+ | subscription-cancel | PASS | `subchg_1d4ee1ea844f4461851400260` | `msubchg_...` | - | `SC202605091446207776140` | - | - | 取消升降级后的新订阅,返回 `MERCHANT_CANCELLED` |
85
+
86
+ ## 订阅通知专项验收
87
+
88
+ | 通知类型 | 结果 | Request ID | Acquiring ID | Subscription ID | Change Request ID | 状态 / 说明 |
89
+ |----------|------|------------|--------------|-----------------|-------------------|-------------|
90
+ | `PAYMENT_NOTIFICATION` | PASS | - | `A202605091445555496872` | - | - | 订阅首期支付通知,`orderStatus=PAY_SUCCESS`,项目 handler 按 `productName=SUBSCRIPTION` 过滤业务履约但保留 webhook delivery |
91
+ | `SUBSCRIPTION_STATUS_NOTIFICATION` | PASS | `sub_80d68d452cd0486c841520b07080` | - | `SC202605091445554676139` | - | 订阅激活通知,`subscriptionStatus=ACTIVE` |
92
+ | `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` | PASS | `sub_80d68d452cd0486c841520b07080` | - | `SC202605091445554676139` | - | 管理页续期 mock 后收到周期变更通知,`renewalCount=1` |
93
+ | `SUBSCRIPTION_CHANGE_NOTIFICATION` | PASS | `sub_80d68d452cd0486c841520b07080` | - | `SC202605091446207776140` | `subchg_1d4ee1ea844f4461851400260` | 订阅升降级完成通知,`subscriptionChangeStatus=SUCCESS` |
94
+
95
+ ## 支付方式覆盖
96
+
97
+ | 支付方式 | 国家 / 币种 | 类型 | 状态 | Request ID | Acquiring ID | 说明 |
98
+ |----------|-------------|------|------|------------|--------------|------|
99
+ | `CC_VISA` | HKG / HKD | Card | TESTED | `pay_...` | `A202605090723532146976` | 信用卡代表路径,覆盖成功支付 |
100
+ | `DANA` | IDN / IDR | E-wallet | TESTED | `pay_...` | `A202605090724164096979` | 印尼 e-wallet 代表路径,Sandbox 成功模拟 |
101
+ | `PIX` | BRA / BRL | Special Params | TESTED | `pay_...` | `A202605090724283856980` | 填写 name/email,使用 `payMethodProperties={"cpf":"52998224725"}` 后成功 |
102
+ | `PERMATA` | IDN / IDR | VA / Bank | TESTED | `pay_...` | `A202605090724444766981` | 印尼 virtual-account/bank 代表路径 |
103
+ | `7ELEVEN` | PHL / PHP | OTC | TESTED | `pay_...` | `A202605090810521186219` | 使用有效金额 `300.00` 后通过 |
104
+ | `APPLEPAY` | Multiple | Device Pay | MANUAL | `ap_4e784d8024cc4414a47411291bc4f` | `A202605090802098626168` | 已创建手工验证订单,需要 Apple Pay 设备/浏览器完成 |
105
+ | `GOOGLEPAY` | Multiple | Device Pay | MANUAL | - | - | 需要 Android/Chrome 或设备钱包环境 |
106
+
107
+ Sandbox active method sample: `DC_VISA`, `CC_MASTERCARD`, `DC_MASTERCARD`, `CC_VISA`, `APPLEPAY`, `GOOGLEPAY`, `CC_AMEX`, `SHOPEEPAY`, `ALIPAYHK`, `MERCADOPAGO`, `DANA`, `PIX`, `ALIPAY`, `7ELEVEN`, `PERMATA`.
108
+
109
+ ## 7ELEVEN 金额边界
110
+
111
+ `payMethodConfig().inquiry()` 能确认 `7ELEVEN` 在 `TWN` 与 `PHL` active,但公开 SDK config response 不暴露 amount min/max。本轮通过 Sandbox order-create 探测确认:
112
+
113
+ | 国家 | 币种 | 已验证最小值 | 已验证最大值 | 证据 |
114
+ |------|------|--------------|--------------|------|
115
+ | `TWN` | `TWD` | `17.00` | `20000.00` | `16.00` 返回 `A0008`;`17.00` 成功。`20000.00` 成功;`20001.00` 返回 `A0008` |
116
+ | `PHL` | `PHP` | `300.00` | `50000.00` | `299.00` 返回 `A0008`;`300.00` 成功。`50000.00` 成功;`50001.00` 返回 `A0008` |
117
+
118
+ ## Apple Pay 手工验证订单
119
+
120
+ `APPLEPAY` 在 Sandbox 对 `ONE_TIME_PAYMENT` 的 `HKG`、`USA`、`GBR`、`JPN`、`BRA`、`EU_LIST` active;对 `SUBSCRIPTION` 的 `HKG` 和 `BRA` active。已创建一笔一次性支付订单供开发者在真实 Apple Pay 环境验证:
121
+
122
+ | 字段 | 值 |
123
+ |------|----|
124
+ | `paymentRequestId` | `ap_4e784d8024cc4414a47411291bc4f` |
125
+ | `merchantOrderId` | `ord_b4d06829ea484778a3f6ab407970` |
126
+ | `acquiringOrderId` | `A202605090802098626168` |
127
+ | 国家 / 币种 / 金额 | `HKG` / `HKD` / `15.00` |
128
+ | Checkout URL | `https://checkout-sandbox.waffo.com/VueXeyNSoEe9DJceA5acx9wcEf5qDUF1TVhHqN9BKPZjZjUJFTNuQPRspbitS4pz5KnjpLoYdE7H3znXb5aDUBVwLn3X2sA65kxSRcWzk77tBM5VGMRLPT2tp1H?lang=zh-Hant-HK&mock` |
129
+
130
+ ## 参数与数据完整性检查
131
+
132
+ - [x] `paymentRequestId`、`refundRequestId`、`subscriptionRequest`、升降级新 `subscriptionRequest` 均为不超过 32 位的商户侧请求 ID。
133
+ - [x] 所有写接口调用前先落库请求 ID,再调用 Waffo。
134
+ - [x] `orderDescription` / subscription description 具体可读。
135
+ - [x] `goodsName` 与 `goodsUrl` 已提供。
136
+ - [x] `userEmail` 使用 `{userId}@example.com` 或明确邮箱,避免使用 `test` 字样。
137
+ - [x] `userTerminal=WEB` 与当前 FastAPI reference integration 一致。
138
+ - [x] success / failed / cancel redirect URL 均有设置。
139
+ - [x] SDK 自动注入 UTC ISO-8601 timestamp。
140
+ - [x] 非卡支付可通过项目接口透传 country、currency、method type/name、`payMethodProperties`。
141
+ - [x] 退款 webhook 以 `acquiringOrderId` 匹配本地退款上下文。
142
+ - [x] Webhook 幂等处理可防止重复履约、重复撤销、重复订阅激活或重复升降级处理。
143
+
144
+ ## 被动验证
145
+
146
+ | Case | 说明 | 结果 | 证据 |
147
+ |------|------|------|------|
148
+ | Payment 1.3 | `C0005` 渠道拒绝 | COVERED | Service 映射为用户友好的 retry/switch-method 错误 |
149
+ | Payment 1.4 | `A0011` 幂等冲突 | COVERED | Request ID 独立生成,UUID hex 截断 32 位 |
150
+ | Payment 1.5 | `C0001` 系统不可用 | COVERED | Service 映射为 retry/switch-method 错误 |
151
+ | Payment 1.6 | `E0001` unknown status | COVERED | `WaffoUnknownStatusError` 后用同一 request ID 查询恢复 |
152
+ | Payment 3.3 | Webhook 签名失败 | COVERED | SDK 在业务 handler 前拒绝无效签名 |
153
+ | Payment 4.5 | Cancel unknown status | COVERED | Cancel recovery 查询订单,不盲目关闭本地状态 |
154
+ | Payment 5.4 | Refund 幂等冲突 | COVERED | `refundRequestId` 独立生成并先落库 |
155
+ | Payment 5.6 | Refund unknown status | COVERED | Refund recovery 用同一 request ID 查询 |
156
+ | Subscription 1.6 | Subscription 幂等冲突 | COVERED | `subscriptionRequest` 独立生成并先落库 |
157
+ | Subscription 1.8 | Subscription unknown status | COVERED | Subscription recovery 用同一 request ID 查询 |
158
+ | Subscription Change | Change unknown status | COVERED | Change recovery 用 `originSubscriptionRequest + subscriptionRequest` 查询 |
159
+ | Subscription 3.5 | Subscription webhook 签名失败 | COVERED | SDK 在业务 handler 前拒绝无效签名 |
160
+ | Data D1 | 时间字段格式 | COVERED | SDK timestamp auto-injection 使用共享 time utility |
161
+ | Data D2 | 写接口先落库 | COVERED | Store insert 在 order/refund/subscription/change API 前执行 |
162
+
163
+ ## 执行命令与结果
164
+
165
+ ```bash
166
+ uv run --python 3.13 --extra dev --extra e2e --with fastapi --with uvicorn ruff check src tests examples
167
+ uv run --python 3.13 --extra dev --extra e2e --with fastapi --with uvicorn pytest tests/unit -q
168
+ uv run --python 3.13 --extra dev --extra e2e --with fastapi --with uvicorn mypy src/waffo examples
169
+ WAFFO_E2E=1 uv run --python 3.13 --extra dev --extra e2e --with fastapi --with uvicorn pytest tests/e2e -v -s --tb=short
170
+ WAFFO_E2E=1 uv run --python 3.13 --extra dev --extra e2e --with fastapi --with uvicorn pytest tests/integration -v -s --tb=short
171
+ WAFFO_E2E=1 uv run --python 3.13 --extra dev --extra e2e --with fastapi --with uvicorn pytest tests/integration/test_fastapi_integration_acceptance.py::test_phase_c2_project_subscription_endpoints -v -s --tb=short
172
+ ./scripts/generate-types.sh validate
173
+ ```
174
+
175
+ 结果摘要:
176
+
177
+ ```text
178
+ ruff: all checks passed
179
+ unit: 50 passed in 2.61s
180
+ mypy: success, no issues in 41 source files
181
+ e2e: 8 passed in 68.91s
182
+ integration: 4 passed in 111.88s
183
+ pay-method rerun: 1 passed in 65.23s
184
+ subscription change rerun: 1 passed in 46.85s
185
+ sync validate: all SDKs appear to be in sync
186
+ ```
187
+
188
+ ## 本轮代码补充
189
+
190
+ | 区域 | 修正 |
191
+ |------|------|
192
+ | FastAPI 项目接口 | 新增 `POST /api/subscriptions/{subscriptionRequest}/change` 与 `GET /api/subscriptions/{originSubscriptionRequest}/changes/{subscriptionRequest}` |
193
+ | Service 层 | 新增 `change_subscription()` 与 `query_subscription_change()`,底层分别调用 SDK `subscription().change()` 和 `subscription().change_inquiry()` |
194
+ | Store 层 | 新增 `SubscriptionChangeRecord`,按 `originSubscriptionRequest + subscriptionRequest` 记录升降级请求、状态、订阅 ID、幂等 key |
195
+ | Webhook handler | `SUBSCRIPTION_CHANGE_NOTIFICATION` 改为独立 handler,不再复用 subscription status handler |
196
+ | Integration test | 订阅验收补充四类 webhook 等待与记录:首期支付、订阅状态、周期变更、升降级完成 |
197
+ | 报告 | 中文化,拆分 ID 列,订阅四通知单独列证据 |
198
+
199
+ ## 结论
200
+
201
+ **CONDITIONAL PASS**
202
+
203
+ Python SDK 已通过 FastAPI 项目式集成验收,覆盖支付、退款、订阅、订阅续期、订阅升降级、升降级查询和 4 类订阅相关通知。原先 `CC_VISA` 单一覆盖、`SUBSCRIPTION_CHANGE_NOTIFICATION` 未实际测试、订阅升降级 API 未测、ID 列混用的问题已补齐。
204
+
205
+ 仍保留条件通过的原因:
206
+
207
+ 1. `APPLEPAY` 需要真实 Apple Pay 设备/浏览器手工验证。
208
+ 2. `GOOGLEPAY` 需要真实 Google Pay 设备/浏览器手工验证。
@@ -0,0 +1 @@
1
+ """Runnable Waffo Python SDK integration examples."""