支付对接往往是个人开发者的心病,由于央妈不断收紧的政策,各大支付平台仅为企业用户提供了便捷的开发者服务。在此我想安利一款第三方支付平台:支付猫,它以0开户费和微信1.38%的低费率俘获了大多数个人开发者的芳心。
下面我将给出微信Native 支付(即用户扫码支付)在我Django项目中的实例,看客老爷们可根据需要修改。
由于缺少Django开发经验,下面的部分代码可能很魔性,代码风格也十分粗放……
支付猫微信Native 支付开发文档:
https://www.paycats.cn/docs/v1/api/wx/native
pay.py(支付数据处理):
# -*- encoding: utf-8 -*-
"""
@File : pay.py
@Time : 2019/8/19 21:42
@Author : MrZilinXiao
@Email : me@mrxiao.net
@Software: PyCharm
"""
import hashlib
import time
from random import Random
def random_str(randomlength=8):
"""
生成随机字符串
:param randomlength: 字符串长度
:return:
"""
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
str += chars[random.randint(0, length)]
return str
def order_num(phone):
"""
生成扫码付款订单号
:param phone: 手机号
:return:
"""
local_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
result = phone + 'T' + local_time + random_str(5)
return result
def get_sign(data_dict, key):
# 签名函数,参数为签名的数据和密钥
params_list = sorted(data_dict.items(), key=lambda e: e[0], reverse=False) # 参数字典倒排序为列表
params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list if v!='') + '&key=' + key
# 组织参数字符串并在末尾添加商户交易密钥
md5 = hashlib.md5() # 使用MD5加密模式
md5.update(params_str.encode('utf-8')) # 将参数字符串传入
sign = md5.hexdigest().upper() # 完成加密并转为大写
return sign
views.py:
@login_required
def wxpay(request):
response = Pay(request, 'WeChat')
return response
@login_required
def alipay(request):
response = Pay(request, 'AliPay')
return response
def Pay(request, method):
try:
if request.method == 'POST':
UserQ = User.objects.get(username=request.user.username)
total_fee = request.POST['amount']
fee_pattern = "(^[1-9](\d+)?(\.\d{1,2})?$)|(^0$)|(^\d\.\d{1,2}$)" # 匹配金额正则
pattern = re.compile(fee_pattern)
match = pattern.match(total_fee)
if match is None:
raise Exception("金额不合法!")
amount = int(float(total_fee) * 100)
self_order_num = pay.order_num()
body = '注入' + total_fee + '神秘点数'
post_paras = {
'mch_id': mch_id,
'total_fee': amount,
'out_trade_no': self_order_num,
'body': body,
'user_id': str(UserQ.UserProfile.uid)
}
sign = pay.get_sign(post_paras, secret_key)
post_paras['sign'] = sign
if method == 'WeChat':
url = wxpay_url
else:
url = alipay_url
response = requests.post(url=url, data=post_paras, timeout=3)
response_dict = response.json()
if response_dict['return_code'] != 0:
raise Exception(
"请求错误,错误代码:" + response_dict['return_code'] + ",错误信息:" + response_dict['return_message'])
else:
qrcode = response_dict['qrcode']
platform_order_no = response_dict['order_no']
order = Orders(user=UserQ, method=method, trade_no=self_order_num, platform_no=platform_order_no,
body=body, total_fee=total_fee)
order.save()
return JsonResponse(
{'status': 200, 'qrcode': qrcode, 'amount': amount / 100, 'order_no': self_order_num})
except Exception as e:
errormsgpay = str(e)
logger.error(errormsgpay)
return JsonResponse({'status': 400, 'msg': errormsgpay})
@login_required
def check_pay(request):
if request.is_ajax:
order_num = request.POST.get('order_num')
order = Orders.objects.get(trade_no=order_num)
if order.status == 1:
order.status = 2 # 2--通知已到位
order.save()
return HttpResponse(int(order.status))
@login_required
def cancel_order(request):
if request.is_ajax:
order_num = request.POST.get('order_num')
order = Orders.objects.get(trade_no=order_num) # 在知道订单号的情况下允许其他用户取消订单
if order.status == 0: # 只有等待中的订单才能取消
post_paras = {
'mch_id': mch_id,
'order_no': order.platform_no,
}
sign = pay.get_sign(post_paras, secret_key)
post_paras['sign'] = sign
response = requests.post(url=cancel_url, data=post_paras)
response_dict = response.json()
if response_dict['return_code'] == 0: # 出错不return
order.status = -1
order.save()
CreateNotification(username=request.user.username, title="订单取消成功",
content="您订单号为" + order.platform_no + "的注入" + str(order.total_fee) + "点数的订单已经成功取消!")
return HttpResponse(int(order.status))
@csrf_exempt # 回调需要关闭csrf
def paycat_callback(request): # 暂时支持支付通知 关闭通知 需要验签 验重
try:
if request.method == 'POST':
data_dict = request.POST.dict() # 商户号和金额为整数 注意转换否则签名错误
data_dict['mch_id'] = int(data_dict['mch_id'])
if data_dict['notify_type'] == 'order.succeeded':
data_dict['total_fee'] = int(data_dict['total_fee'])
sign = data_dict.pop('sign')
back_sign = pay.get_sign(data_dict, secret_key)
if sign == back_sign:
out_trade_no = data_dict['out_trade_no']
order = Orders.objects.get(trade_no=out_trade_no)
if data_dict['notify_type'] == 'order.succeeded':
if order.status == 0:
total_fee = data_dict['total_fee']
transaction_id = data_dict['transaction_id']
payTime = data_dict['pay_at']
if total_fee == order.total_fee * 100:
order.payment_tool_no = transaction_id
order.payTime = payTime
order.status = 1
order.save()
userprofile = order.user.UserProfile
userprofile.points += order.total_fee
userprofile.save()
CreateNotification(username=order.user.username, title="点数注入成功",
content="您订单号为" + order.trade_no + "的注入" + str(
order.total_fee) + "点数的订单已经完成支付!点数已经注入您的账户!")
return HttpResponse(1)
else:
CreateNotification(username=order.user.username, title="点数注入失败",
content="您订单号为" + order.trade_no + "的注入" + str(
order.total_fee) + "点数的订单没有通过金额效验。")
raise Exception("金额效验失败!")
else:
raise Exception("支付成功回调提示:订单已经标记为完成!")
elif data_dict['notify_type'] == 'order.closed':
if order.status == 0:
closedTime = data_dict['closed_at']
order.closedTime = closedTime
order.status = -1
order.save()
return HttpResponse(1)
else:
raise Exception("关闭订单回调提示:订单已经标记为完成!")
else:
raise Exception('签名效验失败!')
except Exception as e:
logger.error(str(e))
return HttpResponse(status=200)
models.py:
class Orders(models.Model):
user = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name='Orders')
method = models.CharField('支付方式', max_length=100, default='') # 支付宝 微信
trade_no = models.CharField('商户单号', max_length=100, default='')
platform_no = models.CharField('第三方平台单号', max_length=100, default='')
payment_tool_no = models.CharField('用户支付工具单号', max_length=100, default='')
body = models.CharField('订单标题', max_length=1000, default='')
total_fee = models.FloatField('订单金额') # 1.33 以元为单位
addTime = models.DateTimeField('订单生成时间', auto_now_add=True)
payTime = models.CharField('订单支付时间', max_length=1000)
closedTime = models.CharField('订单关闭时间', max_length=1000)
status = models.IntegerField('状态', default=0) # 0--等待 -1--失败 1---成功
前端操作示意(以微信支付为例):
$('#wxpay').click(function () {
$.ajax({
url: '{% url "wxpay" %}',
type: 'post',
data: {
'amount': $('#amount').val(),
'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val(),
},
success: function (data) {
if (data.status === 200) {
var qrcode = data.qrcode;
var amount = data.amount;
var order_num = data.order_no;
var int = self.setInterval(function () {
pay_status()
}, 1000);
function pay_status() {
$.ajax({
url: '{% url 'check_pay' %}',
dataType: 'json',
type: 'post',
data: {
'order_num': order_num,
'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val(),
},
success: function (data) {
if (data == '1' || data == '2') {
window.clearInterval(int); //销毁定时器
swal(
'支付成功',
'你成功地注入了' + amount + '点数!',
'success'
)
} else if (data == '-1') {
window.clearInterval(int); //销毁定时器
swal(
'支付失败',
'如果你已经被成功扣款,请联系管理员!',
'error'
);
}
},
error: function () {
window.clearInterval(int);
swal(
'请求出错',
'请重新提交订单!',
'error'
);
},
});
}
swal({
title: '微信支付',
html: true,
text: '订单号: ' + order_num +
'<br /> 支付金额: ' + amount + '元<br />' +
'<div><img src="' + qrcode + '" /></div>',
showConfirmButton: true,
showCancelButton: false,
confirmButtonText:
'取消订单',
closeOnConfirm: false,
showLoaderOnConfirm: true
},
function (isConfirm) {
if (isConfirm) {
window.clearInterval(int);
$.ajax({
url: '{% url 'cancel_order' %}',
dataType: 'json',
type: 'post',
data: {
'order_num': order_num,
'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val(),
},
success: function (data) {
if (data == '-1') {
swal(
'订单已取消',
'如果遇到问题可以及时联系客服!:)',
'success'
);
}
},
error: function () {
swal(
'取消失败',
'如果扣款成功请联系客服!',
'error'
);
}
})
}
}
)
} else{
$('.has-error').html(data.msg)
}
}
})
})
整个过程需要注意的无非是:
- 验证金额有效性,这里采用正则式
(^\[1-9\](\d+)?(\.\d{1,2})?$)|(^0$)|(^\d\.\d{1,2}$)
匹配,注意提交数据以人民币“分”为单位。 - 验证回复合法性,即返回值为0。