UNPKG

@bee-hole/server

Version:

www.bee-hole.com

749 lines (698 loc) 28.9 kB
<!DOCTYPE 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 !important; word-break: break-all !important; 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()">&times;</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>