feat(recharge): USDT 通道改为内嵌二维码弹窗,替代跳转 pay.g7g7.top

- 复用 qr-image 库生成 TRC20 地址二维码(YBF 分支已在用,无需新依赖)
- u-popup 内嵌展示:地址/精确金额/QR/倒计时,可一键复制
- 每 3s 轮询 /epay_order_status,成功后刷新用户信息并自动关闭
- 新增 api.epayOrderStatus
- 后端未返回 qrcode 时回退跳转式(向后兼容)
This commit is contained in:
testadmin
2026-04-16 23:31:52 +08:00
parent 99e4265e01
commit 301af6d91c
2 changed files with 258 additions and 8 deletions
+254 -8
View File
@@ -230,6 +230,48 @@
</template>
</view>
<view class="mb60"></view>
<!-- USDT 内嵌二维码弹窗 -->
<u-popup :show="epayQr.visible" mode="center" round="10" :closeOnClickOverlay="false" @close="closeEpayQr">
<view class="epay-qr-box">
<view class="epay-qr-close" @click="closeEpayQr">×</view>
<view class="epay-qr-title">USDT (TRC20)</view>
<view class="epay-qr-status" :class="epayQr.statusClass">{{ epayQr.statusText }}</view>
<view class="epay-qr-img-wrap">
<u--image v-if="epayQr.qrDataUrl" :src="epayQr.qrDataUrl" width="340rpx" height="340rpx" mode="scaleToFill"></u--image>
</view>
<view class="epay-qr-field">
<view class="epay-qr-label">Số tiền USDT (chính xác)</view>
<view class="epay-qr-value amount">
<text>{{ epayQr.data.usdt_amount }} USDT</text>
<view class="epay-qr-btn" @click="copyEpayText(epayQr.data.usdt_amount, 'amount')">
{{ epayQr.copied === 'amount' ? '' : 'Copy' }}
</view>
</view>
</view>
<view class="epay-qr-field">
<view class="epay-qr-label">Địa chỉ (TRC20)</view>
<view class="epay-qr-value addr">
<text>{{ epayQr.data.usdt_address }}</text>
<view class="epay-qr-btn" @click="copyEpayText(epayQr.data.usdt_address, 'addr')">
{{ epayQr.copied === 'addr' ? '' : 'Copy' }}
</view>
</view>
</view>
<view class="epay-qr-meta">
<text> {{ epayQr.data.vnd_amount }} VND</text>
<text class="epay-qr-countdown"> {{ epayQr.countdownText }}</text>
</view>
<view class="epay-qr-tip">
Vui lòng chuyển chính xác số tiền. Sai số sẽ không thể xác định đơn hàng!
</view>
</view>
</u-popup>
</view>
</template>
@@ -254,6 +296,18 @@ export default {
coin_type: { name: "", key: "" },
// 越南地区在线支付通道:'alin'(银行卡) | 'epay'(USDT)
payChannel: "alin",
// 内嵌 USDT 二维码弹窗状态
epayQr: {
visible: false,
data: { usdt_address: "", usdt_amount: "", vnd_amount: "", expires_at: 0, order_sn: "" },
qrDataUrl: "",
countdownText: "",
statusText: "",
statusClass: "waiting",
copied: "",
},
_epayPollTimer: null,
_epayCountdownTimer: null,
};
},
computed: {
@@ -448,14 +502,12 @@ export default {
.epayDeposit(params)
.then((res) => {
const d = res.Data || {};
if (d.status == 1 && d.url) {
const o = navigator.userAgent;
const isiOS = !!o.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
if (isiOS) {
window.location.href = d.url;
} else {
window.open(d.url, "_blank");
}
if (d.status == 1 && d.qrcode) {
this.openEpayQr(d.qrcode);
} else if (d.status == 1 && d.url) {
// 后端未升级时兼容跳转
const isiOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
if (isiOS) window.location.href = d.url; else window.open(d.url, "_blank");
} else {
this.$toast({ message: d.message || 'Nạp thất bại', duration: 2000 });
}
@@ -467,6 +519,93 @@ export default {
this.$toastHide();
});
},
openEpayQr(qrcode) {
this.epayQr.data = qrcode;
this.epayQr.statusText = "Đang chờ thanh toán...";
this.epayQr.statusClass = "waiting";
this.epayQr.copied = "";
// 生成二维码图片(复用现有 qr-image 库)
try {
this.epayQr.qrDataUrl =
"data:image/png;base64," +
uni.arrayBufferToBase64(qr.imageSync(qrcode.usdt_address, { margin: 2 }));
} catch (e) {
this.epayQr.qrDataUrl = "";
}
this.epayQr.visible = true;
this.startEpayCountdown();
this.startEpayPolling();
},
startEpayCountdown() {
const tick = () => {
const left = Math.max(0, this.epayQr.data.expires_at - Math.floor(Date.now() / 1000));
const m = Math.floor(left / 60);
const s = left % 60;
this.epayQr.countdownText = (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
if (left <= 0) {
this.epayQr.statusText = "Đơn hàng đã hết hạn";
this.epayQr.statusClass = "expired";
this.stopEpayTimers();
}
};
tick();
this._epayCountdownTimer = setInterval(tick, 1000);
},
startEpayPolling() {
const poll = () => {
if (!this.epayQr.visible || !this.epayQr.data.order_sn) return;
this.$api
.epayOrderStatus({
user_id: this.userInfo.id,
order_sn: this.epayQr.data.order_sn,
})
.then((res) => {
const d = res.Data || {};
if (d.status == 1) {
this.epayQr.statusText = "Thanh toán thành công!";
this.epayQr.statusClass = "success";
this.stopEpayTimers();
this.$store.dispatch("app/getUserInfo");
setTimeout(() => this.closeEpayQr(), 2500);
} else if (d.status == 2) {
this.epayQr.statusText = "Đơn hàng đã hết hạn";
this.epayQr.statusClass = "expired";
this.stopEpayTimers();
}
})
.catch(() => {});
};
this._epayPollTimer = setInterval(poll, 3000);
setTimeout(poll, 1000);
},
copyEpayText(text, tag) {
uni.setClipboardData({
data: String(text),
success: () => {
this.epayQr.copied = tag;
setTimeout(() => {
if (this.epayQr.copied === tag) this.epayQr.copied = "";
}, 1500);
},
});
},
closeEpayQr() {
this.epayQr.visible = false;
this.stopEpayTimers();
},
stopEpayTimers() {
if (this._epayPollTimer) {
clearInterval(this._epayPollTimer);
this._epayPollTimer = null;
}
if (this._epayCountdownTimer) {
clearInterval(this._epayCountdownTimer);
this._epayCountdownTimer = null;
}
},
},
beforeDestroy() {
this.stopEpayTimers();
},
};
</script>
@@ -563,4 +702,111 @@ export default {
}
}
}
/* ============ 内嵌 USDT 二维码弹窗 ============ */
.epay-qr-box {
width: 600rpx;
padding: 30rpx 34rpx 24rpx;
background: #1a1206;
border: 1rpx solid #907545;
border-radius: 10px;
color: #e5b932;
box-sizing: border-box;
position: relative;
}
.epay-qr-close {
position: absolute;
top: 8rpx;
right: 18rpx;
color: #aaa;
font-size: 40rpx;
line-height: 1;
padding: 10rpx 16rpx;
}
.epay-qr-title {
text-align: center;
font-size: 30rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.epay-qr-status {
text-align: center;
font-size: 24rpx;
margin-bottom: 16rpx;
&.waiting { color: #e5b932; }
&.success { color: #7cd992; }
&.expired { color: #e74c3c; }
}
.epay-qr-img-wrap {
width: 340rpx;
height: 340rpx;
margin: 0 auto 24rpx;
background: #fff;
padding: 14rpx;
border-radius: 8rpx;
box-sizing: content-box;
display: flex;
align-items: center;
justify-content: center;
}
.epay-qr-field {
margin-bottom: 16rpx;
}
.epay-qr-label {
color: #bbb;
font-size: 22rpx;
margin-bottom: 6rpx;
}
.epay-qr-value {
display: flex;
align-items: center;
gap: 12rpx;
background: rgba(0, 0, 0, 0.35);
border: 1rpx solid #5a4323;
border-radius: 6rpx;
padding: 12rpx 14rpx;
font-size: 24rpx;
word-break: break-all;
&.amount {
color: #fff;
font-weight: bold;
font-size: 28rpx;
}
&.addr {
color: #e5b932;
font-family: 'Courier New', monospace;
font-size: 20rpx;
}
text {
flex: 1;
}
}
.epay-qr-btn {
flex-shrink: 0;
background: #c5971e;
color: #161107;
font-size: 22rpx;
padding: 6rpx 18rpx;
border-radius: 4rpx;
font-weight: bold;
}
.epay-qr-meta {
display: flex;
justify-content: space-between;
color: #bbb;
font-size: 24rpx;
margin: 20rpx 0 12rpx;
}
.epay-qr-countdown {
color: #e5b932;
font-weight: bold;
}
.epay-qr-tip {
color: #e74c3c;
font-size: 22rpx;
text-align: center;
line-height: 1.5;
margin-top: 14rpx;
opacity: 0.85;
}
</style>
+4
View File
@@ -61,6 +61,10 @@ let api = {
epayDeposit: async (params) => {
return await ajax.post("/epay_online_order", params);
},
// 易支付订单状态轮询(内嵌二维码支付用)
epayOrderStatus: async (params) => {
return await ajax.post("/epay_order_status", params);
},
// 越南银行卡提现
applyWithdrawBank: async (params) => {
return await ajax.post("/apply_withdraw_bank", params);