260 lines
11 KiB
HTML
260 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||
<title>微信支付诊断</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #111; color: #eee; }
|
||
.wrap { max-width: 640px; margin: 0 auto; padding: 24px 16px 48px; }
|
||
.title { font-size: 18px; font-weight: 700; margin-bottom: 8px; color: #fff; }
|
||
.sub { font-size: 14px; color: #aaa; line-height: 1.5; margin-bottom: 16px; word-break: break-all; }
|
||
.btn {
|
||
display: block; width: 100%; padding: 14px; font-size: 16px; font-weight: 600;
|
||
border: none; border-radius: 10px; background: #07c160; color: #fff; margin-bottom: 10px;
|
||
}
|
||
.btn.gray { background: #333; }
|
||
.btn[disabled] { opacity: 0.55; }
|
||
pre {
|
||
margin-top: 12px; padding: 12px; background: #000; color: #0f0;
|
||
font-size: 11px; line-height: 1.45; white-space: pre-wrap; word-break: break-all;
|
||
border-radius: 8px; max-height: 70vh; overflow: auto;
|
||
border: 1px solid #333;
|
||
}
|
||
.ok { color: #07c160; }
|
||
.err { color: #ff5a5a; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<div class="title">微信支付(诊断模式)</div>
|
||
<div class="sub" id="tip">初始化…</div>
|
||
<button class="btn" type="button" id="payBtn">立即支付 / 重试</button>
|
||
<button class="btn gray" type="button" id="copyBtn">复制下方全部诊断到剪贴板</button>
|
||
<pre id="log">loading…</pre>
|
||
</div>
|
||
<script>
|
||
(function () {
|
||
var STORAGE_KEY = 'wx_h5_jsapi_pay';
|
||
var tip = document.getElementById('tip');
|
||
var btn = document.getElementById('payBtn');
|
||
var copyBtn = document.getElementById('copyBtn');
|
||
var logEl = document.getElementById('log');
|
||
var lines = [];
|
||
var payload = null;
|
||
var pay = null;
|
||
var returnUrl = '';
|
||
|
||
function now() {
|
||
try { return new Date().toISOString(); } catch (e) { return String(Date.now()); }
|
||
}
|
||
|
||
function push() {
|
||
var args = Array.prototype.slice.call(arguments);
|
||
var s = '[' + now() + '] ' + args.map(function (x) {
|
||
if (x == null) return String(x);
|
||
if (typeof x === 'string') return x;
|
||
try { return JSON.stringify(x, null, 2); } catch (e) { return String(x); }
|
||
}).join(' ');
|
||
lines.push(s);
|
||
logEl.textContent = lines.join('\n\n');
|
||
try { console.log.apply(console, args); } catch (e) {}
|
||
}
|
||
|
||
function setTip(t, isErr) {
|
||
tip.innerHTML = t;
|
||
tip.className = 'sub ' + (isErr ? 'err' : '');
|
||
}
|
||
|
||
function safeGetStorage() {
|
||
try {
|
||
return sessionStorage.getItem(STORAGE_KEY);
|
||
} catch (e) {
|
||
push('sessionStorage.getItem 异常', String(e && e.message || e));
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function dumpEnv(tag) {
|
||
var href = '';
|
||
var hash = '';
|
||
var origin = '';
|
||
var path = '';
|
||
var beforeHash = '';
|
||
try {
|
||
href = String(location.href || '');
|
||
hash = String(location.hash || '');
|
||
origin = String(location.origin || '');
|
||
path = String(location.pathname || '');
|
||
beforeHash = href.split('#')[0];
|
||
} catch (e) {
|
||
push('读 location 失败', String(e));
|
||
}
|
||
push('==== ' + tag + ' ====');
|
||
push('location.href', href);
|
||
push('微信校验用的 URL(# 前面)', beforeHash);
|
||
push('location.origin', origin);
|
||
push('location.pathname', path);
|
||
push('location.hash', hash);
|
||
push('document.URL', String(document.URL || ''));
|
||
push('document.referrer', String(document.referrer || '(空)'));
|
||
push('userAgent', String(navigator.userAgent || ''));
|
||
push('WeixinJSBridge', typeof WeixinJSBridge === 'undefined' ? 'undefined' : 'ready');
|
||
push('应匹配的授权目录前缀', 'https://www.abas.asia/hqhd/dingdan/');
|
||
push('beforeHash 是否以该前缀开头', beforeHash.indexOf('https://www.abas.asia/hqhd/dingdan/') === 0);
|
||
push('beforeHash 是否以 xqh5 开头', beforeHash.indexOf('https://www.abas.asia/xqh5/') === 0);
|
||
}
|
||
|
||
try {
|
||
var raw = safeGetStorage();
|
||
push('sessionStorage 原始长度', raw ? raw.length : 0);
|
||
push('sessionStorage 原始内容', raw || '(空,支付参数没带过来)');
|
||
payload = raw ? JSON.parse(raw) : null;
|
||
} catch (e) {
|
||
payload = null;
|
||
push('解析 sessionStorage JSON 失败', String(e && e.message || e));
|
||
}
|
||
|
||
pay = payload && payload.pay ? payload.pay : null;
|
||
returnUrl = (payload && payload.returnUrl) || '';
|
||
|
||
dumpEnv('页面加载');
|
||
push('payload.orderId', payload && payload.orderId);
|
||
push('payload.returnUrl', returnUrl || '(空)');
|
||
push('payload.ts', payload && payload.ts);
|
||
if (pay) {
|
||
push('pay.appId', pay.appId || pay.appid || '(空)');
|
||
push('pay.timeStamp', pay.timeStamp);
|
||
push('pay.nonceStr', pay.nonceStr);
|
||
push('pay.package', pay.package);
|
||
push('pay.signType', pay.signType || '(默认MD5)');
|
||
push('pay.paySign 前8位', String(pay.paySign || '').slice(0, 8));
|
||
push('pay.paySign 长度', String(pay.paySign || '').length);
|
||
push('完整 pay 对象', pay);
|
||
} else {
|
||
push('pay 对象', '(没有,无法调起支付)');
|
||
}
|
||
|
||
function goBack() {
|
||
try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) {}
|
||
if (returnUrl) {
|
||
location.replace(returnUrl);
|
||
return;
|
||
}
|
||
history.length > 1 ? history.back() : setTip('支付完成,请返回点单页');
|
||
}
|
||
|
||
function invokePay() {
|
||
dumpEnv('点击/自动调起前');
|
||
if (!pay || !pay.timeStamp || !pay.nonceStr || !pay.package || !pay.paySign) {
|
||
setTip('支付参数丢失,请返回重新下单(见下方日志)', true);
|
||
push('中止:pay 参数不完整', pay);
|
||
return;
|
||
}
|
||
if (typeof WeixinJSBridge === 'undefined') {
|
||
setTip('WeixinJSBridge 不存在,请用微信内置浏览器打开', true);
|
||
push('中止:无 WeixinJSBridge');
|
||
return;
|
||
}
|
||
var appId = pay.appId || pay.appid || '';
|
||
if (!appId) {
|
||
setTip('缺少 appId', true);
|
||
push('中止:无 appId');
|
||
return;
|
||
}
|
||
var req = {
|
||
appId: appId,
|
||
timeStamp: String(pay.timeStamp),
|
||
nonceStr: pay.nonceStr,
|
||
package: pay.package,
|
||
signType: pay.signType || 'MD5',
|
||
paySign: pay.paySign
|
||
};
|
||
push('即将 WeixinJSBridge.invoke getBrandWCPayRequest', {
|
||
appId: req.appId,
|
||
timeStamp: req.timeStamp,
|
||
nonceStr: req.nonceStr,
|
||
package: req.package,
|
||
signType: req.signType,
|
||
paySign_len: String(req.paySign || '').length
|
||
});
|
||
setTip('正在调起微信支付…');
|
||
btn.disabled = true;
|
||
try {
|
||
WeixinJSBridge.invoke('getBrandWCPayRequest', req, function (res) {
|
||
btn.disabled = false;
|
||
dumpEnv('微信回调时');
|
||
push('微信回调 res 完整对象', res);
|
||
push('微信回调 JSON', (function () {
|
||
try { return JSON.stringify(res); } catch (e) { return String(res); }
|
||
})());
|
||
var msg = (res && res.err_msg) || '';
|
||
var errDesc = (res && (res.err_desc || res.errMsg || res.err_msg)) || '';
|
||
push('err_msg', msg);
|
||
push('err_desc/其它', errDesc);
|
||
// 把所有可枚举字段打出来
|
||
try {
|
||
var keys = [];
|
||
for (var k in res) {
|
||
if (Object.prototype.hasOwnProperty.call(res, k)) {
|
||
keys.push(k + '=' + res[k]);
|
||
}
|
||
}
|
||
push('res 所有字段', keys.join(' | '));
|
||
} catch (e) {}
|
||
|
||
if (msg === 'get_brand_wcpay_request:ok') {
|
||
setTip('<span class="ok">支付成功,正在返回…</span>');
|
||
setTimeout(goBack, 500);
|
||
} else if (msg.indexOf('cancel') >= 0) {
|
||
setTip('已取消,可点按钮重试');
|
||
} else {
|
||
setTip('<span class="err">支付失败:' + (msg || '未知') + '</span><br/>把下方绿色日志整段复制发给技术', true);
|
||
push('!!!! 支付失败,请把本页全部绿色日志复制发送 !!!!');
|
||
push('若含「URL未注册」,对照:微信校验 URL 必须落在商户 JSAPI 授权目录前缀下');
|
||
push('当前校验 URL', String(location.href || '').split('#')[0]);
|
||
push('调起用的 appId', appId);
|
||
try {
|
||
alert('支付失败诊断\n\n' + msg + '\n\nappId=' + appId + '\nURL=' + String(location.href || '').split('#')[0] + '\n\n完整日志在页面绿色区域,点「复制」');
|
||
} catch (e) {}
|
||
}
|
||
});
|
||
} catch (e) {
|
||
btn.disabled = false;
|
||
push('invoke 抛异常', String(e && e.message || e), e);
|
||
setTip('调起异常:' + String(e && e.message || e), true);
|
||
}
|
||
}
|
||
|
||
copyBtn.onclick = function () {
|
||
var text = logEl.textContent || '';
|
||
function ok() { setTip('已复制,去粘贴发给技术'); }
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(text).then(ok).catch(function () {
|
||
prompt('请手动全选复制:', text);
|
||
});
|
||
} else {
|
||
prompt('请手动全选复制:', text);
|
||
}
|
||
};
|
||
|
||
btn.onclick = invokePay;
|
||
|
||
if (!pay) {
|
||
setTip('支付参数丢失:请从星阙点单页重新点「确认支付」再跳进来', true);
|
||
push('提示:同域 sessionStorage 丢失常见于开了新窗口/清理缓存;请勿复制链接单独打开本页');
|
||
return;
|
||
}
|
||
setTip('参数已就绪,自动调起支付…');
|
||
if (typeof WeixinJSBridge === 'undefined') {
|
||
document.addEventListener('WeixinJSBridgeReady', invokePay, false);
|
||
push('等待 WeixinJSBridgeReady');
|
||
} else {
|
||
setTimeout(invokePay, 80);
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|