1947 lines
74 KiB
HTML
1947 lines
74 KiB
HTML
<!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>
|
||
:root {
|
||
--pay-page-bg: #f5f7fb;
|
||
--pay-ink: #111827;
|
||
--pay-ink-strong: #07111f;
|
||
--pay-muted: #697386;
|
||
--pay-muted-soft: #8b94a7;
|
||
--pay-line: #dde4f0;
|
||
--pay-line-strong: #c6cfde;
|
||
--pay-surface: #ffffff;
|
||
--pay-surface-soft: #f8fafd;
|
||
--pay-surface-hover: #f2f5fa;
|
||
--pay-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
|
||
--pay-shadow-soft: 0 10px 26px rgba(15, 23, 42, 0.06);
|
||
--pay-accent: var(--hy-theme-primary-strong);
|
||
--pay-accent-deep: var(--hy-theme-primary-deep);
|
||
--pay-accent-soft: var(--hy-theme-primary-soft);
|
||
--pay-focus: var(--hy-theme-focus-ring);
|
||
--pay-success: var(--hy-theme-success);
|
||
--pay-danger: var(--hy-theme-danger);
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
[hidden] {
|
||
display: none !important;
|
||
}
|
||
|
||
body {
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
background: var(--pay-page-bg);
|
||
color: var(--pay-ink);
|
||
font-family:
|
||
Inter,
|
||
ui-sans-serif,
|
||
system-ui,
|
||
-apple-system,
|
||
BlinkMacSystemFont,
|
||
'Segoe UI',
|
||
sans-serif;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
button,
|
||
input,
|
||
textarea {
|
||
font: inherit;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
button {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
}
|
||
|
||
input,
|
||
textarea {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
width: 100%;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 12px;
|
||
outline: none;
|
||
background: var(--pay-surface);
|
||
color: var(--pay-ink);
|
||
padding: 12px 14px;
|
||
transition:
|
||
border-color 0.16s ease,
|
||
box-shadow 0.16s ease,
|
||
background 0.16s ease;
|
||
}
|
||
|
||
input {
|
||
min-height: 48px;
|
||
}
|
||
|
||
textarea {
|
||
min-height: 96px;
|
||
resize: vertical;
|
||
}
|
||
|
||
input::placeholder,
|
||
textarea::placeholder {
|
||
color: var(--pay-muted-soft);
|
||
}
|
||
|
||
input:focus,
|
||
textarea:focus {
|
||
border-color: var(--pay-accent);
|
||
box-shadow: 0 0 0 4px var(--pay-focus);
|
||
}
|
||
|
||
.page {
|
||
width: min(1180px, 100%);
|
||
margin: 0 auto;
|
||
padding: 26px 18px 44px;
|
||
}
|
||
|
||
.topbar {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 18px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.title-block {
|
||
min-width: 0;
|
||
}
|
||
|
||
.kicker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 0 0 8px;
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.kicker::before {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 999px;
|
||
background: var(--pay-accent);
|
||
content: '';
|
||
}
|
||
|
||
.title {
|
||
margin: 0;
|
||
color: var(--pay-ink-strong);
|
||
font-size: 28px;
|
||
font-weight: 850;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.meta {
|
||
margin: 8px 0 0;
|
||
color: var(--pay-muted);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.button {
|
||
display: inline-flex;
|
||
min-height: 44px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid transparent;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
padding: 0 16px;
|
||
font-size: 14px;
|
||
font-weight: 850;
|
||
transition:
|
||
transform 0.16s ease,
|
||
box-shadow 0.16s ease,
|
||
border-color 0.16s ease,
|
||
background 0.16s ease,
|
||
color 0.16s ease;
|
||
}
|
||
|
||
.button:hover:not(:disabled) {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.button.primary {
|
||
background: var(--hy-theme-button);
|
||
color: #fff;
|
||
box-shadow: var(--hy-theme-button-shadow);
|
||
}
|
||
|
||
.button.secondary {
|
||
border-color: var(--pay-line);
|
||
background: var(--pay-surface);
|
||
color: var(--pay-accent-deep);
|
||
box-shadow: var(--pay-shadow-soft);
|
||
}
|
||
|
||
.button:disabled {
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
border-color: var(--pay-line);
|
||
background: var(--hy-theme-button-disabled);
|
||
color: var(--hy-theme-button-disabled-text);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.workspace {
|
||
display: grid;
|
||
gap: 18px;
|
||
grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.78fr);
|
||
align-items: start;
|
||
}
|
||
|
||
.column {
|
||
display: grid;
|
||
gap: 18px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.panel {
|
||
min-width: 0;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 18px;
|
||
background: var(--pay-surface);
|
||
box-shadow: var(--pay-shadow);
|
||
padding: 20px;
|
||
}
|
||
|
||
.panel-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 14px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.panel-title {
|
||
min-width: 0;
|
||
}
|
||
|
||
.eyebrow {
|
||
margin: 0 0 5px;
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 850;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.panel h2 {
|
||
margin: 0;
|
||
color: var(--pay-ink-strong);
|
||
font-size: 18px;
|
||
font-weight: 850;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.panel-badge {
|
||
display: inline-flex;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 999px;
|
||
background: var(--pay-surface-soft);
|
||
color: var(--pay-muted);
|
||
padding: 7px 10px;
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.form-grid {
|
||
display: grid;
|
||
gap: 16px;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.field {
|
||
display: grid;
|
||
min-width: 0;
|
||
gap: 8px;
|
||
}
|
||
|
||
.field.full,
|
||
.method-section {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.field-label,
|
||
label {
|
||
color: var(--pay-muted);
|
||
font-size: 13px;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.segmented {
|
||
display: grid;
|
||
gap: 8px;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 14px;
|
||
background: var(--pay-surface-soft);
|
||
padding: 6px;
|
||
}
|
||
|
||
.segment {
|
||
min-height: 42px;
|
||
border: 1px solid transparent;
|
||
border-radius: 10px;
|
||
background: transparent;
|
||
color: var(--pay-muted);
|
||
cursor: pointer;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.segment.is-active {
|
||
border-color: var(--pay-line-strong);
|
||
background: var(--pay-surface);
|
||
color: var(--pay-ink-strong);
|
||
box-shadow: var(--pay-shadow-soft);
|
||
}
|
||
|
||
.combo {
|
||
position: relative;
|
||
min-width: 0;
|
||
}
|
||
|
||
.combo-trigger {
|
||
display: flex;
|
||
width: 100%;
|
||
min-height: 52px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 14px;
|
||
background: var(--pay-surface);
|
||
color: var(--pay-ink);
|
||
cursor: pointer;
|
||
padding: 11px 14px;
|
||
text-align: left;
|
||
transition:
|
||
border-color 0.16s ease,
|
||
box-shadow 0.16s ease;
|
||
}
|
||
|
||
.combo-trigger:focus,
|
||
.combo.is-open .combo-trigger {
|
||
border-color: var(--pay-accent);
|
||
box-shadow: 0 0 0 4px var(--pay-focus);
|
||
}
|
||
|
||
.combo-copy {
|
||
display: grid;
|
||
min-width: 0;
|
||
gap: 3px;
|
||
}
|
||
|
||
.combo-title {
|
||
overflow: hidden;
|
||
color: var(--pay-ink-strong);
|
||
font-weight: 850;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.combo-subtitle {
|
||
overflow: hidden;
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 750;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.chevron {
|
||
flex: 0 0 auto;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-right: 2px solid var(--pay-muted);
|
||
border-bottom: 2px solid var(--pay-muted);
|
||
transform: rotate(45deg);
|
||
transition: transform 0.16s ease;
|
||
}
|
||
|
||
.combo.is-open .chevron {
|
||
transform: rotate(225deg);
|
||
}
|
||
|
||
.combo-menu {
|
||
position: absolute;
|
||
z-index: 20;
|
||
top: calc(100% + 8px);
|
||
right: 0;
|
||
left: 0;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 16px;
|
||
background: var(--pay-surface);
|
||
box-shadow: 0 20px 48px rgba(15, 23, 42, 0.16);
|
||
padding: 10px;
|
||
}
|
||
|
||
.combo-search {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.option-list {
|
||
display: grid;
|
||
max-height: 280px;
|
||
gap: 4px;
|
||
overflow-y: auto;
|
||
padding-right: 2px;
|
||
}
|
||
|
||
.option {
|
||
display: grid;
|
||
width: 100%;
|
||
gap: 3px;
|
||
border: 1px solid transparent;
|
||
border-radius: 12px;
|
||
background: transparent;
|
||
color: var(--pay-ink);
|
||
cursor: pointer;
|
||
padding: 10px;
|
||
text-align: left;
|
||
}
|
||
|
||
.option:hover,
|
||
.option.is-active {
|
||
border-color: var(--pay-line);
|
||
background: var(--pay-surface-hover);
|
||
}
|
||
|
||
.option.is-active {
|
||
box-shadow: inset 3px 0 0 var(--pay-accent);
|
||
}
|
||
|
||
.option-title {
|
||
color: var(--pay-ink-strong);
|
||
font-size: 14px;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.option-meta {
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 750;
|
||
}
|
||
|
||
.empty-state {
|
||
border: 1px dashed var(--pay-line-strong);
|
||
border-radius: 14px;
|
||
background: var(--pay-surface-soft);
|
||
color: var(--pay-muted);
|
||
padding: 18px;
|
||
text-align: center;
|
||
font-size: 13px;
|
||
font-weight: 750;
|
||
}
|
||
|
||
.method-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.method-count {
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.method-grid {
|
||
display: grid;
|
||
max-height: 422px;
|
||
gap: 10px;
|
||
overflow-y: auto;
|
||
padding-right: 3px;
|
||
}
|
||
|
||
.method-card {
|
||
display: grid;
|
||
grid-template-columns: 42px minmax(0, 1fr) auto;
|
||
gap: 12px;
|
||
align-items: center;
|
||
width: 100%;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 14px;
|
||
background: var(--pay-surface);
|
||
color: var(--pay-ink);
|
||
cursor: pointer;
|
||
padding: 12px;
|
||
text-align: left;
|
||
transition:
|
||
border-color 0.16s ease,
|
||
box-shadow 0.16s ease,
|
||
transform 0.16s ease,
|
||
background 0.16s ease;
|
||
}
|
||
|
||
.method-card:hover {
|
||
transform: translateY(-1px);
|
||
border-color: var(--pay-line-strong);
|
||
background: var(--pay-surface-hover);
|
||
}
|
||
|
||
.method-card.is-active {
|
||
border-color: var(--pay-accent);
|
||
background: #fbfaff;
|
||
box-shadow: 0 0 0 4px var(--pay-focus);
|
||
}
|
||
|
||
.method-logo {
|
||
display: grid;
|
||
width: 42px;
|
||
height: 42px;
|
||
place-items: center;
|
||
overflow: hidden;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 12px;
|
||
background: var(--pay-surface-soft);
|
||
color: var(--pay-accent-deep);
|
||
font-size: 12px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.method-logo img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
padding: 6px;
|
||
}
|
||
|
||
.method-copy {
|
||
display: grid;
|
||
min-width: 0;
|
||
gap: 4px;
|
||
}
|
||
|
||
.method-title {
|
||
overflow: hidden;
|
||
color: var(--pay-ink-strong);
|
||
font-weight: 850;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.method-meta {
|
||
overflow: hidden;
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 750;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.method-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
gap: 6px;
|
||
}
|
||
|
||
.pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 999px;
|
||
background: var(--pay-surface-soft);
|
||
color: var(--pay-muted);
|
||
padding: 5px 8px;
|
||
font-size: 11px;
|
||
font-weight: 850;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.pill.accent {
|
||
border-color: transparent;
|
||
background: var(--pay-accent-soft);
|
||
color: var(--pay-accent-deep);
|
||
}
|
||
|
||
.actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.message {
|
||
min-height: 22px;
|
||
margin-top: 12px;
|
||
color: var(--pay-muted);
|
||
font-size: 13px;
|
||
font-weight: 750;
|
||
}
|
||
|
||
.message.error {
|
||
color: var(--pay-danger);
|
||
}
|
||
|
||
.message.success {
|
||
color: var(--pay-success);
|
||
}
|
||
|
||
.result {
|
||
display: grid;
|
||
gap: 12px;
|
||
}
|
||
|
||
.result-row {
|
||
display: grid;
|
||
min-width: 0;
|
||
gap: 5px;
|
||
}
|
||
|
||
.result-row strong {
|
||
color: var(--pay-muted);
|
||
font-size: 12px;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.value {
|
||
min-width: 0;
|
||
overflow-wrap: anywhere;
|
||
border: 1px solid var(--pay-line);
|
||
border-radius: 12px;
|
||
background: var(--pay-surface-soft);
|
||
padding: 11px 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.status {
|
||
display: inline-flex;
|
||
width: fit-content;
|
||
align-items: center;
|
||
border-radius: 999px;
|
||
background: var(--pay-accent-soft);
|
||
color: var(--pay-accent-deep);
|
||
padding: 6px 10px;
|
||
font-size: 12px;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.status.paid {
|
||
background: rgba(49, 198, 168, 0.14);
|
||
color: var(--pay-success);
|
||
}
|
||
|
||
.status.failed {
|
||
background: rgba(217, 93, 114, 0.12);
|
||
color: var(--pay-danger);
|
||
}
|
||
|
||
@media (max-width: 920px) {
|
||
.topbar {
|
||
align-items: stretch;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.toolbar {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.workspace,
|
||
.form-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 560px) {
|
||
.page {
|
||
padding: 18px 12px 32px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.panel {
|
||
border-radius: 14px;
|
||
padding: 16px;
|
||
}
|
||
|
||
.segmented {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.method-card {
|
||
grid-template-columns: 38px minmax(0, 1fr);
|
||
}
|
||
|
||
.method-tags {
|
||
grid-column: 1 / -1;
|
||
justify-content: flex-start;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="page">
|
||
<header class="topbar">
|
||
<div class="title-block">
|
||
<p class="kicker">Payment Operations</p>
|
||
<h1 class="title">三方临时支付链接</h1>
|
||
<p class="meta" id="envMeta">加载环境中...</p>
|
||
</div>
|
||
<div class="toolbar">
|
||
<button
|
||
class="button secondary"
|
||
type="button"
|
||
id="reloadMethods"
|
||
>
|
||
刷新支付方式
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="workspace">
|
||
<div class="column">
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<div class="panel-title">
|
||
<p class="eyebrow">Create</p>
|
||
<h2>创建支付链接</h2>
|
||
</div>
|
||
<span class="panel-badge" id="loadedBadge">
|
||
等待支付方式
|
||
</span>
|
||
</div>
|
||
<form id="createForm" novalidate>
|
||
<div class="form-grid">
|
||
<div class="field full">
|
||
<span class="field-label">Return App</span>
|
||
<div
|
||
class="segmented"
|
||
id="appSegments"
|
||
role="tablist"
|
||
aria-label="Return App"
|
||
>
|
||
<button
|
||
class="segment is-active"
|
||
type="button"
|
||
data-app-code="lalu"
|
||
aria-selected="true"
|
||
>
|
||
lalu
|
||
</button>
|
||
<button
|
||
class="segment"
|
||
type="button"
|
||
data-app-code="yumi"
|
||
aria-selected="false"
|
||
>
|
||
yumi
|
||
</button>
|
||
<button
|
||
class="segment"
|
||
type="button"
|
||
data-app-code="aslan"
|
||
aria-selected="false"
|
||
>
|
||
aslan
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field full">
|
||
<label for="countrySearch">国家</label>
|
||
<div class="combo" id="countryCombo">
|
||
<button
|
||
class="combo-trigger"
|
||
type="button"
|
||
id="countryToggle"
|
||
aria-expanded="false"
|
||
aria-haspopup="listbox"
|
||
>
|
||
<span class="combo-copy">
|
||
<span
|
||
class="combo-title"
|
||
id="countrySelected"
|
||
>
|
||
正在加载国家...
|
||
</span>
|
||
<span
|
||
class="combo-subtitle"
|
||
id="countryMeta"
|
||
>
|
||
根据国家筛选可用支付方式
|
||
</span>
|
||
</span>
|
||
<span
|
||
class="chevron"
|
||
aria-hidden="true"
|
||
></span>
|
||
</button>
|
||
<div
|
||
class="combo-menu"
|
||
id="countryMenu"
|
||
hidden
|
||
>
|
||
<input
|
||
class="combo-search"
|
||
id="countrySearch"
|
||
placeholder="搜索国家名称或代码"
|
||
type="text"
|
||
autocomplete="off"
|
||
/>
|
||
<div
|
||
class="option-list"
|
||
id="countryOptions"
|
||
role="listbox"
|
||
aria-label="国家列表"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label for="usdAmount">美金金额</label>
|
||
<input
|
||
id="usdAmount"
|
||
inputmode="decimal"
|
||
placeholder="例如 300 或 9.99"
|
||
type="text"
|
||
/>
|
||
</div>
|
||
|
||
<div class="field full">
|
||
<label for="returnUrl">
|
||
支付完成返回地址
|
||
</label>
|
||
<input
|
||
id="returnUrl"
|
||
placeholder="默认按选择的 App 返回充值页"
|
||
type="url"
|
||
/>
|
||
</div>
|
||
|
||
<div class="method-section">
|
||
<div class="method-head">
|
||
<span class="field-label">
|
||
支付方式
|
||
</span>
|
||
<span
|
||
class="method-count"
|
||
id="methodCount"
|
||
>
|
||
-
|
||
</span>
|
||
</div>
|
||
<div
|
||
class="method-grid"
|
||
id="methodGrid"
|
||
role="radiogroup"
|
||
aria-label="支付方式"
|
||
>
|
||
<div class="empty-state">
|
||
正在加载支付方式...
|
||
</div>
|
||
</div>
|
||
</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">
|
||
<div class="panel-header">
|
||
<div class="panel-title">
|
||
<p class="eyebrow">Verify</p>
|
||
<h2>验证支付链接</h2>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
|
||
<div class="column">
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<div class="panel-title">
|
||
<p class="eyebrow">Create Result</p>
|
||
<h2>创建结果</h2>
|
||
</div>
|
||
</div>
|
||
<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">
|
||
<div class="panel-header">
|
||
<div class="panel-title">
|
||
<p class="eyebrow">Verify Result</p>
|
||
<h2>验证结果</h2>
|
||
</div>
|
||
</div>
|
||
<div class="result" id="verifyResult">
|
||
<div class="value">还没有验证订单</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<script>
|
||
(function () {
|
||
var TEST_API_BASE_URL =
|
||
'https://api-test.global-interaction.com/';
|
||
var LOCAL_API_BASE_URL = 'http://127.0.0.1:13000/';
|
||
var DEFAULT_API_BASE_URL =
|
||
'https://api.global-interaction.com/';
|
||
var API_BASE_URL = resolveAPIBaseURL();
|
||
var RETURN_URL_BASE =
|
||
'https://h5.global-interaction.com/recharge/index.html';
|
||
var APP_CODES = ['lalu', 'yumi', 'aslan'];
|
||
var methods = [];
|
||
var countries = [];
|
||
var selectedAppCode = 'lalu';
|
||
var selectedCountryCode = '';
|
||
var selectedMethodID = 0;
|
||
var latestPaymentLink = '';
|
||
var latestOrderID = '';
|
||
var returnUrlTouched = false;
|
||
var els = {
|
||
appButtons: [].slice.call(
|
||
document.querySelectorAll('[data-app-code]')
|
||
),
|
||
copyLink: document.getElementById('copyLink'),
|
||
countryCombo: document.getElementById('countryCombo'),
|
||
countryMenu: document.getElementById('countryMenu'),
|
||
countryMeta: document.getElementById('countryMeta'),
|
||
countryOptions: document.getElementById('countryOptions'),
|
||
countrySearch: document.getElementById('countrySearch'),
|
||
countrySelected: document.getElementById('countrySelected'),
|
||
countryToggle: document.getElementById('countryToggle'),
|
||
createButton: document.getElementById('createButton'),
|
||
createForm: document.getElementById('createForm'),
|
||
createMessage: document.getElementById('createMessage'),
|
||
createResult: document.getElementById('createResult'),
|
||
envMeta: document.getElementById('envMeta'),
|
||
loadedBadge: document.getElementById('loadedBadge'),
|
||
methodCount: document.getElementById('methodCount'),
|
||
methodGrid: document.getElementById('methodGrid'),
|
||
openLink: document.getElementById('openLink'),
|
||
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();
|
||
bindEvents();
|
||
loadMethods();
|
||
|
||
function bindEvents() {
|
||
els.appButtons.forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
selectApp(button.getAttribute('data-app-code'));
|
||
});
|
||
});
|
||
els.countryToggle.addEventListener('click', function () {
|
||
setCountryMenuOpen(
|
||
els.countryMenu.hasAttribute('hidden')
|
||
);
|
||
});
|
||
els.countrySearch.addEventListener(
|
||
'input',
|
||
renderCountries
|
||
);
|
||
els.countrySearch.addEventListener(
|
||
'keydown',
|
||
function (event) {
|
||
if (event.key === 'Escape')
|
||
setCountryMenuOpen(false);
|
||
}
|
||
);
|
||
document.addEventListener('click', function (event) {
|
||
if (!els.countryCombo.contains(event.target)) {
|
||
setCountryMenuOpen(false);
|
||
}
|
||
});
|
||
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
|
||
);
|
||
}
|
||
|
||
function loadMethods() {
|
||
setMessage(els.createMessage, '正在加载支付方式...', '');
|
||
setMethodsLoading(true);
|
||
return apiGet('/api/v1/recharge/h5/temporary/methods')
|
||
.then(function (data) {
|
||
methods = Array.isArray(data.methods)
|
||
? data.methods
|
||
: [];
|
||
countries = buildCountries(methods);
|
||
reconcileSelection();
|
||
renderAllSelectionViews();
|
||
setMessage(
|
||
els.createMessage,
|
||
methods.length
|
||
? '支付方式已加载'
|
||
: '没有可用支付方式',
|
||
methods.length ? 'success' : 'error'
|
||
);
|
||
})
|
||
.catch(function (err) {
|
||
methods = [];
|
||
countries = [];
|
||
selectedCountryCode = '';
|
||
selectedMethodID = 0;
|
||
renderAllSelectionViews();
|
||
setMessage(
|
||
els.createMessage,
|
||
err.message || '加载支付方式失败',
|
||
'error'
|
||
);
|
||
})
|
||
.finally(function () {
|
||
setMethodsLoading(false);
|
||
});
|
||
}
|
||
|
||
function setMethodsLoading(loading) {
|
||
els.reloadMethods.disabled = loading;
|
||
els.createButton.disabled = loading;
|
||
if (loading) {
|
||
els.loadedBadge.textContent = '加载中';
|
||
els.methodGrid.innerHTML =
|
||
'<div class="empty-state">正在加载支付方式...</div>';
|
||
}
|
||
}
|
||
|
||
function buildCountries(sourceMethods) {
|
||
var byCode = {};
|
||
sourceMethods.forEach(function (method, index) {
|
||
var code = countryCode(method);
|
||
if (!code) return;
|
||
if (!byCode[code]) {
|
||
byCode[code] = {
|
||
code: code,
|
||
name: method.country_name || code,
|
||
currency: method.currency_code || '',
|
||
count: 0,
|
||
firstSort:
|
||
Number(method.sort_order || 0) || index,
|
||
};
|
||
}
|
||
byCode[code].count += 1;
|
||
});
|
||
return Object.keys(byCode)
|
||
.map(function (code) {
|
||
return byCode[code];
|
||
})
|
||
.sort(function (a, b) {
|
||
return a.firstSort - b.firstSort;
|
||
});
|
||
}
|
||
|
||
function reconcileSelection() {
|
||
if (
|
||
selectedCountryCode &&
|
||
!countries.some(function (country) {
|
||
return country.code === selectedCountryCode;
|
||
})
|
||
) {
|
||
selectedCountryCode = '';
|
||
}
|
||
if (!selectedCountryCode && countries.length) {
|
||
selectedCountryCode = countries[0].code;
|
||
}
|
||
if (
|
||
selectedMethodID &&
|
||
!filteredMethods().some(function (method) {
|
||
return (
|
||
Number(method.method_id) === selectedMethodID
|
||
);
|
||
})
|
||
) {
|
||
selectedMethodID = 0;
|
||
}
|
||
if (!selectedMethodID && filteredMethods().length) {
|
||
selectedMethodID = Number(
|
||
filteredMethods()[0].method_id
|
||
);
|
||
}
|
||
}
|
||
|
||
function renderAllSelectionViews() {
|
||
renderAppSegments();
|
||
renderCountries();
|
||
renderCountrySummary();
|
||
renderMethods();
|
||
renderEnvMeta();
|
||
}
|
||
|
||
function selectApp(appCode) {
|
||
selectedAppCode =
|
||
APP_CODES.indexOf(appCode) >= 0 ? appCode : 'lalu';
|
||
setDefaultReturnURL(false);
|
||
renderAppSegments();
|
||
renderEnvMeta();
|
||
}
|
||
|
||
function renderAppSegments() {
|
||
els.appButtons.forEach(function (button) {
|
||
var active =
|
||
button.getAttribute('data-app-code') ===
|
||
selectedAppCode;
|
||
button.classList.toggle('is-active', active);
|
||
button.setAttribute(
|
||
'aria-selected',
|
||
active ? 'true' : 'false'
|
||
);
|
||
});
|
||
}
|
||
|
||
function renderCountries() {
|
||
var query = normalizeSearch(els.countrySearch.value);
|
||
var rows = countries.filter(function (country) {
|
||
var searchable = normalizeSearch(
|
||
[country.name, country.code, country.currency].join(
|
||
' '
|
||
)
|
||
);
|
||
return !query || searchable.indexOf(query) >= 0;
|
||
});
|
||
clearNode(els.countryOptions);
|
||
if (!rows.length) {
|
||
els.countryOptions.appendChild(
|
||
createNode('div', 'empty-state', '没有匹配的国家')
|
||
);
|
||
return;
|
||
}
|
||
rows.forEach(function (country) {
|
||
var option = createNode('button', 'option');
|
||
option.type = 'button';
|
||
option.setAttribute('role', 'option');
|
||
option.setAttribute(
|
||
'aria-selected',
|
||
country.code === selectedCountryCode
|
||
? 'true'
|
||
: 'false'
|
||
);
|
||
option.classList.toggle(
|
||
'is-active',
|
||
country.code === selectedCountryCode
|
||
);
|
||
option.appendChild(
|
||
createNode(
|
||
'span',
|
||
'option-title',
|
||
countryLabel(country)
|
||
)
|
||
);
|
||
option.appendChild(
|
||
createNode(
|
||
'span',
|
||
'option-meta',
|
||
country.currency +
|
||
' · ' +
|
||
country.count +
|
||
' 个支付方式'
|
||
)
|
||
);
|
||
option.addEventListener('click', function () {
|
||
selectCountry(country.code);
|
||
});
|
||
els.countryOptions.appendChild(option);
|
||
});
|
||
}
|
||
|
||
function selectCountry(countryCodeValue) {
|
||
selectedCountryCode = countryCodeValue || '';
|
||
selectedMethodID = 0;
|
||
reconcileSelection();
|
||
renderCountrySummary();
|
||
renderCountries();
|
||
renderMethods();
|
||
setCountryMenuOpen(false);
|
||
}
|
||
|
||
function renderCountrySummary() {
|
||
var country = selectedCountry();
|
||
if (!country) {
|
||
els.countrySelected.textContent = countries.length
|
||
? '请选择国家'
|
||
: '没有可用国家';
|
||
els.countryMeta.textContent =
|
||
'根据国家筛选可用支付方式';
|
||
return;
|
||
}
|
||
els.countrySelected.textContent = countryLabel(country);
|
||
els.countryMeta.textContent =
|
||
country.currency +
|
||
' · ' +
|
||
country.count +
|
||
' 个支付方式';
|
||
}
|
||
|
||
function renderMethods() {
|
||
var rows = filteredMethods();
|
||
clearNode(els.methodGrid);
|
||
els.methodCount.textContent = selectedCountryCode
|
||
? rows.length + ' 个可用'
|
||
: '-';
|
||
els.loadedBadge.textContent = methods.length
|
||
? countries.length +
|
||
' 个国家 / ' +
|
||
methods.length +
|
||
' 个方式'
|
||
: '没有支付方式';
|
||
if (!selectedCountryCode) {
|
||
els.methodGrid.appendChild(
|
||
createNode('div', 'empty-state', '请先选择国家')
|
||
);
|
||
return;
|
||
}
|
||
if (!rows.length) {
|
||
els.methodGrid.appendChild(
|
||
createNode(
|
||
'div',
|
||
'empty-state',
|
||
'当前国家没有可用支付方式'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
rows.forEach(function (method) {
|
||
els.methodGrid.appendChild(createMethodCard(method));
|
||
});
|
||
}
|
||
|
||
function createMethodCard(method) {
|
||
var selected =
|
||
Number(method.method_id) === Number(selectedMethodID);
|
||
var card = createNode('button', 'method-card');
|
||
card.type = 'button';
|
||
card.setAttribute('role', 'radio');
|
||
card.setAttribute(
|
||
'aria-checked',
|
||
selected ? 'true' : 'false'
|
||
);
|
||
card.classList.toggle('is-active', selected);
|
||
card.addEventListener('click', function () {
|
||
selectedMethodID = Number(method.method_id);
|
||
renderMethods();
|
||
});
|
||
|
||
card.appendChild(methodLogo(method));
|
||
|
||
var copy = createNode('span', 'method-copy');
|
||
copy.appendChild(
|
||
createNode(
|
||
'span',
|
||
'method-title',
|
||
method.method_name ||
|
||
method.pay_type ||
|
||
method.pay_way ||
|
||
'-'
|
||
)
|
||
);
|
||
copy.appendChild(
|
||
createNode(
|
||
'span',
|
||
'method-meta',
|
||
[
|
||
method.provider_name || method.provider_code,
|
||
method.pay_way,
|
||
method.pay_type,
|
||
]
|
||
.filter(Boolean)
|
||
.join(' / ')
|
||
)
|
||
);
|
||
card.appendChild(copy);
|
||
|
||
var tags = createNode('span', 'method-tags');
|
||
tags.appendChild(
|
||
createNode(
|
||
'span',
|
||
'pill accent',
|
||
method.currency_code || '-'
|
||
)
|
||
);
|
||
tags.appendChild(
|
||
createNode('span', 'pill', 'ID ' + method.method_id)
|
||
);
|
||
card.appendChild(tags);
|
||
return card;
|
||
}
|
||
|
||
function methodLogo(method) {
|
||
var logo = createNode('span', 'method-logo');
|
||
if (method.logo_url) {
|
||
var image = document.createElement('img');
|
||
image.alt =
|
||
method.method_name || method.pay_type || 'pay';
|
||
image.src = method.logo_url;
|
||
image.addEventListener('error', function () {
|
||
image.remove();
|
||
logo.textContent = logoText(method);
|
||
});
|
||
logo.appendChild(image);
|
||
return logo;
|
||
}
|
||
logo.textContent = logoText(method);
|
||
return logo;
|
||
}
|
||
|
||
function setCountryMenuOpen(open) {
|
||
els.countryCombo.classList.toggle('is-open', open);
|
||
els.countryToggle.setAttribute(
|
||
'aria-expanded',
|
||
open ? 'true' : 'false'
|
||
);
|
||
if (open) {
|
||
els.countryMenu.removeAttribute('hidden');
|
||
renderCountries();
|
||
window.requestAnimationFrame(function () {
|
||
els.countrySearch.focus();
|
||
els.countrySearch.select();
|
||
});
|
||
} else {
|
||
els.countryMenu.setAttribute('hidden', '');
|
||
els.countrySearch.value = '';
|
||
}
|
||
}
|
||
|
||
function filteredMethods() {
|
||
if (!selectedCountryCode) return [];
|
||
return methods
|
||
.filter(function (method) {
|
||
return countryCode(method) === selectedCountryCode;
|
||
})
|
||
.sort(function (a, b) {
|
||
return (
|
||
(Number(a.sort_order || 0) || 0) -
|
||
(Number(b.sort_order || 0) || 0) ||
|
||
methodLabel(a).localeCompare(methodLabel(b))
|
||
);
|
||
});
|
||
}
|
||
|
||
function selectedCountry() {
|
||
return countries.find(function (country) {
|
||
return country.code === selectedCountryCode;
|
||
});
|
||
}
|
||
|
||
function selectedMethod() {
|
||
var id = Number(selectedMethodID || 0);
|
||
return methods.find(function (method) {
|
||
return Number(method.method_id) === id;
|
||
});
|
||
}
|
||
|
||
function createPaymentLink(event) {
|
||
event.preventDefault();
|
||
var method = selectedMethod();
|
||
var usdMinorAmount = dollarsToMinor(els.usdAmount.value);
|
||
if (!selectedCountryCode) {
|
||
setMessage(
|
||
els.createMessage,
|
||
'请选择国家后再选择支付方式',
|
||
'error'
|
||
);
|
||
return;
|
||
}
|
||
if (!method || !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: 0,
|
||
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(
|
||
'USD ' + formatMoney(order.usd_minor_amount)
|
||
)
|
||
),
|
||
resultRow(
|
||
'支付方式',
|
||
escapeHTML(
|
||
[
|
||
order.provider_code,
|
||
order.pay_way,
|
||
order.pay_type,
|
||
]
|
||
.filter(Boolean)
|
||
.join(' / ') || '-'
|
||
)
|
||
),
|
||
resultRow(
|
||
'国家',
|
||
escapeHTML(
|
||
[
|
||
order.country_name,
|
||
order.country_code,
|
||
order.currency_code,
|
||
]
|
||
.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 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 = {
|
||
// 临时支付链接只做收款和人工核销,不绑定具体 App 发币;统一用 lalu 的支付方式和汇率建单。
|
||
// Return App 只控制付款后回到哪个充值页,不改变收款口径,避免回调查单和汇率口径被前端选择打散。
|
||
'X-App-Code': paymentAppCode(),
|
||
};
|
||
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 resolveAPIBaseURL() {
|
||
var env = currentEnv();
|
||
if (env === 'test') {
|
||
return normalizeBaseURL(
|
||
isLocalDevOrigin()
|
||
? window.location.origin + '/__api_test__/'
|
||
: TEST_API_BASE_URL
|
||
);
|
||
}
|
||
if (env === 'local')
|
||
return normalizeBaseURL(LOCAL_API_BASE_URL);
|
||
return normalizeBaseURL(DEFAULT_API_BASE_URL);
|
||
}
|
||
|
||
function currentEnv() {
|
||
var params = new URLSearchParams(window.location.search);
|
||
var env = String(params.get('env') || '').trim();
|
||
return env === 'test' || env === 'local' ? env : '';
|
||
}
|
||
|
||
function isLocalDevOrigin() {
|
||
return (
|
||
window.location.hostname === '127.0.0.1' ||
|
||
window.location.hostname === 'localhost' ||
|
||
window.location.hostname === '::1'
|
||
);
|
||
}
|
||
|
||
function normalizeBaseURL(value) {
|
||
return String(value || '').replace(/\/+$/, '') + '/';
|
||
}
|
||
|
||
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() {
|
||
return selectedAppCode || 'lalu';
|
||
}
|
||
|
||
function paymentAppCode() {
|
||
return '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 APP_CODES.some(function (appCode) {
|
||
return text === defaultReturnURL(appCode);
|
||
});
|
||
}
|
||
|
||
function renderEnvMeta() {
|
||
els.envMeta.textContent =
|
||
'Return App:' +
|
||
currentAppCode() +
|
||
' | 支付汇率:' +
|
||
paymentAppCode() +
|
||
' | 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 formatMoney(value) {
|
||
if (!(value === 0 || value)) return '-';
|
||
return (Number(value) / 100).toLocaleString('zh-CN', {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2,
|
||
});
|
||
}
|
||
|
||
function countryCode(method) {
|
||
return String(method.country_code || '')
|
||
.trim()
|
||
.toUpperCase();
|
||
}
|
||
|
||
function countryLabel(country) {
|
||
if (!country) return '';
|
||
return country.name && country.name !== country.code
|
||
? country.name + ' · ' + country.code
|
||
: country.code;
|
||
}
|
||
|
||
function methodLabel(method) {
|
||
return [
|
||
method.method_name,
|
||
method.pay_way,
|
||
method.pay_type,
|
||
method.provider_name,
|
||
]
|
||
.filter(Boolean)
|
||
.join(' ');
|
||
}
|
||
|
||
function logoText(method) {
|
||
var value =
|
||
method.method_name ||
|
||
method.pay_type ||
|
||
method.provider_name ||
|
||
method.provider_code ||
|
||
'Pay';
|
||
return String(value)
|
||
.replace(/[^a-z0-9]/gi, '')
|
||
.slice(0, 2)
|
||
.toUpperCase();
|
||
}
|
||
|
||
function normalizeSearch(value) {
|
||
return String(value || '')
|
||
.trim()
|
||
.toLowerCase();
|
||
}
|
||
|
||
function createNode(tagName, className, text) {
|
||
var node = document.createElement(tagName);
|
||
if (className) node.className = className;
|
||
if (text !== undefined) node.textContent = text;
|
||
return node;
|
||
}
|
||
|
||
function clearNode(node) {
|
||
while (node.firstChild) node.removeChild(node.firstChild);
|
||
}
|
||
|
||
function escapeHTML(value) {
|
||
return String(value || '')
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
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>
|