支付对接往往是个人开发者的心病,由于央妈不断收紧的政策,各大支付平台仅为企业用户提供了便捷的开发者服务。在此我想安利一款第三方支付平台:支付猫,它以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。
Last modification:April 25th, 2023 at 10:05 pm
If you think my article is useful to you, please feel free to appreciate