Django小程序微信支付与微信退款
1.微信支付
1.1前期准备
需要准备微信支付的配置参数
# 微信支付的配置参数
client_appid = '' # 小程序appid
client_secret = '' # 小程序secret
Mch_id = '' # 商户号
Mch_key = '' # 商户Key
order_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder' # 订单地址
1.2用户下单
#下单接口
from django.db import transaction
@transaction.atomic 添加事务
def place_order(request):
if request.method == 'POST':
#接收相关订单参数包括地址数量等等,因为前端需要传递数组,选择使用json格式接收,formdata自行改为request.POST.get
r = json.loads(request.body.decode())
user = int(r['user_id'])
good_id = r['good_id']
# 从数据库中获取商品单价和运费,这里的单位还是元
good_obj = models.Goods.objects.get(gid=good_id)
unit_price = float(good_obj.now_price)
unit_freight = float(good_obj.freight)
money = unit_price*int(num) + unit_freight
#获得用户id
openid = getOpenid(user)
# 获取客户端ip,因为这里使用nginx代理,所以这里写服务器ip即可
client_ip = 'xxx.xxx.xxx.xx'
print(request.get_host())
# 请求微信的统一下单url,在上面配置参数有
url = order_url
# 拿到封装好的xml数据
body_data,out_trade_no= get_bodyData(openid, client_ip, money, user)
# 获取时间戳
timeStamp = str(int(time.time()))
# 请求微信接口下单
import requests
respone = requests.post(url, body_data.encode("utf-8"), headers={'Content-Type': 'application/xml'})
# 回复数据为xml,将其转为字典
content = xml_to_dict(respone.content)
ret = {"state": 1000}
sid = transaction.savepoint() #设置断点
if content["return_code"] == 'SUCCESS':
# 获取预支付交易会话标识
prepay_id = content.get("prepay_id")
package = 'prepay_id='+prepay_id
# 获取随机字符串
nonceStr = content.get("nonce_str")
# 获取paySign签名,这个需要我们根据拿到的prepay_id和nonceStr进行计算签名
paySign = get_paysign(prepay_id, timeStamp, nonceStr)
# 封装返回给前端的数据
res_dict = {'rcode': 200, 'package': package, 'paysign': paySign, 'timestamp': timeStamp,
'nonceStr': nonceStr, 'appId': client_appid, 'orderNumber': out_trade_no}
print('=========', res_dict)
try:
#以下是数据库存订单数据,根据自己需求存
order = models.Goods_Order.objects.create(
good_id=good_id,
user_id=user,
num=num,
unit_price = unit_price,
unit_freight=unit_freight,
money=money,
order_number=out_trade_no,
.,
.,
.,
.,
)
order_obj = models.Goods_Order.objects.get(order_number=out_trade_no)
for v in avid:
models.Order2Attr.objects.create(
order_id=order.goid,
attr_value_id=int(v)
)
av_obj = models.Attr_Value.objects.get(avid=int(v))
av_obj.stocks = F('stocks')-int(num)
av_obj.save()
order_detail = models.Order_Detail.objects.update_or_create(
order_id=order_obj.goid,
payment=0,
receipt=-1,
aftersale=0
)
good = models.Goods.objects.get(gid=good_id)
good.stocks = F('stocks')-int(num)
good.save()
return JsonResponse(res_dict,safe=False)
except Exception as e:
print(e)
transaction.savepoint_rollback(sid) #数据库出错立即回滚!
return JsonResponse({'msg':'下单失败'},safe=False)
transaction.clean_savepoints() #清除保存点
else:
ret["rcode"] = 500
ret["msg"] = "下单失败"
return JsonResponse(ret,safe=False)
#上面用到的封装好的相关函数
#获取用户openID
def getOpenid(userid):
userData = models.User.objects.get(uid=int(userid))
return userData.openID
#随机字符串生成算法
def random_str(randomlength=32):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random() #生成0到1的随机浮点数
for i in range(randomlength):
str+=chars[random.randint(0, length)]
return str
# 生成签名的函数
def paysign(appid, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee):
ret = {
"appid": appid,
"body": body,
"mch_id": mch_id,
"nonce_str": nonce_str,
"notify_url": notify_url,
"openid": openid,
"out_trade_no": out_trade_no,
"spbill_create_ip": spbill_create_ip,
"total_fee": total_fee,
"trade_type": 'JSAPI'
}
print(ret)
# 处理函数,对参数按照key=value的格式,并按照参数名ASCII字典序排序
stringA = '&'.join(["{0}={1}".format(k, ret.get(k)) for k in sorted(ret)])
stringSignTemp = '{0}&key={1}'.format(stringA, Mch_key)
sign = hashlib.md5(stringSignTemp.encode("utf-8")).hexdigest()
print(sign.upper())
return sign.upper()
def xml_to_dict(xml_data):
'''
xml to dict
:param xml_data:
:return:
'''
xml_dict = {}
root = ET.fromstring(xml_data)
for child in root:
xml_dict[child.tag] = child.text
return xml_dict
# 生成商品订单号
def getWxPayOrdrID(user):
date = datetime.datetime.now()
# 根据当前系统时间来生成商品订单号。时间精确到微秒
payOrdrID = date.strftime("%Y%m%d%H%M%S%f")
# 加一个用户id保证订单号唯一
payOrdrID = str(user)+payOrdrID
return payOrdrID
# 获取返回给小程序的paySign
def get_paysign(prepay_id, timeStamp, nonceStr):
pay_data = {
'appId': client_appid,
'nonceStr': nonceStr,
'package': "prepay_id=" + prepay_id,
'signType': 'MD5',
'timeStamp': timeStamp
}
stringA = '&'.join(["{0}={1}".format(k, pay_data.get(k)) for k in sorted(pay_data)]) #按照字母顺序依次返回pay_data的值并用&连接
stringSignTemp = '{0}&key={1}'.format(stringA, Mch_key)
sign = hashlib.md5(stringSignTemp.encode("utf-8")).hexdigest()
return sign.upper()
# 获取全部参数信息,封装成xml
def get_bodyData(openid, client_ip, price, user):
body = 'Mytest' # 商品描述
notify_url = 'http://www.xxx.com/goodorderNotify/' # 支付成功的回调地址 可访问 不带参数
nonce_str = random_str() # 随机字符串
out_trade_no = getWxPayOrdrID(user) # 商户订单号
total_fee = str(int(float(price)*100)) # 订单价格 单位是 分
# 获取签名
sign = paysign(client_appid, body, Mch_id, nonce_str, notify_url, openid, out_trade_no, client_ip, total_fee)
bodyData = '<xml>'
bodyData += '<appid>' + client_appid + '</appid>' # 小程序ID
bodyData += '<body>' + body + '</body>' # 商品描述
bodyData += '<mch_id>' + Mch_id + '</mch_id>' # 商户号
bodyData += '<nonce_str>' + nonce_str + '</nonce_str>' # 随机字符串
bodyData += '<notify_url>' + notify_url + '</notify_url>' # 支付成功的回调地址
bodyData += '<openid>' + openid + '</openid>' # 用户标识
bodyData += '<out_trade_no>' + out_trade_no + '</out_trade_no>' # 商户订单号
bodyData += '<spbill_create_ip>' + client_ip + '</spbill_create_ip>' # 客户端终端IP
bodyData += '<total_fee>' + total_fee + '</total_fee>' # 总金额 单位为分
bodyData += '<trade_type>JSAPI</trade_type>' # 交易类型 小程序取值如下:JSAPI
bodyData += '<sign>' + sign + '</sign>'
bodyData += '</xml>'
return bodyData, out_trade_no
2.微信退款
1.1前期准备
微信支付平台–》账户中心 – 》API安全 – 》 API证书,弄到手以后解压存放在后端
我放在了和app同级的目录里
1.2用户退款
#退款api
def refund_api(request):
if request.method == 'POST':
print(111)
order_id = int(request.POST.get('order_id',''))
refund_fee = str(request.POST.get('refund_fee',''))
result = dict()
# try:
# 获得退款订单号和订单号
order_obj = models.Goods_Order.objects.get(goid=order_id)
out_refund_no = getWxPayOrdrID(order_obj.user_id)
trade_no = order_obj.order_number
# 存入提交退款时间和单号
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
After_obj = models.AfterSale.objects.filter(order_id=order_id).update(
retund_time=now,
refund_number=out_refund_no,
)
# 将订单金额转换为分
total_fee = int(float(order_obj.money)*100)
refund_fee = int(float(refund_fee)*100)
res = refund(trade_no, out_refund_no, total_fee, refund_fee, order_id)
if res['flag'] == True:
result['flag'] = True
else:
pass
# except Exception as e:
# print(e)
# result['flag'] = False
return JsonResponse(result,safe=False)
def refund(trade_no, out_refund_no, total_fee, refund_fee,order_id):
'''
:param trade_no: 创建订单时自动生成的订单号
:param out_refund_no: 商户退款单号
:param total_fee: 订单金额
:param refund_fee: 退款金额
:return:
'''
info = {
'appid':client_appid,
'mch_id':Mch_id,
'out_trade_no':trade_no,
'nonce_str':random_str(),
'sign_type':'MD5',
'out_refund_no':out_refund_no,
'total_fee':total_fee,
'refund_fee':refund_fee
}
key = Mch_key
string = "&".join([f"{k}={info[k]}" for k in sorted(info)] + [f"{'key'}={key}"])
info['sign'] = md5(string).upper()
xml = "<xml>{}</xml>".format("".join([f"<{k}>{v}</{k}>" for k,v in info.items()]))
cert = f"{settings.BASE_DIR}/cert/apiclient_cert.pem"
key = f"{settings.BASE_DIR}/cert/apiclient_key.pem"
import requests
res = requests.post(
url = 'https://api.mch.weixin.qq.com/secapi/pay/refund',
data=xml.encode('utf-8'),
headers={
'Accept-Language':'zh-CN,zh;q=0.9'
},
cert=(cert,key),
verify=True
)
r = res.text.encode('ISO-8859-1').decode('utf-8')
response = xmltodict.parse(r)
result = dict()
print(response)
if response['xml']['return_code'] == 'SUCCESS':
#退款成功,修改状态,添加退款到钱包的时间
detail_obj = models.Order_Detail.objects.filter(order_id=order_id).update(aftersale=2)
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
after_obj = models.AfterSale.objects.filter(order_id=order_id).update(
received_time=now,
refund_result=3,
refund_fee=refund_fee
)
result['flag'] = True
else:
result['flag'] = False
return result