hyapp-h5/recharge/create.html
2026-06-25 11:18:15 +08:00

750 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>三方临时支付链接</title>
<link rel="stylesheet" href="../common/theme.css" />
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--hy-theme-bg);
color: var(--hy-theme-text);
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
button,
input,
select,
textarea {
font: inherit;
}
.page {
width: min(1120px, 100%);
margin: 0 auto;
padding: 28px 18px 42px;
}
.header {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.title {
margin: 0;
font-size: 24px;
line-height: 1.3;
}
.meta {
margin: 6px 0 0;
color: var(--hy-theme-muted);
font-size: 13px;
}
.grid {
display: grid;
gap: 16px;
grid-template-columns: minmax(0, 1fr) minmax(320px, 0.75fr);
}
.panel {
border: 1px solid var(--hy-theme-line);
border-radius: 14px;
background: var(--hy-theme-surface);
box-shadow: var(--hy-theme-shadow);
padding: 18px;
}
.panel h2 {
margin: 0 0 16px;
font-size: 18px;
}
.form-grid {
display: grid;
gap: 14px;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.field {
display: flex;
min-width: 0;
flex-direction: column;
gap: 7px;
}
.field.full {
grid-column: 1 / -1;
}
label {
color: var(--hy-theme-muted);
font-size: 13px;
font-weight: 700;
}
input,
select,
textarea {
width: 100%;
min-height: 44px;
border: 1px solid var(--hy-theme-line);
border-radius: 10px;
outline: none;
background: var(--hy-theme-surface-soft);
color: var(--hy-theme-text);
padding: 10px 12px;
}
textarea {
min-height: 86px;
resize: vertical;
}
input:focus,
select:focus,
textarea:focus {
border-color: var(--hy-theme-primary-strong);
box-shadow: 0 0 0 4px var(--hy-theme-focus-ring);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 16px;
}
.button {
min-height: 42px;
border: 1px solid transparent;
border-radius: 10px;
cursor: pointer;
padding: 0 16px;
font-weight: 800;
}
.button.primary {
background: var(--hy-theme-button);
color: #fff;
box-shadow: var(--hy-theme-button-shadow);
}
.button.secondary {
border-color: var(--hy-theme-line);
background: var(--hy-theme-surface-soft);
color: var(--hy-theme-primary-deep);
}
.button:disabled {
cursor: not-allowed;
background: var(--hy-theme-button-disabled);
color: var(--hy-theme-button-disabled-text);
box-shadow: var(--hy-theme-button-disabled-shadow);
}
.result {
display: grid;
gap: 12px;
}
.result-row {
display: grid;
min-width: 0;
gap: 4px;
}
.result-row strong {
color: var(--hy-theme-muted);
font-size: 13px;
}
.value {
min-width: 0;
overflow-wrap: anywhere;
border-radius: 10px;
background: var(--hy-theme-primary-soft);
padding: 10px 12px;
line-height: 1.45;
}
.status {
display: inline-flex;
width: fit-content;
align-items: center;
border-radius: 999px;
background: var(--hy-theme-primary-soft);
color: var(--hy-theme-primary-deep);
padding: 5px 10px;
font-size: 13px;
font-weight: 800;
}
.status.paid {
background: rgba(49, 198, 168, 0.14);
color: var(--hy-theme-success);
}
.status.failed {
background: rgba(217, 93, 114, 0.12);
color: var(--hy-theme-danger);
}
.message {
min-height: 22px;
margin-top: 12px;
color: var(--hy-theme-muted);
font-size: 13px;
}
.message.error {
color: var(--hy-theme-danger);
}
.message.success {
color: var(--hy-theme-success);
}
@media (max-width: 820px) {
.header {
align-items: flex-start;
flex-direction: column;
}
.grid,
.form-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main class="page">
<header class="header">
<div>
<h1 class="title">三方临时支付链接</h1>
<p class="meta" id="envMeta">加载环境中...</p>
</div>
<button
class="button secondary"
type="button"
id="reloadMethods"
>
刷新支付方式
</button>
</header>
<section class="grid">
<div class="panel">
<h2>创建支付链接</h2>
<form id="createForm">
<div class="form-grid">
<div class="field full">
<label for="paymentMethod">支付方式</label>
<select id="paymentMethod" required>
<option value="">
正在加载支付方式...
</option>
</select>
</div>
<div class="field">
<label for="coinAmount">金币数量</label>
<input
id="coinAmount"
inputmode="numeric"
min="1"
placeholder="例如 1000"
required
type="number"
/>
</div>
<div class="field">
<label for="usdAmount">美金金额</label>
<input
id="usdAmount"
inputmode="decimal"
min="0.01"
placeholder="例如 9.99"
required
step="0.01"
type="number"
/>
</div>
<div class="field full">
<label for="returnUrl">支付完成返回地址</label>
<input
id="returnUrl"
placeholder="不填则返回当前页面"
type="url"
/>
</div>
</div>
<div class="actions">
<button
class="button primary"
type="submit"
id="createButton"
>
创建链接
</button>
</div>
<div class="message" id="createMessage"></div>
</form>
</div>
<div class="panel">
<h2>创建结果</h2>
<div class="result" id="createResult">
<div class="value">还没有创建支付链接</div>
</div>
<div class="actions">
<button
class="button secondary"
disabled
type="button"
id="copyLink"
>
复制链接
</button>
<button
class="button secondary"
disabled
type="button"
id="openLink"
>
打开支付
</button>
</div>
</div>
<div class="panel">
<h2>验证支付链接</h2>
<div class="field">
<label for="verifyInput">订单号或支付链接</label>
<input
id="verifyInput"
placeholder="输入 order_id或粘贴创建出来的支付链接"
/>
</div>
<div class="actions">
<button
class="button primary"
type="button"
id="verifyButton"
>
验证支付状态
</button>
</div>
<div class="message" id="verifyMessage"></div>
</div>
<div class="panel">
<h2>验证结果</h2>
<div class="result" id="verifyResult">
<div class="value">还没有验证订单</div>
</div>
</div>
</section>
</main>
<script src="../common/api.js"></script>
<script>
(function () {
var methods = [];
var latestPaymentLink = '';
var latestOrderID = '';
var els = {
coinAmount: document.getElementById('coinAmount'),
copyLink: document.getElementById('copyLink'),
createButton: document.getElementById('createButton'),
createForm: document.getElementById('createForm'),
createMessage: document.getElementById('createMessage'),
createResult: document.getElementById('createResult'),
envMeta: document.getElementById('envMeta'),
openLink: document.getElementById('openLink'),
paymentMethod: document.getElementById('paymentMethod'),
reloadMethods: document.getElementById('reloadMethods'),
returnUrl: document.getElementById('returnUrl'),
usdAmount: document.getElementById('usdAmount'),
verifyButton: document.getElementById('verifyButton'),
verifyInput: document.getElementById('verifyInput'),
verifyMessage: document.getElementById('verifyMessage'),
verifyResult: document.getElementById('verifyResult'),
};
els.envMeta.textContent =
'App' +
window.HyAppAPI.getAppCode() +
' API' +
window.HyAppAPI.baseURL();
els.createForm.addEventListener('submit', createPaymentLink);
els.reloadMethods.addEventListener('click', loadMethods);
els.copyLink.addEventListener('click', function () {
copyText(latestPaymentLink, els.createMessage);
});
els.openLink.addEventListener('click', function () {
if (latestPaymentLink)
window.open(
latestPaymentLink,
'_blank',
'noopener,noreferrer'
);
});
els.verifyButton.addEventListener('click', verifyPaymentLink);
loadMethods();
function loadMethods() {
setMessage(els.createMessage, '正在加载支付方式...', '');
els.paymentMethod.disabled = true;
return window.HyAppAPI.get(
'/api/v1/recharge/h5/temporary/methods'
)
.then(function (data) {
methods = Array.isArray(data.methods)
? data.methods
: [];
renderMethods();
setMessage(
els.createMessage,
methods.length
? '支付方式已加载'
: '没有可用支付方式',
methods.length ? 'success' : 'error'
);
})
.catch(function (err) {
methods = [];
renderMethods();
setMessage(
els.createMessage,
err.message || '加载支付方式失败',
'error'
);
});
}
function renderMethods() {
els.paymentMethod.innerHTML = '';
if (!methods.length) {
appendOption('', '没有可用支付方式');
els.paymentMethod.disabled = true;
return;
}
appendOption('', '请选择支付方式');
methods.forEach(function (method) {
var label = [
method.provider_name || method.provider_code,
method.method_name || method.pay_way,
method.pay_type,
method.currency_code,
method.country_code,
]
.filter(Boolean)
.join(' / ');
appendOption(String(method.method_id), label);
});
els.paymentMethod.disabled = false;
}
function appendOption(value, label) {
var option = document.createElement('option');
option.value = value;
option.textContent = label;
els.paymentMethod.appendChild(option);
}
function createPaymentLink(event) {
event.preventDefault();
var method = selectedMethod();
var coinAmount = parseInt(els.coinAmount.value, 10);
var usdMinorAmount = dollarsToMinor(els.usdAmount.value);
if (
!method ||
!coinAmount ||
coinAmount <= 0 ||
!usdMinorAmount ||
usdMinorAmount <= 0
) {
setMessage(
els.createMessage,
'请选择支付方式,并填写大于 0 的金币和美金金额',
'error'
);
return;
}
setLoading(els.createButton, true, '创建中...');
setMessage(els.createMessage, '正在创建支付链接...', '');
window.HyAppAPI.post(
'/api/v1/recharge/h5/temporary/orders',
{
coin_amount: coinAmount,
usd_minor_amount: usdMinorAmount,
provider_code: method.provider_code,
payment_method_id: method.method_id,
return_url:
els.returnUrl.value.trim() ||
window.location.href,
}
)
.then(function (data) {
latestPaymentLink = data.payment_link || '';
latestOrderID =
data.order && data.order.order_id
? data.order.order_id
: '';
renderOrder(els.createResult, data);
els.copyLink.disabled = !latestPaymentLink;
els.openLink.disabled = !latestPaymentLink;
if (latestOrderID)
els.verifyInput.value = latestOrderID;
setMessage(
els.createMessage,
'支付链接已创建',
'success'
);
})
.catch(function (err) {
setMessage(
els.createMessage,
err.message || '创建支付链接失败',
'error'
);
})
.finally(function () {
setLoading(els.createButton, false, '创建链接');
});
}
function verifyPaymentLink() {
var orderID = extractOrderID(els.verifyInput.value);
if (!orderID) {
setMessage(
els.verifyMessage,
'请输入订单号,或粘贴包含订单号的支付链接',
'error'
);
return;
}
setLoading(els.verifyButton, true, '验证中...');
setMessage(
els.verifyMessage,
'正在向后端查询支付状态...',
''
);
window.HyAppAPI.get(
'/api/v1/recharge/h5/temporary/orders/' +
encodeURIComponent(orderID)
)
.then(function (data) {
renderOrder(els.verifyResult, data);
setMessage(
els.verifyMessage,
statusText(data.order && data.order.status),
data.order && data.order.status === 'paid'
? 'success'
: ''
);
})
.catch(function (err) {
setMessage(
els.verifyMessage,
err.message || '验证支付状态失败',
'error'
);
})
.finally(function () {
setLoading(els.verifyButton, false, '验证支付状态');
});
}
function renderOrder(target, data) {
var order = data.order || {};
var paymentLink = data.payment_link || order.pay_url || '';
target.innerHTML = [
resultRow('订单号', order.order_id || '-'),
resultRow(
'支付状态',
'<span class="status ' +
escapeHTML(order.status || '') +
'">' +
escapeHTML(statusLabel(order.status)) +
'</span>'
),
resultRow(
'金币/美金',
escapeHTML(
formatNumber(order.coin_amount) +
' 金币 / USD ' +
formatMoney(order.usd_minor_amount)
)
),
resultRow(
'支付方式',
escapeHTML(
[
order.provider_code,
order.pay_way,
order.pay_type,
]
.filter(Boolean)
.join(' / ') || '-'
)
),
resultRow('支付链接', escapeHTML(paymentLink || '-')),
data.wrapped
? resultRow(
'中转说明',
escapeHTML(
data.wrapped_note ||
'已生成 COS 中转 HTML'
)
)
: '',
data.raw_pay_url
? resultRow(
'原始链接',
escapeHTML(data.raw_pay_url)
)
: '',
]
.filter(Boolean)
.join('');
}
function resultRow(label, valueHTML) {
return (
'<div class="result-row"><strong>' +
escapeHTML(label) +
'</strong><div class="value">' +
valueHTML +
'</div></div>'
);
}
function selectedMethod() {
var id = Number(els.paymentMethod.value || 0);
return methods.find(function (method) {
return Number(method.method_id) === id;
});
}
function dollarsToMinor(value) {
var text = String(value || '').trim();
if (!/^\d+(\.\d{1,2})?$/.test(text)) return 0;
return Math.round(Number(text) * 100);
}
function extractOrderID(value) {
var text = String(value || '').trim();
if (!text) return '';
try {
var url = new URL(text, window.location.href);
var queryID =
url.searchParams.get('order_id') ||
url.searchParams.get('orderId');
if (queryID) return queryID.trim();
var match = url.pathname.match(
/temporary-pay\/([^/]+)\.html$/
);
if (match) return decodeURIComponent(match[1]);
} catch (_) {
// 输入可能就是订单号,不是 URL。
}
return text.replace(/^order_id=/, '').trim();
}
function setLoading(button, loading, text) {
button.disabled = loading;
button.textContent = text;
}
function setMessage(node, text, tone) {
node.textContent = text || '';
node.className = ['message', tone || '']
.filter(Boolean)
.join(' ');
}
function statusText(status) {
return status === 'paid'
? '支付已成功'
: '当前状态:' + statusLabel(status);
}
function statusLabel(status) {
if (status === 'paid') return '已支付';
if (status === 'redirected') return '已生成链接';
if (status === 'pending') return '待支付';
if (status === 'failed') return '失败';
if (status === 'credited') return '已入账';
return status || '-';
}
function formatNumber(value) {
if (value === 0 || value)
return Number(value).toLocaleString('zh-CN');
return '-';
}
function formatMoney(value) {
if (!(value === 0 || value)) return '-';
return (Number(value) / 100).toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
}
function escapeHTML(value) {
return String(value || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function copyText(value, messageNode) {
if (!value) return;
var done = function () {
setMessage(messageNode, '已复制支付链接', 'success');
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard
.writeText(value)
.then(done)
.catch(function () {
fallbackCopy(value);
done();
});
return;
}
fallbackCopy(value);
done();
}
function fallbackCopy(value) {
var input = document.createElement('textarea');
input.value = value;
input.setAttribute('readonly', '');
input.style.position = 'fixed';
input.style.opacity = '0';
document.body.appendChild(input);
input.select();
document.execCommand('copy');
input.remove();
}
})();
</script>
</body>
</html>