小程序微信支付

1.沙箱环境

一个用于开发测试的环境。

2.微信小程序支付

2.1 微信小程序平台

  • 个人
  • 企业(微信支付)

2.2 商户平台账号(企业)

  • 开通商户平台
  • 小程序 和 商户平台账号关联

2.3 账号

  • AppID
  • 商户号
  • 商户key(关键)

3. 微信支付的步骤

  • 登录,获取用户openid,前端发送openid给后端,后端生成一堆数据返回前端, 前端获取这一大堆数据,然后弹出支付二维码
  • 挑选商品去支付
    • 生成订单(待支付)
    • 用户扫码支付给微信
    • 微信通知咱们系统,咱们系统更改订单状态。

4.案例

4.1 用户登录

  • 小程序

    wx.login
    wx.requestPayment
    
  • 后端

    通过wx_code获取openid
    

4.2 支付

  • 小程序

    • 请求

    index.wxml

    <!--index.wxml-->
     <radio-group bindchange="changeGoods">
        <view class='row' wx:for="{{goodsList}}" wx:key="index">
          <text>{{item.title}} - {{item.price}}</text>
          <radio class="radio" value="{{item.id}}"></radio>
        </view>
    </radio-group>
    
    <button bindtap="doPayment">购买</button>
    

    index.js

    //index.js
    //获取应用实例
    const app = getApp()
    
    Page({
      data: {
        goodsList:[],
        seletedId:null
      },
        
        // 支付
      doPayment:function(){
        // this.data.seletedId
        // 向后台发送一个请求,后端生成一大堆数据返回给前端
        // 前端获取这一大堆数据,然后弹出支付二维码
    
        wx.request({
          url: 'http://127.0.0.1:8002/payment/',
          data: {
            goodsId: this.data.seletedId
          },
          method: 'POST',
          dataType: 'json',
          responseType: 'text',
          success: (res) => {
            console.log(res.data);
            wx.requestPayment(
              {
                'timeStamp': res.data.timeStamp,
                'nonceStr': res.data.nonceStr,
                'package': res.data.package,
                'signType': res.data.signType,
                'paySign': res.data.paySign,
                'success': function (res) {
                    
                 },
                'fail': function (res) {
    
                 },
                'complete': function (res) { 
    
                }
              })
          }
        })
    
      },
        
        // 选中商品
      changeGoods:function(e){
        this.setData({
          seletedId: e.detail.value
        })
      },
        
        	
      onLoad: function () {
        wx.request({
          url: 'http://127.0.0.1:8002/goods/',
          method: 'GET',
          dataType: 'json',
          responseType: 'text',
          success: (res) => {
            this.setData({
              goodsList:res.data
            })
          }
        })
      }
    })
    
    
  • 后端

    • 统一下单-> prepay_id
    • prepay_id + 再签名,给前端返回

    urls.py

       url(r'^login/', views.LoginView.as_view()),
        url(r'^goods/', views.GoodsView.as_view()),
        url(r'^payment/', views.PaymentView.as_view()),
        url(r'^notify/', views.NotifyView.as_view()),
    

    views.py

    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from app01 import models
    import uuid
    import random
    import requests
    from rest_framework.generics import ListAPIView
    from rest_framework import serializers
    import time
    from xml.etree import ElementTree as ET
    
    
    class LoginView(APIView):
    
        def post(self, request, *args, **kwargs):
            """
            用户登录
            :param request:
            :param args:
            :param kwargs:
            :return:
            """
            phone = request.data.get('phone')
            wx_code = request.data.get('wx_code')
            # openid的获取:需要拿着wx_code去微信申请
            #  https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
            info = {
                'appid': "wx55cca0b94f723dc7",  # 微信小程序
                'secret': "c000e3ddc95d2ef723b9b010f0ae05d5",  # 微信小程序
                'js_code': wx_code,
                'grant_type': "authorization_code",
            }
            result = requests.get(url='https://api.weixin.qq.com/sns/jscode2session', params=info)
            openid = result.json()['openid']
            exists = models.UserInfo.objects.filter(phone=phone).exists()
            token = str(uuid.uuid4())
            if not exists:
                models.UserInfo.objects.create(
                    phone=phone,
                    token=token,
                    openid=openid
                )
            else:
                models.UserInfo.objects.filter(phone=phone).update(token=token, openid=openid)
    
            return Response({'token': token})
    
    
    def md5(string):
        import hashlib
        m = hashlib.md5()
        m.update(string.encode('utf-8'))
        return m.hexdigest()
    
    
    class GoodsModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Goods
            fields = "__all__"
    
    
    class GoodsView(ListAPIView):
        queryset = models.Goods.objects
        serializer_class = GoodsModelSerializer
    
    
    class PaymentView(APIView):
    
        def post(self, request, *args, **kwargs):
            goods_id = request.data.get('goodsId')
            order_random_string = str(int(time.time()))
            user_object = models.UserInfo.objects.filter(id=1).first()  # user_object.openid
            goods_object = models.Goods.objects.filter(id=goods_id).first()  # goods_object.price
            order_object = models.Order.objects.create(goods=goods_object, user=user_object, uid=order_random_string,
                                                       status=1)
    
            # 按照微信的规则,去生成支付需要的一大堆的数据
            # https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1
            print(order_random_string)
            # ###################### 1.调用支付统一下单 ######################
            info = {
                'appid': 'wx55cca0b94f723dc7',
                'mch_id': '1526049051',
                'device_info': 'wupeiqi-min-program',
                'nonce_str': "".join([chr(random.randint(65, 90)) for _ in range(12)]),
                'sign_type': "MD5",
                'body': "保证金",
                'detail': '这是一个商品详细描述信息.',
                'attach': '微信小程序',
                'out_trade_no': order_random_string,
                'total_fee': goods_object.price,  # 总金额
                'spbill_create_ip': request.META.get('REMOTE_ADDR'),
                # 终端IP(用户IP) remote_addr = request.META.get('REMOTE_ADDR')
                'notify_url': "http://47.93.4.198:8012/pay/notify/",  # 支付成功之后,微信异步通知
                'trade_type': 'JSAPI',
                'openid': user_object.openid  # openid
            }
            # 1.1 签名
            #       对字典中的key按照ASCII码从小到大排序
            #       将排完序的值拼接 stringA = appid=wx55cca0b94f723dc7&mch_id=1526049051
            #       让stringA和key拼接:stringSignTemp = stringA+"&key=192006250b4c09247ec02edce69f6a2d" key为商户平台设置的密钥key
            #       MD5(stringSignTemp)
            #       将密文转换为大写
            #       得到签名 sign
            #       把签名再添加到info中    info['sign'] = sign值
            pay_key = "2SzCvaKgYExuItWBfYAqJFs72uUleD14"
            temp = "&".join(["{0}={1}".format(k, info[k]) for k in sorted(info)] + ["{0}={1}".format("key", pay_key, ), ])
            sign = md5(temp).upper()
            info['sign'] = sign
            # 1.2 向 https://api.mch.weixin.qq.com/pay/unifiedorder 发请求 (json转换为xml)
    
            xml_string = "<xml>{0}</xml>".format("".join(["<{0}>{1}</{0}>".format(k, v) for k, v in info.items()]))
            prepay = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_string.encode('utf-8'))
            # 1.3 从结果xml中提取 prepay_id
    
            root = ET.XML(prepay.content.decode('utf-8'))
            prepay_dict = {child.tag: child.text for child in root}
            prepay_id = prepay_dict['prepay_id']
    
            # ####################### 2.再次签名 #######################
            info_dict = {
                'appId': "wx55cca0b94f723dc7",
                'timeStamp': str(int(time.time())),  # 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
                'nonceStr': "".join([chr(random.randint(65, 90)) for _ in range(12)]),  # 随机字符串,长度为32个字符以下。
                'package': 'prepay_id={0}'.format(prepay_id),  # 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
                'signType': 'MD5',  # 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致
            }
            temp = "&".join(
                ["{0}={1}".format(k, info_dict[k]) for k in sorted(info_dict)] + ["{0}={1}".format("key", pay_key, ), ])
            sign2 = md5(temp).upper()
            info_dict['paySign'] = sign2
    
            return Response(info_dict)
    
    
    class NotifyView(APIView):
        """
        支付完成之后的通知
        """
    
        def post(self, request, *args, **kwargs):
            # 1. 获取结果把结果XML转换为字典格式
            root = ET.XML(request.body.decode('utf-8'))
            result = {child.tag: child.text for child in root}
    
            # 2. 校验签名是否正确,防止恶意请求。
            sign = result.pop('sign')
    
            # key为商户平台设置的密钥key
            key = "2SzCvaKgYExuItWBfYAqJFs72uUleD14"
            temp = "&".join(
                ["{0}={1}".format(k, result[k]) for k in sorted(result)] + ["{0}={1}".format("key", key, ), ])
            local_sign = md5(temp).upper()
    
            # 签名一致
            if local_sign == sign:
                # 根据订单号,把数据库的订单状态修改为支付成功
                out_trade_no = result.get('out_trade_no')
                models.Order.objects.filter(uid=out_trade_no).update(status=2)
                response = """<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"""
                return Response(response)
    
    

    models.py

    from django.db import models
    
    class UserInfo(models.Model):
        """
        用户表
        """
        phone = models.CharField(verbose_name='手机号',max_length=32)
        token = models.CharField(verbose_name='Token',max_length=32)
        openid = models.CharField(verbose_name='微信唯一标识',max_length=32)
    
    
    class Goods(models.Model):
        """ 商品表 """
        title = models.CharField(verbose_name='商品名称',max_length=32)
        price = models.PositiveIntegerField(verbose_name='价格')
    
    
    class Order(models.Model):
        """ 订单表 """
        status_choices = (
            (1,'待支付'),
            (2,'已支付')
        )
        status = models.SmallIntegerField(verbose_name='状态',choices=status_choices)
        goods = models.ForeignKey(verbose_name='商品',to='Goods')
        user =  models.ForeignKey(verbose_name='用户',to='UserInfo')
    
        uid = models.CharField(verbose_name='订单号',max_length=64)
    
    

4.3 微信通知

  • 向指定接口发送POST

    • 校验是否合法
    • 更改订单状态
  • 问题

    • 为什么要再次进行校验?

    • 通知时服务器宕机如何解决?

      微信的通知如果没有执行成功,那么他会在24小时内向我们的服务器一直请求。
      
posted @ 2022-11-02 07:22  凫弥  阅读(262)  评论(0编辑  收藏  举报