1207 lines
33 KiB
Vue
1207 lines
33 KiB
Vue
<template>
|
||
<view class="recharge">
|
||
<u-navbar
|
||
title=""
|
||
bgColor="transparent"
|
||
:placeholder="true"
|
||
:fixed="true"
|
||
leftIcon=""
|
||
height="60"
|
||
>
|
||
<view slot="left">
|
||
<text class="navbar-lab">{{ $lang.recharge }}</text>
|
||
</view>
|
||
</u-navbar>
|
||
<view class="content">
|
||
<template v-if="userInfo.pay_channel == 'BC' && userInfo.area_id != 3">
|
||
<view class="picker-view" @click="show = true">
|
||
<u-input
|
||
readonly
|
||
:placeholder="$lang.pleaseChoose"
|
||
color="#f7ecd0"
|
||
v-model="type"
|
||
border="none"
|
||
>
|
||
<u--text
|
||
:text="$lang.chooseRecharge"
|
||
slot="prefix"
|
||
margin="0 3px 0 0"
|
||
type="tips"
|
||
></u--text>
|
||
<u-icon
|
||
slot="suffix"
|
||
name="arrow-down-fill"
|
||
color="#b9aa84"
|
||
size="16"
|
||
></u-icon>
|
||
</u-input>
|
||
</view>
|
||
<u-picker
|
||
:title="$lang.chooseRecharge"
|
||
closeOnClickOverlay
|
||
:show="show"
|
||
:columns="columns"
|
||
:cancelText="$lang.cancel"
|
||
:confirmText="$lang.confirm"
|
||
@cancel="close"
|
||
@close="close"
|
||
@confirm="confirm"
|
||
></u-picker>
|
||
<view class="list">
|
||
<view class="lab">{{ $lang.rechargeWithdrawNetwork }}</view>
|
||
<view class="type">TRC20</view>
|
||
</view>
|
||
|
||
<view class="list">
|
||
<view class="lab">{{ $lang.walletAddress }}:</view>
|
||
<view class="box">
|
||
<u-input
|
||
readonly
|
||
:placeholder="$lang.walletAddress"
|
||
fontSize="12px"
|
||
color="#f7ecd0"
|
||
:value="address"
|
||
>
|
||
<template slot="suffix">
|
||
<u-button
|
||
class="btn"
|
||
:text="$lang.copy"
|
||
@click="copyCode"
|
||
color="#e5b932"
|
||
></u-button>
|
||
</template>
|
||
</u-input>
|
||
</view>
|
||
</view>
|
||
<view class="list qrcode">
|
||
<u--image
|
||
radius="5px"
|
||
:showLoading="true"
|
||
:src="qrcode"
|
||
width="200px"
|
||
height="200px"
|
||
mode="scaleToFill"
|
||
></u--image>
|
||
</view>
|
||
<view class="list">
|
||
<view class="tip">
|
||
{{ $lang.rechargeTip }}
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<template v-if="userInfo.pay_channel == 'YBF' && userInfo.area_id != 3">
|
||
<view
|
||
class="picker-view"
|
||
@click="fnPicker('currency')"
|
||
v-if="currency_list.length"
|
||
>
|
||
<u-input
|
||
readonly
|
||
:placeholder="$lang.chooseCoin"
|
||
color="#f7ecd0"
|
||
v-model="currency.name"
|
||
border="none"
|
||
>
|
||
<u--text
|
||
:text="$lang.chooseCoin"
|
||
slot="prefix"
|
||
margin="0 3px 0 0"
|
||
type="tips"
|
||
></u--text>
|
||
<u-icon
|
||
slot="suffix"
|
||
name="arrow-down-fill"
|
||
color="#b9aa84"
|
||
size="16"
|
||
></u-icon>
|
||
</u-input>
|
||
</view>
|
||
<view
|
||
class="picker-view"
|
||
@click="fnPicker('coin_type')"
|
||
v-if="coin_type_list.length"
|
||
>
|
||
<u-input
|
||
readonly
|
||
:placeholder="$lang.chooseLine"
|
||
color="#f7ecd0"
|
||
v-model="coin_type.name"
|
||
border="none"
|
||
>
|
||
<u--text
|
||
:text="$lang.chooseLine"
|
||
slot="prefix"
|
||
margin="0 3px 0 0"
|
||
type="tips"
|
||
></u--text>
|
||
<u-icon
|
||
slot="suffix"
|
||
name="arrow-down-fill"
|
||
color="#b9aa84"
|
||
size="16"
|
||
></u-icon>
|
||
</u-input>
|
||
</view>
|
||
<view class="picker-view">
|
||
<u-input
|
||
:placeholder="$lang.enterMoney"
|
||
color="#f7ecd0"
|
||
v-model="amount"
|
||
border="none"
|
||
>
|
||
<u--text
|
||
:text="$lang.rechargeAmount"
|
||
slot="prefix"
|
||
margin="0 3px 0 0"
|
||
type="tips"
|
||
></u--text>
|
||
</u-input>
|
||
</view>
|
||
<view class="list mb60">
|
||
<u-button
|
||
class="button"
|
||
color="#e5b932"
|
||
:text="$lang.submit"
|
||
@click="submit"
|
||
></u-button>
|
||
</view>
|
||
<view class="list">
|
||
<view class="tip">
|
||
{{ $lang.rechargeTip }}
|
||
</view>
|
||
</view>
|
||
<u-picker
|
||
:title="$lang.chooseRecharge"
|
||
closeOnClickOverlay
|
||
:show="showPicker"
|
||
:columns="columnsPicker"
|
||
keyName="name"
|
||
:cancelText="$lang.cancel"
|
||
:confirmText="$lang.confirm"
|
||
@cancel="closePicker"
|
||
@close="closePicker"
|
||
@confirm="confirmPicker"
|
||
></u-picker>
|
||
</template>
|
||
<!-- 越南地区:银行卡 (AlinPay) / USDT (Epay) 双通道(和 PC 同步按 area_id 判断) -->
|
||
<template v-if="userInfo.area_id == 3">
|
||
<view class="pay-tabs">
|
||
<view
|
||
class="pay-tab"
|
||
:class="{ active: payChannel === 'alin' }"
|
||
@click="payChannel = 'alin'"
|
||
>Thẻ ngân hàng</view>
|
||
<view
|
||
class="pay-tab"
|
||
:class="{ active: payChannel === 'epay' }"
|
||
@click="payChannel = 'epay'"
|
||
>USDT (TRC20)</view>
|
||
<view
|
||
class="pay-tab"
|
||
:class="{ active: payChannel === 'ccpay' }"
|
||
@click="payChannel = 'ccpay'"
|
||
>Ngân hàng QR</view>
|
||
</view>
|
||
<view class="picker-view">
|
||
<u-input
|
||
:placeholder="payChannel === 'epay' ? 'Nhập số tiền VND (vd: 1,000,000)' : 'Tối thiểu 1,000,000'"
|
||
color="#f7ecd0"
|
||
v-model="amount"
|
||
type="number"
|
||
border="none"
|
||
>
|
||
<u--text
|
||
:text="payChannel === 'epay' ? ($lang.rechargeAmount || '充值金额') + ' (VND)' : ($lang.rechargeAmount || '充值金额')"
|
||
slot="prefix"
|
||
margin="0 3px 0 0"
|
||
type="tips"
|
||
></u--text>
|
||
</u-input>
|
||
</view>
|
||
<view class="list mb60">
|
||
<u-button
|
||
class="button"
|
||
color="#e5b932"
|
||
:text="$lang.submit"
|
||
@click="payChannel === 'ccpay' ? submitCcpay() : (payChannel === 'epay' ? submitEpay() : submitAlin())"
|
||
></u-button>
|
||
</view>
|
||
<view class="list">
|
||
<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.' : (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>
|
||
</template>
|
||
</view>
|
||
<view class="mb60"></view>
|
||
|
||
<!-- AlinPay 内嵌支付弹窗 -->
|
||
<view class="alin-overlay" v-if="alinPay.visible" @click.self="closeAlinPay">
|
||
<view class="alin-pay-box">
|
||
<view class="alin-pay-header">
|
||
<text class="alin-pay-title">Thanh toán ngân hàng</text>
|
||
<view class="epay-qr-close" @click="closeAlinPay">×</view>
|
||
</view>
|
||
<iframe v-if="alinPay.url" :src="alinPay.url" class="alin-iframe" frameborder="0" allow="payment"></iframe>
|
||
</view>
|
||
</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ỉ ví (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>
|
||
|
||
<!-- 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>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapState } from "vuex";
|
||
import qr from "qr-image";
|
||
export default {
|
||
data() {
|
||
return {
|
||
address: "",
|
||
qrcode: "",
|
||
// 币种选择
|
||
type: "USDT",
|
||
show: false,
|
||
columns: [["USDT"]],
|
||
// 支付方式
|
||
showPicker: false,
|
||
columnsPicker: [],
|
||
pickerType: "",
|
||
amount: "",
|
||
currency: { name: "", key: "" },
|
||
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,
|
||
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,
|
||
alinPay: {
|
||
visible: false,
|
||
url: "",
|
||
},
|
||
};
|
||
},
|
||
computed: {
|
||
...mapState({
|
||
userInfo: (state) => state.app.userInfo,
|
||
$lang: (state) => state.app.lang[state.app.language],
|
||
coin_type_list: (state) => {
|
||
return state.app?.userInfo?.pay_channel_coin_chain || [];
|
||
},
|
||
currency_list: (state) => {
|
||
return state.app?.userInfo?.pay_channel_currency || [];
|
||
},
|
||
}),
|
||
},
|
||
onLoad() {
|
||
if (this.userInfo && this.userInfo.wallet) {
|
||
this.address = this.userInfo.wallet.address;
|
||
this.setQrCode();
|
||
}
|
||
if (
|
||
this.userInfo &&
|
||
this.userInfo.pay_channel_coin_chain &&
|
||
this.userInfo.pay_channel_coin_chain.length
|
||
) {
|
||
this.coin_type = this.userInfo.pay_channel_coin_chain[0];
|
||
}
|
||
if (
|
||
this.userInfo &&
|
||
this.userInfo.pay_channel_currency &&
|
||
this.userInfo.pay_channel_currency.length
|
||
) {
|
||
this.currency = this.userInfo.pay_channel_currency[0];
|
||
}
|
||
},
|
||
methods: {
|
||
confirm(chose) {
|
||
this.type = chose.value[0];
|
||
this.close();
|
||
},
|
||
close() {
|
||
this.show = false;
|
||
},
|
||
setQrCode() {
|
||
const qrcode =
|
||
"data:image/png;base64," +
|
||
uni.arrayBufferToBase64(
|
||
qr.imageSync(this.address, {
|
||
margin: 2,
|
||
})
|
||
);
|
||
this.qrcode = qrcode;
|
||
},
|
||
// 复制链接
|
||
copyCode() {
|
||
uni.setClipboardData({
|
||
data: this.address,
|
||
success: () => {
|
||
this.$toast({
|
||
message: this.$lang.copySuccessfully,
|
||
duration: 2000,
|
||
});
|
||
},
|
||
});
|
||
},
|
||
fnPicker(type) {
|
||
this.pickerType = type;
|
||
this.columnsPicker = [this[`${type}_list`]];
|
||
this.showPicker = true;
|
||
},
|
||
confirmPicker(chose) {
|
||
this[this.pickerType] = chose.value[0];
|
||
this.closePicker();
|
||
},
|
||
closePicker() {
|
||
this.showPicker = false;
|
||
},
|
||
submit() {
|
||
if (!this.amount) {
|
||
this.$toast({
|
||
message: this.$lang.enterWithdrawalamount,
|
||
duration: 2000,
|
||
});
|
||
} else {
|
||
const params = {
|
||
user_id: this.userInfo.id,
|
||
pay_channel: this.userInfo.pay_channel,
|
||
amount: this.amount,
|
||
currency: this.currency.key,
|
||
coin_type: this.coin_type.key,
|
||
};
|
||
this.$toast({
|
||
type: "loading",
|
||
message: `${this.$lang.submit}...`,
|
||
duration: 200000,
|
||
});
|
||
this.$api
|
||
.ybfDeposit(params)
|
||
.then((res) => {
|
||
if (res.Success == 1) {
|
||
const o = navigator.userAgent;
|
||
const isiOS = !!o.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
|
||
if (isiOS) {
|
||
window.location.href = res.data.payUrl;
|
||
} else {
|
||
window.open(res.data.payUrl, "_blank");
|
||
}
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.log(err);
|
||
})
|
||
.finally(() => {
|
||
this.$toastHide();
|
||
});
|
||
}
|
||
},
|
||
// 越南 AlinPay 充值
|
||
submitAlin() {
|
||
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
|
||
.alinDeposit(params)
|
||
.then((res) => {
|
||
const d = res.Data || {};
|
||
if (d.status == 1 && d.url) {
|
||
this.alinPay.url = d.url;
|
||
this.alinPay.visible = true;
|
||
} else {
|
||
this.$toast({
|
||
message: d.message || '充值失败',
|
||
duration: 2000,
|
||
});
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.log(err);
|
||
})
|
||
.finally(() => {
|
||
this.$toastHide();
|
||
});
|
||
},
|
||
submitEpay() {
|
||
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,
|
||
pay_type: 'usdt',
|
||
};
|
||
this.$toast({
|
||
type: "loading",
|
||
message: `${this.$lang.submit}...`,
|
||
duration: 200000,
|
||
});
|
||
this.$api
|
||
.epayDeposit(params)
|
||
.then((res) => {
|
||
const d = res.Data || {};
|
||
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 });
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.log(err);
|
||
})
|
||
.finally(() => {
|
||
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);
|
||
},
|
||
});
|
||
},
|
||
closeAlinPay() {
|
||
this.alinPay.visible = false;
|
||
this.alinPay.url = "";
|
||
},
|
||
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;
|
||
}
|
||
},
|
||
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() {
|
||
this.stopEpayTimers();
|
||
this.stopBankTimers();
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.pay-tabs {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.pay-tab {
|
||
flex: 1;
|
||
min-height: 44px;
|
||
padding: 0 10rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
color: #b9aa84;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(21, 18, 15, 0.96) 0%,
|
||
rgba(13, 11, 9, 0.96) 100%
|
||
);
|
||
border: 1rpx solid rgba(223, 189, 120, 0.14);
|
||
border-radius: 16px;
|
||
font-size: 24rpx;
|
||
line-height: 1.3;
|
||
transition: all 0.2s;
|
||
|
||
&.active {
|
||
color: #231705;
|
||
font-weight: 700;
|
||
background: linear-gradient(180deg, #f2dfb6 0%, #dfbd78 52%, #bd8d39 100%);
|
||
border-color: rgba(242, 223, 182, 0.5);
|
||
box-shadow: 0 12px 24px rgba(125, 84, 22, 0.18),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.36);
|
||
}
|
||
}
|
||
|
||
.recharge {
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #060505 0%, #0d0b09 100%);
|
||
color: #f7ecd0;
|
||
|
||
::v-deep .u-navbar {
|
||
background: rgba(0, 0, 0, 0.72) !important;
|
||
}
|
||
|
||
::v-deep .u-navbar__content {
|
||
background: transparent !important;
|
||
}
|
||
|
||
::v-deep .u-picker__view__column__item {
|
||
color: #f3e3b8 !important;
|
||
}
|
||
|
||
::v-deep .u-toolbar {
|
||
background: #15120f !important;
|
||
border-bottom-color: rgba(223, 189, 120, 0.12) !important;
|
||
}
|
||
|
||
::v-deep .u-toolbar__title {
|
||
color: #f4dfae !important;
|
||
}
|
||
|
||
.navbar-lab {
|
||
color: #fbf2de;
|
||
font-weight: 700;
|
||
font-size: 19px;
|
||
letter-spacing: 0.3px;
|
||
}
|
||
|
||
.content {
|
||
width: calc(100% - 24px);
|
||
max-width: 760px;
|
||
margin: 10px auto 0;
|
||
padding: 18px 16px 22px;
|
||
box-sizing: border-box;
|
||
border-radius: 24px;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(16, 13, 11, 0.96) 0%,
|
||
rgba(10, 8, 7, 0.96) 100%
|
||
);
|
||
border: 1px solid rgba(223, 189, 120, 0.18);
|
||
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.24),
|
||
inset 0 1px 0 rgba(255, 245, 223, 0.03);
|
||
|
||
.picker-view {
|
||
min-height: 56px;
|
||
margin-bottom: 14px;
|
||
padding: 0 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(25, 21, 18, 0.96) 0%,
|
||
rgba(17, 14, 12, 0.96) 100%
|
||
);
|
||
border: 1px solid rgba(223, 189, 120, 0.14);
|
||
border-radius: 16px;
|
||
box-shadow: inset 0 1px 0 rgba(255, 244, 220, 0.03),
|
||
inset 0 -1px 0 rgba(0, 0, 0, 0.12);
|
||
|
||
::v-deep .u-input {
|
||
flex: 1;
|
||
}
|
||
|
||
::v-deep .u-input__content {
|
||
min-height: 54px;
|
||
padding: 0;
|
||
}
|
||
|
||
::v-deep .u-input__content__field-wrapper {
|
||
width: 100%;
|
||
}
|
||
|
||
::v-deep input {
|
||
color: #f7ecd0 !important;
|
||
font-size: 15px !important;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
::v-deep input::placeholder {
|
||
color: rgba(245, 228, 184, 0.4) !important;
|
||
}
|
||
|
||
::v-deep .u-text__value {
|
||
color: #baa67f !important;
|
||
font-size: 13px !important;
|
||
}
|
||
}
|
||
|
||
.list {
|
||
margin-bottom: 14px;
|
||
padding: 14px;
|
||
border-radius: 16px;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(23, 19, 16, 0.92) 0%,
|
||
rgba(14, 12, 10, 0.92) 100%
|
||
);
|
||
border: 1px solid rgba(223, 189, 120, 0.1);
|
||
font-size: 14px;
|
||
text-align: center;
|
||
|
||
&.qrcode {
|
||
display: flex;
|
||
width: 100%;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px 14px;
|
||
|
||
::v-deep .u-image {
|
||
border-radius: 18px !important;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(223, 189, 120, 0.12);
|
||
background: #fff;
|
||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16);
|
||
}
|
||
}
|
||
|
||
.lab {
|
||
color: #b9aa84;
|
||
margin: 0 0 10px;
|
||
text-align: left;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.box {
|
||
::v-deep .u-input {
|
||
min-height: 48px;
|
||
padding: 0 12px;
|
||
background: rgba(255, 255, 255, 0.02);
|
||
border: 1px solid rgba(223, 189, 120, 0.12);
|
||
border-radius: 14px;
|
||
}
|
||
|
||
::v-deep .u-input__content {
|
||
min-height: 48px;
|
||
}
|
||
|
||
::v-deep input {
|
||
color: #f7ecd0 !important;
|
||
}
|
||
}
|
||
|
||
.btn {
|
||
min-width: 76px;
|
||
height: 34px;
|
||
font-weight: 700;
|
||
border: none !important;
|
||
border-radius: 12px !important;
|
||
color: #231705 !important;
|
||
box-shadow: 0 8px 18px rgba(125, 84, 22, 0.18),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.28);
|
||
}
|
||
|
||
.type {
|
||
min-width: 86px;
|
||
height: 36px;
|
||
padding: 0 16px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(180deg, #f2dfb6 0%, #dfbd78 52%, #bd8d39 100%);
|
||
border-radius: 999px;
|
||
text-align: center;
|
||
line-height: 36px;
|
||
font-weight: 700;
|
||
color: #231705;
|
||
box-shadow: 0 8px 18px rgba(125, 84, 22, 0.16),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.28);
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.button {
|
||
height: 50px !important;
|
||
margin-top: 8px;
|
||
margin-bottom: 2px;
|
||
border: none !important;
|
||
border-radius: 16px !important;
|
||
color: #231705 !important;
|
||
font-weight: 700;
|
||
letter-spacing: 0.3px;
|
||
box-shadow: 0 12px 24px rgba(125, 84, 22, 0.18),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.36);
|
||
}
|
||
|
||
.tip {
|
||
color: #bca47a;
|
||
font-size: 13px;
|
||
text-align: justify;
|
||
line-height: 1.6;
|
||
}
|
||
}
|
||
|
||
.mb60 {
|
||
margin-bottom: 0 !important;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ============ 内嵌 USDT / 银行二维码弹窗 ============ */
|
||
.epay-qr-box {
|
||
width: 620rpx;
|
||
padding: 34rpx 34rpx 28rpx;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(20, 16, 12, 0.98) 0%,
|
||
rgba(11, 9, 7, 0.98) 100%
|
||
);
|
||
border: 1rpx solid rgba(223, 189, 120, 0.18);
|
||
border-radius: 20rpx;
|
||
color: #f0dbab;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
box-shadow: 0 24rpx 60rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.epay-qr-close {
|
||
position: absolute;
|
||
top: 12rpx;
|
||
right: 16rpx;
|
||
color: #b7a37d;
|
||
font-size: 38rpx;
|
||
line-height: 1;
|
||
padding: 10rpx 16rpx;
|
||
}
|
||
|
||
.epay-qr-title {
|
||
text-align: center;
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
margin-bottom: 10rpx;
|
||
color: #f7e6bd;
|
||
}
|
||
|
||
.epay-qr-status {
|
||
width: fit-content;
|
||
min-width: 180rpx;
|
||
margin: 0 auto 20rpx;
|
||
padding: 8rpx 18rpx;
|
||
text-align: center;
|
||
font-size: 22rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
|
||
&.waiting {
|
||
color: #e5b932;
|
||
}
|
||
|
||
&.success {
|
||
color: #7cd992;
|
||
}
|
||
|
||
&.expired {
|
||
color: #e67e72;
|
||
}
|
||
}
|
||
|
||
.epay-qr-img-wrap {
|
||
width: 340rpx;
|
||
height: 340rpx;
|
||
margin: 0 auto 24rpx;
|
||
background: #fff;
|
||
padding: 16rpx;
|
||
border-radius: 20rpx;
|
||
box-sizing: content-box;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 10rpx 26rpx rgba(0, 0, 0, 0.18);
|
||
}
|
||
|
||
.epay-qr-field {
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.epay-qr-label {
|
||
color: #ab9770;
|
||
font-size: 22rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.epay-qr-value {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1rpx solid rgba(223, 189, 120, 0.12);
|
||
border-radius: 14rpx;
|
||
padding: 14rpx 16rpx;
|
||
font-size: 24rpx;
|
||
word-break: break-all;
|
||
|
||
&.amount {
|
||
color: #fff8e6;
|
||
font-weight: 700;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
&.addr {
|
||
color: #f0dbab;
|
||
font-family: "Courier New", monospace;
|
||
font-size: 20rpx;
|
||
}
|
||
|
||
text {
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
.epay-qr-btn {
|
||
flex-shrink: 0;
|
||
min-width: 90rpx;
|
||
text-align: center;
|
||
background: linear-gradient(180deg, #f2dfb6 0%, #dfbd78 52%, #bd8d39 100%);
|
||
color: #231705;
|
||
font-size: 22rpx;
|
||
padding: 8rpx 18rpx;
|
||
border-radius: 10rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.epay-qr-meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
color: #b7a37d;
|
||
font-size: 24rpx;
|
||
margin: 20rpx 0 12rpx;
|
||
}
|
||
|
||
.epay-qr-countdown {
|
||
color: #e5b932;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.epay-qr-tip {
|
||
color: #d58c65;
|
||
font-size: 22rpx;
|
||
text-align: center;
|
||
line-height: 1.6;
|
||
margin-top: 14rpx;
|
||
opacity: 0.92;
|
||
}
|
||
|
||
.alin-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 9999;
|
||
background: rgba(0, 0, 0, 0.68);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
|
||
.alin-pay-box {
|
||
width: 92vw;
|
||
height: 85vh;
|
||
background: linear-gradient(180deg, #17130f 0%, #0d0b09 100%);
|
||
border: 1px solid rgba(223, 189, 120, 0.16);
|
||
border-radius: 20px;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: 0 20px 46px rgba(0, 0, 0, 0.26);
|
||
}
|
||
|
||
.alin-pay-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
background: rgba(20, 17, 14, 0.96);
|
||
border-bottom: 1px solid rgba(223, 189, 120, 0.12);
|
||
}
|
||
|
||
.alin-pay-title {
|
||
color: #f4dca3;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.alin-iframe {
|
||
flex: 1;
|
||
width: 100%;
|
||
border: none;
|
||
background: #fff;
|
||
}
|
||
</style>
|