mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-17 21:03:17 +08:00
675 lines
23 KiB
HTML
675 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<title>建行生活 H5 集成示例</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
h1 {
|
||
font-size: 24px;
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
text-align: center;
|
||
}
|
||
|
||
.info-section {
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
padding: 15px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.info-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.label {
|
||
color: #666;
|
||
}
|
||
|
||
.value {
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.btn {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 12px 20px;
|
||
background: #0066cc;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
margin-bottom: 15px;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.btn:hover {
|
||
background: #0052a3;
|
||
}
|
||
|
||
.btn:disabled {
|
||
background: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #6c757d;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #545b62;
|
||
}
|
||
|
||
.btn-success {
|
||
background: #28a745;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
.result {
|
||
background: #f0f8ff;
|
||
border: 1px solid #b6d4fe;
|
||
border-radius: 4px;
|
||
padding: 15px;
|
||
margin-top: 20px;
|
||
white-space: pre-wrap;
|
||
font-family: monospace;
|
||
font-size: 12px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.status {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 3px;
|
||
font-size: 12px;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.status-success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
}
|
||
|
||
.status-error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
}
|
||
|
||
.status-warning {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
}
|
||
|
||
.loading {
|
||
display: none;
|
||
text-align: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.loading.active {
|
||
display: block;
|
||
}
|
||
|
||
.spinner {
|
||
border: 3px solid #f3f3f3;
|
||
border-top: 3px solid #0066cc;
|
||
border-radius: 50%;
|
||
width: 30px;
|
||
height: 30px;
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.divider {
|
||
height: 1px;
|
||
background: #e9ecef;
|
||
margin: 20px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>建行生活 H5 集成示例</h1>
|
||
|
||
<!-- 环境信息 -->
|
||
<div class="info-section">
|
||
<h3>环境信息</h3>
|
||
<div class="info-item">
|
||
<span class="label">当前环境:</span>
|
||
<span class="value" id="env-status">检测中...</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">设备类型:</span>
|
||
<span class="value" id="device-type">-</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">JSBridge 状态:</span>
|
||
<span class="value" id="bridge-status">未就绪</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">Bridge 对象:</span>
|
||
<span class="value" id="bridge-object">检测中...</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">登录状态:</span>
|
||
<span class="value" id="login-status">未登录</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">建行用户ID:</span>
|
||
<span class="value" id="ccb-user-id">-</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">建行参数:</span>
|
||
<span class="value" id="ccb-param">无</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- 功能测试 -->
|
||
<h3 style="margin-bottom: 15px;">功能测试</h3>
|
||
|
||
<button class="btn" onclick="refreshEnv()">🔄 刷新环境检测</button>
|
||
<button class="btn" onclick="decryptCcbParam()">🔓 解密建行参数</button>
|
||
<button class="btn btn-success" onclick="testLogin()">🔐 测试登录入库</button>
|
||
<button class="btn" onclick="getUserInfo()">获取建行用户信息</button>
|
||
<button class="btn btn-secondary" onclick="doAutoLogin()">执行自动登录</button>
|
||
<button class="btn btn-success" onclick="testPayment()">测试支付功能</button>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- 调试功能 -->
|
||
<h3 style="margin-bottom: 15px;">调试功能</h3>
|
||
|
||
<button class="btn btn-secondary" onclick="checkUrlParams()">检查 URL 参数</button>
|
||
<button class="btn btn-secondary" onclick="showUserAgent()">显示 User-Agent</button>
|
||
<button class="btn btn-secondary" onclick="checkBridgeObjects()">检查 Bridge 对象</button>
|
||
<button class="btn btn-secondary" onclick="clearCache()">清除缓存</button>
|
||
<button class="btn btn-secondary" onclick="toggleDebug()">切换调试模式</button>
|
||
|
||
<!-- 加载状态 -->
|
||
<div class="loading" id="loading">
|
||
<div class="spinner"></div>
|
||
<p style="margin-top: 10px;">处理中...</p>
|
||
</div>
|
||
|
||
<!-- 结果显示 -->
|
||
<div class="result" id="result" style="display: none;"></div>
|
||
</div>
|
||
|
||
<!-- 引入 JSBridge -->
|
||
<script src="/assets/js/ccblife-bridge.js"></script>
|
||
|
||
<script>
|
||
// 初始化配置
|
||
window.onload = function() {
|
||
// 初始化 CcbLifeBridge
|
||
CcbLifeBridge.init({
|
||
debug: true,
|
||
apiBaseUrl: '/addons/shopro'
|
||
});
|
||
|
||
// 更新环境状态
|
||
updateEnvStatus();
|
||
|
||
// 监听登录成功事件
|
||
window.addEventListener('ccb:login:success', function(e) {
|
||
console.log('登录成功事件:', e.detail);
|
||
updateLoginStatus(e.detail);
|
||
});
|
||
|
||
// 监听 Bridge 就绪
|
||
CcbLifeBridge.ready(function() {
|
||
document.getElementById('bridge-status').innerHTML =
|
||
'<span class="status status-success">已就绪</span>';
|
||
});
|
||
};
|
||
|
||
// 更新环境状态
|
||
function updateEnvStatus() {
|
||
var isInApp = CcbLifeBridge.isInCcbApp();
|
||
var envEl = document.getElementById('env-status');
|
||
|
||
if (isInApp) {
|
||
envEl.innerHTML = '<span class="status status-success">建行生活 App 内</span>';
|
||
} else {
|
||
envEl.innerHTML = '<span class="status status-warning">普通浏览器</span>';
|
||
}
|
||
|
||
// 设备类型
|
||
var deviceType = CcbLifeBridge.isIOS() ? 'iOS' : 'Android';
|
||
document.getElementById('device-type').textContent = deviceType;
|
||
|
||
// 检查 Bridge 对象
|
||
checkBridgeObjectsStatus();
|
||
|
||
// 检查建行参数
|
||
var params = CcbLifeBridge.getUrlParams();
|
||
if (params.ccbParamSJ) {
|
||
document.getElementById('ccb-param').innerHTML =
|
||
'<span class="status status-warning">已加密(点击解密按钮)</span>';
|
||
} else {
|
||
document.getElementById('ccb-param').textContent = '无';
|
||
}
|
||
|
||
// 检查登录状态
|
||
var token = localStorage.getItem('ccb_token');
|
||
var userInfo = localStorage.getItem('ccb_user_info');
|
||
|
||
if (token && userInfo) {
|
||
try {
|
||
var user = JSON.parse(userInfo);
|
||
updateLoginStatus({ userInfo: user });
|
||
} catch(e) {
|
||
console.error('解析用户信息失败', e);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查 Bridge 对象状态
|
||
function checkBridgeObjectsStatus() {
|
||
var bridgeObjects = [];
|
||
|
||
if (window.WebViewJavascriptBridge) {
|
||
bridgeObjects.push('WebViewJavascriptBridge');
|
||
}
|
||
if (window.mbspay) {
|
||
bridgeObjects.push('mbspay');
|
||
}
|
||
|
||
var bridgeEl = document.getElementById('bridge-object');
|
||
if (bridgeObjects.length > 0) {
|
||
bridgeEl.innerHTML = '<span class="status status-success">' +
|
||
bridgeObjects.join(', ') + '</span>';
|
||
} else {
|
||
bridgeEl.innerHTML = '<span class="status status-error">未检测到</span>';
|
||
}
|
||
}
|
||
|
||
// 刷新环境检测
|
||
function refreshEnv() {
|
||
updateEnvStatus();
|
||
|
||
// 重新检查 Bridge 就绪状态
|
||
if (CcbLifeBridge.isReady) {
|
||
document.getElementById('bridge-status').innerHTML =
|
||
'<span class="status status-success">已就绪</span>';
|
||
} else {
|
||
document.getElementById('bridge-status').innerHTML =
|
||
'<span class="status status-error">未就绪</span>';
|
||
|
||
// 等待 3 秒后重新检查
|
||
setTimeout(function() {
|
||
if (CcbLifeBridge.isReady) {
|
||
document.getElementById('bridge-status').innerHTML =
|
||
'<span class="status status-success">已就绪</span>';
|
||
}
|
||
}, 3000);
|
||
}
|
||
|
||
showResult('环境信息已刷新');
|
||
}
|
||
|
||
// 更新登录状态
|
||
function updateLoginStatus(data) {
|
||
document.getElementById('login-status').innerHTML =
|
||
'<span class="status status-success">已登录</span>';
|
||
|
||
if (data.userInfo && data.userInfo.ccb_user_id) {
|
||
document.getElementById('ccb-user-id').textContent = data.userInfo.ccb_user_id;
|
||
}
|
||
}
|
||
|
||
// 获取用户信息
|
||
function getUserInfo() {
|
||
showLoading(true);
|
||
|
||
CcbLifeBridge.getUserInfo(function(result) {
|
||
showLoading(false);
|
||
|
||
if (result.success) {
|
||
showResult('获取用户信息成功:\n' + JSON.stringify(result.data, null, 2));
|
||
} else {
|
||
showResult('获取用户信息失败:' + result.error);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 执行自动登录
|
||
function doAutoLogin() {
|
||
if (!CcbLifeBridge.isInCcbApp()) {
|
||
showResult('错误:不在建行生活 App 内,无法执行自动登录');
|
||
return;
|
||
}
|
||
|
||
showLoading(true);
|
||
|
||
CcbLifeBridge.getUserInfo(function(result) {
|
||
if (result.success) {
|
||
CcbLifeBridge.doLogin(result.data);
|
||
showLoading(false);
|
||
showResult('正在登录...\n用户信息:' + JSON.stringify(result.data, null, 2));
|
||
} else {
|
||
showLoading(false);
|
||
showResult('获取用户信息失败:' + result.error);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 测试支付功能
|
||
function testPayment() {
|
||
// 检查是否已登录
|
||
var token = localStorage.getItem('ccb_token');
|
||
if (!token) {
|
||
showResult('错误:未登录\n\n请先执行"测试登录入库"获取 Token');
|
||
return;
|
||
}
|
||
|
||
// 检查是否在建行 App 内
|
||
if (!CcbLifeBridge.isInCcbApp()) {
|
||
showResult('警告:不在建行生活 App 内\n\n支付功能需要在建行 App 中才能调起');
|
||
}
|
||
|
||
// 输入订单ID
|
||
var orderId = prompt('请输入订单ID(测试用):', '1');
|
||
|
||
if (!orderId) {
|
||
return;
|
||
}
|
||
|
||
showLoading(true);
|
||
showResult('正在生成支付串...\n1. 验证订单\n2. 生成支付串\n3. 调起建行支付');
|
||
|
||
// 调用后端生成支付串
|
||
fetch('/addons/shopro/ccbpayment/createPayment', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'token': token
|
||
},
|
||
body: JSON.stringify({
|
||
order_id: orderId
|
||
})
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
showLoading(false);
|
||
|
||
if (data.code === 1) {
|
||
var paymentData = data.data;
|
||
|
||
showResult('✅ 支付串生成成功!\n\n' +
|
||
'订单编号:' + paymentData.order_sn + '\n' +
|
||
'支付流水号:' + paymentData.pay_flow_id + '\n' +
|
||
'支付金额:¥' + paymentData.amount + '\n' +
|
||
'MAC 签名:' + paymentData.mac.substr(0, 20) + '...\n\n' +
|
||
'支付串:\n' + paymentData.payment_string.substr(0, 100) + '...\n\n' +
|
||
'正在调起支付...'
|
||
);
|
||
|
||
// 调起建行支付
|
||
if (CcbLifeBridge.isInCcbApp()) {
|
||
CcbLifeBridge.payment({
|
||
payment_string: paymentData.payment_string
|
||
}, function(result) {
|
||
if (result.success) {
|
||
showResult('✅ 支付调起成功!\n\n请在建行 App 内完成支付\n\n' +
|
||
'支付完成后,页面会自动跳转');
|
||
|
||
// 轮询查询订单状态
|
||
pollPaymentStatus(orderId);
|
||
} else {
|
||
showResult('❌ 支付调起失败:' + result.error);
|
||
}
|
||
});
|
||
} else {
|
||
showResult('⚠️ 不在建行 App 内,无法调起支付\n\n' +
|
||
'但支付串已生成,可以手动测试:\n\n' +
|
||
'支付 URL:\n' + paymentData.payment_url
|
||
);
|
||
}
|
||
} else {
|
||
showResult('❌ 生成支付串失败:' + data.msg);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
showLoading(false);
|
||
showResult('❌ 请求失败:' + error.message);
|
||
});
|
||
}
|
||
|
||
// 轮询查询支付状态
|
||
function pollPaymentStatus(orderId) {
|
||
var token = localStorage.getItem('ccb_token');
|
||
var pollCount = 0;
|
||
var maxPoll = 60; // 最多轮询60次(5分钟)
|
||
|
||
var pollInterval = setInterval(function() {
|
||
pollCount++;
|
||
|
||
fetch('/addons/shopro/order/detail?id=' + orderId, {
|
||
method: 'GET',
|
||
headers: {
|
||
'token': token
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.code === 1) {
|
||
var order = data.data;
|
||
|
||
if (order.status === 'paid' || order.status === 'completed') {
|
||
clearInterval(pollInterval);
|
||
showResult('✅ 支付成功!\n\n' +
|
||
'订单编号:' + order.order_sn + '\n' +
|
||
'订单状态:' + order.status + '\n' +
|
||
'支付时间:' + new Date(order.paid_time).toLocaleString()
|
||
);
|
||
} else if (pollCount >= maxPoll) {
|
||
clearInterval(pollInterval);
|
||
showResult('⏱ 查询超时\n\n请手动刷新订单状态');
|
||
}
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('轮询订单状态失败:', error);
|
||
});
|
||
|
||
}, 5000); // 每5秒查询一次
|
||
}
|
||
|
||
// 测试登录入库
|
||
function testLogin() {
|
||
var params = CcbLifeBridge.getUrlParams();
|
||
|
||
if (!params.ccbParamSJ) {
|
||
showResult('错误:URL 中没有 ccbParamSJ 参数\n\n请从建行生活 App 跳转到此页面,或在 URL 中添加 ccbParamSJ 参数');
|
||
return;
|
||
}
|
||
|
||
showLoading(true);
|
||
showResult('正在登录...\n1. 解密建行参数\n2. 验证用户信息\n3. 创建/更新用户\n4. 生成登录 Token');
|
||
|
||
// 调用后端登录接口
|
||
fetch('/addons/shopro/ccblife/login?ccbParamSJ=' + encodeURIComponent(params.ccbParamSJ), {
|
||
method: 'GET'
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
showLoading(false);
|
||
|
||
if (data.code === 1) {
|
||
var loginData = data.data;
|
||
|
||
// 保存 token 和用户信息
|
||
localStorage.setItem('ccb_token', loginData.token);
|
||
localStorage.setItem('ccb_user_info', JSON.stringify(loginData.user_info));
|
||
|
||
// 更新页面显示
|
||
updateLoginStatus({ userInfo: loginData.user_info });
|
||
|
||
showResult('✅ 登录成功!\n\n' +
|
||
'用户信息:\n' + JSON.stringify(loginData.user_info, null, 2) + '\n\n' +
|
||
'Token:' + loginData.token.substr(0, 20) + '...\n\n' +
|
||
'是否新用户:' + (loginData.user_info.is_new ? '是' : '否') + '\n\n' +
|
||
'跳转URL:' + loginData.redirect_url
|
||
);
|
||
} else {
|
||
showResult('❌ 登录失败:' + data.msg);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
showLoading(false);
|
||
showResult('❌ 请求失败:' + error.message);
|
||
});
|
||
}
|
||
|
||
// 解密建行参数
|
||
function decryptCcbParam() {
|
||
var params = CcbLifeBridge.getUrlParams();
|
||
|
||
if (!params.ccbParamSJ) {
|
||
showResult('错误:URL 中没有 ccbParamSJ 参数');
|
||
return;
|
||
}
|
||
|
||
showLoading(true);
|
||
|
||
// 调用后端接口解密
|
||
fetch('/addons/shopro/ccblife/decryptParam', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
ccbParamSJ: params.ccbParamSJ
|
||
})
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
showLoading(false);
|
||
|
||
if (data.code === 1) {
|
||
var decrypted = data.data;
|
||
showResult('解密成功:\n' + JSON.stringify(decrypted, null, 2));
|
||
|
||
// 更新显示
|
||
document.getElementById('ccb-param').innerHTML =
|
||
'<span class="status status-success">已解密</span>';
|
||
|
||
// 如果有用户ID,显示出来
|
||
if (decrypted.userid) {
|
||
document.getElementById('ccb-user-id').textContent = decrypted.userid;
|
||
}
|
||
} else {
|
||
showResult('解密失败:' + data.msg);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
showLoading(false);
|
||
showResult('请求失败:' + error.message);
|
||
});
|
||
}
|
||
|
||
// 检查 URL 参数
|
||
function checkUrlParams() {
|
||
var params = CcbLifeBridge.getUrlParams();
|
||
showResult('URL 参数:\n' + JSON.stringify(params, null, 2));
|
||
}
|
||
|
||
// 显示 User-Agent
|
||
function showUserAgent() {
|
||
var ua = navigator.userAgent;
|
||
showResult('User-Agent:\n' + ua);
|
||
}
|
||
|
||
// 检查 Bridge 对象
|
||
function checkBridgeObjects() {
|
||
var info = {
|
||
WebViewJavascriptBridge: typeof window.WebViewJavascriptBridge !== 'undefined',
|
||
mbspay: typeof window.mbspay !== 'undefined',
|
||
bridgeReady: CcbLifeBridge.isReady,
|
||
isIOS: CcbLifeBridge.isIOS(),
|
||
isInCcbApp: CcbLifeBridge.isInCcbApp()
|
||
};
|
||
|
||
showResult('Bridge 对象检测:\n' + JSON.stringify(info, null, 2));
|
||
}
|
||
|
||
// 清除缓存
|
||
function clearCache() {
|
||
localStorage.removeItem('ccb_token');
|
||
localStorage.removeItem('ccb_user_info');
|
||
|
||
document.getElementById('login-status').innerHTML =
|
||
'<span class="status status-warning">未登录</span>';
|
||
document.getElementById('ccb-user-id').textContent = '-';
|
||
|
||
showResult('缓存已清除');
|
||
}
|
||
|
||
// 切换调试模式
|
||
function toggleDebug() {
|
||
CcbLifeBridge.config.debug = !CcbLifeBridge.config.debug;
|
||
showResult('调试模式:' + (CcbLifeBridge.config.debug ? '开启' : '关闭'));
|
||
}
|
||
|
||
// 显示加载状态
|
||
function showLoading(show) {
|
||
var loading = document.getElementById('loading');
|
||
if (show) {
|
||
loading.classList.add('active');
|
||
} else {
|
||
loading.classList.remove('active');
|
||
}
|
||
}
|
||
|
||
// 显示结果
|
||
function showResult(message) {
|
||
var resultEl = document.getElementById('result');
|
||
resultEl.style.display = 'block';
|
||
resultEl.textContent = message;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |