python实现微信h5支付
1.首先微信h5支付的含义:
①手机浏览器访问
②非微信浏览器访问
如果是微信浏览器访问会抛异常
常见错误见官方文档
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4
官方文档:
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4
业务流程:
商户系统和微信支付系统主要交互说明:
#步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
用户在app页面选择商品,确定数量,提交订单,提供必要的参数
app支付需要的前端参数
1. body 商品描述
2. total_fee 商品价格
#步骤2: 商户后台生成本地订单
1.生成订单号,保证唯一性
order_sn = "%s%s%s" % (request.user.id, datetime.datetime.now().strftime('%Y%m%d%H%M%S'),
random.randint(1, 99))
2.根据订单号,生成本地订单(生成订单对象,订单状态肯定是未支付,支付成功的回调时再改为支付成功状态)
def generate_order(user, post_data, order_sn, nickname, openid):
good_kind = post_data.get("good_kind")
good_id = post_data.get('good_id')
paid_amount = post_data.get('paid_amount', 0)
try:
good_kind = int(good_kind)
good_id = int(good_id)
paid_amount = float(paid_amount)
#1 课程 2 会员卡
if good_kind == 1:
course = Course.objects.get(pk=good_id)
record = Record.objects.create(
user=user, course=course, telephone=user.phone, center=get_center_for_appid(),
price=paid_amount, order_sn=order_sn, nickname=nickname, openid=openid)
if not paid_amount:
record.status = 2
record.save()
elif good_kind == 2:
card = Vip.objects.get(pk=good_id)
record = Record.objects.create(
user=user, card=card, price=paid_amount, center=get_center_for_appid(),
order_sn=order_sn, nickname=nickname, telephone=user.phone, openid=openid)
else:
record = None
except Exception as e:
record = None
return record
#步骤3:由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
使用pip install weixin-python==0.5.4(模块)
1.导入from weixin import Weixin
2.初始化(https://www.cnblogs.com/gjh99/p/10536905.html)
we_chat = Weixin(dict(
#app_id 微信开放平台审核通过的应用APPID(请登录open.weixin.qq.com查看,注意与公众号的APPID不同)
WEIXIN_APP_ID=WECHAT_APP_ID,
#mch_id 微信支付分配的商户号
WEIXIN_MCH_ID=WECHAT_MCH_ID,
#mch_key 微信支付密钥
WEIXIN_MCH_KEY=WECHAT_MCH_KEY,
#回调url
WEIXIN_NOTIFY_URL=WECHAT_NOTIFY_URL,
#公钥文件
WEIXIN_MCH_KEY_FILE='',
#私钥文件
WEIXIN_MCH_CERT_FILE=''
))
3.因为原模块封装好的方法jsapi没有将mweb_url放入签名,故重写了h5_jsapi方法,将mweb_url字段加入签名
def h5_jsapi(we_chat, **kwargs):
kwargs.setdefault("trade_type", "JSAPI")
raw = we_chat.unified_order(**kwargs)
package = "prepay_id={0}".format(raw["prepay_id"])
mweb_url = raw["mweb_url"]
timestamp = str(int(time.time()))
nonce_str = we_chat.nonce_str
raw = dict(appId=we_chat.app_id, timeStamp=timestamp, mweb_url=mweb_url,
nonceStr=nonce_str, package=package, signType="MD5")
sign = we_chat.sign(raw)
return dict(package=package, appId=we_chat.app_id, signType="MD5",
timeStamp=timestamp, nonceStr=nonce_str, mweb_url=mweb_url, sign=sign)
spbill_create_ip = request.META.get("REMOTE_ADDR")
jsdict = h5_jsapi(
we_chat,
#订单号
out_trade_no=order_sn,
#商品描述
body=body,
#支付价格,以分为单位
total_fee=int(float(total_fee) * 100),
#交易类型
trade_type='MWEB',
#用户ip(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_5)
spbill_create_ip=spbill_create_ip
)
4.接收微信后台返回的数据字典jsdict,需要返回给前台,后台再写个回调函数就ok
{
"status": 1,
"data": {
"mweb_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb? prepay_id=wx11165744343104a03ec402871636571500&package=3494349132",
"appId": "你的appid",
"package": "prepay_id=wx11165744343104a03ec402871636571500",
"timeStamp": "1576054664",
"nonceStr": "b7t5brZBvvClNsAZtN9jyVPpawhiLCqP",
"signType": "MD5",
"sign": "9DBF2BE500204B0B686A1EEB8C7AA82A"
}
}
5.回调函数
@api_view(['POST'])
@permission_classes([AllowAny])
def wechat_callback(request):
if request.method == 'POST':
try:
data = we_chat.to_dict(request.body)
order_sn = data.get('out_trade_no', '')
# 支付结果,用户正常支付,会返回SUCCESS
result_code = data.get('result_code', '')
# check 检查微信回传数据是否可靠
if not we_chat.check(data):
return HttpResponse(we_chat.reply("签名验证失败", False))
if result_code == 'SUCCESS':
return HttpResponse(we_chat.reply("OK", True), content_type='text/xml')
except Exception as e:
return HttpResponse(we_chat.reply("签名验证失败", False), content_type='text/xml')
#后台代码
@list_route(methods=["POST"])
def h5_pay(self, request, *args, **kwargs):
post_data = request.DATA
body = post_data.get('body', '微信支付测试')
total_fee = post_data.get('total_fee', 0.01)
if not total_fee:
return Response({"status": 0, "errormsg": "缺少参数"})
# 订单号生成规则:用户id+当前时间+随机数
order_sn = "%s%s%s" % (request.user.id, datetime.datetime.now().strftime('%Y%m%d%H%M%S'),
random.randint(1, 99))
# 生成本地订单
order = generate_order()
spbill_create_ip = request.META.get("REMOTE_ADDR")
jsdict = h5_jsapi(we_chat, out_trade_no=order_sn, body=body, total_fee=int(float(total_fee) * 100),
trade_type='MWEB', spbill_create_ip=spbill_create_ip)
return Response({"status": 1, "data": jsdict})
以上是微信h5支付,实际应用时,用户一般会使用微信分享h5,其他微信用户点开时基本是默认微信浏览器打开h5导致抛异常
所以判断用户是否使用微信浏览器打开h5页面,如果不是就走h5支付,如果是的话,此处我的解决方法是让用户走JSAPI支付,
判断浏览器是否是微信浏览器
https://www.cnblogs.com/sherryweb/p/11239127.html
function is_weixin_function() {
var ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
return 1
} else {
//不是微信浏览器
return 0
}
}
JSAPI支付
#1.通过前端返回的code获取openid,使用jsapi支付方式时,openid是必传参数
#通过werobot模块获取openid
import werobot(python2 -m pip install werobot==1.2.0)
def jsapi_pay(self, request, *args, **kwargs):
code = request.DATA.get("code")
robot = werobot.WeRoBot(token=settings.WECHAT_TOKEN)
robot.config["APP_ID"] = settings.WECHAT_APP_ID
robot.config["APP_SECRET"] = settings.WECHAT_APP_SECRET
client = robot.client
params = {
'appid': robot.config["APP_ID"],
'secret': robot.config["APP_SECRET"],
'code': code,
'grant_type': 'authorization_code',
}
r_json = client.request('GET', 'https://api.weixin.qq.com/sns/oauth2/access_token',
params=params, timeout=10)
openid = r_json.get('openid')
#2.生成本地订单
#3.调用统一下单接口
jsdict = we_chat.jsapi(
out_trade_no=order_sn,
body=body,
total_fee=total_fee,
trade_type='JSAPI',
openid=openid
)
#4.返回的jsdict给前端,后台就ok了
{
"status": 1,
"data": {
"appId": "你的appid",
"package": "prepay_id=wx11165744343104a03ec402871636571500",
"timeStamp": "1576054664",
"nonceStr": "b7t5brZBvvClNsAZtN9jyVPpawhiLCqP",
"signType": "MD5",
"sign": "9DBF2BE500204B0B686A1EEB8C7AA82A"
}
}
#后台代码
@list_route(methods=["POST"])
def jsapi_pay(self, request, *args, **kwargs):
#这里的appid是公众号的哦
wei_xin = Weixin(dict(WEIXIN_APP_ID=settings.WECHAT_APP_ID, WEIXIN_MCH_ID=settings.WEIXIN_MCH_ID,
WEIXIN_MCH_KEY=settings.WEIXIN_MCH_KEY,
WEIXIN_NOTIFY_URL=settings.WECHAT_NOTIFY_URL, WEIXIN_MCH_KEY_FILE='', WEIXIN_MCH_CERT_FILE=''))
post_data = request.DATA
code = post_data.get("code")
redis_cli = get_redis_cli("default")
if redis_cli.keys(code):
return Response({"status": 0, "errormsg": "code已使用"})
else:
redis_cli.set(code, "", ex=24*60*60)
body = post_data.get('body', '微信支付测试')
try:
total_fee = int(float(post_data.get('total_fee'))*100)
except TypeError:
return Response({"status": 0, "errormsg": "缺少支付价格或价格格式错误"})
robot = werobot.WeRoBot(token=settings.WECHAT_TOKEN)
robot.config["APP_ID"] = settings.WECHAT_APP_ID
robot.config["APP_SECRET"] = settings.WECHAT_APP_SECRET
client = robot.client
params = {
'appid': robot.config["APP_ID"],
'secret': robot.config["APP_SECRET"],
'code': code,
'grant_type': 'authorization_code',
}
r_json = client.request('GET', 'https://api.weixin.qq.com/sns/oauth2/access_token',
params=params, timeout=10)
openid = r_json.get('openid')
print(code, openid)
if not openid or not total_fee:
return Response({"status": 0, "errormsg": "缺少参数"})
# 订单号生成规则:用户id+当前时间+随机数
order_sn = "%s%s%s" % (request.user.id, datetime.datetime.now().strftime('%Y%m%d%H%M%S'),
random.randint(1, 99))
# 生成本地订单
order = generate_order()
if not order:
return Response({"status": 0, "errormsg": "创建本地订单失败"})
jsdict = wei_xin.jsapi(out_trade_no=order_sn, body=body, total_fee=total_fee, trade_type='JSAPI', openid=openid)
return Response({"status": 1, "data": jsdict})
这里遇到了问题,苹果可以正常支付,安卓不能正常支付,前端发起支付代码如下
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": appId, //公众号名称,由商户传入
"timeStamp": timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": nonceStr, //随机串
"package": package_1,
"signType": signType, //微信签名方式:
"paySign": paySign //微信签名
},
function (res) {
//writeObj(res)
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
alert('支付成功')
} else if (res.err_msg == 'get_brand_wcpay_request:fail') {
alert('支付失败')
window.location.href = '/pay/order'
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert('支付取消')
window.location.href = '/pay/order'
}
});
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}
} else {
onBridgeReady();
}
}
}
}
百度下,说推荐使用 wx.chooseWXPay,结果安卓可以了,苹果又挂了,无语,只好判断安卓,苹果分开用了
$('.js-order-pay').click(function () {
var _msg = $(this).attr('data-message')
var auth = 'JWT ' + localStorage.getItem('token')
if (_msg == '1') {
console.log(1111111111)
//支付宝
var formData = {
total_fee: 0.01,
}
$.ajax({
type: "POST",
url: '/api/pay/ali_pay/h5_pay/',
data: JSON.stringify(formData),
dataType: "json",
contentType: "application/json",
processData: false,
headers: {"X-CSRFToken": getCookie("csrftoken")},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", auth);
},
success: function (data) {
if (data.status == 1) {
var div = document.createElement('div');
div.innerHTML = data.data.form
document.body.appendChild(div);
var subForm = document.getElementsByTagName("form")[0];
subForm.submit();
} else {
alert(data.errormsg)
}
},
})
} else {
//微信
var _is_weixin = is_weixin_function()
if (_is_weixin == 1) {
//微信浏览器
var formData = {
total_fee: 0.01,
code: _code
}
$.ajax({
type: "POST",
url: '/api/pay/wechat_pay/jsapi_pay/',
data: JSON.stringify(formData),
dataType: "json",
contentType: "application/json",
processData: false,
headers: {"X-CSRFToken": getCookie("csrftoken")},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", auth);
},
success: function (data) {
if (data.status == 1) {
var appId = data.data.appId
var timeStamp = data.data.timeStamp
var nonceStr = data.data.nonceStr
var package_1 = data.data.package
var signType = data.data.signType
var paySign = data.data.sign
if (isAndroid) {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appId, // 必填,公众号的唯一标识
timestamp: timeStamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: paySign,// 必填,签名,见附录1
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2,如果只是支付,只用这一个参数就够了
});
wx.ready(function () {
wx.chooseWXPay({
appId: appId,
timestamp: timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: nonceStr, // 支付签名随机串,不长于 32 位
package: package_1, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: paySign, // 支付签名
success: function (res) {
// 支付成功后的回调函数
},
//如果你按照正常的jQuery逻辑,下面如果发送错误,一定是error,那你就太天真了,当然,jssdk文档中也有提到
fail: function (res) {
//接口调用失败时执行的回调函数。
window.location.reload()
},
complete: function (res) {
//接口调用完成时执行的回调函数,无论成功或失败都会执行。
},
cancel: function (res) {
//用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
//writeObj(res)
if (res.errMsg == 'chooseWXPay:cancel') {
window.location.href = '/pay/order'
}
},
trigger: function (res) {
//监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。
}
});
});
}
if (isiOS) {
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": appId, //公众号名称,由商户传入
"timeStamp": timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": nonceStr, //随机串
"package": package_1,
"signType": signType, //微信签名方式:
"paySign": paySign //微信签名
},
function (res) {
//writeObj(res)
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
alert('支付成功')
} else if (res.err_msg == 'get_brand_wcpay_request:fail') {
alert('支付失败')
window.location.href = '/pay/order'
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert('支付取消')
window.location.href = '/pay/order'
}
});
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}
} else {
onBridgeReady();
}
}
}
}