1. 前提准备

1.1 开通了微信支付功能(每年需要交300元)

  • 需要自己去申请,需要上传营业执照及其所要求的信息

1.2 小程序id&&小程序密钥

python pc微信小程序自动化 微信小程序调用python_python pc微信小程序自动化

1.3 商户密钥key

  •  key设置路径:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->设置API密钥

1.4 支付的官方文档

支付文档

python pc微信小程序自动化 微信小程序调用python_数据_02

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3

2.使用

前端

2.1 目录

python pc微信小程序自动化 微信小程序调用python_微信小程序_03

2.2 login(登录获取openid,小程序支付必须需要openid)

html

<!--pages/login/login.wxml-->
<input placeholder="请输入手机号" bindinput="inputPhone" value="{{phone}}"></input>
<button bindtap="getUserProfile">登录</button>

js

// pages/login/login.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    phone: '',
    openid: ''
  },

  // 获取input数据
  inputPhone: function (e) {
    // 获取input信息
    var phone = e.detail.value
    this.setData({
      phone: phone
    })
  },
  // 登录
  getUserProfile: function (e) {
    // 获取openid
    wx.login({
      success: res => {
        this.setData({
          openid: res.code
        })
      }
    })
    
    // 向后台发送数据
    wx.request({
      url: 'http://127.0.0.1:8023/login/',
      data: {
        phone: this.data.phone,
        openid: this.data.openid
      },
      dataType: "json",
      method: "POST",
      success: (result) => {
        console.log(result.data)
      },
    })
  },


  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

2.3 index

html

<!--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>

js

// pages/index/index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    goodsList: [],
    seletedId: ''
  },
  // 选中物品的id
  changeGoods: function (e) {
    // console.log(e.detail.value)
    this.setData({
      seletedId: e.detail.value
    })
    // console.log(this.data.seletedId)
  },
  // 点击购买

  doPayment: function () {
    this.data.seletedId
    // 向后台发送一个请求,生成一大堆数据
    // 获取这一大堆数据,然后弹出支付二维码

    wx.request({
      url: 'http://127.0.0.1:8023/payment/',
      data: {
        goodsId: this.data.seletedId
      },
      method: 'POST',
      dataType: 'json',
      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) {

          }
        })
      }
    })
  },



  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    // 物品渲染
    wx.request({
      url: 'http://127.0.0.1:8023/index/',
      method: 'GET',
      dataType: 'json',
      success: (res) => {
        this.setData({
          goodsList: res.data
        })
      }
    })
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

2.4 全局app.json

{
  "pages": [
    "pages/index/index",
    "pages/login/login"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "Weixin",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "backgroundColor": "#fafafa",
    "borderStyle": "white",
    "color": "#666",
    "selectedColor": "#b4282d",
    "position": "bottom",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页"
      },
      {
        "pagePath": "pages/login/login",
        "text": "登录"
      }
    ]
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json"
}

后端

2.1 url

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.IndexData.as_view()),
    path('login/', views.LoginView.as_view()),
    path('payment/', views.PaymentView.as_view()),
    path('pay/notify/', views.NotifyView.as_view()),
]

2.2 view

import time
import random

import requests
from rest_framework import serializers
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.generics import ListAPIView
from xml.etree import ElementTree as ET

from app01 import models


class IndexDataModelSerializer(serializers.ModelSerializer):
    """首页序列化器"""

    class Meta:
        model = models.Goods
        fields = '__all__'


class IndexData(ListAPIView):
    """首页渲染"""
    queryset = models.Goods.objects.all()
    serializer_class = IndexDataModelSerializer


class LoginView(APIView):
    """登录视图"""

    def post(self, request, *args, **kwargs):
        # 从前端获取数据
        phone = request.data.get('phone')
        wx_code = request.data.get('openid')
        # print(wx_code)
        if not wx_code:
            return Response('无openid')
        # print(wx_code)
        # openid的获取:需要拿着wx_code去微信申请
        #  https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
        # login服务端,发送请求的路由地址
        # https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
        info = {
            'appid': "微信小程序appid",  # 微信小程序appid
            'secret': "微信小程序密钥key",  # 微信小程序密钥key
            'js_code': wx_code,  # openid
            'grant_type': "authorization_code",  # 默认值即可
        }
        result = requests.get(url='https://api.weixin.qq.com/sns/jscode2session', params=info)
        openid = result.json()['openid']
        models.UserInfo.objects.get_or_create(phone=phone, defaults={'openid': openid})

        return Response('ok')


# 进行加密
def md5(string):
    """md5加密"""
    import hashlib
    m = hashlib.md5()
    m.update(string.encode('utf-8'))
    return m.hexdigest()


# 支付通知
class PaymentView(APIView):
    """支付视图及操作"""

    def post(self, request, *args, **kwargs):
        # 前端传送过来的商品id
        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 = {
            # 小程序id T
            'appid': '小程序id',
            # 商户关联id T
            'mch_id': '商户关联id',
            # 设备号 F
            'device_info': 'ymq-text',
            # 随机字符串 T
            'nonce_str': "".join([chr(random.randint(65, 90)) for _ in range(12)]),
            # 签名类型 F
            'sign_type': "MD5",
            # 商品描述 F
            'body': "支付测试",
            # 商品详情 F
            'detail': '这是一个商品详细描述信息.',
            # 附加数据 F
            'attach': '河南彭于晏',
            # 商户订单号 T
            'out_trade_no': order_random_string,
            # 标价金额 T
            'total_fee': goods_object.price,  # 总金额
            # 终端IP(用户ip) F
            'spbill_create_ip': request.META.get('REMOTE_ADDR'),
            # # 支付成功之后,微信异步通知(post) T
            'notify_url': "http://106.14.42.256:8023/pay/notify/",
            # 交易类型 T
            'trade_type': 'JSAPI',
            # 用户标识(trade_type=JSAPI,此参数必传)
            'openid': user_object.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值

        # 商户密钥key
        pay_key = "key为商户平台设置的密钥key"
        #
        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
            'appId': "小程序的appid",
            '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

        # print(info_dict)
        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 = "key为商户平台设置的密钥key"
        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)