目录
一, 小程序支付官方文档
二, 小程序支付流程
三, 支付流程图 (下方有接口)
业务流程时序图
四, 签名
五, xml 解析模块
接收 xml 二进制数据, 转换为字典
六, 再次签名
七, 前台吊起支付接口
案例
一, 小程序支付官方文档
# 接口
二, 小程序支付流程
用户发起请求下单支付
我们要保证用是登入状态.
组织数据, 请求统一下单接口, 微信官方会同步返回一个 prepay_id
重新组织数据, 进行签名, 将重新组织的数据返回给小程序, 小程序在吊起支付.
用户就可以进行支付, 支付结果会同步返回给小程序
后台修改订单支付状态是通过微信官方服务器的异步通知
三, 支付流程图 (下方有接口)
业务流程时序图
小程序支付的交互图如下:
商户系统和微信支付系统主要交互:
1, 小程序内调用登录接口, 获取到用户的 openid,API 参见公共 API[小程序登录 API]
2, 商户 server 调用支付统一下单, API 参见公共 API[统一下单 API]
3, 商户 server 调用再次签名, API 参见公共 API[再次签名]
4, 商户 server 接收支付通知, API 参见公共 API[支付结果通知 API]
5, 商户 server 查询支付结果, API 参见公共 API[查询订单 API]
四, 签名
- # 签名
- def get_sign(self):
- data_dict = {
- "appid": self.appid,
- "mch_id": self.mch_id,
- "nonce_str": self.nonce_str,
- "body": self.body,
- "out_trade_no": self.out_trade_no,
- "total_fee": self.total_fee,
- "spbill_create_ip": self.ip,
- "notify_url": self.notify_url,
- "trade_type": self.trade_type,
- "openid": self.openid,
- }
- # 列表推导式,
- sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
- sign_str = f"{sign_str}&key={settings.pay_apikey}"
- print("sign_str", sign_str)
- md5 = hashlib.md5()
- md5.update(sign_str.encode("utf-8"))
- sign = md5.hexdigest()
- return sign.upper()
五, xml 解析模块
- <xml>
- <appid name="属性值">{.child.text}</appid>
child.tag 表示 appid
- </xml>
- import xml.etree.ElementTree as ET
如果我们要解析一个 xml 文件
- tree = ET.parse('country_data.xml')
- root = tree.getroot()
如果解析字符串
root = ET.fromstring(country_data_as_string)
这个 root 是 Element
- for child in root:
- print(child.tag, child.attrib)
- #child.tag 表是标签名, child.attrib 表示获取属性
- #child.text 就表示获取内容
- # 拼接的 xml 数据
- body_data = f'''
- <xml>
- <appid>{self.appid}</appid>
- <mch_id>{self.mch_id}</mch_id>
- <nonce_str>{self.nonce_str}</nonce_str>
- <body>{self.body}</body>
- <out_trade_no>{self.out_trade_no}</out_trade_no>
- <total_fee>{self.total_fee}</total_fee>
- <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
- <notify_url>{self.notify_url}</notify_url>
- <trade_type>{self.trade_type }</trade_type>
- <openid>{self.openid }</openid>
- <sign>{self.sign}</sign>
- </xml>
- '''
接收 xml 二进制数据, 转换为字典
- # 接收 xml 二进制数据, 转换为字典
- def xml_to_dict(self, xml_data):
- import xml.etree.ElementTree as ET
- xml_dict = {}
- root = ET.fromstring(xml_data)
- for child in root:
- xml_dict[child.tag] = child.text
- return xml_dict
六, 再次签名
- # 再次签名
- def get_two_sign(self, data):
- data_dict = {
- "appId": settings.AppId,
- "timeStamp": str(int(time.time())),
- "nonceStr": data['nonce_str'],
- "package": f"prepay_id={data['prepay_id']}",
- "signType": "MD5"
- }
- sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
- sign_str = f"{sign_str}&key={settings.pay_apikey}"
- md5 = hashlib.md5()
- md5.update(sign_str.encode("utf-8"))
- sign = md5.hexdigest()
- return sign.upper(), data_dict['timeStamp']
七, 前台吊起支付接口
- wx.requestPayment(
- {
- 'timeStamp':e.data.data.timeStamp,
- 'nonceStr': e.data.data.nonceStr,
- 'package': e.data.data.package,
- 'signType': e.data.data.signType,
- 'paySign': e.data.data.paySign,
- 'success':function(res){
- console.log("成功",res)
- },
- 'fail':function(res){
- console.log("失败",res)
- },
- })
- from rest_framework.views import APIView
- from rest_framework.response import Response
- from django.core.cache import cache
- from API.wx import settings
- import hashlib
- import requests
- import time
- class Pay(APIView):
- def post(self, request):
- param = request.data
- if param.get("token") and param.get("money"):
- # 根据传过来的 token, 从缓存中拿出 openid_session_key
- openid_session_key = cache.get(param.get("token"))
- # 如果存在, 证明传过来的 token 合法有效
- if openid_session_key:
- if request.META.get('HTTP_X_FORWARDED_FOR'):
- # 有负载均衡就用这个
- self.ip = request.META['HTTP_X_FORWARDED_FOR']
- else:
- # 没有负载均衡就用这个
- self.ip = request.META['REMOTE_ADDR']
- self.openid = openid_session_key.split("&")[1]
- self.money = param.get("money")
- data = self.get_pay_data() # 调用支付接口
- return Response({"code": 0, "msg": "ok", "data": data})
- else:
- return Response({"code": 2, "msg": "token 无效"})
- else:
- return Response({"code": 1, "msg": "缺少参数"})
- # 支付接口
- def get_pay_data(self):
- self.appid = settings.AppId
- self.mch_id = settings.pay_mchid # 公司商户号, 选哟公司三证申请
- self.nonce_str = self.get_nonce_str() # 生成一个随机数
- self.body = "老男孩学费" # 商品描述
- self.out_trade_no = self.get_order_id() # 商户订单号, 模拟
- self.total_fee = self.money # 支付金额
- self.spbill_create_ip = self.ip # 调用微信支付 API 的机器 IP
- self.notify_url = "htttp://www.test.com" # 通知地址
- self.trade_type = "JSAPI" # 交易类型
- self.openid = self.openid # 用户标识
- self.sign = self.get_sign() # 签名
- # 拼接的 xml 数据
- body_data = f'''
- <xml>
- <appid>{self.appid}</appid>
- <mch_id>{self.mch_id}</mch_id>
- <nonce_str>{self.nonce_str}</nonce_str>
- <body>{self.body}</body>
- <out_trade_no>{self.out_trade_no}</out_trade_no>
- <total_fee>{self.total_fee}</total_fee>
- <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
- <notify_url>{self.notify_url}</notify_url>
- <trade_type>{self.trade_type }</trade_type>
- <openid>{self.openid }</openid>
- <sign>{self.sign}</sign>
- </xml>
- ''' url ="https://api.mch.weixin.qq.com/pay/unifiedorder" # 如果发送的 xml 数据要把数据转化二进制. body_data.encode("utf-8")
- # headers={"content-type": "application/json"} JSON 数据
- # headers={"content-type": "application/xml"} xml 数据
- response = requests.post(url, data=body_data.encode("utf-8"), headers={"content-type": "application/xml"})
- # 接收一个二进制的响应, 调接口
- data_dict = self.xml_to_dict(response.content)
- print('data_dict:', data_dict)
- # 再次签名
- pay_sign, timeStamp = self.get_two_sign(data_dict)
- data = {
- "timeStamp": timeStamp,
- "nonceStr": data_dict['nonce_str'],
- "package": f"prepay_id={data_dict['prepay_id']}",
- "signType": "MD5",
- "paySign": pay_sign
- }
- return data
- # 随机字符串
- def get_nonce_str(self):
- import random
- data = "123456789abcdefghijklmn"
- nonce_str = "".join(random.sample(data, 10))
- # random.sample(从哪里取, 取多小个), 变成列表
- return nonce_str
- # 模拟订单号
- def get_order_id(self):
- import time
- import random
- data = "123456789abcdefghijklmn"
- order_no = str(time.strftime("%Y%m%d%H%M%S")) + "".join(random.sample(data, 5))
- return order_no
- # 签名
- def get_sign(self):
- data_dict = {
- "appid": self.appid,
- "mch_id": self.mch_id,
- "nonce_str": self.nonce_str,
- "body": self.body,
- "out_trade_no": self.out_trade_no,
- "total_fee": self.total_fee,
- "spbill_create_ip": self.ip,
- "notify_url": self.notify_url,
- "trade_type": self.trade_type,
- "openid": self.openid,
- }
- # 列表推导式,
- sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
- sign_str = f"{sign_str}&key={settings.pay_apikey}"
- print("sign_str", sign_str)
- md5 = hashlib.md5()
- md5.update(sign_str.encode("utf-8"))
- sign = md5.hexdigest()
- return sign.upper()
- # 接收 xml 二进制数据, 转换为字典
- def xml_to_dict(self, xml_data):
- import xml.etree.ElementTree as ET
- xml_dict = {}
- root = ET.fromstring(xml_data)
- for child in root:
- xml_dict[child.tag] = child.text
- print('xml_dict:',xml_dict)
- return xml_dict
- # 再次签名
- def get_two_sign(self, data):
- data_dict = {
- "appId": settings.AppId,
- "timeStamp": str(int(time.time())),
- "nonceStr": data['nonce_str'],
- "package": f"prepay_id={data['prepay_id']}",
- "signType": "MD5"
- }
- sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
- sign_str = f"{sign_str}&key={settings.pay_apikey}"
- md5 = hashlib.md5()
- md5.update(sign_str.encode("utf-8"))
- sign = md5.hexdigest()
- return sign.upper(), data_dict['timeStamp']
- pay:function(){
- wx.request({
- url: App.globalData.baseurl + "pay/",
- // 支付的钱, 和携带的 token,1 代表 1 分钱
- data: { "money": 1, token: wx.getStorageSync('token') },
- method: "POST",
- success(e){
- console.log('支付数据',e)
- wx.requestPayment(
- {
- 'timeStamp': e.data.data.timeStamp,
- 'nonceStr': e.data.data.nonceStr,
- 'package': e.data.data.package,
- 'signType': e.data.data.signType,
- 'paySign': e.data.data.paySign,
- 'success': function (res) {
- console.log("成功", res)
- },
- 'fail': function (res) {
- console.log("失败", res)
- },
- })
- }
- })
- }
来源: http://www.bubuko.com/infodetail-3460575.html