mpvue1.0+python3.7+Django2.0.4实现微信小程序的支付功能

by Liu Yue/2019-09-15

    其实微信支付有很多种形式,刷脸,扫码,APP支付,小程序支付等,这边只说明小程序支付的实现,不过原理上都大同小异。

    首先,需要注册微信公众号平台https://mp.weixin.qq.com,并且开通微信支付功能,随后将你的小程序关联一个微信商户:pay.weixin.qq.com,这一系列申请下来之后,你手中需要有微信小程序appid,微信小程序秘钥,商户号,以及商户秘钥,这四个关键的支付配置变量。

    然后大体流程分两步:

    1 在自己的后台服务器上访问微信提供的接口,拿到预支付交易会话标识prepay_id、微信返回的随机字符串nonce_str,这两个参数是要返回给自己的小程序的,小程序在调起微信支付接口时需要这两个参数。

    2 小程序拿到后台传递的参数,需要后台传递5个参数,timeStamp,nonceStr,package,signType,paySign。然后在小程序上调起支付接口时传递我们拿到的参数,就可以完成支付。

    先构造后台接口,这里我们使用Django作为后台服务:

    

from django.shortcuts import render
#导包
from django.http import HttpResponse,HttpResponseRedirect,JsonResponse
#导入类视图
from django.views import View
import requests
import hashlib
import xmltodict

client_appid = '你的小程序appid'
Mch_id = '你的商户编号'
Mch_key = '商户交易秘钥'

def myindex(request):
    return HttpResponse('这里是首页')

def myback(request):
    return HttpResponse('这里是回调网址')

def get_nonce_str():
    import uuid
    return str(uuid.uuid4()).replace('-', '')

def getWxPayOrdrID():
    import datetime
 
    date=datetime.datetime.now()
    #根据当前系统时间来生成商品订单号。时间精确到微秒
    payOrdrID=date.strftime("%Y%m%d%H%M%S%f")

    return payOrdrID

#生成签名的函数
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'
    }
 
    #处理函数,对参数按照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()
    return sign.upper()


def generate_sign(param):
    '''生成签名'''
    stringA = ''
    ks = sorted(param.keys())
    print(param)
    # 参数排序
    for k in ks:
        stringA += (k + '=' + param[k] + '&')
    # 拼接商户KEY
    stringSignTemp = stringA + "key=" + Mch_key
    # md5加密,也可以用其他方式
    hash_md5 = hashlib.md5(stringSignTemp.encode('utf8'))
    sign = hash_md5.hexdigest().upper()
    return sign

#获取全部参数信息,封装成xml,传递过来的openid和客户端ip,和价格需要我们自己获取传递进来
def get_bodyData(openid,client_ip,price):
    body = 'Mytest'                    #商品描述
    notify_url = 'http://localhost:8000/back'         #填写支付成功的回调地址,微信确认支付成功会访问这个接口
    nonce_str =get_nonce_str()           #随机字符串
    out_trade_no =getWxPayOrdrID()     #商户订单号
    total_fee =str(price)              #订单价格,单位是 分

    

    #获取签名                              
    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


#统一下单支付接口
def payOrder(request):
    import time
    #获取价格 单位是分
    price= int(request.GET.get("price",1))

    #获取客户端ip
    client_ip,port=request.get_host().split(":")

    #获取小程序openid
    #openid='of2Fa5C2BNn77OOh1hfydxK4pVJc'
    openid = request.GET.get("openid")

    #请求微信的url
    url='https://api.mch.weixin.qq.com/pay/unifiedorder'

    #拿到封装好的xml数据
    body_data=get_bodyData(openid,client_ip,price)

    #获取时间戳
    timeStamp=str(int(time.time()))

    #请求微信接口下单
    respone=requests.post(url,body_data.encode("utf-8"),headers={'Content-Type': 'application/xml'})
    print(respone.content)
    #回复数据为xml,将其转为字典
    content=xmltodict.parse(respone.content)
    print(content)

    return_code = content['xml']['return_code']

    if return_code=='SUCCESS':
        prepay_id = content['xml']['prepay_id']
        # 时间戳
        timeStamp = str(int(time.time()))
        # 5. 五个参数
        data = {
            "appId":client_appid ,
            "nonceStr": get_nonce_str(),
            "package": "prepay_id=" + prepay_id,
            "signType": 'MD5',
            "timeStamp": timeStamp,
        }
        # 6. paySign签名
        paySign = generate_sign(data)
        data["paySign"] = paySign  # 加入签名
        print(data)
        # 7. 传给前端的签名后的参数
        return JsonResponse(data,safe=False,json_dumps_params={'ensure_ascii':False})
    else:
        return HttpResponse("请求支付失败")


    剩下的就简单了,在前端mpvue写支付请求逻辑,由前端请求后端的django统一支付接口,获取关键的五个变量,随后利用这五个变量,请求微信官网支付接口,完成支付逻辑

    

paytest(){

      console.log('支付测试');
      console.log(this.userinfo.openid);

      wx.request({
      url: 'http://127.0.0.1:8000/pay/',
      header: {
        'content-type': 'application/json'
      },
      data: {'openid': this.userinfo.openid,'price': 1},
      success: function (res) {
        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) {
            console.log(res)
          },
          'fail': function (res) {
            console.log(res)
          }
        })
      }
    })


    }

    需要注意的是,请求后台接口时,openid和price是必须要传递的,openid是微信小程序当前用户的唯一标识,而price是价格,单位是分

    


    最后,完成了支付,没什么难的,有一些地方需要提醒:后台接口如果需要在本地调试的话,只能用127.0.0.1这种ip的形式,微信不支持localhost,另外需要xmltodict这个三方库将微信统一支付接口返回的xml转成dict,话说都什么年代了,微信接口居然还在使用xml。

    附上代码:

    后端django:https://gitee.com/QiHanXiBei/mydjango

    前端mpvue:https://gitee.com/QiHanXiBei/mpvue