fork download
  1. import hashlib
  2. import hmac
  3. import json
  4. import time
  5. import requests
  6.  
  7.  
  8. def generate_signature(body: str, api_secret: str, timestamp: int) -> str:
  9. """
  10. 生成签名
  11.  
  12. Args:
  13. body: 请求体字符串
  14. api_secret: 商户密钥
  15. timestamp: 时间戳(秒)
  16.  
  17. Returns:
  18. 签名字符串
  19. """
  20. # 拼接签名字符串
  21. sign_string = f"{body}|{timestamp}"
  22.  
  23. # 计算HMAC-SHA256签名
  24. signature = hmac.new(
  25. api_secret.encode('utf-8'),
  26. sign_string.encode('utf-8'),
  27. hashlib.sha256
  28. ).hexdigest()
  29.  
  30. return signature
  31.  
  32.  
  33. def generate_headers(merchant_id: str, api_key: str, api_secret: str, body: dict, timestamp: int = None):
  34. """
  35. 生成带签名的请求头部
  36.  
  37. Args:
  38. merchant_id: 商户ID
  39. api_key: API Key
  40. api_secret: 商户密钥
  41. body: 请求体字典
  42. timestamp: 时间戳,默认为当前时间
  43.  
  44. Returns:
  45. 请求头部字典
  46. """
  47. if timestamp is None:
  48. timestamp = int(time.time())
  49.  
  50. # 将body转换为JSON字符串
  51. # body_string = json.dumps(body, separators=(',', ':'), ensure_ascii=False)
  52. body_string = json.dumps(body, ensure_ascii=False)
  53.  
  54. # 生成签名
  55. signature = generate_signature(body_string, api_secret, timestamp)
  56.  
  57. # 返回请求头部
  58. return {
  59. 'X-MERCHANT-ID': merchant_id,
  60. 'X-API-KEY': api_key,
  61. 'X-SIGN': signature,
  62. 'X-TIMESTAMP': str(timestamp),
  63. 'Content-Type': 'application/json'
  64. }
  65.  
  66.  
  67. def create_order(headers, data, timestamp=None):
  68. url = "https://y...content-available-to-author-only...d.xyz/api/payment/create_orders"
  69.  
  70. try:
  71. request_body = json.dumps(data, ensure_ascii=False)
  72.  
  73. # 打印请求参数详情
  74. print("\n=== 创建订单 ===")
  75. print(f"请求URL: {url}")
  76. print(f"请求方法: POST")
  77. print(f"请求体 (格式化JSON):")
  78. print(json.dumps(data, ensure_ascii=False, indent=2))
  79. print(f"请求体 (原始字符串): {request_body}")
  80. if timestamp:
  81. print(f"时间戳: {timestamp}")
  82. print(f"签名原始字符串: {request_body}|{timestamp}")
  83. print("请求头部:")
  84. for key, value in headers.items():
  85. print(f" {key}: {value}")
  86. print("=" * 40)
  87.  
  88. resp = requests.post(url, data=request_body, headers=headers, timeout=10)
  89.  
  90. # 打印响应数据详情
  91. print(f"\n=== 创建订单接口响应数据 ===")
  92. print(f"响应状态码: {resp.status_code}")
  93. print(f"响应内容 (原始): {resp.text}")
  94.  
  95. if resp.status_code == 200:
  96. try:
  97. response_data = resp.json()
  98. print(f"响应内容 (格式化JSON):")
  99. print(json.dumps(response_data, ensure_ascii=False, indent=2))
  100. print("=" * 40)
  101. return response_data
  102. except json.JSONDecodeError:
  103. print(f"响应不是有效的JSON格式")
  104. print("=" * 40)
  105. return None
  106. else:
  107. print("=" * 40)
  108. return None
  109. except requests.RequestException as e:
  110. print(f"请求失败: {e}")
  111. return None
  112.  
  113.  
  114. def query_order(headers, out_trade_no):
  115. url = "https://y...content-available-to-author-only...d.xyz/api/payment/query_orders_status"
  116. data = {
  117. "out_trade_no": out_trade_no
  118. }
  119.  
  120. try:
  121. resp = requests.post(url, data=json.dumps(data), headers=headers, timeout=10)
  122. print(f"\n查询订单响应状态码: {resp.status_code}")
  123. print(f"查询订单响应内容: {resp.text}")
  124.  
  125. if resp.status_code == 200:
  126. response_data = resp.json()
  127. return response_data
  128. else:
  129. return None
  130. except requests.RequestException as e:
  131. print(f"请求失败: {e}")
  132. return None
  133.  
  134.  
  135. def refund_order(headers, out_trade_no):
  136. """
  137. 订单退款接口
  138.  
  139. Args:
  140. headers: 请求头部字典
  141. out_trade_no: 需要退款的支付订单编号
  142.  
  143. Returns:
  144. 响应数据字典,如果请求失败则返回 None
  145. """
  146. url = "https://y...content-available-to-author-only...d.xyz/api/payment/orders_refund"
  147. data = {
  148. "out_trade_no": out_trade_no
  149. }
  150.  
  151. try:
  152. request_body = json.dumps(data, ensure_ascii=False)
  153. print(f"\n发送请求:")
  154. print(f"URL: {url}")
  155. print(f"请求体: {request_body}")
  156. resp = requests.post(url, data=request_body, headers=headers, timeout=10)
  157. print(f"\n订单退款响应状态码: {resp.status_code}")
  158. print(f"订单退款响应内容: {resp.text}")
  159.  
  160. if resp.status_code == 200:
  161. response_data = resp.json()
  162. return response_data
  163. else:
  164. return None
  165. except requests.RequestException as e:
  166. print(f"请求失败: {e}")
  167. return None
  168.  
  169. # 使用示例
  170. if __name__ == "__main__":
  171. # api link: https://y...content-available-to-author-only...d.xyz
  172. # api_secret: 7d5bcedf57f6460f20e22895532273f695ec940acb3bf118
  173. # key: sk_d987cc44b7ae5bef0b15618a
  174. api_secret = "7d5bcedf57f6460f20e22895532273f695ec940acb3bf118"
  175. api_key = 'sk_d987cc44b7ae5bef0b15618a'
  176. merchant_id = "MCH007881CF6E42C6"
  177.  
  178. # 调用创建订单接口
  179. body_data = {
  180. "amount": "100.00",
  181. "symbol": "usd",
  182. "attach": "Create an order"
  183. }
  184. create_timestamp = int(time.time()) - 5 * 60
  185. create_headers = generate_headers(
  186. merchant_id=merchant_id,
  187. api_key=api_key,
  188. api_secret=api_secret,
  189. body=body_data,
  190. timestamp=create_timestamp
  191. )
  192.  
  193. # 调用创建订单接口
  194. create_response = create_order(create_headers, body_data, create_timestamp)
  195.  
  196. # 从创建订单响应中获取 out_trade_no(可选,用于后续退款)
  197. out_trade_no = None
  198. if create_response:
  199. if 'data' in create_response and isinstance(create_response['data'], dict) and 'out_trade_no' in create_response['data']:
  200. out_trade_no = create_response['data']['out_trade_no']
  201. elif 'out_trade_no' in create_response:
  202. out_trade_no = create_response['out_trade_no']
  203.  
  204. # 使用固定的订单号请求退款接口(如果创建订单失败或需要测试特定订单)
  205. if not out_trade_no:
  206. out_trade_no = "YA20260106103346813599"
  207.  
  208. print(f"\n使用订单号进行退款: {out_trade_no}")
  209.  
  210. # 订单退款接口
  211. refund_data = {
  212. "out_trade_no": out_trade_no
  213. }
  214.  
  215. # 为退款接口生成请求头和签名
  216. refund_timestamp = int(time.time())
  217. refund_headers = generate_headers(
  218. merchant_id=merchant_id,
  219. api_key=api_key,
  220. api_secret=api_secret,
  221. body=refund_data,
  222. timestamp=refund_timestamp
  223. )
  224.  
  225. print("\n订单退款请求头部:")
  226. for key, value in refund_headers.items():
  227. print(f"{key}: {value}")
  228.  
  229. # 打印请求参数详情
  230. print("\n=== 订单退款 ===")
  231. print(f"请求URL: https://y...content-available-to-author-only...d.xyz/api/payment/orders_refund")
  232. print(f"请求方法: POST")
  233. print(f"请求体 (JSON): {json.dumps(refund_data, ensure_ascii=False, indent=2)}")
  234. print(f"时间戳: {refund_timestamp}")
  235. print(f"签名原始字符串: {json.dumps(refund_data, ensure_ascii=False)}|{refund_timestamp}")
  236. print("=" * 40)
  237.  
  238. # 调用订单退款接口
  239. refund_response = refund_order(refund_headers, out_trade_no)
Success #stdin #stdout 1.53s 36576KB
stdin
Standard input is empty
stdout
=== 创建订单 ===
请求URL: https://y...content-available-to-author-only...d.xyz/api/payment/create_orders
请求方法: POST
请求体 (格式化JSON):
{
  "amount": "100.00",
  "symbol": "usd",
  "attach": "Create an order"
}
请求体 (原始字符串): {"amount": "100.00", "symbol": "usd", "attach": "Create an order"}
时间戳: 1767748277
签名原始字符串: {"amount": "100.00", "symbol": "usd", "attach": "Create an order"}|1767748277
请求头部:
  X-MERCHANT-ID: MCH007881CF6E42C6
  X-API-KEY: sk_d987cc44b7ae5bef0b15618a
  X-SIGN: cb6f8bcf1d2b52a4eb4551d606e9f9262b6bbc8d1c7f4afb5161fe8776666a46
  X-TIMESTAMP: 1767748277
  Content-Type: application/json
========================================
请求失败: HTTPSConnectionPool(host='yastapayadmin.aitechprod.xyz', port=443): Max retries exceeded with url: /api/payment/create_orders (Caused by NameResolutionError("<urllib3.connection.HTTPSConnection object at 0x147fd0726240>: Failed to resolve 'yastapayadmin.aitechprod.xyz' ([Errno -3] Temporary failure in name resolution)"))

使用订单号进行退款: YA20260106103346813599

订单退款请求头部:
X-MERCHANT-ID: MCH007881CF6E42C6
X-API-KEY: sk_d987cc44b7ae5bef0b15618a
X-SIGN: 96390cde27b3a59cdafbee8db5f8518d0616bb6307697db09efb05699e7cb529
X-TIMESTAMP: 1767748577
Content-Type: application/json

=== 订单退款 ===
请求URL: https://y...content-available-to-author-only...d.xyz/api/payment/orders_refund
请求方法: POST
请求体 (JSON): {
  "out_trade_no": "YA20260106103346813599"
}
时间戳: 1767748577
签名原始字符串: {"out_trade_no": "YA20260106103346813599"}|1767748577
========================================

发送请求:
URL: https://y...content-available-to-author-only...d.xyz/api/payment/orders_refund
请求体: {"out_trade_no": "YA20260106103346813599"}
请求失败: HTTPSConnectionPool(host='yastapayadmin.aitechprod.xyz', port=443): Max retries exceeded with url: /api/payment/orders_refund (Caused by NameResolutionError("<urllib3.connection.HTTPSConnection object at 0x147fd0371310>: Failed to resolve 'yastapayadmin.aitechprod.xyz' ([Errno -3] Temporary failure in name resolution)"))