bebebus-merchant-openapi-sdk 1.0.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.
- bebebus_merchant_openapi_sdk-1.0.0/LICENSE +21 -0
- bebebus_merchant_openapi_sdk-1.0.0/PKG-INFO +155 -0
- bebebus_merchant_openapi_sdk-1.0.0/README.md +136 -0
- bebebus_merchant_openapi_sdk-1.0.0/bebebus_merchant_openapi_sdk.egg-info/PKG-INFO +155 -0
- bebebus_merchant_openapi_sdk-1.0.0/bebebus_merchant_openapi_sdk.egg-info/SOURCES.txt +15 -0
- bebebus_merchant_openapi_sdk-1.0.0/bebebus_merchant_openapi_sdk.egg-info/dependency_links.txt +1 -0
- bebebus_merchant_openapi_sdk-1.0.0/bebebus_merchant_openapi_sdk.egg-info/requires.txt +2 -0
- bebebus_merchant_openapi_sdk-1.0.0/bebebus_merchant_openapi_sdk.egg-info/top_level.txt +1 -0
- bebebus_merchant_openapi_sdk-1.0.0/openapi_sdk/__init__.py +31 -0
- bebebus_merchant_openapi_sdk-1.0.0/openapi_sdk/client.py +374 -0
- bebebus_merchant_openapi_sdk-1.0.0/openapi_sdk/config.py +71 -0
- bebebus_merchant_openapi_sdk-1.0.0/openapi_sdk/exceptions.py +46 -0
- bebebus_merchant_openapi_sdk-1.0.0/openapi_sdk/signer.py +95 -0
- bebebus_merchant_openapi_sdk-1.0.0/pyproject.toml +33 -0
- bebebus_merchant_openapi_sdk-1.0.0/setup.cfg +4 -0
- bebebus_merchant_openapi_sdk-1.0.0/tests/test_client.py +284 -0
- bebebus_merchant_openapi_sdk-1.0.0/tests/test_signer.py +176 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bebebus
|
|
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,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bebebus-merchant-openapi-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 商户支付 OpenAPI Python SDK(零第三方依赖)
|
|
5
|
+
Author: bebebus
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bebebus/SDK/tree/main/python
|
|
8
|
+
Project-URL: Repository, https://github.com/bebebus/SDK
|
|
9
|
+
Project-URL: Issues, https://github.com/bebebus/SDK/issues
|
|
10
|
+
Keywords: payment,openapi,merchant,sdk
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# 商户支付 OpenAPI — Python SDK
|
|
21
|
+
|
|
22
|
+
**零第三方依赖**的 Python 3 SDK:HTTP 用 `urllib.request`,签名用 `hmac`/`hashlib`,测试用标准库 `unittest`。无需 `pip install` 任何运行时依赖。
|
|
23
|
+
|
|
24
|
+
签名算法与服务端签名实现逐字节一致,单测对 [`../test-vectors.json`](../test-vectors.json) 全量复现 `base` 与 `sign`。
|
|
25
|
+
|
|
26
|
+
## 引入(无需安装依赖)
|
|
27
|
+
|
|
28
|
+
把 `openapi_sdk/` 目录放进你的项目(或把本目录加入 `sys.path` / `PYTHONPATH`)即可:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import sys
|
|
32
|
+
sys.path.insert(0, "/path/to/python")
|
|
33
|
+
|
|
34
|
+
from openapi_sdk import Client, Config, Environment
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
可选地用标准打包安装(仍零依赖):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd python
|
|
41
|
+
pip install . # 或 python3 -m build;本身不拉任何第三方运行时依赖
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 快速开始
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from openapi_sdk import Client, Config, Environment, ApiError, TransportError
|
|
48
|
+
|
|
49
|
+
config = Config(
|
|
50
|
+
merchant_no="M00000001",
|
|
51
|
+
api_key="ak_xxx",
|
|
52
|
+
api_secret_pay="sk_pay_xxx", # pay 类接口 + 代收/退款回调
|
|
53
|
+
api_secret_payout="sk_payout_xxx", # payout 类接口 + 代付回调
|
|
54
|
+
environment=Environment.SANDBOX, # 或 Environment.PRODUCTION(须显式传 base_url)
|
|
55
|
+
)
|
|
56
|
+
client = Client(config)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
# 金额是最小单位整数:10000 = 1 元
|
|
60
|
+
order = client.pay_create(
|
|
61
|
+
out_order_no="ORD1", amount=10000, currency="PHP",
|
|
62
|
+
pay_method="gcash", country="PH",
|
|
63
|
+
notify_url="https://m.example.com/api/notify/pay",
|
|
64
|
+
)
|
|
65
|
+
print(order["pay_url"])
|
|
66
|
+
except ApiError as e: # 业务失败 code != 0
|
|
67
|
+
print(e.code, e.message, e.data)
|
|
68
|
+
except TransportError as e: # HTTP / 网络 / 超时
|
|
69
|
+
print(e, e.status_code)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 双环境与自定义基址
|
|
73
|
+
|
|
74
|
+
| 环境 | 基址 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| `Environment.PRODUCTION` | 无内置基址,**必须显式传 `base_url`** |
|
|
77
|
+
| `Environment.SANDBOX` | `http://127.0.0.1:3090/api/open/v1` |
|
|
78
|
+
|
|
79
|
+
正式真实地址按上级代理专有域名派生(`https://api.<agent_domain>/api/open/v1`),用 `base_url=` 显式传入。选 `PRODUCTION` 又不传 `base_url` 会抛 `ValueError`(提示 `baseUrl is required`):
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
config = Config(
|
|
83
|
+
merchant_no="M00000001", api_key="ak_xxx",
|
|
84
|
+
api_secret_pay="...", api_secret_payout="...",
|
|
85
|
+
base_url="https://api.agent.example.com/api/open/v1", # 正式环境必传
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 全部 11 个端点
|
|
90
|
+
|
|
91
|
+
代收(密钥 `api_secret_pay`,自动选用):
|
|
92
|
+
|
|
93
|
+
| 方法 | 端点 |
|
|
94
|
+
|------|------|
|
|
95
|
+
| `pay_create(...)` | `/merchant/pay/create` |
|
|
96
|
+
| `pay_query(order_no=, out_order_no=)` | `/merchant/pay/query` |
|
|
97
|
+
| `pay_methods_query(country=)` | `/merchant/pay-methods/query` |
|
|
98
|
+
| `balance_query(currency=)` | `/merchant/balance/query` |
|
|
99
|
+
| `pay_test_complete(result=, ...)` | `/merchant/pay/test/complete`(仅测试密钥) |
|
|
100
|
+
|
|
101
|
+
代付(密钥 `api_secret_payout`,自动选用):
|
|
102
|
+
|
|
103
|
+
| 方法 | 端点 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `payout_create(...)` | `/merchant/payout/create` |
|
|
106
|
+
| `payout_query(payout_no=, out_payout_no=)` | `/merchant/payout/query` |
|
|
107
|
+
| `payout_banks_query(pay_method=, country=, currency=)` | `/merchant/payout/banks/query` |
|
|
108
|
+
| `payout_proof_query(payout_no=, out_payout_no=)` | `/merchant/payout/proof/query` |
|
|
109
|
+
| `payout_receipt_query(..., inline=)` | `/merchant/payout/receipt/query` |
|
|
110
|
+
| `payout_test_complete(result=, ...)` | `/merchant/payout/test/complete`(仅测试密钥) |
|
|
111
|
+
|
|
112
|
+
约定:
|
|
113
|
+
|
|
114
|
+
- 每请求自动注入 `merchant_no`/`api_key`/`timestamp`(Unix 秒)/唯一 `nonce` 与 `sign`。
|
|
115
|
+
- 值为 `None` 的参数不放入请求体、也不参与签名。
|
|
116
|
+
- `payout_receipt_query` 的 `inline` 以整数 **1/0** 发送(`True`→1 内联 base64 图片;`False`→0 返回带 token 的 URL)。
|
|
117
|
+
- 金额是最小单位整数(`10000 = 1 元`),用 `int` 类型传入。
|
|
118
|
+
- 成功返回 `data`(dict);`code != 0` 抛 `ApiError`(携带 `code`/`message`/`data`);HTTP/网络错误抛 `TransportError`。
|
|
119
|
+
- 需要原始信封时用 `client.call_raw(path, body, secret)`(不因 `code != 0` 抛异常)。
|
|
120
|
+
|
|
121
|
+
## 签名工具(可单独使用)
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from openapi_sdk import build_sign_base, sign, verify_callback
|
|
125
|
+
|
|
126
|
+
base = build_sign_base(payload, secret) # 逐字节可断言的签名 base
|
|
127
|
+
sig = sign(payload, secret) # HMAC-SHA256 -> hex 小写
|
|
128
|
+
ok = verify_callback(callback, secret) # 时序安全(hmac.compare_digest),字段无关
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 回调验签 + 处理
|
|
132
|
+
|
|
133
|
+
见 [`examples/callback_verify.py`](examples/callback_verify.py):解析原始 body → `verify_callback`(时序安全)→ 按 `status` 幂等处理(success/failed)→ 应答 **HTTP 200 + 纯文本 `success`**。代收回调用 `api_secret_pay`、代付回调用 `api_secret_payout`,示例各演示一次。验签失败不回成功,让平台重试;处理务必幂等(同一订单可能多次回调)。
|
|
134
|
+
|
|
135
|
+
## 示例
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
cd python
|
|
139
|
+
python3 examples/pay_create.py
|
|
140
|
+
python3 examples/payout_create.py
|
|
141
|
+
python3 examples/callback_verify.py # 自演示:造签名回调 -> 验签 -> 应答 -> 篡改反例
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 跑测试
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cd python
|
|
148
|
+
python3 -m unittest discover -s tests
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
测试包含:
|
|
152
|
+
|
|
153
|
+
- 读取 `../test-vectors.json`,对每个向量断言 `build_sign_base == base` 且 `sign == sign`;
|
|
154
|
+
- 回调验签正例 + 篡改一字节反例(含错误密钥、缺 sign);
|
|
155
|
+
- 客户端请求构建(通用字段注入、`None` 过滤、密钥选择、`inline` 整数化、信封解析与异常分类)。
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# 商户支付 OpenAPI — Python SDK
|
|
2
|
+
|
|
3
|
+
**零第三方依赖**的 Python 3 SDK:HTTP 用 `urllib.request`,签名用 `hmac`/`hashlib`,测试用标准库 `unittest`。无需 `pip install` 任何运行时依赖。
|
|
4
|
+
|
|
5
|
+
签名算法与服务端签名实现逐字节一致,单测对 [`../test-vectors.json`](../test-vectors.json) 全量复现 `base` 与 `sign`。
|
|
6
|
+
|
|
7
|
+
## 引入(无需安装依赖)
|
|
8
|
+
|
|
9
|
+
把 `openapi_sdk/` 目录放进你的项目(或把本目录加入 `sys.path` / `PYTHONPATH`)即可:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import sys
|
|
13
|
+
sys.path.insert(0, "/path/to/python")
|
|
14
|
+
|
|
15
|
+
from openapi_sdk import Client, Config, Environment
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
可选地用标准打包安装(仍零依赖):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd python
|
|
22
|
+
pip install . # 或 python3 -m build;本身不拉任何第三方运行时依赖
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 快速开始
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from openapi_sdk import Client, Config, Environment, ApiError, TransportError
|
|
29
|
+
|
|
30
|
+
config = Config(
|
|
31
|
+
merchant_no="M00000001",
|
|
32
|
+
api_key="ak_xxx",
|
|
33
|
+
api_secret_pay="sk_pay_xxx", # pay 类接口 + 代收/退款回调
|
|
34
|
+
api_secret_payout="sk_payout_xxx", # payout 类接口 + 代付回调
|
|
35
|
+
environment=Environment.SANDBOX, # 或 Environment.PRODUCTION(须显式传 base_url)
|
|
36
|
+
)
|
|
37
|
+
client = Client(config)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# 金额是最小单位整数:10000 = 1 元
|
|
41
|
+
order = client.pay_create(
|
|
42
|
+
out_order_no="ORD1", amount=10000, currency="PHP",
|
|
43
|
+
pay_method="gcash", country="PH",
|
|
44
|
+
notify_url="https://m.example.com/api/notify/pay",
|
|
45
|
+
)
|
|
46
|
+
print(order["pay_url"])
|
|
47
|
+
except ApiError as e: # 业务失败 code != 0
|
|
48
|
+
print(e.code, e.message, e.data)
|
|
49
|
+
except TransportError as e: # HTTP / 网络 / 超时
|
|
50
|
+
print(e, e.status_code)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 双环境与自定义基址
|
|
54
|
+
|
|
55
|
+
| 环境 | 基址 |
|
|
56
|
+
|------|------|
|
|
57
|
+
| `Environment.PRODUCTION` | 无内置基址,**必须显式传 `base_url`** |
|
|
58
|
+
| `Environment.SANDBOX` | `http://127.0.0.1:3090/api/open/v1` |
|
|
59
|
+
|
|
60
|
+
正式真实地址按上级代理专有域名派生(`https://api.<agent_domain>/api/open/v1`),用 `base_url=` 显式传入。选 `PRODUCTION` 又不传 `base_url` 会抛 `ValueError`(提示 `baseUrl is required`):
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
config = Config(
|
|
64
|
+
merchant_no="M00000001", api_key="ak_xxx",
|
|
65
|
+
api_secret_pay="...", api_secret_payout="...",
|
|
66
|
+
base_url="https://api.agent.example.com/api/open/v1", # 正式环境必传
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 全部 11 个端点
|
|
71
|
+
|
|
72
|
+
代收(密钥 `api_secret_pay`,自动选用):
|
|
73
|
+
|
|
74
|
+
| 方法 | 端点 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| `pay_create(...)` | `/merchant/pay/create` |
|
|
77
|
+
| `pay_query(order_no=, out_order_no=)` | `/merchant/pay/query` |
|
|
78
|
+
| `pay_methods_query(country=)` | `/merchant/pay-methods/query` |
|
|
79
|
+
| `balance_query(currency=)` | `/merchant/balance/query` |
|
|
80
|
+
| `pay_test_complete(result=, ...)` | `/merchant/pay/test/complete`(仅测试密钥) |
|
|
81
|
+
|
|
82
|
+
代付(密钥 `api_secret_payout`,自动选用):
|
|
83
|
+
|
|
84
|
+
| 方法 | 端点 |
|
|
85
|
+
|------|------|
|
|
86
|
+
| `payout_create(...)` | `/merchant/payout/create` |
|
|
87
|
+
| `payout_query(payout_no=, out_payout_no=)` | `/merchant/payout/query` |
|
|
88
|
+
| `payout_banks_query(pay_method=, country=, currency=)` | `/merchant/payout/banks/query` |
|
|
89
|
+
| `payout_proof_query(payout_no=, out_payout_no=)` | `/merchant/payout/proof/query` |
|
|
90
|
+
| `payout_receipt_query(..., inline=)` | `/merchant/payout/receipt/query` |
|
|
91
|
+
| `payout_test_complete(result=, ...)` | `/merchant/payout/test/complete`(仅测试密钥) |
|
|
92
|
+
|
|
93
|
+
约定:
|
|
94
|
+
|
|
95
|
+
- 每请求自动注入 `merchant_no`/`api_key`/`timestamp`(Unix 秒)/唯一 `nonce` 与 `sign`。
|
|
96
|
+
- 值为 `None` 的参数不放入请求体、也不参与签名。
|
|
97
|
+
- `payout_receipt_query` 的 `inline` 以整数 **1/0** 发送(`True`→1 内联 base64 图片;`False`→0 返回带 token 的 URL)。
|
|
98
|
+
- 金额是最小单位整数(`10000 = 1 元`),用 `int` 类型传入。
|
|
99
|
+
- 成功返回 `data`(dict);`code != 0` 抛 `ApiError`(携带 `code`/`message`/`data`);HTTP/网络错误抛 `TransportError`。
|
|
100
|
+
- 需要原始信封时用 `client.call_raw(path, body, secret)`(不因 `code != 0` 抛异常)。
|
|
101
|
+
|
|
102
|
+
## 签名工具(可单独使用)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from openapi_sdk import build_sign_base, sign, verify_callback
|
|
106
|
+
|
|
107
|
+
base = build_sign_base(payload, secret) # 逐字节可断言的签名 base
|
|
108
|
+
sig = sign(payload, secret) # HMAC-SHA256 -> hex 小写
|
|
109
|
+
ok = verify_callback(callback, secret) # 时序安全(hmac.compare_digest),字段无关
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 回调验签 + 处理
|
|
113
|
+
|
|
114
|
+
见 [`examples/callback_verify.py`](examples/callback_verify.py):解析原始 body → `verify_callback`(时序安全)→ 按 `status` 幂等处理(success/failed)→ 应答 **HTTP 200 + 纯文本 `success`**。代收回调用 `api_secret_pay`、代付回调用 `api_secret_payout`,示例各演示一次。验签失败不回成功,让平台重试;处理务必幂等(同一订单可能多次回调)。
|
|
115
|
+
|
|
116
|
+
## 示例
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd python
|
|
120
|
+
python3 examples/pay_create.py
|
|
121
|
+
python3 examples/payout_create.py
|
|
122
|
+
python3 examples/callback_verify.py # 自演示:造签名回调 -> 验签 -> 应答 -> 篡改反例
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 跑测试
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cd python
|
|
129
|
+
python3 -m unittest discover -s tests
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
测试包含:
|
|
133
|
+
|
|
134
|
+
- 读取 `../test-vectors.json`,对每个向量断言 `build_sign_base == base` 且 `sign == sign`;
|
|
135
|
+
- 回调验签正例 + 篡改一字节反例(含错误密钥、缺 sign);
|
|
136
|
+
- 客户端请求构建(通用字段注入、`None` 过滤、密钥选择、`inline` 整数化、信封解析与异常分类)。
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bebebus-merchant-openapi-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 商户支付 OpenAPI Python SDK(零第三方依赖)
|
|
5
|
+
Author: bebebus
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bebebus/SDK/tree/main/python
|
|
8
|
+
Project-URL: Repository, https://github.com/bebebus/SDK
|
|
9
|
+
Project-URL: Issues, https://github.com/bebebus/SDK/issues
|
|
10
|
+
Keywords: payment,openapi,merchant,sdk
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# 商户支付 OpenAPI — Python SDK
|
|
21
|
+
|
|
22
|
+
**零第三方依赖**的 Python 3 SDK:HTTP 用 `urllib.request`,签名用 `hmac`/`hashlib`,测试用标准库 `unittest`。无需 `pip install` 任何运行时依赖。
|
|
23
|
+
|
|
24
|
+
签名算法与服务端签名实现逐字节一致,单测对 [`../test-vectors.json`](../test-vectors.json) 全量复现 `base` 与 `sign`。
|
|
25
|
+
|
|
26
|
+
## 引入(无需安装依赖)
|
|
27
|
+
|
|
28
|
+
把 `openapi_sdk/` 目录放进你的项目(或把本目录加入 `sys.path` / `PYTHONPATH`)即可:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import sys
|
|
32
|
+
sys.path.insert(0, "/path/to/python")
|
|
33
|
+
|
|
34
|
+
from openapi_sdk import Client, Config, Environment
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
可选地用标准打包安装(仍零依赖):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd python
|
|
41
|
+
pip install . # 或 python3 -m build;本身不拉任何第三方运行时依赖
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 快速开始
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from openapi_sdk import Client, Config, Environment, ApiError, TransportError
|
|
48
|
+
|
|
49
|
+
config = Config(
|
|
50
|
+
merchant_no="M00000001",
|
|
51
|
+
api_key="ak_xxx",
|
|
52
|
+
api_secret_pay="sk_pay_xxx", # pay 类接口 + 代收/退款回调
|
|
53
|
+
api_secret_payout="sk_payout_xxx", # payout 类接口 + 代付回调
|
|
54
|
+
environment=Environment.SANDBOX, # 或 Environment.PRODUCTION(须显式传 base_url)
|
|
55
|
+
)
|
|
56
|
+
client = Client(config)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
# 金额是最小单位整数:10000 = 1 元
|
|
60
|
+
order = client.pay_create(
|
|
61
|
+
out_order_no="ORD1", amount=10000, currency="PHP",
|
|
62
|
+
pay_method="gcash", country="PH",
|
|
63
|
+
notify_url="https://m.example.com/api/notify/pay",
|
|
64
|
+
)
|
|
65
|
+
print(order["pay_url"])
|
|
66
|
+
except ApiError as e: # 业务失败 code != 0
|
|
67
|
+
print(e.code, e.message, e.data)
|
|
68
|
+
except TransportError as e: # HTTP / 网络 / 超时
|
|
69
|
+
print(e, e.status_code)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 双环境与自定义基址
|
|
73
|
+
|
|
74
|
+
| 环境 | 基址 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| `Environment.PRODUCTION` | 无内置基址,**必须显式传 `base_url`** |
|
|
77
|
+
| `Environment.SANDBOX` | `http://127.0.0.1:3090/api/open/v1` |
|
|
78
|
+
|
|
79
|
+
正式真实地址按上级代理专有域名派生(`https://api.<agent_domain>/api/open/v1`),用 `base_url=` 显式传入。选 `PRODUCTION` 又不传 `base_url` 会抛 `ValueError`(提示 `baseUrl is required`):
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
config = Config(
|
|
83
|
+
merchant_no="M00000001", api_key="ak_xxx",
|
|
84
|
+
api_secret_pay="...", api_secret_payout="...",
|
|
85
|
+
base_url="https://api.agent.example.com/api/open/v1", # 正式环境必传
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 全部 11 个端点
|
|
90
|
+
|
|
91
|
+
代收(密钥 `api_secret_pay`,自动选用):
|
|
92
|
+
|
|
93
|
+
| 方法 | 端点 |
|
|
94
|
+
|------|------|
|
|
95
|
+
| `pay_create(...)` | `/merchant/pay/create` |
|
|
96
|
+
| `pay_query(order_no=, out_order_no=)` | `/merchant/pay/query` |
|
|
97
|
+
| `pay_methods_query(country=)` | `/merchant/pay-methods/query` |
|
|
98
|
+
| `balance_query(currency=)` | `/merchant/balance/query` |
|
|
99
|
+
| `pay_test_complete(result=, ...)` | `/merchant/pay/test/complete`(仅测试密钥) |
|
|
100
|
+
|
|
101
|
+
代付(密钥 `api_secret_payout`,自动选用):
|
|
102
|
+
|
|
103
|
+
| 方法 | 端点 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `payout_create(...)` | `/merchant/payout/create` |
|
|
106
|
+
| `payout_query(payout_no=, out_payout_no=)` | `/merchant/payout/query` |
|
|
107
|
+
| `payout_banks_query(pay_method=, country=, currency=)` | `/merchant/payout/banks/query` |
|
|
108
|
+
| `payout_proof_query(payout_no=, out_payout_no=)` | `/merchant/payout/proof/query` |
|
|
109
|
+
| `payout_receipt_query(..., inline=)` | `/merchant/payout/receipt/query` |
|
|
110
|
+
| `payout_test_complete(result=, ...)` | `/merchant/payout/test/complete`(仅测试密钥) |
|
|
111
|
+
|
|
112
|
+
约定:
|
|
113
|
+
|
|
114
|
+
- 每请求自动注入 `merchant_no`/`api_key`/`timestamp`(Unix 秒)/唯一 `nonce` 与 `sign`。
|
|
115
|
+
- 值为 `None` 的参数不放入请求体、也不参与签名。
|
|
116
|
+
- `payout_receipt_query` 的 `inline` 以整数 **1/0** 发送(`True`→1 内联 base64 图片;`False`→0 返回带 token 的 URL)。
|
|
117
|
+
- 金额是最小单位整数(`10000 = 1 元`),用 `int` 类型传入。
|
|
118
|
+
- 成功返回 `data`(dict);`code != 0` 抛 `ApiError`(携带 `code`/`message`/`data`);HTTP/网络错误抛 `TransportError`。
|
|
119
|
+
- 需要原始信封时用 `client.call_raw(path, body, secret)`(不因 `code != 0` 抛异常)。
|
|
120
|
+
|
|
121
|
+
## 签名工具(可单独使用)
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from openapi_sdk import build_sign_base, sign, verify_callback
|
|
125
|
+
|
|
126
|
+
base = build_sign_base(payload, secret) # 逐字节可断言的签名 base
|
|
127
|
+
sig = sign(payload, secret) # HMAC-SHA256 -> hex 小写
|
|
128
|
+
ok = verify_callback(callback, secret) # 时序安全(hmac.compare_digest),字段无关
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 回调验签 + 处理
|
|
132
|
+
|
|
133
|
+
见 [`examples/callback_verify.py`](examples/callback_verify.py):解析原始 body → `verify_callback`(时序安全)→ 按 `status` 幂等处理(success/failed)→ 应答 **HTTP 200 + 纯文本 `success`**。代收回调用 `api_secret_pay`、代付回调用 `api_secret_payout`,示例各演示一次。验签失败不回成功,让平台重试;处理务必幂等(同一订单可能多次回调)。
|
|
134
|
+
|
|
135
|
+
## 示例
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
cd python
|
|
139
|
+
python3 examples/pay_create.py
|
|
140
|
+
python3 examples/payout_create.py
|
|
141
|
+
python3 examples/callback_verify.py # 自演示:造签名回调 -> 验签 -> 应答 -> 篡改反例
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 跑测试
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cd python
|
|
148
|
+
python3 -m unittest discover -s tests
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
测试包含:
|
|
152
|
+
|
|
153
|
+
- 读取 `../test-vectors.json`,对每个向量断言 `build_sign_base == base` 且 `sign == sign`;
|
|
154
|
+
- 回调验签正例 + 篡改一字节反例(含错误密钥、缺 sign);
|
|
155
|
+
- 客户端请求构建(通用字段注入、`None` 过滤、密钥选择、`inline` 整数化、信封解析与异常分类)。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
bebebus_merchant_openapi_sdk.egg-info/PKG-INFO
|
|
5
|
+
bebebus_merchant_openapi_sdk.egg-info/SOURCES.txt
|
|
6
|
+
bebebus_merchant_openapi_sdk.egg-info/dependency_links.txt
|
|
7
|
+
bebebus_merchant_openapi_sdk.egg-info/requires.txt
|
|
8
|
+
bebebus_merchant_openapi_sdk.egg-info/top_level.txt
|
|
9
|
+
openapi_sdk/__init__.py
|
|
10
|
+
openapi_sdk/client.py
|
|
11
|
+
openapi_sdk/config.py
|
|
12
|
+
openapi_sdk/exceptions.py
|
|
13
|
+
openapi_sdk/signer.py
|
|
14
|
+
tests/test_client.py
|
|
15
|
+
tests/test_signer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openapi_sdk
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""商户支付 OpenAPI Python SDK(零第三方依赖)。
|
|
2
|
+
|
|
3
|
+
公开 API::
|
|
4
|
+
|
|
5
|
+
from openapi_sdk import (
|
|
6
|
+
Client, Config, Environment,
|
|
7
|
+
sign, build_sign_base, verify_callback,
|
|
8
|
+
ApiError, TransportError, OpenApiError,
|
|
9
|
+
)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from .client import Client
|
|
15
|
+
from .config import Config, Environment
|
|
16
|
+
from .exceptions import ApiError, OpenApiError, TransportError
|
|
17
|
+
from .signer import build_sign_base, sign, verify_callback
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Client",
|
|
21
|
+
"Config",
|
|
22
|
+
"Environment",
|
|
23
|
+
"sign",
|
|
24
|
+
"build_sign_base",
|
|
25
|
+
"verify_callback",
|
|
26
|
+
"ApiError",
|
|
27
|
+
"TransportError",
|
|
28
|
+
"OpenApiError",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
__version__ = "1.0.0"
|