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();
}
}
}
}