开发文档:https://open.swiftpass.cn/openapi?code=hsty
这个聚合平台大概是集合了微信、支付宝、云闪付等支付平台,大概有下图这么多:
一、付款码支付
付款码的大概业务功能:
收银员使用扫码设备读取用户微信/支付宝/云闪付等APP付款码以后,二维码或条码信息传送至商户收银台,由商户收银台或者商户后台调用该接口发起支付对用户进行收款
备注:我使用的是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() # 获取未经处理过的原始数据而不管内容类型
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
# 获取请求参数,并增加必带参数
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 # 请求的参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 将xml数据转为python中的dict字典数据
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):
"""
签名函数
:param data_dict: 需要签名的参数,格式为字典
:param key: 密钥 ,即上面的MD5_KEY
:return: 字符串
注意:签名是需要将所有的请求的参赛参与计算,空值不参与计算
"""
data = {}
for k in sorted(data_dict.keys()): # 遍历字典参数名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) # 参数字典参数名ASCII字典序排序为列表
params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + MD5_KEY # 组织参数字符串并在末尾添加商户交易密钥
md5 = hashlib.md5() # 使用MD5加密模式
md5.update(params_str.encode('utf-8')) # 将参数字符串传入
sign = md5.hexdigest().upper() # 完成加密并转为大写
return sign
(字典转XML)
def trans_dict_to_xml(data_dict):
"""
定义字典转XML的函数
: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:
# 必带参数
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 # 必带参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', "https://pay.hstypay.com/v2/pay/gateway", data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
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() # 获取未经处理过的原始数据而不管内容类型
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:
# 必带参数
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 # 必带参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 将xml数据转为python中的dict字典数据
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"] # 退款原因
} # 必带参数
sign = get_sign(data) # 生成签名
data['sign'] = sign # 必带参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 将xml数据转为python中的dict字典数据
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:
# 必带参数
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方式向微信公众平台服务器发起请求
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() # 获取未经处理过的原始数据而不管内容类型
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
# 获取请求参数,并增加必带参数
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 # 请求的参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 将xml数据转为python中的dict字典数据
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() # 获取未经处理过的原始数据而不管内容类型
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"] # 退款原因
} # 必带参数
sign = get_sign(data) # 生成签名
data['sign'] = sign # 必带参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 将xml数据转为python中的dict字典数据
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):
"""
签名函数
:param data_dict: 需要签名的参数,格式为字典
:param key: 密钥 ,即上面的MD5_KEY
:return: 字符串
注意:签名是需要将所有的请求的参赛参与计算,空值不参与计算
"""
data = {}
for k in sorted(data_dict.keys()): # 遍历字典参数名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) # 参数字典参数名ASCII字典序排序为列表
params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + MD5_KEY # 组织参数字符串并在末尾添加商户交易密钥
md5 = hashlib.md5() # 使用MD5加密模式
md5.update(params_str.encode('utf-8')) # 将参数字符串传入
sign = md5.hexdigest().upper() # 完成加密并转为大写
return sign
def trans_dict_to_xml(data_dict):
"""
定义字典转XML的函数
: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:
# 必带参数
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 # 必带参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', "https://pay.hstypay.com/v2/pay/gateway", data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
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:
# 必带参数
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方式向微信公众平台服务器发起请求
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:
# 必带参数
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 # 必带参数中添加签名
xml_str = trans_dict_to_xml(data) # 字典转换xml
res = requests.request('post', url, data=xml_str.encode()) # 以POST方式向微信公众平台服务器发起请求
result = json.loads(json.dumps(xmltodict.parse(res.content))) # 将xml数据转为python中的dict字典数据
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
网友评论