feat: 充值页新增 CCpay 银行QR 第三通道
- recharge.vue: 新增 Ngân hàng QR tab + 银行QR弹窗 + 轮询到账 - api.js: 新增 ccpayDeposit / ccpayOrderStatus 接口
This commit is contained in:
+149
-2
@@ -197,6 +197,11 @@
|
|||||||
:class="{ active: payChannel === 'epay' }"
|
:class="{ active: payChannel === 'epay' }"
|
||||||
@click="payChannel = 'epay'"
|
@click="payChannel = 'epay'"
|
||||||
>USDT (TRC20)</view>
|
>USDT (TRC20)</view>
|
||||||
|
<view
|
||||||
|
class="pay-tab"
|
||||||
|
:class="{ active: payChannel === 'ccpay' }"
|
||||||
|
@click="payChannel = 'ccpay'"
|
||||||
|
>Ngân hàng QR</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="picker-view">
|
<view class="picker-view">
|
||||||
<u-input
|
<u-input
|
||||||
@@ -219,12 +224,12 @@
|
|||||||
class="button"
|
class="button"
|
||||||
color="#e5b932"
|
color="#e5b932"
|
||||||
:text="$lang.submit"
|
:text="$lang.submit"
|
||||||
@click="payChannel === 'epay' ? submitEpay() : submitAlin()"
|
@click="payChannel === 'ccpay' ? submitCcpay() : (payChannel === 'epay' ? submitEpay() : submitAlin())"
|
||||||
></u-button>
|
></u-button>
|
||||||
</view>
|
</view>
|
||||||
<view class="list">
|
<view class="list">
|
||||||
<view class="tip">
|
<view class="tip">
|
||||||
{{ payChannel === 'epay' ? 'Nhập số tiền theo VND — hệ thống tự động quy đổi sang USDT theo tỷ giá 1 USDT ≈ 27,100 VND. Thanh toán qua mạng TRC20.' : $lang.rechargeTip }}
|
{{ payChannel === 'epay' ? 'Nhập số tiền theo VND — hệ thống tự động quy đổi sang USDT theo tỷ giá 1 USDT ≈ 27,100 VND. Thanh toán qua mạng TRC20.' : (payChannel === 'ccpay' ? 'Nhập số tiền VND — hệ thống tạo mã QR ngân hàng. Quét mã thanh toán qua ứng dụng ngân hàng.' : $lang.rechargeTip) }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -272,6 +277,45 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|
||||||
|
<!-- CCpay 银行QR弹窗 -->
|
||||||
|
<u-popup :show="bankQr.visible" mode="center" round="10" :closeOnClickOverlay="false" @close="closeBankQr">
|
||||||
|
<view class="epay-qr-box">
|
||||||
|
<view class="epay-qr-close" @click="closeBankQr">×</view>
|
||||||
|
<view class="epay-qr-title">Ngân hàng QR</view>
|
||||||
|
<view class="epay-qr-status" :class="bankQr.statusClass">{{ bankQr.statusText }}</view>
|
||||||
|
|
||||||
|
<view class="epay-qr-img-wrap" v-if="bankQr.qrDataUrl">
|
||||||
|
<u--image :src="bankQr.qrDataUrl" width="340rpx" height="340rpx" mode="scaleToFill"></u--image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="epay-qr-field">
|
||||||
|
<view class="epay-qr-label">Số tài khoản</view>
|
||||||
|
<view class="epay-qr-value amount">
|
||||||
|
<text>{{ bankQr.data.bank_account }}</text>
|
||||||
|
<view class="epay-qr-btn" @click="copyEpayText(bankQr.data.bank_account, 'bank_acc')">
|
||||||
|
{{ epayQr.copied === 'bank_acc' ? '✓' : 'Copy' }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="epay-qr-field">
|
||||||
|
<view class="epay-qr-label">Tên tài khoản — Ngân hàng</view>
|
||||||
|
<view class="epay-qr-value addr">
|
||||||
|
<text>{{ bankQr.data.bank_name }} — {{ bankQr.data.bank_short }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="epay-qr-meta">
|
||||||
|
<text>{{ Number(bankQr.data.amount).toLocaleString() }} VND</text>
|
||||||
|
<text class="epay-qr-countdown">⏱ {{ bankQr.countdownText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="epay-qr-tip">
|
||||||
|
⚠ Vui lòng chuyển khoản chính xác số tiền. Sai thông tin sẽ không thể xác nhận đơn hàng!
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -308,6 +352,16 @@ export default {
|
|||||||
},
|
},
|
||||||
_epayPollTimer: null,
|
_epayPollTimer: null,
|
||||||
_epayCountdownTimer: null,
|
_epayCountdownTimer: null,
|
||||||
|
bankQr: {
|
||||||
|
visible: false,
|
||||||
|
data: { bank_account: "", bank_name: "", bank_short: "", qr_data: "", amount: 0, order_sn: "", expires_at: 0, timeout_sec: 600 },
|
||||||
|
qrDataUrl: "",
|
||||||
|
countdownText: "",
|
||||||
|
statusText: "",
|
||||||
|
statusClass: "waiting",
|
||||||
|
},
|
||||||
|
_bankPollTimer: null,
|
||||||
|
_bankCountdownTimer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -603,9 +657,102 @@ export default {
|
|||||||
this._epayCountdownTimer = null;
|
this._epayCountdownTimer = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
submitCcpay() {
|
||||||
|
if (!this.amount || this.amount <= 0) {
|
||||||
|
this.$toast({ message: 'Vui lòng nhập số tiền', duration: 2000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.amount < 1000000) {
|
||||||
|
this.$toast({ message: 'Số tiền tối thiểu là 1,000,000', duration: 2000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = {
|
||||||
|
user_id: this.userInfo.id,
|
||||||
|
price: this.amount,
|
||||||
|
client: 3,
|
||||||
|
username: this.userInfo.username,
|
||||||
|
};
|
||||||
|
this.$toast({ type: "loading", message: `${this.$lang.submit}...`, duration: 200000 });
|
||||||
|
this.$api
|
||||||
|
.ccpayDeposit(params)
|
||||||
|
.then((res) => {
|
||||||
|
const d = res.Data || {};
|
||||||
|
if (d.status == 1 && d.qrcode) {
|
||||||
|
this.openBankQr(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 });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => { console.log(err); })
|
||||||
|
.finally(() => { this.$toastHide(); });
|
||||||
|
},
|
||||||
|
openBankQr(qrcode) {
|
||||||
|
this.bankQr.data = qrcode;
|
||||||
|
this.bankQr.statusText = "Đang chờ thanh toán...";
|
||||||
|
this.bankQr.statusClass = "waiting";
|
||||||
|
try {
|
||||||
|
this.bankQr.qrDataUrl = qrcode.qr_data
|
||||||
|
? "data:image/png;base64," + uni.arrayBufferToBase64(qr.imageSync(qrcode.qr_data, { margin: 2 }))
|
||||||
|
: "";
|
||||||
|
} catch (e) { this.bankQr.qrDataUrl = ""; }
|
||||||
|
this.bankQr.visible = true;
|
||||||
|
this.startBankCountdown();
|
||||||
|
this.startBankPolling();
|
||||||
|
},
|
||||||
|
startBankCountdown() {
|
||||||
|
const tick = () => {
|
||||||
|
const left = Math.max(0, this.bankQr.data.expires_at - Math.floor(Date.now() / 1000));
|
||||||
|
const m = Math.floor(left / 60);
|
||||||
|
const s = left % 60;
|
||||||
|
this.bankQr.countdownText = (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
|
||||||
|
if (left <= 0) {
|
||||||
|
this.bankQr.statusText = "Đơn hàng đã hết hạn";
|
||||||
|
this.bankQr.statusClass = "expired";
|
||||||
|
this.stopBankTimers();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
this._bankCountdownTimer = setInterval(tick, 1000);
|
||||||
|
},
|
||||||
|
startBankPolling() {
|
||||||
|
const poll = () => {
|
||||||
|
if (!this.bankQr.visible || !this.bankQr.data.order_sn) return;
|
||||||
|
this.$api
|
||||||
|
.ccpayOrderStatus({ user_id: this.userInfo.id, order_sn: this.bankQr.data.order_sn })
|
||||||
|
.then((res) => {
|
||||||
|
const d = res.Data || {};
|
||||||
|
if (d.status == 1) {
|
||||||
|
this.bankQr.statusText = "Thanh toán thành công!";
|
||||||
|
this.bankQr.statusClass = "success";
|
||||||
|
this.stopBankTimers();
|
||||||
|
this.$store.dispatch("app/getUserInfo");
|
||||||
|
setTimeout(() => this.closeBankQr(), 2500);
|
||||||
|
} else if (d.status == 2) {
|
||||||
|
this.bankQr.statusText = "Đơn hàng đã hết hạn";
|
||||||
|
this.bankQr.statusClass = "expired";
|
||||||
|
this.stopBankTimers();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
this._bankPollTimer = setInterval(poll, 3000);
|
||||||
|
setTimeout(poll, 1000);
|
||||||
|
},
|
||||||
|
closeBankQr() {
|
||||||
|
this.bankQr.visible = false;
|
||||||
|
this.stopBankTimers();
|
||||||
|
},
|
||||||
|
stopBankTimers() {
|
||||||
|
if (this._bankPollTimer) { clearInterval(this._bankPollTimer); this._bankPollTimer = null; }
|
||||||
|
if (this._bankCountdownTimer) { clearInterval(this._bankCountdownTimer); this._bankCountdownTimer = null; }
|
||||||
|
},
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.stopEpayTimers();
|
this.stopEpayTimers();
|
||||||
|
this.stopBankTimers();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -65,6 +65,14 @@ let api = {
|
|||||||
epayOrderStatus: async (params) => {
|
epayOrderStatus: async (params) => {
|
||||||
return await ajax.post("/epay_order_status", params);
|
return await ajax.post("/epay_order_status", params);
|
||||||
},
|
},
|
||||||
|
// CCpay越南银行QR充值
|
||||||
|
ccpayDeposit: async (params) => {
|
||||||
|
return await ajax.post("/ccpay_online_order", params);
|
||||||
|
},
|
||||||
|
// CCpay订单状态轮询
|
||||||
|
ccpayOrderStatus: async (params) => {
|
||||||
|
return await ajax.post("/ccpay_order_status", params);
|
||||||
|
},
|
||||||
// 越南银行卡提现
|
// 越南银行卡提现
|
||||||
applyWithdrawBank: async (params) => {
|
applyWithdrawBank: async (params) => {
|
||||||
return await ajax.post("/apply_withdraw_bank", params);
|
return await ajax.post("/apply_withdraw_bank", params);
|
||||||
|
|||||||
Reference in New Issue
Block a user