需求:
微信打开商品列表页面-> 点击商品后直接显示付款页面-> 点击付款调用微信支付
说明
微信支付需要你申请了公众号(appid, key - 用于签名), 商户号(mch_id, AppSecret - 用于获取openid, 获取code)
调起微信支付的页面需要配置授权, 如你的页面是http://www.shazuihuo.com/goods/index.html. 那么你需要配置为: http://www.shazuihuo.com/goods/ 即可, 这个是在商户号中配置
签名算法和校验工具
坑
- 签名校验通过时还是提示签名错误, 可能时候商户号KEY配置的问题了, 重置一下KEY, 你可以继续使用原来的KEY来重置
- 公众号变更时记得修改后台和前台代码中的APPID
需要的ID和KEY
# 微信配置基础数据
WPC = {'APPID': 'wx53c1xxxxad626eb8','APPSECRET': 'fdd177a7xxxxxxxxxxxxx856eeeb187c','MCHID': '14222000000','KEY': 'd7810713e1exxxxxxxxxxadc9617d0a6','GOODDESC': '商户号中的公司简称或全称-无要求的商品名字','NOTIFY_URL': 'https://www.xxxx.com/service/applesson/wechatordernotice',
}
流程简介
- 网页内调起微信支付需要一个微信统一下单生成的订单号(prepay_id)
- 调用微信的统一下单接口需要一个用户在商户下的唯一标示(openid)
- 获取openid需要code参数加上AppID和AppSecret等,通过API换取access_token(openid)
- 其中code又需要通过页面跳转来获取, 需要appid和重定向url(可以带有你自己的参数, 会原样返回)
那么开发思路便是一步步回朔了.
1. 获取code
用户点击按钮跳转到微信授权页, 微信处理完后重定向到redirect_uri, 并给我们加上code=xxx的参数, 这个code就是我们需要的
$('#buy').click(function() {var param = {appid: 'wx53c1xxxxad626eb8',redirect_uri: 'https://www.xxxxx.com/wcpay/pay.html',response_type: 'code',scope: 'snsapi_base',state: '1'}window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?' + $.param(param);})
2. 获取openid
这个在后台完成, WPC中配置了你的APPSECRET, 这个不能泄露, 接口调用成功会拿到一个openid, 这里都不会有什么问题
@classmethoddef getOpenID(cls, kwargs):param = {'code': kwargs['code'],'appid': WPC['APPID'],'secret': WPC['APPSECRET'],'grant_type': 'authorization_code',}# 通过code获取access_tokenopenIdUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token'resp = requests.get(openIdUrl, params=param)# {openid, accss_token, refresh_token, openid, scope, expires_in}# openId = json.loads(resp.text)['openid']return resp.text
3. 微信统一下单
统一下单 时参数传递需要签名(微信用我们设定的密匙对参数进行MD5加密, 通过双方的签名判断请求是否被篡改)
签名算法
@classmethod
def getSign(cls, kwargs):# 计算签名keys, paras = sorted(kwargs), []paras = ['{}={}'.format(key, kwargs[key]) for key in keys if key != 'appkey'] # and kwargs[key] != '']stringA = '&'.join(paras)stringSignTemp = stringA + '&key=' + WPC['KEY']sign = MD5(stringSignTemp).upper()return sign
MD5函数
import hashlib# 获取MD5
def MD5(str):md5 = hashlib.md5()md5.update(str.encode('utf-8'))return md5.hexdigest()
参数转xml
@classmethod
def getxml(cls, kwargs):kwargs['sign'] = Utility.getSign(kwargs)# 生成xmlxml = ''for key, value in kwargs.items():xml += '<{0}>{1}</{0}>'.format(key, value)xml = '<xml>{0}</xml>'.format(xml)# print(xml)return xml
统一下单代码
code = self.POST.get('code')
openidresp = Utility.getOpenID({'code': code})
openid = json.loads(openidresp).get('openid')UnifieOrderRequest = {'appid': 'wx53c1xxxxad626eb8', # 公众账号ID'body': '公司名称-商品', # 商品描述'mch_id': '1397xxxxxx8', # 商户号:深圳市泽慧文化传播有限公司'nonce_str': '', # 随机字符串'notify_url': 'https://service.xxxx.com/service/applesson/wechatordernotice', # 微信支付结果异步通知地址'openid': '', # trade_type为JSAPI时,openid为必填参数!此参数为微信用户在商户对应appid下的唯一标识, 统一支付接口中,缺少必填参数openid!'out_trade_no': '', # 商户订单号'spbill_create_ip': '', # 终端IP'total_fee': '', # 标价金额'trade_type': 'JSAPI', # 交易类型
}
UnifieOrderRequest['nonce_str'] = Utility.getnoncestr()
UnifieOrderRequest['openid'] = openid
UnifieOrderRequest['out_trade_no'] = UnifieOrderRequest['mch_id'] + str(order.id) # 内部订单号码
UnifieOrderRequest['spbill_create_ip'] = self.request.remote_ip
UnifieOrderRequest['total_fee'] = int(lesson.price * 100)
# 签名并生成xml
xml = Utility.getxml(UnifieOrderRequest)resp = requests.post("https://api.mch.weixin.qq.com/pay/unifiedorder", data=xml.encode('utf-8'), headers={'Content-Type': 'text/xml'})
msg = resp.text.encode('ISO-8859-1').decode('utf-8')
xmlresp = xmltodict.parse(msg)
prepay_id = ''if xmlresp['xml']['return_code'] == 'SUCCESS':if xmlresp['xml']['result_code'] == 'SUCCESS':prepay_id = xmlresp['xml']['prepay_id']timestamp = str(int(time.time()))data = {"appId": xmlresp['xml']['appid'],"nonceStr": Utility.getnoncestr(),"package": "prepay_id=" + xmlresp['xml']['prepay_id'],"signType": "MD5","timeStamp": timestamp}data['paySign'] = Utility.getSign(data)data['orderid'] = order.id # 付款后操作的订单# 签名后返回给前端做支付参数return JsonResponse(self, '000', data=data)else:msg = xmlresp['xml']['err_code_des']return JsonResponse(self, '002', msg=msg)
else:msg = xmlresp['xml']['return_msg']return JsonResponse(self, '002', msg=msg)
统一下单成功返回后直接调用微信支付, 显示支付界面, 其中的paySign是我们自己的签名
try {// statements// 微信统一订单, 返回预支付信息var code = query('code'),origin = query('groupid');// alert(code);$.post({url: orderurl,data: {origin: origin,mobile: phone,code: code}}).then(function(resp) {if (resp.code && resp.code == "000") {// 后台返回订单信息var wepaydata = {appId: resp.data.appId,nonceStr: resp.data.nonceStr,package: resp.data.package,paySign: resp.data.paySign,signType: "MD5",timeStamp: resp.data.timeStamp};var orderid = resp.data.orderid || 0;window.jsApiCall = function() {WeixinJSBridge.invoke('getBrandWCPayRequest',wepaydata,function(res) {WeixinJSBridge.log(res.err_msg);// alert(res.err_code + res.err_desc + res.err_msg);// alert(res.err_msg)if (res.err_msg == 'get_brand_wcpay_request:ok') {$.get(orderurl, { orderid: orderid }, function(resp) {if (resp.code == '000') {window.location.href = window.location.href.replace('pay.html', 'success.html');} else {alert(resp.msg);// 一个code只能请求一次, 重新进入indexif (resp.code == '002') {window.location.href = window.location.href.replace('pay.html', 'index.html');}}});} else {// 其他支付异常微信有显示消息// alert(res.err_msg);}});}window.callpay = function() {if (typeof WeixinJSBridge == "undefined") {if (document.addEventListener) {document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);} else if (document.attachEvent) {document.attachEvent('WeixinJSBridgeReady', jsApiCall);document.attachEvent('onWeixinJSBridgeReady', jsApiCall);}} else {jsApiCall();}}// 发起支付window.callpay();} else {alert(resp.msg);// alert(JSON.stringify(resp) + resp.msg);}}, function(resp) {alert(resp)alert(JSON.stringify(resp))// alert('请求失败, 请重试');});
} catch (e) {// statementsalert(e)
}
4. 订单查询
订单查询 是为了确认我们的支付是成功的
# 查询微信付款情况
orderid = self.GET.get('orderid')
orderquery = {'appid': WPC['APPID'],'mch_id': WPC['MCHID'],'nonce_str': Utility.getnoncestr(),'out_trade_no': WPC['MCHID'] + orderid
}
xml = Utility.getxml(orderquery)
print(xml)resp = requests.post("https://api.mch.weixin.qq.com/pay/orderquery", data=xml.encode('utf-8'), headers={'Content-Type': 'text/xml'})
msg = resp.text.encode('ISO-8859-1').decode('utf-8')
xmlresp = xmltodict.parse(msg)
print(xmlresp)orderPaid = 0
if xmlresp['xml']['return_code'] == 'SUCCESS':if xmlresp['xml']['result_code'] == 'SUCCESS':if xmlresp['xml']['trade_state'] == 'SUCCESS':orderPaid = 1else:msg = xmlresp['xml']['trade_state_desc']return JsonResponse(self, '001', msg=smg)else:msg = xmlresp['xml']['err_code_des']return JsonResponse(self, '001', msg=msg)
else:msg = xmlresp['xml']['return_msg']return JsonResponse(self, '001', msg=msg)
官方Demo
SDK与DEMO下载, 用python就需要自己码代码, 当时看的是PHP的