- 重新设计支付页面UI,采用毛玻璃效果和动态背景- 更新支付接口调用方式,从表单提交改为JSON请求 -优化支付签名验证逻辑,统一错误处理方式 - 修改京东支付接口参数和返回结构体 - 移除OpenTelemetry追踪相关代码 - 添加支付倒计时功能和本地存储支持 - 优化支付按钮加载状态和交互反馈 - 调整HTML结构和CSS样式,提升用户体验
435 lines
12 KiB
HTML
435 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>微信支付</title>
|
|
<script src="../static/js/tailwindcss.js"></script>
|
|
<script src="../static/js/framer-motion.js"></script>
|
|
<style>
|
|
:root {
|
|
--primary: #07C160;
|
|
--primary-soft: rgba(7, 193, 96, .15);
|
|
--apple-white: #ffffff;
|
|
--gray: #8e8e93;
|
|
--light: #f5f5f7;
|
|
--border: #e5e5ea
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box
|
|
}
|
|
|
|
html, body {
|
|
height: 100%
|
|
}
|
|
|
|
body {
|
|
font-family: 'SF Pro Text', 'SF Pro Display', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
background: var(--apple-white);
|
|
color: #1d1d1f;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: hidden
|
|
}
|
|
|
|
.container {
|
|
position: relative;
|
|
text-align: center;
|
|
padding: 40px 30px;
|
|
max-width: 420px;
|
|
width: 92%;
|
|
z-index: 10
|
|
}
|
|
|
|
.bg-shapes {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 1;
|
|
overflow: hidden
|
|
}
|
|
|
|
.bg-shape {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
filter: blur(70px)
|
|
}
|
|
|
|
.bg-shape:nth-child(1) {
|
|
top: -12%;
|
|
left: -12%;
|
|
width: 520px;
|
|
height: 520px;
|
|
background: linear-gradient(to right, rgba(7, 193, 96, .25), rgba(7, 193, 96, 0));
|
|
animation: float 16s ease-in-out infinite
|
|
}
|
|
|
|
.bg-shape:nth-child(2) {
|
|
bottom: -18%;
|
|
right: -12%;
|
|
width: 620px;
|
|
height: 620px;
|
|
background: linear-gradient(to left, rgba(7, 193, 96, .18), rgba(7, 193, 96, 0));
|
|
animation: float 22s ease-in-out infinite reverse
|
|
}
|
|
|
|
.bg-shape:nth-child(3) {
|
|
top: 42%;
|
|
left: 62%;
|
|
width: 320px;
|
|
height: 320px;
|
|
background: linear-gradient(to top, rgba(7, 193, 96, .12), rgba(7, 193, 96, 0));
|
|
animation: float 19s ease-in-out infinite 2s
|
|
}
|
|
|
|
@keyframes float {
|
|
0% {
|
|
transform: translate(0, 0) rotate(0)
|
|
}
|
|
50% {
|
|
transform: translate(28px, 18px) rotate(4deg)
|
|
}
|
|
100% {
|
|
transform: translate(0, 0) rotate(0)
|
|
}
|
|
}
|
|
|
|
.glass {
|
|
background: rgba(255, 255, 255, .75);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, .25);
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, .06);
|
|
border-radius: 20px
|
|
}
|
|
|
|
.logo-wrap {
|
|
width: 100px;
|
|
height: 100px;
|
|
margin: 0 auto 26px;
|
|
position: relative
|
|
}
|
|
|
|
.logo {
|
|
position: relative;
|
|
z-index: 2;
|
|
width: 100px;
|
|
height: 100px;
|
|
background: var(--primary);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: #fff;
|
|
box-shadow: 0 10px 25px rgba(7, 193, 96, .35);
|
|
animation: scaleIn .5s cubic-bezier(.175, .885, .32, 1.275) forwards
|
|
}
|
|
|
|
.pulse {
|
|
position: absolute;
|
|
inset: 0;
|
|
margin: auto;
|
|
width: 150px;
|
|
height: 150px;
|
|
background: radial-gradient(circle, rgba(7, 193, 96, .8) 0%, rgba(7, 193, 96, 0) 70%);
|
|
border-radius: 50%;
|
|
z-index: 1;
|
|
opacity: 0;
|
|
animation: pulse 2s ease-in-out infinite
|
|
}
|
|
|
|
.pulse:nth-child(1) {
|
|
animation-delay: .0s
|
|
}
|
|
|
|
.pulse:nth-child(2) {
|
|
width: 180px;
|
|
height: 180px;
|
|
animation-delay: .5s
|
|
}
|
|
|
|
.pulse:nth-child(3) {
|
|
width: 210px;
|
|
height: 210px;
|
|
animation-delay: 1s
|
|
}
|
|
|
|
@keyframes scaleIn {
|
|
0% {
|
|
transform: scale(0);
|
|
opacity: 0
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
opacity: 1
|
|
}
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% {
|
|
transform: translate(-0%, -0%) scale(.8);
|
|
opacity: 0
|
|
}
|
|
50% {
|
|
transform: scale(1);
|
|
opacity: .25
|
|
}
|
|
100% {
|
|
transform: scale(1.2);
|
|
opacity: 0
|
|
}
|
|
}
|
|
|
|
h1 {
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
margin-bottom: 10px;
|
|
opacity: 0;
|
|
animation: fadeInUp .8s ease .25s forwards
|
|
}
|
|
|
|
p.desc {
|
|
font-size: 15px;
|
|
color: var(--gray);
|
|
margin-bottom: 22px;
|
|
opacity: 0;
|
|
animation: fadeInUp .8s ease .4s forwards
|
|
}
|
|
|
|
.order-card {
|
|
padding: 22px;
|
|
margin-bottom: 22px;
|
|
text-align: left;
|
|
opacity: 0;
|
|
animation: fadeInUp .8s ease .55s forwards
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 16px;
|
|
font-size: 15px;
|
|
gap: 14px;
|
|
min-height: 24px
|
|
}
|
|
|
|
.row:last-child {
|
|
margin-bottom: 0;
|
|
padding-top: 18px;
|
|
border-top: 1px solid rgba(0, 0, 0, .06)
|
|
}
|
|
|
|
.label {
|
|
color: var(--gray);
|
|
flex-shrink: 0;
|
|
min-width: 80px;
|
|
text-align: left
|
|
}
|
|
|
|
.value {
|
|
font-weight: 500;
|
|
color: #1d1d1f;
|
|
text-align: left;
|
|
flex: 1;
|
|
word-break: break-word
|
|
}
|
|
|
|
.amount {
|
|
font-size: 20px;
|
|
color: var(--primary);
|
|
font-weight: 700
|
|
}
|
|
|
|
.countdown {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 10px;
|
|
border-radius: 10px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
background: var(--primary-soft);
|
|
color: var(--primary)
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
padding: 15px;
|
|
border: none;
|
|
border-radius: 14px;
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: all .25s ease;
|
|
margin-top: 8px;
|
|
font-family: 'SF Pro Text', sans-serif;
|
|
background: var(--primary);
|
|
color: #fff;
|
|
box-shadow: 0 4px 15px rgba(7, 193, 96, .35);
|
|
opacity: 0;
|
|
animation: fadeInUp .8s ease .7s forwards
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 22px rgba(7, 193, 96, .45)
|
|
}
|
|
|
|
.btn:disabled {
|
|
background: #c7c7cc;
|
|
color: #fff;
|
|
cursor: not-allowed;
|
|
box-shadow: none;
|
|
transform: none
|
|
}
|
|
|
|
.btn.loading {
|
|
background: var(--primary);
|
|
color: #fff;
|
|
cursor: not-allowed;
|
|
box-shadow: none;
|
|
transform: none;
|
|
position: relative;
|
|
padding-left: 44px;
|
|
}
|
|
|
|
.btn.loading::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 15px;
|
|
top: 50%;
|
|
width: 18px;
|
|
height: 18px;
|
|
margin-top: -9px;
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-top: 2px solid #fff;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.safe {
|
|
margin-top: 14px;
|
|
font-size: 12px;
|
|
color: #8e8e93
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
0% {
|
|
transform: translateY(18px);
|
|
opacity: 0
|
|
}
|
|
100% {
|
|
transform: translateY(0);
|
|
opacity: 1
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="bg-shapes">
|
|
<div class="bg-shape"></div>
|
|
<div class="bg-shape"></div>
|
|
<div class="bg-shape"></div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="logo-wrap">
|
|
<div class="pulse"></div>
|
|
<div class="pulse"></div>
|
|
<div class="pulse"></div>
|
|
<div class="logo">
|
|
<svg width="44" height="44" viewBox="0 0 24 24" fill="none">
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15l-5-5 1.41-1.41L11 14.17l7.59-7.59L20 8l-9 9z"
|
|
fill="#fff"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<h1>微信支付</h1>
|
|
<p class="desc">请在有效期内完成支付,超时订单将自动取消</p>
|
|
|
|
<div class="order-card glass">
|
|
<div class="row">
|
|
<span class="label">订单金额</span>
|
|
<span class="value amount">¥{{.mmValue}}</span>
|
|
</div>
|
|
<div class="row">
|
|
<span class="label">订单编号</span>
|
|
<span class="value">{{.orderNo}}</span>
|
|
</div>
|
|
<div class="row">
|
|
<span class="label">商品名称</span>
|
|
<span class="value">微信支付</span>
|
|
</div>
|
|
<div class="row">
|
|
<span class="label">剩余时间</span>
|
|
<span class="value"><span id="countdown" class="countdown">--:--:--</span></span>
|
|
</div>
|
|
</div>
|
|
|
|
<button id="payBtn" class="btn">确认支付</button>
|
|
<div class="safe">本次交易安全可靠</div>
|
|
</div>
|
|
|
|
|
|
<script>
|
|
const payBtn = document.getElementById('payBtn');
|
|
const countdownEl = document.getElementById('countdown');
|
|
const orderId = '{{.orderNo}}';
|
|
const EXP_KEY = 'order_expiry_' + orderId;
|
|
const now = Date.now();
|
|
let expiry = parseInt(localStorage.getItem(EXP_KEY) || '0', 10);
|
|
if (!expiry || expiry < now) {
|
|
expiry = now + 24 * 60 * 60 * 1000;
|
|
localStorage.setItem(EXP_KEY, String(expiry));
|
|
}
|
|
|
|
async function requestOrder() {
|
|
try {
|
|
payBtn.disabled = true;
|
|
payBtn.classList.add('loading');
|
|
payBtn.textContent = '支付处理中…';
|
|
const payload = {
|
|
productCode: '{{.productCode}}',
|
|
orderId: '{{.orderNo}}',
|
|
sign: '{{.sign}}',
|
|
returnUrl: '{{.returnUrl}}',
|
|
factMMValue: '{{.mmValue}}'
|
|
};
|
|
const resp = await fetch('/order/pay/original/jd', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
const res = await resp.json();
|
|
if (res && res.code === 0 && res.data && res.data.wxPay) {
|
|
window.location.href = res.data.wxPay;
|
|
} else {
|
|
alert(res && res.msg ? res.msg : '下单失败');
|
|
payBtn.disabled = false;
|
|
payBtn.classList.remove('loading');
|
|
payBtn.textContent = '确认支付';
|
|
}
|
|
} catch (e) {
|
|
alert('网络异常,请稍后重试');
|
|
payBtn.disabled = false;
|
|
payBtn.classList.remove('loading');
|
|
payBtn.textContent = '确认支付';
|
|
}
|
|
}
|
|
|
|
payBtn.addEventListener('click', () => {
|
|
if (payBtn.disabled) return;
|
|
requestOrder();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |