From 301af6d91c8799b7d0f26a15c07bd829a9608f67 Mon Sep 17 00:00:00 2001 From: testadmin Date: Thu, 16 Apr 2026 23:31:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(recharge):=20USDT=20=E9=80=9A=E9=81=93?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=86=85=E5=B5=8C=E4=BA=8C=E7=BB=B4=E7=A0=81?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=EF=BC=8C=E6=9B=BF=E4=BB=A3=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=20pay.g7g7.top?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 复用 qr-image 库生成 TRC20 地址二维码(YBF 分支已在用,无需新依赖) - u-popup 内嵌展示:地址/精确金额/QR/倒计时,可一键复制 - 每 3s 轮询 /epay_order_status,成功后刷新用户信息并自动关闭 - 新增 api.epayOrderStatus - 后端未返回 qrcode 时回退跳转式(向后兼容) --- pages/recharge.vue | 262 +++++++++++++++++++++++++++++++++++++++++++-- request/api.js | 4 + 2 files changed, 258 insertions(+), 8 deletions(-) diff --git a/pages/recharge.vue b/pages/recharge.vue index 175ad0a..08e8472 100644 --- a/pages/recharge.vue +++ b/pages/recharge.vue @@ -230,6 +230,48 @@ + + + + + × + USDT (TRC20) + {{ epayQr.statusText }} + + + + + + + Số tiền USDT (chính xác) + + {{ epayQr.data.usdt_amount }} USDT + + {{ epayQr.copied === 'amount' ? '✓' : 'Copy' }} + + + + + + Địa chỉ ví (TRC20) + + {{ epayQr.data.usdt_address }} + + {{ epayQr.copied === 'addr' ? '✓' : 'Copy' }} + + + + + + ≈ {{ epayQr.data.vnd_amount }} VND + ⏱ {{ epayQr.countdownText }} + + + + ⚠ Vui lòng chuyển chính xác số tiền. Sai số sẽ không thể xác định đơn hàng! + + + @@ -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(); }, }; @@ -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; +} diff --git a/request/api.js b/request/api.js index b9ae2d4..12757f2 100644 --- a/request/api.js +++ b/request/api.js @@ -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);