threeypay-sdk 1.1.0__py3-none-any.whl

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.
threeypay/__init__.py ADDED
@@ -0,0 +1,37 @@
1
+ """
2
+ 3YPay Python SDK
3
+ """
4
+
5
+ __version__ = "1.1.0"
6
+ __author__ = "3YPay Team"
7
+
8
+ from threeypay.sdk import ThreeYPay
9
+ from threeypay.config import Config
10
+ from threeypay.client import Client
11
+ from threeypay.exceptions import HttpException, InvalidSignException
12
+ from threeypay.payment import Payment
13
+ from threeypay.transfer import Transfer
14
+ from threeypay.profitsharing import ProfitSharing
15
+ from threeypay.account import Account
16
+ from threeypay.merchant import Merchant
17
+ from threeypay.payout import Payout
18
+ from threeypay.balanceshare import BalanceShare
19
+ from threeypay.nonmch import NonMch
20
+ from threeypay.channel import Channel
21
+
22
+ __all__ = [
23
+ "ThreeYPay",
24
+ "Config",
25
+ "Client",
26
+ "HttpException",
27
+ "InvalidSignException",
28
+ "Payment",
29
+ "Transfer",
30
+ "ProfitSharing",
31
+ "Account",
32
+ "Merchant",
33
+ "Payout",
34
+ "BalanceShare",
35
+ "NonMch",
36
+ "Channel",
37
+ ]
@@ -0,0 +1,5 @@
1
+ """账户模块"""
2
+
3
+ from threeypay.account.account import Account
4
+
5
+ __all__ = ["Account"]
@@ -0,0 +1,77 @@
1
+ """
2
+ 分账账户与提现类
3
+ """
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+
8
+ class Account:
9
+ """账户管理类"""
10
+
11
+ API_BALANCE_QUERY = "/mchShare/withdrawal/account"
12
+ API_WITHDRAW = "/mchShare/withdrawal/commit"
13
+ API_WITHDRAW_QUERY = "/mchShare/withdrawal/query"
14
+
15
+ def __init__(self, client):
16
+ self.client = client
17
+
18
+ def balance_query(self, params: Dict[str, Any]) -> Dict[str, Any]:
19
+ """账户余额查询(accountNo 必填)"""
20
+ self._validate_required(params, ["accountNo"])
21
+ response = self.client.post(self.API_BALANCE_QUERY, params)
22
+ return self.client.parse_response(response)
23
+
24
+ def query_balance(self, account_no: str) -> Dict[str, Any]:
25
+ return self.balance_query({"accountNo": account_no})
26
+
27
+ def withdraw(self, params: Dict[str, Any]) -> Dict[str, Any]:
28
+ """提现申请(mchSerialNo、accountNo、amount、remark 必填,notifyUrl 可选)"""
29
+ payload = dict(params)
30
+ self._validate_required(payload, ["mchSerialNo", "accountNo", "remark"])
31
+ payload["amount"] = self._amount(payload.get("amount"), "amount")
32
+
33
+ response = self.client.post(self.API_WITHDRAW, payload)
34
+ return self.client.parse_response(response)
35
+
36
+ def submit_withdraw(
37
+ self, mch_serial_no: str, account_no: str, amount: str, remark: str,
38
+ notify_url: str = "",
39
+ ) -> Dict[str, Any]:
40
+ params: Dict[str, Any] = {
41
+ "mchSerialNo": mch_serial_no,
42
+ "accountNo": account_no,
43
+ "amount": amount,
44
+ "remark": remark,
45
+ }
46
+ if notify_url:
47
+ params["notifyUrl"] = notify_url
48
+ return self.withdraw(params)
49
+
50
+ def withdraw_query(self, params: Dict[str, Any]) -> Dict[str, Any]:
51
+ """提现查询(mchSerialNo 与 serialNo 二选一)"""
52
+ self._validate_one_of(params, ["mchSerialNo", "serialNo"])
53
+ response = self.client.post(self.API_WITHDRAW_QUERY, params)
54
+ return self.client.parse_response(response)
55
+
56
+ @staticmethod
57
+ def _amount(value: Any, field: str) -> float:
58
+ try:
59
+ amount = float(value)
60
+ except (TypeError, ValueError):
61
+ raise ValueError(f"金额参数无效:{field}")
62
+ if amount <= 0:
63
+ raise ValueError(f"金额参数无效:{field}")
64
+ return amount
65
+
66
+ @staticmethod
67
+ def _validate_required(params: Dict[str, Any], fields: List[str]) -> None:
68
+ for field in fields:
69
+ if field not in params or params[field] is None or params[field] == "":
70
+ raise ValueError(f"必填参数不能为空:{field}")
71
+
72
+ @staticmethod
73
+ def _validate_one_of(params: Dict[str, Any], fields: List[str]) -> None:
74
+ for field in fields:
75
+ if field in params and params[field] is not None and params[field] != "":
76
+ return
77
+ raise ValueError(f"以下参数至少填一个:{', '.join(fields)}")
@@ -0,0 +1,5 @@
1
+ """余额分账模块"""
2
+
3
+ from threeypay.balanceshare.balance_share import BalanceShare
4
+
5
+ __all__ = ["BalanceShare"]
@@ -0,0 +1,86 @@
1
+ """
2
+ 余额分账类
3
+
4
+ 金额单位:元(number)。
5
+ """
6
+
7
+ from typing import Any, Dict, List
8
+
9
+
10
+ class BalanceShare:
11
+ """余额分账类"""
12
+
13
+ API_BALANCE_SHARE = "/mchShare/balance/share"
14
+ API_BALANCE_SHARE_QUERY = "/mchShare/balance/shareQuery"
15
+ API_BALANCE_REFUND = "/mchShare/balance/refund"
16
+ API_BALANCE_REFUND_QUERY = "/mchShare/balance/refundQuery"
17
+
18
+ def __init__(self, client):
19
+ self.client = client
20
+
21
+ def apply(self, params: Dict[str, Any]) -> Dict[str, Any]:
22
+ """余额分账申请(mchBatchNo、accountNo、appChannelSn 必填,acctInfos 子项需 accountNo/amount/description)"""
23
+ payload = dict(params)
24
+ self._validate_required(payload, ["mchBatchNo", "accountNo", "appChannelSn"])
25
+ self._validate_acct_infos(payload, ["accountNo", "amount", "description"])
26
+
27
+ response = self.client.post(self.API_BALANCE_SHARE, payload)
28
+ return self.client.parse_response(response)
29
+
30
+ def query(self, params: Dict[str, Any]) -> Dict[str, Any]:
31
+ """余额分账查询(batchNo 与 mchBatchNo 二选一)"""
32
+ self._validate_one_of(params, ["batchNo", "mchBatchNo"])
33
+ response = self.client.post(self.API_BALANCE_SHARE_QUERY, params)
34
+ return self.client.parse_response(response)
35
+
36
+ def refund(self, params: Dict[str, Any]) -> Dict[str, Any]:
37
+ """余额分账退款(mchRefundOrderNo 必填,批次二选一,acctInfos 子项需 accOrderNo/amount/description)"""
38
+ payload = dict(params)
39
+ self._validate_required(payload, ["mchRefundOrderNo"])
40
+ self._validate_one_of(payload, ["batchNo", "mchBatchNo"])
41
+ self._validate_acct_infos(payload, ["accOrderNo", "amount", "description"])
42
+
43
+ response = self.client.post(self.API_BALANCE_REFUND, payload)
44
+ return self.client.parse_response(response)
45
+
46
+ def refund_query(self, params: Dict[str, Any]) -> Dict[str, Any]:
47
+ """余额分账退款查询(batchNo 与 mchBatchNo 二选一)"""
48
+ self._validate_one_of(params, ["batchNo", "mchBatchNo"])
49
+ response = self.client.post(self.API_BALANCE_REFUND_QUERY, params)
50
+ return self.client.parse_response(response)
51
+
52
+ def _validate_acct_infos(self, params: Dict[str, Any], required_fields: List[str]) -> None:
53
+ acct_infos = params.get("acctInfos")
54
+ if not isinstance(acct_infos, list) or not acct_infos:
55
+ raise ValueError("acctInfos参数格式错误")
56
+
57
+ for index, item in enumerate(acct_infos):
58
+ if not isinstance(item, dict):
59
+ raise ValueError(f"acctInfos[{index}]格式错误")
60
+ for field in required_fields:
61
+ if field not in item or item[field] is None or item[field] == "":
62
+ raise ValueError(f"acctInfos[{index}].{field}不能为空")
63
+ item["amount"] = self._amount(item["amount"], f"acctInfos[{index}].amount")
64
+
65
+ @staticmethod
66
+ def _amount(value: Any, field: str) -> float:
67
+ try:
68
+ amount = float(value)
69
+ except (TypeError, ValueError):
70
+ raise ValueError(f"金额参数无效:{field}")
71
+ if amount <= 0:
72
+ raise ValueError(f"金额参数无效:{field}")
73
+ return amount
74
+
75
+ @staticmethod
76
+ def _validate_required(params: Dict[str, Any], fields: List[str]) -> None:
77
+ for field in fields:
78
+ if field not in params or params[field] is None or params[field] == "":
79
+ raise ValueError(f"必填参数不能为空:{field}")
80
+
81
+ @staticmethod
82
+ def _validate_one_of(params: Dict[str, Any], fields: List[str]) -> None:
83
+ for field in fields:
84
+ if field in params and params[field] is not None and params[field] != "":
85
+ return
86
+ raise ValueError(f"以下参数至少填一个:{', '.join(fields)}")
@@ -0,0 +1,5 @@
1
+ """分账关系模块"""
2
+
3
+ from threeypay.channel.channel import Channel
4
+
5
+ __all__ = ["Channel"]
@@ -0,0 +1,65 @@
1
+ """
2
+ 分账关系管理类
3
+ """
4
+
5
+ from typing import Any, Dict, List
6
+
7
+
8
+ class Channel:
9
+ """分账关系类"""
10
+
11
+ API_BINDING = "/mchShare/channel/binding"
12
+ API_UNBINDING = "/mchShare/channel/unBinding"
13
+ API_UPDATE = "/mchShare/channel/update"
14
+ API_BINDING_QUERY = "/mchShare/channel/bindingQuery"
15
+
16
+ def __init__(self, client):
17
+ self.client = client
18
+
19
+ def binding(self, params: Dict[str, Any]) -> Dict[str, Any]:
20
+ """分账关系绑定(accountNo、appChannelSn、fundName、defaultRate 必填)"""
21
+ self._validate_required(params, ["accountNo", "appChannelSn", "fundName", "defaultRate"])
22
+ self._validate_rate(params, "defaultRate")
23
+ self._validate_rate(params, "defaultFee", required=False)
24
+ response = self.client.post(self.API_BINDING, params)
25
+ return self.client.parse_response(response)
26
+
27
+ def unbind(self, params: Dict[str, Any]) -> Dict[str, Any]:
28
+ """分账关系解绑"""
29
+ self._validate_required(params, ["accountNo", "appChannelSn"])
30
+ response = self.client.post(self.API_UNBINDING, params)
31
+ return self.client.parse_response(response)
32
+
33
+ def update(self, params: Dict[str, Any]) -> Dict[str, Any]:
34
+ """分账关系更新(accountNo、appChannelSn、defaultRate 必填)"""
35
+ self._validate_required(params, ["accountNo", "appChannelSn", "defaultRate"])
36
+ self._validate_rate(params, "defaultRate")
37
+ self._validate_rate(params, "defaultFee", required=False)
38
+ response = self.client.post(self.API_UPDATE, params)
39
+ return self.client.parse_response(response)
40
+
41
+ def query(self, params: Dict[str, Any]) -> Dict[str, Any]:
42
+ """分账关系查询(appChannelSn 必填)"""
43
+ self._validate_required(params, ["appChannelSn"])
44
+ response = self.client.post(self.API_BINDING_QUERY, params)
45
+ return self.client.parse_response(response)
46
+
47
+ @staticmethod
48
+ def _validate_required(params: Dict[str, Any], fields: List[str]) -> None:
49
+ for field in fields:
50
+ if field not in params or params[field] is None or params[field] == "":
51
+ raise ValueError(f"必填参数不能为空:{field}")
52
+
53
+ @staticmethod
54
+ def _validate_rate(params: Dict[str, Any], field: str, required: bool = True) -> None:
55
+ if field not in params or params[field] is None or params[field] == "":
56
+ if required:
57
+ raise ValueError(f"必填参数不能为空:{field}")
58
+ return
59
+ try:
60
+ rate = float(params[field])
61
+ except (TypeError, ValueError):
62
+ raise ValueError(f"比例参数无效:{field}")
63
+ if rate < 0 or rate > 100:
64
+ raise ValueError(f"比例参数无效:{field}")
65
+ params[field] = rate
threeypay/client.py ADDED
@@ -0,0 +1,146 @@
1
+ """
2
+ HTTP 客户端
3
+ """
4
+
5
+ import json
6
+ import time
7
+ from typing import Any, Dict, Optional
8
+
9
+ import requests
10
+
11
+ from threeypay.config import Config
12
+ from threeypay.exceptions import HttpException
13
+ from threeypay.utils.signer import Signer
14
+
15
+
16
+ class Client:
17
+ """3YPay HTTP 客户端类"""
18
+
19
+ def __init__(self, config: Config, session: Optional[requests.Session] = None):
20
+ self.config = config
21
+ self.session = session or requests.Session()
22
+ self.session.headers.update({
23
+ "Content-Type": "application/json",
24
+ "Accept": "application/json",
25
+ })
26
+ self.sub_sn = ""
27
+
28
+ def set_sub_sn(self, sub_sn: str) -> "Client":
29
+ self.sub_sn = sub_sn
30
+ return self
31
+
32
+ def set_timeout(self, timeout: int) -> "Client":
33
+ self.config.set_timeout(timeout)
34
+ return self
35
+
36
+ def post(self, endpoint: str, biz_content: Dict[str, Any]) -> Dict[str, Any]:
37
+ """发送 POST 请求"""
38
+ public_params = self._build_public_params(biz_content)
39
+ public_params["sign"] = Signer.sign(public_params, self.config.get_private_key())
40
+
41
+ url = self.config.get_gateway_url().rstrip("/") + endpoint
42
+
43
+ try:
44
+ response = self.session.post(
45
+ url,
46
+ json=public_params,
47
+ timeout=self.config.get_timeout(),
48
+ )
49
+ except requests.RequestException as exc:
50
+ raise HttpException(f"网络请求异常:{exc}") from exc
51
+
52
+ try:
53
+ response_data = response.json()
54
+ except ValueError as exc:
55
+ raise HttpException(
56
+ "无效的响应格式",
57
+ response_body=response.text,
58
+ status_code=response.status_code,
59
+ ) from exc
60
+
61
+ if not isinstance(response_data, dict):
62
+ raise HttpException(
63
+ "无效的响应格式",
64
+ response_body=response_data,
65
+ status_code=response.status_code,
66
+ )
67
+
68
+ if response.status_code < 200 or response.status_code >= 300:
69
+ code = response_data.get("code", response.status_code)
70
+ msg = response_data.get("msg") or f"HTTP {response.status_code}"
71
+ raise HttpException(
72
+ f"API 请求失败:{msg}",
73
+ self._to_int(code, response.status_code),
74
+ response_data,
75
+ response.status_code,
76
+ )
77
+
78
+ return response_data
79
+
80
+ def _build_public_params(self, biz_content: Dict[str, Any]) -> Dict[str, Any]:
81
+ """构建公共请求参数"""
82
+ params = {
83
+ "appId": self.config.get_app_id(),
84
+ "requestId": Signer.generate_request_id(),
85
+ "signType": self.config.get_sign_type(),
86
+ "timestamp": int(time.time() * 1000),
87
+ "version": self.config.get_version(),
88
+ "charset": self.config.get_charset(),
89
+ "bizContent": json.dumps(biz_content, ensure_ascii=False, separators=(",", ":")),
90
+ }
91
+
92
+ if self.sub_sn:
93
+ params["subSn"] = self.sub_sn
94
+
95
+ return params
96
+
97
+ def verify_notify(self, data: Dict[str, Any]) -> bool:
98
+ """验证回调通知签名"""
99
+ return Signer.verify(data, self.config.get_platform_public_key())
100
+
101
+ def verify_response(self, response: Dict[str, Any]) -> bool:
102
+ """验证响应签名"""
103
+ if not isinstance(response, dict) or not response.get("sign"):
104
+ return False
105
+ return Signer.verify(response, self.config.get_platform_public_key())
106
+
107
+ def is_success(self, response: Dict[str, Any]) -> bool:
108
+ """提现类接口文档以 0 表示成功,其余接口为 200。"""
109
+ if not isinstance(response, dict) or "code" not in response:
110
+ return False
111
+ try:
112
+ code = int(response["code"])
113
+ except (TypeError, ValueError):
114
+ return False
115
+ return code in (200, 0)
116
+
117
+ def parse_response(self, response: Dict[str, Any], verify_sign: bool = True) -> Dict[str, Any]:
118
+ """解析业务数据(先判断业务码,成功后再验签)"""
119
+ if not isinstance(response, dict) or "code" not in response:
120
+ raise HttpException("无效的响应格式", response_body=response)
121
+
122
+ if not self.is_success(response):
123
+ msg = response.get("msg", "未知错误")
124
+ raise HttpException(
125
+ f"API 请求失败:code={response['code']}, msg={msg}",
126
+ self._to_int(response["code"], 0),
127
+ response,
128
+ )
129
+
130
+ if verify_sign and not self.verify_response(response):
131
+ raise HttpException("签名验证失败", response_body=response)
132
+
133
+ if isinstance(response.get("data"), str) and response["data"] != "":
134
+ try:
135
+ response["data"] = json.loads(response["data"])
136
+ except ValueError:
137
+ pass
138
+
139
+ return response
140
+
141
+ @staticmethod
142
+ def _to_int(value: Any, default: int) -> int:
143
+ try:
144
+ return int(value)
145
+ except (TypeError, ValueError):
146
+ return default
threeypay/config.py ADDED
@@ -0,0 +1,68 @@
1
+ """
2
+ 配置类
3
+ """
4
+
5
+
6
+ class Config:
7
+ """3YPay SDK 配置类"""
8
+
9
+ GATEWAY_PROD = "https://openapi.3ypay.com"
10
+ GATEWAY_TEST = "https://test-openapi.3ypay.com"
11
+
12
+ def __init__(self, app_id: str, private_key: str, platform_public_key: str, sandbox: bool = False):
13
+ self.app_id = app_id
14
+ self.private_key = private_key
15
+ self.platform_public_key = platform_public_key
16
+ self.sandbox = sandbox
17
+ self.gateway_url = self.GATEWAY_TEST if sandbox else self.GATEWAY_PROD
18
+ self.sign_type = "RSA2"
19
+ self.version = "1.0"
20
+ self.charset = "UTF-8"
21
+ self.timeout = 30
22
+
23
+ def get_app_id(self) -> str:
24
+ return self.app_id
25
+
26
+ def get_private_key(self) -> str:
27
+ return self.private_key
28
+
29
+ def get_platform_public_key(self) -> str:
30
+ return self.platform_public_key
31
+
32
+ def get_gateway_url(self) -> str:
33
+ return self.gateway_url
34
+
35
+ def get_sign_type(self) -> str:
36
+ return self.sign_type
37
+
38
+ def get_version(self) -> str:
39
+ return self.version
40
+
41
+ def get_charset(self) -> str:
42
+ return self.charset
43
+
44
+ def get_timeout(self) -> int:
45
+ return self.timeout
46
+
47
+ def set_timeout(self, timeout: int) -> "Config":
48
+ self.timeout = timeout
49
+ return self
50
+
51
+ def set_sign_type(self, sign_type: str) -> "Config":
52
+ self.sign_type = sign_type
53
+ return self
54
+
55
+ def set_version(self, version: str) -> "Config":
56
+ self.version = version
57
+ return self
58
+
59
+ def set_charset(self, charset: str) -> "Config":
60
+ self.charset = charset
61
+ return self
62
+
63
+ def set_gateway_url(self, gateway_url: str) -> "Config":
64
+ self.gateway_url = gateway_url.rstrip("/")
65
+ return self
66
+
67
+ def is_sandbox(self) -> bool:
68
+ return self.sandbox
@@ -0,0 +1,27 @@
1
+ """
2
+ HTTP 客户端异常
3
+ """
4
+
5
+ from typing import Any, Optional
6
+
7
+
8
+ class HttpException(Exception):
9
+ """HTTP / 业务异常类"""
10
+
11
+ def __init__(
12
+ self,
13
+ message: str,
14
+ code: int = 0,
15
+ response_body: Optional[Any] = None,
16
+ status_code: Optional[int] = None,
17
+ ):
18
+ super().__init__(message)
19
+ self.code = code
20
+ self.status_code = status_code
21
+ self.response_body = response_body
22
+
23
+
24
+ class InvalidSignException(Exception):
25
+ """无效签名异常类"""
26
+
27
+ pass
@@ -0,0 +1,5 @@
1
+ """商户模块"""
2
+
3
+ from threeypay.merchant.merchant import Merchant
4
+
5
+ __all__ = ["Merchant"]
@@ -0,0 +1,128 @@
1
+ """
2
+ 商户进件与 AUT 类
3
+ """
4
+
5
+ import base64
6
+ import mimetypes
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from threeypay.utils.signer import Signer
11
+
12
+
13
+ class Merchant:
14
+ """商户管理类"""
15
+
16
+ API_FILE_UPLOAD = "/mchAccess/mch/fileUp"
17
+ API_MERCHANT_ACCESS = "/mchAccess/mch/access"
18
+ API_ACCESS_QUERY = "/mchAccess/mch/query"
19
+ API_MODIFY_MCH_CARD = "/mchAccess/mch/modify/mchCard"
20
+ API_MODIFY_PRODUCT = "/mchAccess/mch/modify/product"
21
+ API_MODIFY_QUERY = "/mchAccess/mch/modify/query"
22
+ API_AUT_QUERY = "/mchAccess/mch/aut/query"
23
+ API_AUT_CONFIG = "/mchAccess/mch/aut/config"
24
+ API_AUT_CONFIG_INFO = "/mchAccess/mch/aut/config/info"
25
+ API_AUT_SPLIT = "/mchAccess/mch/aut/split"
26
+
27
+ def __init__(self, client):
28
+ self.client = client
29
+
30
+ def file_upload(self, params: Dict[str, Any]) -> Dict[str, Any]:
31
+ """进件文件上传(fileContent 必填,传 Base64;fileName/contentType 可选)"""
32
+ self._validate_required(params, ["fileContent"])
33
+ payload = dict(params)
34
+ content = str(payload["fileContent"])
35
+ if not Signer.is_base64(content):
36
+ payload["fileContent"] = base64.b64encode(content.encode("utf-8")).decode("utf-8")
37
+
38
+ if not payload.get("contentType") and payload.get("fileName"):
39
+ payload["contentType"] = self._detect_content_type(str(payload["fileName"]))
40
+
41
+ response = self.client.post(self.API_FILE_UPLOAD, payload)
42
+ return self.client.parse_response(response)
43
+
44
+ def file_upload_from_path(self, file_path: str) -> Dict[str, Any]:
45
+ path = Path(file_path)
46
+ if not path.is_file():
47
+ raise ValueError(f"文件不存在或不可读:{file_path}")
48
+ return self.file_upload({
49
+ "fileName": path.name,
50
+ "contentType": self._detect_content_type(str(path)),
51
+ "fileContent": base64.b64encode(path.read_bytes()).decode("utf-8"),
52
+ })
53
+
54
+ @staticmethod
55
+ def _detect_content_type(file_name: str) -> str:
56
+ return mimetypes.guess_type(file_name)[0] or "application/octet-stream"
57
+
58
+ def access(self, params: Dict[str, Any]) -> Dict[str, Any]:
59
+ """商户入网申请(mchAccessNo、mchInfo、mchBusiness、mchCard、products 必填)"""
60
+ self._validate_required(params, ["mchAccessNo", "mchInfo", "mchBusiness", "mchCard", "products"])
61
+ response = self.client.post(self.API_MERCHANT_ACCESS, params)
62
+ return self.client.parse_response(response)
63
+
64
+ def query(self, params: Dict[str, Any]) -> Dict[str, Any]:
65
+ """商户入网信息查询(accessSn、mchAccessNo、requestId 三选一)"""
66
+ self._validate_one_of(params, ["accessSn", "mchAccessNo", "requestId"])
67
+ response = self.client.post(self.API_ACCESS_QUERY, params)
68
+ return self.client.parse_response(response)
69
+
70
+ def modify_mch_card(self, params: Dict[str, Any]) -> Dict[str, Any]:
71
+ """商户结算信息变更(mchModifyNo、mchCard 必填,accessSn/mchAccessNo 二选一)"""
72
+ self._validate_required(params, ["mchModifyNo", "mchCard"])
73
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
74
+ response = self.client.post(self.API_MODIFY_MCH_CARD, params)
75
+ return self.client.parse_response(response)
76
+
77
+ def modify_product(self, params: Dict[str, Any]) -> Dict[str, Any]:
78
+ """商户产品信息变更(mchModifyNo、products 必填,accessSn/mchAccessNo 二选一)"""
79
+ self._validate_required(params, ["mchModifyNo", "products"])
80
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
81
+ response = self.client.post(self.API_MODIFY_PRODUCT, params)
82
+ return self.client.parse_response(response)
83
+
84
+ def modify_query(self, params: Dict[str, Any]) -> Dict[str, Any]:
85
+ """商户信息变更查询(accessSn/mchAccessNo、modifyId/mchModifyNo 分别二选一)"""
86
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
87
+ self._validate_one_of(params, ["modifyId", "mchModifyNo"])
88
+ response = self.client.post(self.API_MODIFY_QUERY, params)
89
+ return self.client.parse_response(response)
90
+
91
+ def aut_query(self, params: Dict[str, Any]) -> Dict[str, Any]:
92
+ """商户 AUT 报备查询(accessSn/mchAccessNo 二选一)"""
93
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
94
+ response = self.client.post(self.API_AUT_QUERY, params)
95
+ return self.client.parse_response(response)
96
+
97
+ def aut_config(self, params: Dict[str, Any]) -> Dict[str, Any]:
98
+ """微信支付配置(accessSn/mchAccessNo 二选一,code 必填)"""
99
+ self._validate_required(params, ["code"])
100
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
101
+ response = self.client.post(self.API_AUT_CONFIG, params)
102
+ return self.client.parse_response(response)
103
+
104
+ def aut_config_info(self, params: Dict[str, Any]) -> Dict[str, Any]:
105
+ """微信支付配置查询(accessSn/mchAccessNo 二选一,code 必填)"""
106
+ self._validate_required(params, ["code"])
107
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
108
+ response = self.client.post(self.API_AUT_CONFIG_INFO, params)
109
+ return self.client.parse_response(response)
110
+
111
+ def aut_split(self, params: Dict[str, Any]) -> Dict[str, Any]:
112
+ """商户 AUT 拆分(accessSn/mchAccessNo 二选一)"""
113
+ self._validate_one_of(params, ["accessSn", "mchAccessNo"])
114
+ response = self.client.post(self.API_AUT_SPLIT, params)
115
+ return self.client.parse_response(response)
116
+
117
+ @staticmethod
118
+ def _validate_required(params: Dict[str, Any], fields: List[str]) -> None:
119
+ for field in fields:
120
+ if field not in params or params[field] is None or params[field] == "":
121
+ raise ValueError(f"必填参数不能为空:{field}")
122
+
123
+ @staticmethod
124
+ def _validate_one_of(params: Dict[str, Any], fields: List[str]) -> None:
125
+ for field in fields:
126
+ if field in params and params[field] is not None and params[field] != "":
127
+ return
128
+ raise ValueError(f"以下参数至少填一个:{', '.join(fields)}")
@@ -0,0 +1,5 @@
1
+ """非收单商户模块"""
2
+
3
+ from threeypay.nonmch.non_mch import NonMch
4
+
5
+ __all__ = ["NonMch"]