fix: H5支付OAuth后跳干净URL再调起,避免长query秒关

This commit is contained in:
XingQue committed 2026-08-12 21:02:26 +08:00
1 parent 6c5c9a378a
commit ba46f73e97
2 files changed
+72 -18

No files matched your search

+51 -14
View File
@@ -535,18 +535,26 @@ def wx_h5_jsapi_pay_page(request):
不是扫码。流程:
1) 带 out_trade_no 进入 → 服务号静默 OAuth 取当前微信 openid
2) 用该 openid 对未付款订单重新统一下单,服务端注入 pay 参数
3) 页面仅用户点击后 WeixinJSBridge.invoke(避免自动调起被微信秒关)
2) 用该 openid 对未付款订单重新统一下单
3) 支付参数写入短时 cache,302 到干净 URL ?k=token(去掉 code/长 query)
4) 前端 history.replaceState 成无 query 的目录 URL,再手动点支付
sessionStorage 仅作无 OAuth / 旧链接兜底。
微信校验整页 URL;OAuth 回调带超长 query 时部分机型会秒关 cancel。
"""
import secrets
from urllib.parse import quote
from django.shortcuts import redirect
from jituan.services.club_resolver import get_club_official_credentials, oauth2_access_token
from jituan.services.wechat_native_pay import (
build_official_jsapi_pay_params,
build_h5_jsapi_bridge_url,
)
CACHE_PREFIX = 'wx_h5_jsapi_pay_pack_'
CACHE_TTL = 60 * 15
CLEAN_PAY_URL = 'https://www.abas.asia/hqhd/dingdan/wx-h5-pay/'
pack_key = (request.GET.get('k') or '').strip()
out_trade_no = (request.GET.get('out_trade_no') or request.GET.get('dingdanid') or '').strip()
club_id = (request.GET.get('club_id') or '').strip() or 'ufo'
code = (request.GET.get('code') or '').strip()
@@ -560,13 +568,27 @@ def wx_h5_jsapi_pay_page(request):
'return_url': return_url,
'oauth_openid_preview': '',
'pay_source': 'sessionStorage',
'force_clean_url': True,
}
if not out_trade_no:
# 兼容仅 sessionStorage 的旧跳转
# ---- 干净页:用 k 取回 OAuth 后缓存的支付包 ----
if pack_key and not code:
pack = cache.get(f'{CACHE_PREFIX}{pack_key}')
if not isinstance(pack, dict) or not pack.get('pay'):
ctx['server_error'] = '支付会话已过期,请返回点单页重新下单'
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
pay = pack['pay']
ctx['server_pay'] = pay
ctx['out_trade_no'] = pack.get('out_trade_no') or ''
ctx['club_id'] = pack.get('club_id') or club_id
ctx['return_url'] = pack.get('return_url') or ''
ctx['oauth_openid_preview'] = pack.get('openid_preview') or ''
ctx['pay_source'] = 'oauth_server_clean'
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
if not out_trade_no:
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
# 查未付款订单
order = None
jine = None
try:
@@ -587,15 +609,20 @@ def wx_h5_jsapi_pay_page(request):
ctx['server_error'] = f'俱乐部 {club_id} 未配置服务号 official_appid/secret'
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
if return_url:
cache.set(f'wx_h5_return_{out_trade_no}', return_url, CACHE_TTL)
else:
return_url = (cache.get(f'wx_h5_return_{out_trade_no}') or '').strip()
if not code:
redirect_uri = build_h5_jsapi_bridge_url(out_trade_no, club_id, return_url)
# OAuth 回调 URI 尽量短,不带 return_url
redirect_uri = build_h5_jsapi_bridge_url(out_trade_no, club_id, return_url='')
oauth = (
'https://open.weixin.qq.com/connect/oauth2/authorize'
f'?appid={quote(appid)}'
f'&redirect_uri={quote(redirect_uri, safe="")}'
'&response_type=code&scope=snsapi_base&state=h5pay#wechat_redirect'
)
from django.shortcuts import redirect
return redirect(oauth)
wx = oauth2_access_token(code, club_id=club_id)
@@ -605,7 +632,7 @@ def wx_h5_jsapi_pay_page(request):
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
oid = openid.strip()
ctx['oauth_openid_preview'] = (oid[:6] + '...' + oid[-4:]) if len(oid) > 10 else oid
oid_preview = (oid[:6] + '...' + oid[-4:]) if len(oid) > 10 else oid
xff = request.META.get('HTTP_X_FORWARDED_FOR')
client_ip = (xff.split(',')[0].strip() if xff else '') or request.META.get('REMOTE_ADDR') or '127.0.0.1'
@@ -621,13 +648,23 @@ def wx_h5_jsapi_pay_page(request):
except Exception as exc:
logger.exception('[WX_H5_PAY] OAuth 重下单失败 dingdan=%s', out_trade_no)
ctx['server_error'] = f'统一下单失败:{exc}'
ctx['oauth_openid_preview'] = oid_preview
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
# 去掉前端不需要的私有字段副本;_diag 保留给诊断页
ctx['server_pay'] = pay
ctx['club_id'] = club_id
ctx['pay_source'] = 'oauth_server'
return render(request, 'wechat/h5_jsapi_pay.html', ctx)
token = secrets.token_urlsafe(16)
cache.set(
f'{CACHE_PREFIX}{token}',
{
'pay': pay,
'out_trade_no': out_trade_no,
'club_id': club_id,
'return_url': return_url,
'openid_preview': oid_preview,
},
CACHE_TTL,
)
return redirect(f'{CLEAN_PAY_URL}?k={quote(token)}')
def wx_native_pay_page(request):
+21 -4
View File
@@ -41,8 +41,9 @@
<div class="title">微信支付(诊断模式)</div>
<div class="banner" id="banner">收款商户号加载中…</div>
<div class="warn">
请<strong>手动点下方绿色按钮</strong>调起支付(不要依赖自动弹窗)。<br/>
若闪一下就关:看有没有灰字「当前页面的URL未注册」——有=目录仍未生效;没有=把下方绿日志整段复制发回。
请<strong>手动点下方绿色按钮</strong>调起支付。<br/>
秒关时看有没有灰字「当前页面的URL未注册」。<br/>
目录必须挂在<strong>公众号 AppID wxd400adf1911c534f</strong>下(不要挂在小程序 AppID 下)。
</div>
<div class="sub" id="tip">初始化…</div>
<button class="btn" type="button" id="payBtn">立即支付 / 重试</button>
@@ -95,9 +96,22 @@
'club_id = ' + club + '<br/>' +
'pay来源 = ' + PAY_SOURCE + '<br/>' +
'OAuth openid = ' + (SERVER_OID || '(无)') + '<br/>' +
'请到<strong>这个商户号</strong>的「产品中心 → JSAPI支付 → 支付授权目录」配:<br/>' +
'请到商户 <strong>1748512512</strong> → 选中公众号 AppID <strong>wxd400adf1911c534f</strong><br/>' +
'「产品中心 → JSAPI支付 → 支付授权目录」添加:<br/>' +
'https://www.abas.asia/hqhd/dingdan/<br/>' +
'以及精确目录:https://www.abas.asia/hqhd/dingdan/wx-h5-pay/';
'https://www.abas.asia/hqhd/dingdan/wx-h5-pay/';
}
function cleanPayUrl() {
var clean = 'https://www.abas.asia/hqhd/dingdan/wx-h5-pay/';
try {
if (String(location.href || '').split('#')[0] !== clean) {
history.replaceState(null, '', clean);
push('已 replaceState 成干净校验 URL', clean);
}
} catch (e) {
push('replaceState 失败', String(e && e.message || e));
}
}
function now() {
@@ -243,6 +257,7 @@
var invokeAt = 0;
function invokePay() {
cleanPayUrl();
dumpEnv('点击调起前');
if (!pay || !pay.timeStamp || !pay.nonceStr || !pay.package || !pay.paySign) {
setTip('支付参数丢失:' + (SERVER_ERROR || '请返回重新下单'), true);
@@ -373,6 +388,8 @@
push('提示:请勿单独打开本页;须从点单页跳转');
return;
}
cleanPayUrl();
dumpEnv('干净 URL 后');
setTip('参数已就绪。请<strong>手动点击</strong>「立即支付」(已关闭自动调起)');
push('已关闭自动调起,等待用户点击');
})();