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

907 lines
35 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="appCode">App</label>
<select id="appCode">
<option value="lalu">lalu</option>
<option value="yumi">yumi</option>
<option value="aslan">aslan</option>
</select>
</div>
<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="默认按选择的 App 返回充值页"
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>
(function () {
var API_BASE_URL = 'https://api.global-interaction.com/';
var RETURN_URL_BASE =
'https://h5.global-interaction.com/recharge/index.html';
var methods = [];
var latestPaymentLink = '';
var latestOrderID = '';
var returnUrlTouched = false;
var els = {
appCode: document.getElementById('appCode'),
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'),
};
setDefaultReturnURL(true);
renderEnvMeta();
els.appCode.addEventListener('change', function () {
setDefaultReturnURL(false);
renderEnvMeta();
loadMethods();
});
els.returnUrl.addEventListener('input', function () {
returnUrlTouched = true;
});
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 apiGet('/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, '正在创建支付链接...', '');
apiPost('/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: currentReturnURL(),
})
.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,
'正在向后端查询支付状态...',
''
);
apiGet(
'/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('订单ID', 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 apiGet(path) {
return apiRequest(path, { method: 'GET' });
}
function apiPost(path, body) {
return apiRequest(path, {
method: 'POST',
body: body || {},
});
}
function apiRequest(path, options) {
var opts = options || {};
var headers = {
'X-App-Code': currentAppCode(),
};
var body;
if (opts.body) {
headers['Content-Type'] = 'application/json';
body = JSON.stringify(opts.body);
}
return fetch(apiURL(path), {
method: opts.method || 'GET',
headers: headers,
body: body,
credentials: 'omit',
}).then(function (response) {
return response.text().then(function (text) {
var payload = parsePayload(text);
if (!response.ok) {
var error = new Error(
errorMessage(response, payload, text)
);
error.status = response.status;
error.payload = payload;
throw error;
}
if (isAPIFailure(payload)) {
throw new Error(
payload.message ||
payload.errorMsg ||
payload.error ||
'接口请求失败'
);
}
return Object.prototype.hasOwnProperty.call(
payload,
'data'
)
? payload.data
: payload;
});
});
}
function apiURL(path) {
return new URL(
path.replace(/^\/+/, ''),
API_BASE_URL
).toString();
}
function parsePayload(text) {
if (!text) return {};
try {
return JSON.parse(text);
} catch (_) {
return { message: text };
}
}
function errorMessage(response, payload, text) {
if (response.status === 404) {
return '接口不存在或后端还没有部署到线上';
}
return (
payload.message ||
payload.errorMsg ||
payload.error ||
text ||
response.statusText ||
'接口请求失败'
);
}
function isAPIFailure(payload) {
var code = payload && payload.code;
var errorCode = payload && payload.errorCode;
return (
(typeof code === 'number' && code !== 0) ||
(typeof code === 'string' &&
code !== '' &&
code !== 'OK') ||
(typeof errorCode === 'number' && errorCode !== 0) ||
(typeof errorCode === 'string' &&
errorCode !== '' &&
errorCode !== '0' &&
errorCode !== 'OK')
);
}
function currentAppCode() {
var value = String(els.appCode.value || 'lalu')
.trim()
.toLowerCase();
return value === 'yumi' || value === 'aslan'
? value
: 'lalu';
}
function currentReturnURL() {
return (
els.returnUrl.value.trim() ||
defaultReturnURL(currentAppCode())
);
}
function defaultReturnURL(appCode) {
var url = new URL(RETURN_URL_BASE);
url.searchParams.set('app_code', appCode || 'lalu');
return url.toString();
}
function setDefaultReturnURL(force) {
var nextURL = defaultReturnURL(currentAppCode());
if (
force ||
!returnUrlTouched ||
isKnownDefaultReturnURL(els.returnUrl.value)
) {
els.returnUrl.value = nextURL;
returnUrlTouched = false;
}
}
function isKnownDefaultReturnURL(value) {
var text = String(value || '').trim();
return ['lalu', 'yumi', 'aslan'].some(function (appCode) {
return text === defaultReturnURL(appCode);
});
}
function renderEnvMeta() {
els.envMeta.textContent =
'App' + currentAppCode() + ' API' + API_BASE_URL;
}
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>