開發(fā)文檔:https://open.swiftpass.cn/openapi?code=hsty
這個聚合平臺大概是集合了微信、支付寶、云閃付等支付平臺,大概有下圖這么多:

企業(yè)微信截圖_af26a470-e849-4a32-a9c5-19a3041298c6.png
一、付款碼支付
付款碼的大概業(yè)務功能:
收銀員使用掃碼設備讀取用戶微信/支付寶/云閃付等APP付款碼以后,二維碼或條碼信息傳送至商戶收銀臺,由商戶收銀臺或者商戶后臺調用該接口發(fā)起支付對用戶進行收款
備注:我使用的是flask框架
1、支付
import time
import flask
from flask import Flask
from tool import *
app = Flask(__name__)
@app.route('/Pay', methods=['GET', 'POST'])
def gello_world():
# 提交付款碼支付API
url = 'https://pay.hstypay.com/v2/pay/gateway'
try:
form_data = flask.request.get_data() # 獲取未經(jīng)處理過的原始數(shù)據(jù)而不管內容類型
json_data = json.loads(form_data.decode('utf-8')) # 字符串轉化為字典
for i in ('service', 'mch_id', 'out_trade_no', 'body', 'total_fee', 'mch_create_ip', 'auth_code'):
if i not in json_data.keys():
# 將沒有指定參添加上
json_data[i] = None
# 獲取請求參數(shù),并增加必帶參數(shù)
data = {
"service": "unified.trade.micropay", # 接口類型
"mch_id": MCH_ID, # 門店編號
"out_trade_no": json_data["out_trade_no"], # 商戶訂單號
"body": json_data["body"], # 商品描述
"total_fee": json_data["total_fee"], # 總金額
"mch_create_ip": get_ip(), # 終端IP
"auth_code": json_data["auth_code"], # 授權碼
"nonce_str": random_str() # 隨機字符串
}
sign = get_sign(data) # 生成簽名
data["sign"] = sign # 請求的參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 將xml數(shù)據(jù)轉為python中的dict字典數(shù)據(jù)
try:
if result['xml']['err_code'] == 'USERPAYING': # 需要輸入密碼的情況
# 然后開始查詢
for i in range(6):
print(i+1, '次')
time.sleep(5)
rest = query_tool(data)
if i != 5 and rest['xml']['trade_state'] != 'SUCCESS':
continue
elif rest['xml']['trade_state'] == 'SUCCESS':
print('成功支付', rest)
result = query_tool(data)
result['xml']['pay_code'] = "支付成功"
return result
elif rest['xml']['trade_state'] != 'SUCCESS' and i == 5: # 需要輸入密碼的情況
revoke_tool(data) # 調用撤銷接口
result = query_tool(data)
return result
else:
result = query_tool(data)
return result
else:
return result
except:
return result
except Exception as e:
print(e)
raise e
這里我有使用到查詢訂單、簽名、遠程調取配置信息、生成隨機字符串、字典轉XML、獲取本機ip,方法如下:
(遠程調取配置信息)
def get_configuration_info():
"""
獲取前置機賬戶配置
"""
url = "http://*******"
Port = "***"
CpnID = "***"
res = requests.request('post', url, data={"Port": Port, "CpnID": CpnID})
result = json.loads(res.content)
result["data"] = json.loads(result["data"])
aws_s3_url = result["data"]["cpnActMchinRcd"][0]["ActCfg"]
res = requests.request('GET', aws_s3_url)
aws_str = str(res.content)
awx_json = parse.unquote(aws_str)
for a in json.loads(awx_json[2:-1]):
if a["Keyname"] == "mch_id":
MCH_ID = a["Keyval"]
elif a["Keyname"] == "key":
MD5_KEY = a["Keyval"]
return {'MCH_ID': MCH_ID, 'MD5_KEY': MD5_KEY}
MCH_ID = get_configuration_info()['MCH_ID'] # mch_id
MD5_KEY = get_configuration_info()['MD5_KEY'] # MD5密鑰
(生成16位隨機字符串)
def random_str(randomlength=16):
"""
生成隨機字符串
:param randomlength: 字符串長度
:return:
"""
strs = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
strs += chars[random.randint(0, length)]
return strs
(計算簽名)
def get_sign(data_dict):
"""
簽名函數(shù)
:param data_dict: 需要簽名的參數(shù),格式為字典
:param key: 密鑰 ,即上面的MD5_KEY
:return: 字符串
注意:簽名是需要將所有的請求的參賽參與計算,空值不參與計算
"""
data = {}
for k in sorted(data_dict.keys()): # 遍歷字典參數(shù)名ASCII字典序排序后的key
v = data_dict.get(k) # 取出字典中key對應的value
if type(v) == list: # 添加XML標記
v = '![CDATA[{}]]'.format(v)
data[k] = v
params_list = sorted(data.items(), key=lambda e: e[0], reverse=False) # 參數(shù)字典參數(shù)名ASCII字典序排序為列表
params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + MD5_KEY # 組織參數(shù)字符串并在末尾添加商戶交易密鑰
md5 = hashlib.md5() # 使用MD5加密模式
md5.update(params_str.encode('utf-8')) # 將參數(shù)字符串傳入
sign = md5.hexdigest().upper() # 完成加密并轉為大寫
return sign
(字典轉XML)
def trans_dict_to_xml(data_dict):
"""
定義字典轉XML的函數(shù)
:param data_dict:
:return:
"""
data_xml = []
for k in data_dict.keys(): # 遍歷字典的key
v = data_dict.get(k) # 取出字典中key對應的value
if type(v) == list: # 添加XML標記
v = '![CDATA[{}]]'.format(v)
data_xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
return '<xml>{}</xml>'.format(''.join(data_xml)) # 返回XML
(獲取本機ip)
def get_ip():
# 獲取當前ip
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('1.1.1.1', 80))
ip = s.getsockname()[0]
s.close()
return ip
(查詢訂單)
def query_tool(json_data):
"""
查詢
"""
try:
# 必帶參數(shù)
data = {
"service": "unified.trade.query", # 接口類型
"mch_id": MCH_ID, # 門店編號
"nonce_str": random_str(), # 隨機字符串
"out_trade_no": json_data["out_trade_no"] # 商戶訂單號
}
sign = get_sign(data) # 生成簽名
data['sign'] = sign # 必帶參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', "https://pay.hstypay.com/v2/pay/gateway", data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content)))
return result
except Exception as e:
raise e
2、查詢訂單(方法調用的是上面的查詢方法)
@app.route('/Query', methods=['GET', 'POST'])
def query():
# 查詢
try:
form_data = flask.request.get_data() # 獲取未經(jīng)處理過的原始數(shù)據(jù)而不管內容類型
json_data = json.loads(form_data.decode('utf-8')) # 字符串轉化為字典
result = query_tool(json_data)
return result
except Exception as e:
print(e)
raise e
3、撤銷訂單
@app.route('/Revoke', methods=['GET', 'POST'])
def revoke():
# 撤銷
try:
form_data = flask.request.get_data() # 獲取到請求帶的信息
json_data = json.loads(form_data.decode('utf-8')) # 轉為json格式
result = revoke_tool(json_data)
return result
except Exception as e:
print(e)
raise e
(撤銷訂單方法)
def revoke_tool(json_data):
"""
撤銷訂單
"""
url = "https://pay.hstypay.com/v2/pay/gateway" # 撤銷請求接口
try:
# 必帶參數(shù)
data = {
"service": "unified.micropay.reverse",
"mch_id": MCH_ID,
"out_trade_no": json_data['out_trade_no'],
"nonce_str": random_str(),
}
sign = get_sign(data) # 生成簽名
data['sign'] = sign # 必帶參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 將xml數(shù)據(jù)轉為python中的dict字典數(shù)據(jù)
return result
except Exception as e:
raise e
4、退款
@app.route('/Refund', methods=['GET', 'POST'])
def refund():
# 退款
try:
url = 'https://pay.hstypay.com/v2/pay/gateway'
form_data = flask.request.get_data() # 獲取到請求帶的信息
json_data = json.loads(form_data.decode('utf-8')) # 轉為json格式
data = {
"service": "unified.trade.refund", # 接口類型
"mch_id": MCH_ID, # 門店編號
"out_trade_no": json_data["out_trade_no"], # 訂單號
"out_refund_no": json_data["out_refund_no"], # 商戶退款單號
"total_fee": json_data["total_fee"], # 總金額
"refund_fee": json_data["refund_fee"], # 退款金額
"op_user_id": json_data["op_user_id"], # 操作員
"nonce_str": random_str(), # 隨機字符串
"body": json_data["body"] # 退款原因
} # 必帶參數(shù)
sign = get_sign(data) # 生成簽名
data['sign'] = sign # 必帶參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 將xml數(shù)據(jù)轉為python中的dict字典數(shù)據(jù)
print(result)
if result['xml']['result_code'] == 0:
result = query_refund_tool(data)
return result
else:
return result
except Exception as e:
raise e
5、退款查詢
@app.route('/QueryRefund', methods=['GET', 'POST'])
def query_refund():
# 退款查詢
try:
form_data = flask.request.get_data() # 獲取到請求帶的信息
json_data = json.loads(form_data.decode('utf-8')) # 轉為json格式
result = query_refund_tool(json_data)
return result
except Exception as e:
raise e
(退款查詢方法)
def query_refund_tool(json_data):
"""
查詢退款
"""
url = "https://pay.hstypay.com/v2/pay/gateway" # 退款請求接口
try:
# 必帶參數(shù)
data = {
"service": "unified.trade.refundquery",
"mch_id": MCH_ID,
"out_trade_no": json_data["out_trade_no"],
"out_refund_no": json_data["out_refund_no"],
"nonce_str": random_str(), # 隨機字符串
}
sign = get_sign(data)
data['sign'] = sign
xml_str = trans_dict_to_xml(data)
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content)))
return result
except Exception as e:
raise e
備注:如果看暈了 可以直接拷貝下面的整體
因為我就只寫了2個腳本,1個用于跑主代碼、1個跑工具
pay.py(跑主要代碼的)
import time
import flask
from flask import Flask
from tool import *
app = Flask(__name__)
@app.route('/Pay', methods=['GET', 'POST'])
def gello_world():
# 提交付款碼支付API
url = 'https://pay.hstypay.com/v2/pay/gateway'
try:
form_data = flask.request.get_data() # 獲取未經(jīng)處理過的原始數(shù)據(jù)而不管內容類型
json_data = json.loads(form_data.decode('utf-8')) # 字符串轉化為字典
for i in ('service', 'mch_id', 'out_trade_no', 'body', 'total_fee', 'mch_create_ip', 'auth_code'):
if i not in json_data.keys():
# 將沒有指定參添加上
json_data[i] = None
# 獲取請求參數(shù),并增加必帶參數(shù)
data = {
"service": "unified.trade.micropay", # 接口類型
"mch_id": MCH_ID, # 門店編號
"out_trade_no": json_data["out_trade_no"], # 商戶訂單號
"body": json_data["body"], # 商品描述
"total_fee": json_data["total_fee"], # 總金額
"mch_create_ip": get_ip(), # 終端IP
"auth_code": json_data["auth_code"], # 授權碼
"nonce_str": random_str() # 隨機字符串
}
sign = get_sign(data) # 生成簽名
data["sign"] = sign # 請求的參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 將xml數(shù)據(jù)轉為python中的dict字典數(shù)據(jù)
try:
if result['xml']['err_code'] == 'USERPAYING': # 需要輸入密碼的情況
# 然后開始查詢
for i in range(6):
print(i+1, '次')
time.sleep(5)
rest = query_tool(data)
if i != 5 and rest['xml']['trade_state'] != 'SUCCESS':
continue
elif rest['xml']['trade_state'] == 'SUCCESS':
print('成功支付', rest)
result = query_tool(data)
result['xml']['pay_code'] = "支付成功"
return result
elif rest['xml']['trade_state'] != 'SUCCESS' and i == 5: # 需要輸入密碼的情況
revoke_tool(data) # 調用撤銷接口
result = query_tool(data)
return result
else:
result = query_tool(data)
return result
else:
return result
except:
return result
except Exception as e:
print(e)
raise e
@app.route('/Query', methods=['GET', 'POST'])
def query():
# 查詢
try:
form_data = flask.request.get_data() # 獲取未經(jīng)處理過的原始數(shù)據(jù)而不管內容類型
json_data = json.loads(form_data.decode('utf-8')) # 字符串轉化為字典
result = query_tool(json_data)
return result
except Exception as e:
print(e)
raise e
@app.route('/Revoke', methods=['GET', 'POST'])
def revoke():
# 撤銷
try:
form_data = flask.request.get_data() # 獲取到請求帶的信息
json_data = json.loads(form_data.decode('utf-8')) # 轉為json格式
result = revoke_tool(json_data)
return result
except Exception as e:
print(e)
raise e
@app.route('/Refund', methods=['GET', 'POST'])
def refund():
# 退款
try:
url = 'https://pay.hstypay.com/v2/pay/gateway'
form_data = flask.request.get_data() # 獲取到請求帶的信息
json_data = json.loads(form_data.decode('utf-8')) # 轉為json格式
data = {
"service": "unified.trade.refund", # 接口類型
"mch_id": MCH_ID, # 門店編號
"out_trade_no": json_data["out_trade_no"], # 訂單號
"out_refund_no": json_data["out_refund_no"], # 商戶退款單號
"total_fee": json_data["total_fee"], # 總金額
"refund_fee": json_data["refund_fee"], # 退款金額
"op_user_id": json_data["op_user_id"], # 操作員
"nonce_str": random_str(), # 隨機字符串
"body": json_data["body"] # 退款原因
} # 必帶參數(shù)
sign = get_sign(data) # 生成簽名
data['sign'] = sign # 必帶參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 將xml數(shù)據(jù)轉為python中的dict字典數(shù)據(jù)
print(result)
if result['xml']['result_code'] == 0:
result = query_refund_tool(data)
return result
else:
return result
except Exception as e:
raise e
@app.route('/QueryRefund', methods=['GET', 'POST'])
def query_refund():
# 退款查詢
try:
form_data = flask.request.get_data() # 獲取到請求帶的信息
json_data = json.loads(form_data.decode('utf-8')) # 轉為json格式
result = query_refund_tool(json_data)
return result
except Exception as e:
raise e
if __name__ == '__main__':
app.run()
tools.py(跑工具的)
import hashlib
import json
import os
import socket
from collections import defaultdict
from random import Random
from urllib import parse
import xmltodict
import requests
import OpenSSL
def get_configuration_info():
"""
獲取前置機賬戶配置
"""
url = "http://******"
Port = "4013"
CpnID = "001111"
res = requests.request('post', url, data={"Port": Port, "CpnID": CpnID})
result = json.loads(res.content)
result["data"] = json.loads(result["data"])
aws_s3_url = result["data"]["cpnActMchinRcd"][0]["ActCfg"]
res = requests.request('GET', aws_s3_url)
aws_str = str(res.content)
awx_json = parse.unquote(aws_str)
for a in json.loads(awx_json[2:-1]):
if a["Keyname"] == "mch_id":
MCH_ID = a["Keyval"]
elif a["Keyname"] == "key":
MD5_KEY = a["Keyval"]
return {'MCH_ID': MCH_ID, 'MD5_KEY': MD5_KEY}
MCH_ID = get_configuration_info()['MCH_ID'] # mch_id
MD5_KEY = get_configuration_info()['MD5_KEY'] # MD5密鑰
def random_str(randomlength=16):
"""
生成隨機字符串
:param randomlength: 字符串長度
:return:
"""
strs = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
strs += chars[random.randint(0, length)]
return strs
def get_sign(data_dict):
"""
簽名函數(shù)
:param data_dict: 需要簽名的參數(shù),格式為字典
:param key: 密鑰 ,即上面的MD5_KEY
:return: 字符串
注意:簽名是需要將所有的請求的參賽參與計算,空值不參與計算
"""
data = {}
for k in sorted(data_dict.keys()): # 遍歷字典參數(shù)名ASCII字典序排序后的key
v = data_dict.get(k) # 取出字典中key對應的value
if type(v) == list: # 添加XML標記
v = '![CDATA[{}]]'.format(v)
data[k] = v
params_list = sorted(data.items(), key=lambda e: e[0], reverse=False) # 參數(shù)字典參數(shù)名ASCII字典序排序為列表
params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + MD5_KEY # 組織參數(shù)字符串并在末尾添加商戶交易密鑰
md5 = hashlib.md5() # 使用MD5加密模式
md5.update(params_str.encode('utf-8')) # 將參數(shù)字符串傳入
sign = md5.hexdigest().upper() # 完成加密并轉為大寫
return sign
def trans_dict_to_xml(data_dict):
"""
定義字典轉XML的函數(shù)
:param data_dict:
:return:
"""
data_xml = []
for k in data_dict.keys(): # 遍歷字典的key
v = data_dict.get(k) # 取出字典中key對應的value
if type(v) == list: # 添加XML標記
v = '![CDATA[{}]]'.format(v)
data_xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
return '<xml>{}</xml>'.format(''.join(data_xml)) # 返回XML
def query_tool(json_data):
"""
查詢
"""
try:
# 必帶參數(shù)
data = {
"service": "unified.trade.query", # 接口類型
"mch_id": MCH_ID, # 門店編號
"nonce_str": random_str(), # 隨機字符串
"out_trade_no": json_data["out_trade_no"] # 商戶訂單號
}
sign = get_sign(data) # 生成簽名
data['sign'] = sign # 必帶參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', "https://pay.hstypay.com/v2/pay/gateway", data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content)))
return result
except Exception as e:
raise e
def query_refund_tool(json_data):
"""
查詢退款
"""
url = "https://pay.hstypay.com/v2/pay/gateway" # 退款請求接口
try:
# 必帶參數(shù)
data = {
"service": "unified.trade.refundquery",
"mch_id": MCH_ID,
"out_trade_no": json_data["out_trade_no"],
"out_refund_no": json_data["out_refund_no"],
"nonce_str": random_str(), # 隨機字符串
}
sign = get_sign(data)
data['sign'] = sign
xml_str = trans_dict_to_xml(data)
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content)))
return result
except Exception as e:
raise e
def revoke_tool(json_data):
"""
撤銷訂單
"""
url = "https://pay.hstypay.com/v2/pay/gateway" # 撤銷請求接口
try:
# 必帶參數(shù)
data = {
"service": "unified.micropay.reverse",
"mch_id": MCH_ID,
"out_trade_no": json_data['out_trade_no'],
"nonce_str": random_str(),
}
sign = get_sign(data) # 生成簽名
data['sign'] = sign # 必帶參數(shù)中添加簽名
xml_str = trans_dict_to_xml(data) # 字典轉換xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公眾平臺服務器發(fā)起請求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 將xml數(shù)據(jù)轉為python中的dict字典數(shù)據(jù)
return result
except Exception as e:
raise e
def get_ip():
# 獲取當前ip
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('1.1.1.1', 80))
ip = s.getsockname()[0]
s.close()
return ip