pay-py-kit 0.2.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 (48) hide show
  1. pay_py_kit-0.2.0/.github/workflows/ci.yml +56 -0
  2. pay_py_kit-0.2.0/.github/workflows/release.yml +90 -0
  3. pay_py_kit-0.2.0/.gitignore +13 -0
  4. pay_py_kit-0.2.0/CHANGELOG.md +37 -0
  5. pay_py_kit-0.2.0/PKG-INFO +257 -0
  6. pay_py_kit-0.2.0/README.md +246 -0
  7. pay_py_kit-0.2.0/pyproject.toml +46 -0
  8. pay_py_kit-0.2.0/src/pay_kit/__init__.py +3 -0
  9. pay_py_kit-0.2.0/src/pay_kit/exceptions.py +44 -0
  10. pay_py_kit-0.2.0/src/pay_kit/models.py +85 -0
  11. pay_py_kit-0.2.0/src/pay_kit/providers/__init__.py +5 -0
  12. pay_py_kit-0.2.0/src/pay_kit/providers/alipay/__init__.py +6 -0
  13. pay_py_kit-0.2.0/src/pay_kit/providers/alipay/config.py +13 -0
  14. pay_py_kit-0.2.0/src/pay_kit/providers/alipay/errors.py +7 -0
  15. pay_py_kit-0.2.0/src/pay_kit/providers/alipay/http_client.py +102 -0
  16. pay_py_kit-0.2.0/src/pay_kit/providers/alipay/provider.py +308 -0
  17. pay_py_kit-0.2.0/src/pay_kit/providers/alipay/sign.py +37 -0
  18. pay_py_kit-0.2.0/src/pay_kit/providers/base.py +62 -0
  19. pay_py_kit-0.2.0/src/pay_kit/providers/crypto.py +64 -0
  20. pay_py_kit-0.2.0/src/pay_kit/providers/errors.py +24 -0
  21. pay_py_kit-0.2.0/src/pay_kit/providers/http_client.py +49 -0
  22. pay_py_kit-0.2.0/src/pay_kit/providers/wechat/__init__.py +6 -0
  23. pay_py_kit-0.2.0/src/pay_kit/providers/wechat/config.py +17 -0
  24. pay_py_kit-0.2.0/src/pay_kit/providers/wechat/errors.py +7 -0
  25. pay_py_kit-0.2.0/src/pay_kit/providers/wechat/http_client.py +122 -0
  26. pay_py_kit-0.2.0/src/pay_kit/providers/wechat/provider.py +343 -0
  27. pay_py_kit-0.2.0/src/pay_kit/providers/wechat/sign.py +63 -0
  28. pay_py_kit-0.2.0/src/pay_kit/py.typed +0 -0
  29. pay_py_kit-0.2.0/src/pay_kit/types.py +47 -0
  30. pay_py_kit-0.2.0/tests/__init__.py +0 -0
  31. pay_py_kit-0.2.0/tests/conftest.py +59 -0
  32. pay_py_kit-0.2.0/tests/test_alipay_config.py +54 -0
  33. pay_py_kit-0.2.0/tests/test_alipay_errors.py +58 -0
  34. pay_py_kit-0.2.0/tests/test_alipay_http_client.py +169 -0
  35. pay_py_kit-0.2.0/tests/test_alipay_provider.py +468 -0
  36. pay_py_kit-0.2.0/tests/test_alipay_sign.py +48 -0
  37. pay_py_kit-0.2.0/tests/test_exceptions.py +82 -0
  38. pay_py_kit-0.2.0/tests/test_http_client.py +72 -0
  39. pay_py_kit-0.2.0/tests/test_init.py +90 -0
  40. pay_py_kit-0.2.0/tests/test_models.py +133 -0
  41. pay_py_kit-0.2.0/tests/test_providers.py +120 -0
  42. pay_py_kit-0.2.0/tests/test_types.py +74 -0
  43. pay_py_kit-0.2.0/tests/test_wechat_config.py +87 -0
  44. pay_py_kit-0.2.0/tests/test_wechat_errors.py +59 -0
  45. pay_py_kit-0.2.0/tests/test_wechat_http_client.py +198 -0
  46. pay_py_kit-0.2.0/tests/test_wechat_provider.py +549 -0
  47. pay_py_kit-0.2.0/tests/test_wechat_sign.py +138 -0
  48. pay_py_kit-0.2.0/uv.lock +803 -0
@@ -0,0 +1,56 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ run: uv python install ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: uv sync
27
+
28
+ - name: Lint (ruff)
29
+ run: |
30
+ uv run ruff check .
31
+ uv run ruff format --check .
32
+
33
+ - name: Type check (mypy)
34
+ run: uv run mypy src/
35
+
36
+ - name: Test with coverage
37
+ run: uv run pytest --cov=pay_kit --cov-report=term-missing --cov-fail-under=80
38
+
39
+ lint-only:
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - name: Install uv
45
+ uses: astral-sh/setup-uv@v4
46
+
47
+ - name: Set up Python
48
+ run: uv python install 3.11
49
+
50
+ - name: Install dependencies
51
+ run: uv sync
52
+
53
+ - name: Check ruff
54
+ run: |
55
+ uv run ruff check .
56
+ uv run ruff format --check .
@@ -0,0 +1,90 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v4
18
+
19
+ - name: Set up Python
20
+ run: uv python install 3.11
21
+
22
+ - name: Install dependencies
23
+ run: uv sync
24
+
25
+ - name: Run tests
26
+ run: uv run pytest --cov=pay_kit --cov-fail-under=80
27
+
28
+ - name: Build package
29
+ run: uv build
30
+
31
+ - name: Upload artifacts
32
+ uses: actions/upload-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ publish-pypi:
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment: pypi
41
+ steps:
42
+ - name: Set up Python
43
+ uses: actions/setup-python@v5
44
+ with:
45
+ python-version: "3.11"
46
+
47
+ - name: Install twine
48
+ run: pip install twine
49
+
50
+ - name: Download artifacts
51
+ uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+
56
+ - name: Publish to PyPI
57
+ env:
58
+ TWINE_USERNAME: __token__
59
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
60
+ run: twine upload dist/*
61
+
62
+ github-release:
63
+ needs: build
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+
68
+ - name: Download artifacts
69
+ uses: actions/download-artifact@v4
70
+ with:
71
+ name: dist
72
+ path: dist/
73
+
74
+ - name: Extract changelog
75
+ id: changelog
76
+ run: |
77
+ VERSION=${GITHUB_REF_NAME#v}
78
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
79
+ awk "/^## ${VERSION}/{flag=1; next} /^## /{flag=0} flag" CHANGELOG.md > /tmp/release_notes.md
80
+ if [ ! -s /tmp/release_notes.md ]; then
81
+ echo "Release ${VERSION}" > /tmp/release_notes.md
82
+ fi
83
+
84
+ - name: Create GitHub Release
85
+ uses: softprops/action-gh-release@v2
86
+ with:
87
+ name: v${{ steps.changelog.outputs.version }}
88
+ body_path: /tmp/release_notes.md
89
+ files: dist/*
90
+ generate_release_notes: false
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ site/
4
+ .claude/
5
+ docs/
6
+ .venv/
7
+ dist/
8
+ .idea/
9
+ /.coverage
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ CLAUDE.md
@@ -0,0 +1,37 @@
1
+ # 变更日志
2
+
3
+ ## 0.2.0
4
+
5
+ ### 新增
6
+
7
+ - **支付宝 V3 Provider** — 当面付扫码、电脑网站、手机网站、App 四种支付方式
8
+ - **微信支付扩展方式** — JSAPI(公众号/小程序)、H5(手机浏览器)、App 支付
9
+ - **转账能力** — 微信商家转账到零钱 + 支付宝单笔转账(WithdrawProvider)
10
+ - **余额查询** — 微信 + 支付宝余额查询(WalletProvider)
11
+ - **Yuan 金额类型** — 支付宝元单位支持,fen_to_yuan / yuan_to_fen 精确转换(Decimal)
12
+ - **共享密码学模块** — providers/crypto.py 提取 RSA 签名/验签共享函数
13
+ - **错误转换统一** — providers/errors.py 共享错误转换,provider_name 常量化
14
+
15
+ ### 改进
16
+
17
+ - WithdrawRequest 添加 recipient(收款人)和 description(转账说明)字段
18
+ - PaymentResult 添加 credential 字段(支付凭证:二维码 URL、前端参数等)
19
+ - RefundRequest 添加 total_amount 字段(原订单总额)
20
+ - WechatPay 提取 _build_payment_payload 消除三个 create 方法重复
21
+ - Alipay 同样提取 _build_payment_payload
22
+ - raise_for_error 参数化 error_cls 支持不同异常类型
23
+
24
+ ## 0.1.0
25
+
26
+ ### 新增
27
+
28
+ - 项目初始化
29
+ - 搭建工程脚手架:pyproject.toml、src/ 布局、工具链配置
30
+ - 添加 httpx、pydantic、cryptography 核心依赖
31
+ - 配置 ruff、mypy、pytest 开发工具
32
+ - 核心抽象层:PayProvider / WithdrawProvider / WalletProvider ABC
33
+ - 异常体系:PayKitError 及 7 个子类
34
+ - 数据模型:BaseProviderConfig + 9 个请求/响应模型
35
+ - 类型定义:Fen NewType + 状态枚举
36
+ - BaseHttpClient:传输层错误转换
37
+ - 微信支付 V3 Native Provider(PayProvider)
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: pay-py-kit
3
+ Version: 0.2.0
4
+ Summary: 统一支付基础设施 SDK,支持微信支付和支付宝
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: cryptography>=43.0
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: pydantic>=2.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # pay-kit
13
+
14
+ 统一支付基础设施 SDK,支持微信支付和支付宝商户。一个 SDK 即可处理支付、转账、余额查询。
15
+
16
+ ## 特性
17
+
18
+ - **双平台支持** — 微信支付 V3 + 支付宝 V3
19
+ - **多种支付方式** — 扫码、JSAPI、H5、App、电脑网站
20
+ - **统一接口** — PayProvider / WithdrawProvider / WalletProvider 三个 ABC
21
+ - **类型安全** — 全面类型注解,mypy strict 通过
22
+ - **纯异步** — 基于 httpx,async/await 原生支持
23
+ - **安全** — RSA-SHA256 签名/验签、AES-GCM 回调解密、SecretStr 敏感字段保护
24
+
25
+ ## 安装
26
+
27
+ ```bash
28
+ pip install pay-kit
29
+ ```
30
+
31
+ ## 快速开始
32
+
33
+ ### 微信支付 — Native 扫码支付
34
+
35
+ ```python
36
+ from pay_kit.providers.wechat import WechatPay, WechatPayConfig
37
+ from pay_kit.models import PaymentRequest
38
+ from pay_kit.types import Fen
39
+
40
+ config = WechatPayConfig(
41
+ appid="wx_your_appid",
42
+ mch_id="your_mch_id",
43
+ private_key=open("apiclient_key.pem").read(), # 商户 API 私钥
44
+ cert_serial_no="YOUR_CERT_SERIAL_NO", # 商户证书序列号
45
+ wechat_public_key=open("wechat_public_key.pem").read(), # 微信支付公钥
46
+ wechat_public_key_id="YOUR_PUB_KEY_ID",
47
+ apiv3_key="your_apiv3_key",
48
+ notify_url="https://your-domain.com/callback/wechat",
49
+ )
50
+
51
+ async with WechatPay(config) as pay:
52
+ # 创建支付订单
53
+ result = await pay.create_payment(
54
+ PaymentRequest(out_trade_no="ORDER_001", total_amount=Fen(100), subject="商品名称")
55
+ )
56
+ print(result.credential["code_url"]) # 用此 URL 生成二维码
57
+ ```
58
+
59
+ ### 支付宝 — 当面付扫码支付
60
+
61
+ ```python
62
+ from pay_kit.providers.alipay import Alipay, AlipayConfig
63
+ from pay_kit.models import PaymentRequest
64
+ from pay_kit.types import Fen
65
+
66
+ config = AlipayConfig(
67
+ app_id="your_app_id",
68
+ private_key=open("app_private_key.pem").read(), # 应用私钥
69
+ alipay_public_key=open("alipay_public_key.pem").read(), # 支付宝公钥
70
+ notify_url="https://your-domain.com/callback/alipay",
71
+ )
72
+
73
+ async with Alipay(config) as pay:
74
+ result = await pay.create_payment(
75
+ PaymentRequest(out_trade_no="ORDER_001", total_amount=Fen(100), subject="商品名称")
76
+ )
77
+ print(result.credential["qr_code"]) # 二维码 URL
78
+ ```
79
+
80
+ ## 支付方式
81
+
82
+ ### 微信支付
83
+
84
+ ```python
85
+ async with WechatPay(config) as pay:
86
+ # Native 扫码支付(通用接口)
87
+ result = await pay.create_payment(request)
88
+ # credential: {"code_url": "weixin://wxpay/..."}
89
+
90
+ # JSAPI 公众号/小程序支付
91
+ result = await pay.create_jsapi_payment(request, openid="user_openid")
92
+ # credential: {"appId", "timeStamp", "nonceStr", "package", "signType", "paySign"}
93
+
94
+ # H5 手机浏览器支付
95
+ result = await pay.create_h5_payment(request, payer_client_ip="1.2.3.4")
96
+ # credential: {"h5_url": "https://wx.tenpay.com/..."}
97
+
98
+ # App 支付
99
+ result = await pay.create_app_payment(request)
100
+ # credential: {"appId", "partnerId", "prepayId", "package", "nonceStr", "timeStamp", "sign"}
101
+ ```
102
+
103
+ ### 支付宝
104
+
105
+ ```python
106
+ async with Alipay(config) as pay:
107
+ # 当面付扫码支付(通用接口)
108
+ result = await pay.create_payment(request)
109
+ # credential: {"qr_code": "https://qr.alipay.com/..."}
110
+
111
+ # 电脑网站支付
112
+ result = await pay.create_page_payment(request, return_url="https://your-site.com/return")
113
+ # credential: {"pay_url": "https://openapi.alipay.com/..."}
114
+
115
+ # 手机网站支付
116
+ result = await pay.create_wap_payment(request, return_url="https://your-site.com/return")
117
+ # credential: {"pay_url": "https://openapi.alipay.com/..."}
118
+
119
+ # App 支付
120
+ result = await pay.create_app_payment(request)
121
+ # credential: {"order_str": "alipay_sdk=..."}
122
+ ```
123
+
124
+ ## 通用操作
125
+
126
+ 查询、关单、退款等操作两个平台用法一致:
127
+
128
+ ```python
129
+ # 查询订单
130
+ result = await pay.query_payment("ORDER_001")
131
+ print(result.status) # PaymentStatus.PAID
132
+
133
+ # 关闭订单
134
+ await pay.close_payment("ORDER_001")
135
+
136
+ # 发起退款
137
+ from pay_kit.models import RefundRequest
138
+ refund = await pay.refund(
139
+ RefundRequest(
140
+ out_trade_no="ORDER_001",
141
+ out_refund_no="REFUND_001",
142
+ total_amount=Fen(100),
143
+ refund_amount=Fen(50),
144
+ )
145
+ )
146
+
147
+ # 查询退款
148
+ refund = await pay.query_refund("REFUND_001")
149
+ ```
150
+
151
+ ## 转账
152
+
153
+ ```python
154
+ from pay_kit.models import WithdrawRequest
155
+
156
+ async with WechatPay(config) as pay:
157
+ # 商家转账到零钱
158
+ result = await pay.create_withdrawal(
159
+ WithdrawRequest(
160
+ out_withdrawal_no="TRANSFER_001",
161
+ amount=Fen(1000),
162
+ recipient="user_openid", # 微信: openid / 支付宝: user_id
163
+ description="佣金提现",
164
+ )
165
+ )
166
+ print(result.status) # WithdrawStatus.PENDING
167
+
168
+ # 查询转账状态
169
+ result = await pay.query_withdrawal("TRANSFER_001")
170
+ ```
171
+
172
+ ## 余额查询
173
+
174
+ ```python
175
+ async with WechatPay(config) as pay:
176
+ balance = await pay.query_balance()
177
+ print(f"可用余额: {balance.available} 分")
178
+ print(f"待结算: {balance.pending} 分")
179
+ ```
180
+
181
+ ## 回调验签
182
+
183
+ ```python
184
+ # FastAPI 示例
185
+ from fastapi import Request
186
+
187
+ @app.post("/callback/wechat")
188
+ async def wechat_callback(request: Request):
189
+ headers = dict(request.headers)
190
+ body = await request.body()
191
+
192
+ async with WechatPay(config) as pay:
193
+ event = await pay.verify_callback(headers, body)
194
+
195
+ print(event.event_type) # "TRANSACTION.SUCCESS"
196
+ print(event.out_trade_no) # "ORDER_001"
197
+ print(event.trade_no) # 微信交易号
198
+ print(event.raw) # 完整回调数据
199
+
200
+ return {"code": "SUCCESS"}
201
+ ```
202
+
203
+ ## 异常处理
204
+
205
+ ```python
206
+ from pay_kit.exceptions import (
207
+ PayKitError, # 所有异常基类
208
+ ConfigError, # 配置错误
209
+ NetworkError, # 网络超时/连接失败
210
+ SignatureError, # 签名验证失败
211
+ PaymentError, # 支付业务错误
212
+ WithdrawError, # 转账业务错误
213
+ WalletError, # 余额查询错误
214
+ )
215
+
216
+ try:
217
+ result = await pay.create_payment(request)
218
+ except NetworkError:
219
+ # 网络问题,可重试
220
+ pass
221
+ except SignatureError:
222
+ # 签名验证失败,响应不可信
223
+ pass
224
+ except PaymentError as e:
225
+ # 业务错误
226
+ print(e.error_code) # 支付商错误码,如 "ORDER_CLOSED"
227
+ print(e.provider_name) # "wechat" 或 "alipay"
228
+ ```
229
+
230
+ ## 金额类型
231
+
232
+ pay-kit 使用 `Fen`(分)作为金额单位,避免浮点精度问题:
233
+
234
+ ```python
235
+ from pay_kit.types import Fen, Yuan, fen_to_yuan, yuan_to_fen
236
+
237
+ amount = Fen(100) # 100 分 = 1 元
238
+ yuan = fen_to_yuan(Fen(100)) # Yuan("1.00")
239
+ fen = yuan_to_fen(Yuan("1.00")) # Fen(100)
240
+ ```
241
+
242
+ ## 开发
243
+
244
+ ```bash
245
+ git clone https://github.com/Xinzz995/pay-kit.git
246
+ cd pay-kit
247
+ uv sync
248
+
249
+ uv run pytest # 运行测试
250
+ uv run pytest --cov=pay_kit # 覆盖率
251
+ uv run ruff check . # Lint
252
+ uv run mypy src/ # 类型检查
253
+ ```
254
+
255
+ ## 许可证
256
+
257
+ MIT
@@ -0,0 +1,246 @@
1
+ # pay-kit
2
+
3
+ 统一支付基础设施 SDK,支持微信支付和支付宝商户。一个 SDK 即可处理支付、转账、余额查询。
4
+
5
+ ## 特性
6
+
7
+ - **双平台支持** — 微信支付 V3 + 支付宝 V3
8
+ - **多种支付方式** — 扫码、JSAPI、H5、App、电脑网站
9
+ - **统一接口** — PayProvider / WithdrawProvider / WalletProvider 三个 ABC
10
+ - **类型安全** — 全面类型注解,mypy strict 通过
11
+ - **纯异步** — 基于 httpx,async/await 原生支持
12
+ - **安全** — RSA-SHA256 签名/验签、AES-GCM 回调解密、SecretStr 敏感字段保护
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install pay-kit
18
+ ```
19
+
20
+ ## 快速开始
21
+
22
+ ### 微信支付 — Native 扫码支付
23
+
24
+ ```python
25
+ from pay_kit.providers.wechat import WechatPay, WechatPayConfig
26
+ from pay_kit.models import PaymentRequest
27
+ from pay_kit.types import Fen
28
+
29
+ config = WechatPayConfig(
30
+ appid="wx_your_appid",
31
+ mch_id="your_mch_id",
32
+ private_key=open("apiclient_key.pem").read(), # 商户 API 私钥
33
+ cert_serial_no="YOUR_CERT_SERIAL_NO", # 商户证书序列号
34
+ wechat_public_key=open("wechat_public_key.pem").read(), # 微信支付公钥
35
+ wechat_public_key_id="YOUR_PUB_KEY_ID",
36
+ apiv3_key="your_apiv3_key",
37
+ notify_url="https://your-domain.com/callback/wechat",
38
+ )
39
+
40
+ async with WechatPay(config) as pay:
41
+ # 创建支付订单
42
+ result = await pay.create_payment(
43
+ PaymentRequest(out_trade_no="ORDER_001", total_amount=Fen(100), subject="商品名称")
44
+ )
45
+ print(result.credential["code_url"]) # 用此 URL 生成二维码
46
+ ```
47
+
48
+ ### 支付宝 — 当面付扫码支付
49
+
50
+ ```python
51
+ from pay_kit.providers.alipay import Alipay, AlipayConfig
52
+ from pay_kit.models import PaymentRequest
53
+ from pay_kit.types import Fen
54
+
55
+ config = AlipayConfig(
56
+ app_id="your_app_id",
57
+ private_key=open("app_private_key.pem").read(), # 应用私钥
58
+ alipay_public_key=open("alipay_public_key.pem").read(), # 支付宝公钥
59
+ notify_url="https://your-domain.com/callback/alipay",
60
+ )
61
+
62
+ async with Alipay(config) as pay:
63
+ result = await pay.create_payment(
64
+ PaymentRequest(out_trade_no="ORDER_001", total_amount=Fen(100), subject="商品名称")
65
+ )
66
+ print(result.credential["qr_code"]) # 二维码 URL
67
+ ```
68
+
69
+ ## 支付方式
70
+
71
+ ### 微信支付
72
+
73
+ ```python
74
+ async with WechatPay(config) as pay:
75
+ # Native 扫码支付(通用接口)
76
+ result = await pay.create_payment(request)
77
+ # credential: {"code_url": "weixin://wxpay/..."}
78
+
79
+ # JSAPI 公众号/小程序支付
80
+ result = await pay.create_jsapi_payment(request, openid="user_openid")
81
+ # credential: {"appId", "timeStamp", "nonceStr", "package", "signType", "paySign"}
82
+
83
+ # H5 手机浏览器支付
84
+ result = await pay.create_h5_payment(request, payer_client_ip="1.2.3.4")
85
+ # credential: {"h5_url": "https://wx.tenpay.com/..."}
86
+
87
+ # App 支付
88
+ result = await pay.create_app_payment(request)
89
+ # credential: {"appId", "partnerId", "prepayId", "package", "nonceStr", "timeStamp", "sign"}
90
+ ```
91
+
92
+ ### 支付宝
93
+
94
+ ```python
95
+ async with Alipay(config) as pay:
96
+ # 当面付扫码支付(通用接口)
97
+ result = await pay.create_payment(request)
98
+ # credential: {"qr_code": "https://qr.alipay.com/..."}
99
+
100
+ # 电脑网站支付
101
+ result = await pay.create_page_payment(request, return_url="https://your-site.com/return")
102
+ # credential: {"pay_url": "https://openapi.alipay.com/..."}
103
+
104
+ # 手机网站支付
105
+ result = await pay.create_wap_payment(request, return_url="https://your-site.com/return")
106
+ # credential: {"pay_url": "https://openapi.alipay.com/..."}
107
+
108
+ # App 支付
109
+ result = await pay.create_app_payment(request)
110
+ # credential: {"order_str": "alipay_sdk=..."}
111
+ ```
112
+
113
+ ## 通用操作
114
+
115
+ 查询、关单、退款等操作两个平台用法一致:
116
+
117
+ ```python
118
+ # 查询订单
119
+ result = await pay.query_payment("ORDER_001")
120
+ print(result.status) # PaymentStatus.PAID
121
+
122
+ # 关闭订单
123
+ await pay.close_payment("ORDER_001")
124
+
125
+ # 发起退款
126
+ from pay_kit.models import RefundRequest
127
+ refund = await pay.refund(
128
+ RefundRequest(
129
+ out_trade_no="ORDER_001",
130
+ out_refund_no="REFUND_001",
131
+ total_amount=Fen(100),
132
+ refund_amount=Fen(50),
133
+ )
134
+ )
135
+
136
+ # 查询退款
137
+ refund = await pay.query_refund("REFUND_001")
138
+ ```
139
+
140
+ ## 转账
141
+
142
+ ```python
143
+ from pay_kit.models import WithdrawRequest
144
+
145
+ async with WechatPay(config) as pay:
146
+ # 商家转账到零钱
147
+ result = await pay.create_withdrawal(
148
+ WithdrawRequest(
149
+ out_withdrawal_no="TRANSFER_001",
150
+ amount=Fen(1000),
151
+ recipient="user_openid", # 微信: openid / 支付宝: user_id
152
+ description="佣金提现",
153
+ )
154
+ )
155
+ print(result.status) # WithdrawStatus.PENDING
156
+
157
+ # 查询转账状态
158
+ result = await pay.query_withdrawal("TRANSFER_001")
159
+ ```
160
+
161
+ ## 余额查询
162
+
163
+ ```python
164
+ async with WechatPay(config) as pay:
165
+ balance = await pay.query_balance()
166
+ print(f"可用余额: {balance.available} 分")
167
+ print(f"待结算: {balance.pending} 分")
168
+ ```
169
+
170
+ ## 回调验签
171
+
172
+ ```python
173
+ # FastAPI 示例
174
+ from fastapi import Request
175
+
176
+ @app.post("/callback/wechat")
177
+ async def wechat_callback(request: Request):
178
+ headers = dict(request.headers)
179
+ body = await request.body()
180
+
181
+ async with WechatPay(config) as pay:
182
+ event = await pay.verify_callback(headers, body)
183
+
184
+ print(event.event_type) # "TRANSACTION.SUCCESS"
185
+ print(event.out_trade_no) # "ORDER_001"
186
+ print(event.trade_no) # 微信交易号
187
+ print(event.raw) # 完整回调数据
188
+
189
+ return {"code": "SUCCESS"}
190
+ ```
191
+
192
+ ## 异常处理
193
+
194
+ ```python
195
+ from pay_kit.exceptions import (
196
+ PayKitError, # 所有异常基类
197
+ ConfigError, # 配置错误
198
+ NetworkError, # 网络超时/连接失败
199
+ SignatureError, # 签名验证失败
200
+ PaymentError, # 支付业务错误
201
+ WithdrawError, # 转账业务错误
202
+ WalletError, # 余额查询错误
203
+ )
204
+
205
+ try:
206
+ result = await pay.create_payment(request)
207
+ except NetworkError:
208
+ # 网络问题,可重试
209
+ pass
210
+ except SignatureError:
211
+ # 签名验证失败,响应不可信
212
+ pass
213
+ except PaymentError as e:
214
+ # 业务错误
215
+ print(e.error_code) # 支付商错误码,如 "ORDER_CLOSED"
216
+ print(e.provider_name) # "wechat" 或 "alipay"
217
+ ```
218
+
219
+ ## 金额类型
220
+
221
+ pay-kit 使用 `Fen`(分)作为金额单位,避免浮点精度问题:
222
+
223
+ ```python
224
+ from pay_kit.types import Fen, Yuan, fen_to_yuan, yuan_to_fen
225
+
226
+ amount = Fen(100) # 100 分 = 1 元
227
+ yuan = fen_to_yuan(Fen(100)) # Yuan("1.00")
228
+ fen = yuan_to_fen(Yuan("1.00")) # Fen(100)
229
+ ```
230
+
231
+ ## 开发
232
+
233
+ ```bash
234
+ git clone https://github.com/Xinzz995/pay-kit.git
235
+ cd pay-kit
236
+ uv sync
237
+
238
+ uv run pytest # 运行测试
239
+ uv run pytest --cov=pay_kit # 覆盖率
240
+ uv run ruff check . # Lint
241
+ uv run mypy src/ # 类型检查
242
+ ```
243
+
244
+ ## 许可证
245
+
246
+ MIT