@bee-hole/server
Version:
www.bee-hole.com
749 lines (698 loc) • 28.9 kB
HTML
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>支付页面</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f4f4f4;
}
.payment-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
.payment-header {
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 20px;
}
.payment-title {
font-size: 24px;
font-weight: bold;
margin: 0;
}
.payment-details {
margin-bottom: 20px;
}
.payment-details p {
margin: 5px 0;
}
.payment-amount {
font-size: 18px;
font-weight: bold;
color: #e53e3e;
}
.payment-methods {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.payment-method, .xianxia-payment-method {
border: 2px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.payment-method:hover, .xianxia-payment-method:hover {
border-color: #3182ce;
background-color: #ebf8ff;
}
.payment-method img, .xianxia-payment-method img {
height: 22px;
margin-bottom: 10px;
}
.xianxia-payment-method h3 p span , .xianxia-payment-method h3 span{
white-space: pre-wrap ;
word-break: break-all ;
display: block;
text-align: left;
}
.currency-select {
display: block;
width: 100%;
padding: 8px;
margin-top: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.pay-button {
display: block;
width: 100%;
padding: 12px;
background-color: #3182ce;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.pay-button:hover {
background-color: #2c5282;
}
#loading {
text-align: center;
font-size: 18px;
margin: 20px 0;
}
.language-selector {
text-align: right;
margin-bottom: 10px;
}
.notification {
position: fixed;
top: -100px;
left: 0;
right: 0;
background-color: #4CAF50;
color: white;
text-align: center;
padding: 16px;
transition: top 0.5s ease;
z-index: 1000;
}
.notification.show {
top: 0;
}
.notification.error {
background-color: #f44336;
}
.notification.warning {
background-color: #ff9800;
}
.close-btn {
margin-left: 15px;
color: white;
font-weight: bold;
float: right;
font-size: 22px;
line-height: 20px;
cursor: pointer;
transition: 0.3s;
}
.close-btn:hover {
color: black;
}
</style>
</head>
<body>
<div id="notification" class="notification">
<span id="notificationMessage"></span>
<span class="close-btn" onclick="closeNotification()">×</span>
</div>
<div class="language-selector">
<select id="language-select">
<option value="zh-CN">中文</option>
<option value="en-US">English</option>
</select>
</div>
<div class="payment-card">
<div class="payment-header">
<h1 class="payment-title" data-i18n="paymentDetails">支付详情</h1>
</div>
<div class="payment-details">
<p><span data-i18n="invoiceNumber">支付单据号</span>:<strong id="orderId"></strong></p>
<p><span data-i18n="amountDue">应付金额</span>:<span class="payment-amount"></span></p>
</div>
<div id="payCode"></div>
<div id="loading" data-i18n="loading">加载支付方式中...</div>
<div id="payment-methods" class="payment-methods"></div>
<button id="pay-button" class="pay-button" disabled data-i18n="payNow">去支付</button>
<div id="xianxia-payment-methods" class="payment-methods" style="margin-top: 1rem;"></div>
</div>
<script>
var debug = false;
// const httpurl = 'https://beehole.bee-hole.com';
// const httpurl = 'https://xbuy.96101210.com';
const httpurl = 'http:///192.168.3.58:8299';
const translations = {
'zh-CN': {
paymentDetails: '支付详情',
invoiceNumber: '支付单据号',
amountDue: '应付金额',
loading: '加载支付方式中...',
payNow: '去支付',
selectCurrency: '选择货币',
paymentProcessing: '正在使用 {method} 进行 {currency} 支付',
loadingFailed: '加载支付方式失败,请刷新页面重试。',
noPayMethod: '暂无可用支付方式',
creditCard: '信用卡',
debitCard: '借记卡',
bankTransfer: '银行转账',
digitalWallet: '电子钱包',
translatePrice: '换算金额',
balance: '余额',
paySuccess: '支付成功',
sanbarcodePay: '请扫上面的支付码支付',
xianxiaPayTitle: '线下支付',
},
'en-US': {
paymentDetails: 'Payment Details',
invoiceNumber: 'Invoice Number',
amountDue: 'Amount Due',
loading: 'Loading payment methods...',
payNow: 'Pay Now',
selectCurrency: 'Select Currency',
paymentProcessing: 'Processing payment with {method} in {currency}',
loadingFailed: 'Failed to load payment methods. Please refresh the page and try again.',
noPayMethod: 'No payment methods available',
creditCard: 'Credit Card',
debitCard: 'Debit Card',
bankTransfer: 'Bank Transfer',
digitalWallet: 'Digital Wallet',
translatePrice: 'Translate Price',
balance: 'Balance',
paySuccess: 'Payment Success',
sanbarcodePay: 'Please scan the above payment code to pay',
xianxiaPayTitle: 'Offline Payment',
}
};
let currentLanguage = 'zh-CN';
let payRes = null;
let yueAmount = 0;
function translate(key, params = {}) {
let text = translations[currentLanguage][key] || key;
for (const [param, value] of Object.entries(params)) {
text = text.replace(`{${param}}`, value);
}
return text;
}
function showNotification(message, type = 'success', ttl = 5000) {
const notification = document.getElementById('notification');
const notificationMessage = document.getElementById('notificationMessage');
notification.className = 'notification';
if (type === 'error') {
notification.classList.add('error');
} else if (type === 'warning') {
notification.classList.add('warning');
}
notificationMessage.textContent = message;
notification.classList.add('show');
// Auto hide after 5 seconds
setTimeout(() => {
closeNotification();
}, ttl);
}
function closeNotification() {
const notification = document.getElementById('notification');
notification.classList.remove('show');
}
function showErrText(text) {
document.getElementById('loading').style.display = 'block';
document.getElementById('loading').textContent = text;
}
function hiddenErrText() {
document.getElementById('loading').style.display = 'none';
}
function hiidenPayCode() {
document.getElementById('payCode').style.display = 'none';
}
function showPayCode() {
document.getElementById('payCode').style.display = 'block';
}
function updatePageLanguage() {
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
element.textContent = translate(key);
});
document.title = translate('paymentDetails');
}
function getRouterParams() {
let url = decodeURI(window.location.href);
// 获取地址栏参数
let params = url.split('?')[1];
// 获取参数数组
let paramArr = params.split('&');
// 定义参数对象
let paramObj = {};
// 遍历参数数组
paramArr.forEach((item) => {
// 获取参数键值对
let key = item.split('=')[0];
let value = decodeURIComponent(item.split('=')[1]);
// // 添加到参数对象
// if (key == 'path') {
// value = decodeBase64(value);
// }
paramObj[key] = value;
})
return paramObj;
}
function fetchPaymentMethods(credential_id) {
return new Promise((resolve) => {
var api = '/admin/payment/methods'
if (debug) {
api = httpurl + api;
}
fetch(api, {
method: 'POST',
headers: {
'admin-locale': 'zh-CN',
'Content-Type': 'application/json' // 如果需要 token 认证
},
body: JSON.stringify({
// 参数
credential_id: credential_id
})
}).then(response => response.json())
.then(result => {
if (result && result.data) {
// this.paymentList = result.data.list
// this.initPaymenthods();
if (result.data && result.data.balance) {
if (!result.data.list) {
// 向第一个追加余额支付
result.data.list = [];
}
result.data.list.unshift({
"pay_way_id": 0,
"description": translate('balance'),
"logo": "",
"balance": result.data.balance_currency + result.data.balance
});
yueAmount = result.data.balance_currency + '' + result.data.amount;
}
if (!result.data.list || result.data.list.length == 0) {
showNotification(translate('noPayMethod'), 'error');
return;
}
resolve(result.data.list);
} else {
if (result && result.code != 0) {
// this.ErrMsg = result.message;
// showErrText(result.message);
showNotification(result.message, 'error');
}
}
})
.catch(error => {
console.error('Error:', error);
// showErrText(error);
showNotification(error, 'error');
});
});
}
function renderPaymentMethods(methods) {
const container = document.getElementById('payment-methods');
container.innerHTML = methods.map(method => {
if (method.pay_way_id !== '9999'){
return `
<div class="payment-method" data-id="${method.pay_way_id}">
${method.logo ? `<img src="${method.logo}" alt="${translate(method.description)}">` : `<div style="height: 22px;"></div>`}
<h3 data-i18n="${method.description}" data-id="${method.pay_way_id}">${translate(method.description)}</h3>
${method.pay_way_id == 0 ? `<p>余额:${method.balance || 0}</p>` : method.currencies ? `<select class="currency-select" data-id="${method.pay_way_id}">
<option value="" data-i18n="selectCurrency">${translate('selectCurrency')}</option>
${method.currencies.map(currency => `<option value="${currency}">${currency}</option>`).join('')}
</select>` : ''}
</div>
`;
} else {
return renderPaymentMethodsXianxia(method);
}
}).join('');
container.querySelectorAll('.payment-method').forEach(method => {
method.addEventListener('click', selectPaymentMethod);
});
container.querySelectorAll('.currency-select').forEach(select => {
select.addEventListener('change', (event) => {
selectPaymentMethodAndCurrency(event);
});
});
setTimeout(() => {
initPay();
}, 200);
}
function renderPaymentMethodsXianxia(method) {
console.log(method , 'method');
const container = document.getElementById('xianxia-payment-methods');
container.innerHTML = `
<div class="xianxia-payment-method" data-id="${method.pay_way_id}">
${method.logo ? `<img src="${method.logo}" alt="${translate(method.description)}">` : `<div style="height: 22px;"></div>`}
<h3 data-i18n="${('xianxiaPayTitle')}" data-id="${method.pay_way_id}">${translate('xianxiaPayTitle')}</h3>
<h3>${method.description}</h3>
</div>
`;
}
function selectPaymentMethod(event) {
hiddenErrText();
hiidenPayCode();
document.querySelectorAll('.payment-method').forEach(method => {
method.style.borderColor = '#ddd';
method.style.backgroundColor = '#fff';
});
const selectedMethod = event.currentTarget;
selectedMethod.style.borderColor = '#3182ce';
selectedMethod.style.backgroundColor = '#ebf8ff';
updatePayButton();
}
async function selectPaymentMethodAndCurrency(event) {
if (event) {
event.stopPropagation(); // 阻止事件冒泡
}
const select = event.target;
const paymentMethod = select.closest('.payment-method');
// // 重置所有支付方式的样式
// document.querySelectorAll('.payment-method').forEach(method => {
// method.style.borderColor = '#ddd';
// method.style.backgroundColor = '#fff';
// });
// // 设置当前选中的支付方式样式
// paymentMethod.style.borderColor = '#3182ce';
// paymentMethod.style.backgroundColor = '#ebf8ff';
hiddenErrText();
await updatePayButton();
}
async function updatePayButton() {
const selectedMethod = document.querySelector('.payment-method[style*="border-color: rgb(49, 130, 206)"]');
const payWayId = selectedMethod?.getAttribute('data-id')
const selectedCurrency = selectedMethod ? selectedMethod.querySelector('.currency-select')?.value : '';
const payButton = document.getElementById('pay-button');
if (payWayId + '' === '0') {
// payButton.textContent = '余额支付';
payButton.disabled = false
} else {
payButton.disabled = !selectedMethod || !selectedCurrency;
}
const methodName = selectedMethod.querySelector('[data-i18n]').getAttribute('data-i18n');
console.log('methodName-------', methodName)
if (!payButton.disabled && payWayId + '' !== '0') {
// alert(translate('paymentProcessing', { method: translate(methodName), currency: selectedCurrency }));
payRes = await Pay(selectedCurrency, payWayId, methodName);
}
if(payWayId + '' === '0'){
setPayAmount(yueAmount);
}
}
function setPayAmount(amount, payAmount) {
let payAmountText ='';
if (payAmount) {
payAmountText = ', ' + translate('translatePrice') + ': ' + payAmount;
}
document.querySelector('.payment-amount').textContent = amount + payAmountText;
}
function backUrl() {
return window.location.href;
};
function Pay(currency, payWayId, name) {
return new Promise((resolve) => {
const params = getRouterParams();
var query = {
credential_id: params.credential_id,
currency: currency,
scene_type: name === 'Alipay' ? 'H5' : 'NATIVE',
cancel_return_url: backUrl(),
paid_return_url: params.backUrl,
pay_way_id: payWayId
}
var api = '/admin/payment'
if (debug) {
api = httpurl + api;
}
fetch(api, {
method: 'POST',
headers: {
'admin-locale': currentLanguage,
'Content-Type': 'application/json' // 如果需要 token 认证
},
body: JSON.stringify(query)
}).then(response => response.json())
.then(result => {
const res = result.data;
// console.log('res', res, result)
if (res) {
// that.prepayAmountCurrency = (res.currency)
// that.prepayAmount = res.amount;
setPayAmount((res.price_currency) + '' + res.price_amount, res.trade_currency + '' + res.trade_amount);
const url64 = res?.jsapi?.auth_link;
const h5Url = res?.h5?.h5_url;
const authLink = res?.jsapi?.auth_link;
const qrCode = res?.native?.qr_code_summary;
// that.payLoding = true;
if (url64) {
// 对url进行base64解码
const url = Buffer.from(url64, 'base64').toString('binary');
// that.payUrl = url;
resolve({ url, type: 'url' });
}
if (h5Url) {
// that.payUrl = h5Url;
resolve({ url: h5Url, type: 'url' });
}
if (authLink) {
// that.payUrl = authLink;
resolve({ url: authLink, type: 'url' });
}
if (qrCode) {
// that.payQrCode = 'data:image/jpeg;base64, ' + qrCode
resolve({ url: qrCode, type: 'qrCode' });
// this.lunxunOrder();
}
if (payWayId === '' && result.code === 0) {
resolve(result);
}
} else {
if (result && result.code != 0) {
// that.ErrMsg = result.message;
// console.log('result.message', result.message);
// showErrText(result.message);
showNotification(result.message, 'error');
}
}
})
.catch(error => {
console.error('Error:', error);
// that.ErrMsg = error.message;
// showErrText(error);
showNotification(error, 'error');
});
});
}
let payTime = null;
function getOrderStatus() {
return new Promise((resolve) => {
var api = '/admin/payment/query'
if (debug) {
api = httpurl + api;
}
const params = getRouterParams();
var query = {
credential_id: params?.credential_id
}
fetch(api, {
method: 'POST',
headers: {
'admin-locale': currentLanguage,
'Content-Type': 'application/json' // 如果需要 token 认证
},
body: JSON.stringify(query)
}).then(response => response.json())
.then(result => {
const res = result.data;
if (res.status === 'PENDING') {
payTime = setTimeout(() => {
getOrderStatus();
}, 1000);
} else if (res.status === 'PAID') {
window.close();
} else {
if (res && res.code != 0) {
// showErrText(res.message);
showNotification(result.message, 'error');
}
}
})
.catch(error => {
console.error('Error:', error);
// showErrText(error);
showNotification(error, 'error');
});
});
}
function initPay() {
// // 默认选中第一个支付方式
// const payMentd = document.querySelector('.payment-method')
// payMentd.click();
// // 触发选中第一个支付方式的第一个货币
// // document.querySelector('.currency-select').dispatchEvent(new Event('change'));
// // 默认选中第二个货币
// // document.querySelector('.currency-select').value = document.querySelector('.currency-select option').value;
// const currencySelect = payMentd.querySelector('.currency-select');
// // 选中第二个 option
// currencySelect.selectedIndex = 1;
// console.log('currencySelect', currencySelect)
// payMentd.querySelector('.currency-select').dispatchEvent(new Event('change'));
// document.querySelector('.currency-select').value = document.querySelector('.currency-select option').value;
// 默认点击支付按钮
// 默认选中第一个支付方式的第一个有效货币
// console.log('all', document.querySelectorAll('.payment-method'));
// const firstPaymentMethod = document.querySelector('.payment-method');
// const currencySelect = firstPaymentMethod.querySelector('.currency-select');
// console.log('firstPaymentMethod', firstPaymentMethod);
// // 选中第一个有效的货币选项(跳过空选项)
// if(currencySelect){
// for (let i = 1; i < currencySelect.options.length; i++) {
// if (currencySelect.options[i].value) {
// currencySelect.selectedIndex = i;
// break;
// }
// }
// // 手动触发选择事件
// const mockEvent = {
// target: currencySelect,
// stopPropagation: () => { } // 添加一个空的 stopPropagation 方法
// };
// selectPaymentMethodAndCurrency(mockEvent);
// }else{
// firstPaymentMethod.click();
// }
// 获取所有
let isInit = false;
document.querySelectorAll('.payment-method').forEach((method) => {
if (!isInit) {
method.click();
isInit = true;
}
const currencySelect = method.querySelector('.currency-select');
if (currencySelect) {
for (let i = 1; i < currencySelect.options.length; i++) {
if (currencySelect.options[i].value) {
currencySelect.selectedIndex = i;
break;
}
}
// 手动触发选择事件
const mockEvent = {
target: currencySelect,
stopPropagation: () => { } // 添加一个空的 stopPropagation 方法
};
selectPaymentMethodAndCurrency(mockEvent);
}
});
}
async function initPage() {
try {
const params = getRouterParams();
if (params.debug === 'true') {
debug = true;
if (params.httpurl) {
httpurl = params.httpurl;
}
}
document.getElementById('orderId').textContent = params.credential_id;
const paymentMethods = await fetchPaymentMethods(params.credential_id);
document.getElementById('loading').style.display = 'none';
renderPaymentMethods(paymentMethods);
// renderPaymentMethodsXianxia({
// xianxiaPayWayId: params.xianxiaPayWayId,
// xianxiaPayWayName: params.xianxiaPayWayName,
// content: `<div>paypal支付:<a href="https://paypal.me/pay2leeyinn" target="_blank" rel="noopener">支付给我</a></div>
// <div>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">Cash on Delivery Instructions I. Operational Process </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">1. Select the cash - on - delivery option when placing an order. </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">2. After we dispatch the goods, the courier will deliver the merchandise to the location you specify. </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">3. Open the package on the spot and check whether the appearance and quantity of the goods are consistent with the order. </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">4. After confirmation, pay the purchase price to the courier to complete the transaction. II. Precautions </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">1. Ensure that the receiving information is accurate. </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">2. There may be a handling charge for cash on delivery, subject to the actual situation. </span></p>
// <p><span style="color: #1f2329; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro SC', 'SF Pro Display', 'SF Pro Icons', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: pre; background-color: #ffffff;">3. If there are quality problems with the goods or they do not match the order, contact customer service in a timely manner to solve the problem.</span></p>
// </div>`
// });
updatePageLanguage();
// getOrderStatus();
} catch (error) {
// document.getElementById('loading').textContent = translate('loadingFailed');
// showErrText(translate('loadingFailed'));
console.log('err', error)
showNotification(translate('loadingFailed'), 'error');
}
}
window.addEventListener('load', initPage);
document.getElementById('pay-button').addEventListener('click', async () => {
const selectedMethod = document.querySelector('.payment-method[style*="border-color: rgb(49, 130, 206)"]');
const selectedCurrency = selectedMethod ? selectedMethod.querySelector('.currency-select')?.value : '';
// 我想通过selectedCurrency获取他标签上的data-id
if (selectedMethod && selectedCurrency) {
// const methodName = selectedMethod.querySelector('[data-i18n]').getAttribute('data-i18n');
// console.log('selectedCurrency', selectedCurrency, methodName);
// // alert(translate('paymentProcessing', { method: translate(methodName), currency: selectedCurrency }));
// payRes = await Pay(selectedCurrency, selectedMethod.querySelector('.currency-select').getAttribute('data-id'), methodName);
// console.log('payRes-------------', payRes)
if (payRes) {
if (payRes.type === 'url') {
window.location.href = payRes.url;
} else {
showPayCode();
document.getElementById('payCode').innerHTML = `<img src="data:image/jpeg;base64, ${payRes.url}" alt="支付二维码">`;
showNotification(translate('sanbarcodePay'), 'success');
}
} else {
// showErrText('支付失败');
}
} else {
const selectedMethod = document.querySelector('.payment-method[style*="border-color: rgb(49, 130, 206)"]');
const payWayId = selectedMethod?.getAttribute('data-id')
if (payWayId + '' === '0') {
// 余额支付
const res = await Pay(selectedCurrency, '', '余额');
if (res.code === 0) {
showNotification(translate('paySuccess'), 'success');
setTimeout(() => {
window.close();
}, 2000);
} else {
// showErrText('支付失败');
}
}
}
});
document.getElementById('language-select').addEventListener('change', (event) => {
currentLanguage = event.target.value;
updatePageLanguage();
});
</script>
</body>
</html>