比索
This commit is contained in:
parent
e5b0ae1c19
commit
bbaf345509
42
backend/src/migrations/013-add-dual-currency-balance.js
Normal file
42
backend/src/migrations/013-add-dual-currency-balance.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Migration: Add dual currency balance support
|
||||
* - Add balance_peso field to user table
|
||||
* - Add currency field to commissions table
|
||||
* - Add currency field to withdrawal table
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// Add balance_peso to user table
|
||||
await queryInterface.addColumn('user', 'balance_peso', {
|
||||
type: Sequelize.DECIMAL(10, 2),
|
||||
defaultValue: 0.00,
|
||||
allowNull: false,
|
||||
comment: '比索余额',
|
||||
});
|
||||
|
||||
// Add currency to commissions table
|
||||
await queryInterface.addColumn('commissions', 'currency', {
|
||||
type: Sequelize.ENUM('RMB', 'PHP'),
|
||||
defaultValue: 'RMB',
|
||||
allowNull: false,
|
||||
comment: '佣金货币类型',
|
||||
});
|
||||
|
||||
// Add currency to withdrawal table
|
||||
await queryInterface.addColumn('withdrawal', 'currency', {
|
||||
type: Sequelize.ENUM('CNY', 'PHP'),
|
||||
defaultValue: 'CNY',
|
||||
allowNull: false,
|
||||
comment: '提现货币类型',
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.removeColumn('user', 'balance_peso');
|
||||
await queryInterface.removeColumn('commissions', 'currency');
|
||||
await queryInterface.removeColumn('withdrawal', 'currency');
|
||||
},
|
||||
};
|
||||
|
|
@ -59,6 +59,12 @@ const Commission = sequelize.define('Commission', {
|
|||
field: 'commission_amount',
|
||||
comment: '佣金金额',
|
||||
},
|
||||
currency: {
|
||||
type: DataTypes.ENUM('RMB', 'PHP'),
|
||||
defaultValue: 'RMB',
|
||||
allowNull: false,
|
||||
comment: '佣金货币类型',
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('credited', 'cancelled'),
|
||||
defaultValue: 'credited',
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ const User = sequelize.define('User', {
|
|||
type: DataTypes.DECIMAL(10, 2),
|
||||
defaultValue: 0.00,
|
||||
allowNull: false,
|
||||
comment: '人民币余额',
|
||||
},
|
||||
balancePeso: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
defaultValue: 0.00,
|
||||
allowNull: false,
|
||||
field: 'balance_peso',
|
||||
comment: '比索余额',
|
||||
},
|
||||
invitationCode: {
|
||||
type: DataTypes.STRING(20),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ const Withdrawal = sequelize.define('Withdrawal', {
|
|||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
},
|
||||
currency: {
|
||||
type: DataTypes.ENUM('CNY', 'PHP'),
|
||||
defaultValue: 'CNY',
|
||||
allowNull: false,
|
||||
comment: '提现货币类型',
|
||||
},
|
||||
paymentMethod: {
|
||||
type: DataTypes.ENUM('wechat', 'alipay', 'bank'),
|
||||
allowNull: false,
|
||||
|
|
|
|||
73
backend/src/scripts/addDualCurrencyFields.js
Normal file
73
backend/src/scripts/addDualCurrencyFields.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Script to add dual currency balance fields
|
||||
* - Add balance_peso to user table
|
||||
* - Add currency to commissions table
|
||||
* - Add currency to withdrawal table
|
||||
*/
|
||||
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const addDualCurrencyFields = async () => {
|
||||
try {
|
||||
console.log('Starting dual currency fields migration...');
|
||||
|
||||
// Add balance_peso to user table
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE user
|
||||
ADD COLUMN balance_peso DECIMAL(10, 2) NOT NULL DEFAULT 0.00
|
||||
COMMENT '比索余额'
|
||||
`);
|
||||
console.log('✓ Added balance_peso to user table');
|
||||
} catch (error) {
|
||||
if (error.message.includes('Duplicate column')) {
|
||||
console.log('⊘ balance_peso already exists in user table');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Add currency to commissions table
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE commissions
|
||||
ADD COLUMN currency ENUM('RMB', 'PHP') NOT NULL DEFAULT 'RMB'
|
||||
COMMENT '佣金货币类型'
|
||||
`);
|
||||
console.log('✓ Added currency to commissions table');
|
||||
} catch (error) {
|
||||
if (error.message.includes('Duplicate column')) {
|
||||
console.log('⊘ currency already exists in commissions table');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Add currency to withdrawal table
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE withdrawal
|
||||
ADD COLUMN currency ENUM('CNY', 'PHP') NOT NULL DEFAULT 'CNY'
|
||||
COMMENT '提现货币类型'
|
||||
`);
|
||||
console.log('✓ Added currency to withdrawal table');
|
||||
} catch (error) {
|
||||
if (error.message.includes('Duplicate column')) {
|
||||
console.log('⊘ currency already exists in withdrawal table');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('Dual currency migration completed!');
|
||||
console.log('========================================\n');
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
addDualCurrencyFields();
|
||||
|
|
@ -37,8 +37,22 @@ const calculateCommission = async (paymentOrderId) => {
|
|||
// Get current commission rate
|
||||
const commissionRate = await commissionConfigService.getCommissionRate();
|
||||
|
||||
// Calculate commission amount - use amountRmb if available, otherwise use amount
|
||||
const paymentAmount = parseFloat(paymentOrder.amountRmb || paymentOrder.amount || 0);
|
||||
// Calculate commission amount - prioritize RMB, then Peso, then legacy amount
|
||||
// Commission is calculated based on whichever currency amount is available
|
||||
let paymentAmount = 0;
|
||||
let currency = 'RMB';
|
||||
|
||||
if (paymentOrder.amountRmb && parseFloat(paymentOrder.amountRmb) > 0) {
|
||||
paymentAmount = parseFloat(paymentOrder.amountRmb);
|
||||
currency = 'RMB';
|
||||
} else if (paymentOrder.amountPeso && parseFloat(paymentOrder.amountPeso) > 0) {
|
||||
paymentAmount = parseFloat(paymentOrder.amountPeso);
|
||||
currency = 'PHP';
|
||||
} else if (paymentOrder.amount && parseFloat(paymentOrder.amount) > 0) {
|
||||
paymentAmount = parseFloat(paymentOrder.amount);
|
||||
currency = 'RMB';
|
||||
}
|
||||
|
||||
if (paymentAmount <= 0) {
|
||||
return null; // No valid payment amount
|
||||
}
|
||||
|
|
@ -48,7 +62,7 @@ const calculateCommission = async (paymentOrderId) => {
|
|||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// Create commission record
|
||||
// Create commission record with currency
|
||||
const commission = await Commission.create({
|
||||
inviterId: inviteeUser.invitedBy,
|
||||
inviteeId: inviteeUser.id,
|
||||
|
|
@ -56,13 +70,18 @@ const calculateCommission = async (paymentOrderId) => {
|
|||
paymentAmount: paymentAmount,
|
||||
commissionRate: commissionRate,
|
||||
commissionAmount: commissionAmount,
|
||||
currency: currency,
|
||||
status: 'credited',
|
||||
}, { transaction });
|
||||
|
||||
// Update inviter's balance
|
||||
// Update inviter's balance based on currency
|
||||
const inviter = await User.findByPk(inviteeUser.invitedBy, { transaction });
|
||||
if (inviter) {
|
||||
inviter.balance = parseFloat(inviter.balance) + commissionAmount;
|
||||
if (currency === 'PHP') {
|
||||
inviter.balancePeso = parseFloat(inviter.balancePeso || 0) + commissionAmount;
|
||||
} else {
|
||||
inviter.balance = parseFloat(inviter.balance) + commissionAmount;
|
||||
}
|
||||
await inviter.save({ transaction });
|
||||
}
|
||||
|
||||
|
|
@ -172,16 +191,24 @@ const getCommissionStats = async (inviterId) => {
|
|||
where: { invitedBy: inviterId },
|
||||
});
|
||||
|
||||
// Get commission statistics
|
||||
// Get commission statistics by currency
|
||||
const commissions = await Commission.findAll({
|
||||
where: { inviterId, status: 'credited' },
|
||||
attributes: ['commissionAmount'],
|
||||
attributes: ['commissionAmount', 'currency'],
|
||||
});
|
||||
|
||||
const totalCommission = commissions.reduce(
|
||||
(sum, c) => sum + parseFloat(c.commissionAmount),
|
||||
0
|
||||
);
|
||||
// Calculate total commission by currency
|
||||
let totalCommissionRmb = 0;
|
||||
let totalCommissionPeso = 0;
|
||||
|
||||
commissions.forEach(c => {
|
||||
const amount = parseFloat(c.commissionAmount);
|
||||
if (c.currency === 'PHP') {
|
||||
totalCommissionPeso += amount;
|
||||
} else {
|
||||
totalCommissionRmb += amount;
|
||||
}
|
||||
});
|
||||
|
||||
// Get paid invites count (invitees who have made payments)
|
||||
const paidInvitesCount = await Commission.count({
|
||||
|
|
@ -193,9 +220,13 @@ const getCommissionStats = async (inviterId) => {
|
|||
return {
|
||||
totalInvites,
|
||||
paidInvites: paidInvitesCount,
|
||||
totalCommission: totalCommission.toFixed(2),
|
||||
totalCommission: totalCommissionRmb.toFixed(2),
|
||||
totalCommissionRmb: totalCommissionRmb.toFixed(2),
|
||||
totalCommissionPeso: totalCommissionPeso.toFixed(2),
|
||||
commissionCount: commissions.length,
|
||||
availableBalance: parseFloat(user.balance).toFixed(2),
|
||||
availableBalanceRmb: parseFloat(user.balance).toFixed(2),
|
||||
availableBalancePeso: parseFloat(user.balancePeso || 0).toFixed(2),
|
||||
invitationCode: invitationCode,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const generateWithdrawalNo = () => {
|
|||
* @returns {Object} Created withdrawal
|
||||
*/
|
||||
const createWithdrawal = async (userId, withdrawalData) => {
|
||||
const { amount, paymentMethod, paymentDetails } = withdrawalData;
|
||||
const { amount, currency = 'CNY', paymentMethod, paymentDetails } = withdrawalData;
|
||||
|
||||
// Validate amount
|
||||
const withdrawalAmount = parseFloat(amount);
|
||||
|
|
@ -41,6 +41,12 @@ const createWithdrawal = async (userId, withdrawalData) => {
|
|||
throw new Error(`Minimum withdrawal amount is ${MIN_WITHDRAWAL_AMOUNT}`);
|
||||
}
|
||||
|
||||
// Validate currency
|
||||
const validCurrencies = ['CNY', 'PHP'];
|
||||
if (!validCurrencies.includes(currency)) {
|
||||
throw new Error('Invalid currency');
|
||||
}
|
||||
|
||||
// Validate payment method
|
||||
const validMethods = ['wechat', 'alipay', 'bank'];
|
||||
if (!validMethods.includes(paymentMethod)) {
|
||||
|
|
@ -66,8 +72,14 @@ const createWithdrawal = async (userId, withdrawalData) => {
|
|||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Check if user has sufficient balance
|
||||
const userBalance = parseFloat(user.balance);
|
||||
// Check if user has sufficient balance based on currency
|
||||
let userBalance;
|
||||
if (currency === 'PHP') {
|
||||
userBalance = parseFloat(user.balancePeso || 0);
|
||||
} else {
|
||||
userBalance = parseFloat(user.balance);
|
||||
}
|
||||
|
||||
if (userBalance < withdrawalAmount) {
|
||||
throw new Error('Insufficient balance');
|
||||
}
|
||||
|
|
@ -90,6 +102,7 @@ const createWithdrawal = async (userId, withdrawalData) => {
|
|||
userId,
|
||||
withdrawalNo,
|
||||
amount: withdrawalAmount,
|
||||
currency,
|
||||
paymentMethod,
|
||||
paymentDetails,
|
||||
status: 'waiting',
|
||||
|
|
@ -139,6 +152,7 @@ const getWithdrawals = async (userId, options = {}) => {
|
|||
id: withdrawal.id,
|
||||
withdrawalNo: withdrawal.withdrawalNo,
|
||||
amount: parseFloat(withdrawal.amount).toFixed(2),
|
||||
currency: withdrawal.currency || 'CNY',
|
||||
paymentMethod: withdrawal.paymentMethod,
|
||||
paymentDetails: withdrawal.paymentDetails,
|
||||
status: withdrawal.status,
|
||||
|
|
@ -181,6 +195,7 @@ const getWithdrawalById = async (userId, withdrawalId) => {
|
|||
id: withdrawal.id,
|
||||
withdrawalNo: withdrawal.withdrawalNo,
|
||||
amount: parseFloat(withdrawal.amount).toFixed(2),
|
||||
currency: withdrawal.currency || 'CNY',
|
||||
paymentMethod: withdrawal.paymentMethod,
|
||||
paymentDetails: withdrawal.paymentDetails,
|
||||
status: withdrawal.status,
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ If you have privacy questions, please contact us through the application.`
|
|||
paidYes: '¥ 12',
|
||||
paidNo: '¥ 4',
|
||||
statusWaiting: 'Waiting',
|
||||
statusWaitingRmb: 'CNY Pending',
|
||||
statusWaitingPeso: 'PHP Pending',
|
||||
statusProcessing: 'Processing',
|
||||
statusCompleted: 'Completed',
|
||||
ruleTitle: 'Rules',
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ Si tiene preguntas sobre privacidad, contáctenos a través de la aplicación.`
|
|||
paidYes: '¥ 12',
|
||||
paidNo: '¥ 4',
|
||||
statusWaiting: 'Esperando',
|
||||
statusWaitingRmb: 'Yuan Pendiente',
|
||||
statusWaitingPeso: 'Peso Pendiente',
|
||||
statusProcessing: 'Procesando',
|
||||
statusCompleted: 'Completado',
|
||||
ruleTitle: 'Reglas',
|
||||
|
|
|
|||
|
|
@ -255,6 +255,8 @@ export default {
|
|||
paidYes: '¥ 12',
|
||||
paidNo: '¥ 4',
|
||||
statusWaiting: '待提现',
|
||||
statusWaitingRmb: '人民币待提现',
|
||||
statusWaitingPeso: '比索待提现',
|
||||
statusProcessing: '提现中',
|
||||
statusCompleted: '已提现',
|
||||
ruleTitle: '活动规则',
|
||||
|
|
|
|||
|
|
@ -86,9 +86,13 @@
|
|||
<image src="/static/new_bg2.png" class="record-bg-image" mode="scaleToFill"></image>
|
||||
|
||||
<view class="reward-cards">
|
||||
<view class="reward-card" v-for="(item, index) in withdrawRecords" :key="index">
|
||||
<text class="reward-amount">{{ item.amount }}{{ $t('common.currency') }}</text>
|
||||
<text class="reward-status">{{ item.status }}</text>
|
||||
<view class="reward-card">
|
||||
<text class="reward-amount">¥{{ commissionStats.availableBalanceRmb || '0.00' }}</text>
|
||||
<text class="reward-status">{{ $t('invite.statusWaitingRmb') || '人民币待提现' }}</text>
|
||||
</view>
|
||||
<view class="reward-card">
|
||||
<text class="reward-amount">₱{{ commissionStats.availableBalancePeso || '0.00' }}</text>
|
||||
<text class="reward-status">{{ $t('invite.statusWaitingPeso') || '比索待提现' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
|
@ -150,7 +154,7 @@
|
|||
<view class="modal-table-body">
|
||||
<view class="modal-table-row" v-for="(item, index) in withdrawDetailList" :key="index">
|
||||
<text class="modal-table-cell">{{ item.time }}</text>
|
||||
<text class="modal-table-cell">¥{{ item.amount }}</text>
|
||||
<text class="modal-table-cell">{{ item.currency === 'PHP' ? '₱' : '¥' }}{{ item.amount }}</text>
|
||||
<text class="modal-table-cell">{{ item.status }}</text>
|
||||
</view>
|
||||
<view v-if="withdrawDetailList.length === 0" class="modal-empty-state">
|
||||
|
|
@ -335,7 +339,6 @@
|
|||
cardholderName: '',
|
||||
bankName: '',
|
||||
swiftCode: '',
|
||||
withdrawRecords: [],
|
||||
withdrawDetailList: [],
|
||||
inviteRecords: [],
|
||||
// 佣金统计
|
||||
|
|
@ -483,6 +486,7 @@
|
|||
this.withdrawDetailList = res.data.records.map(item => ({
|
||||
time: this.formatDate(item.createdAt),
|
||||
amount: item.amount,
|
||||
currency: item.currency || 'CNY',
|
||||
status: this.getWithdrawStatusText(item.status)
|
||||
}))
|
||||
}
|
||||
|
|
@ -491,13 +495,10 @@
|
|||
}
|
||||
},
|
||||
|
||||
// 更新提现显示
|
||||
// 更新提现显示 - 不再需要,因为直接使用 commissionStats 中的双币种余额
|
||||
updateWithdrawDisplay() {
|
||||
// 显示待提现、处理中、已完成的金额
|
||||
this.withdrawRecords = [{
|
||||
amount: this.commissionStats.availableBalance || '0',
|
||||
status: this.$t('invite.statusWaiting')
|
||||
}]
|
||||
// 双币种余额已在 commissionStats 中返回
|
||||
// availableBalanceRmb 和 availableBalancePeso
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
|
|
@ -752,8 +753,14 @@
|
|||
return
|
||||
}
|
||||
|
||||
// 最高不超过待提现金额
|
||||
const availableBalance = parseFloat(this.commissionStats.availableBalance || '0')
|
||||
// 根据选择的货币类型检查对应的余额
|
||||
let availableBalance = 0
|
||||
if (this.withdrawCurrency === 'PHP') {
|
||||
availableBalance = parseFloat(this.commissionStats.availableBalancePeso || '0')
|
||||
} else {
|
||||
availableBalance = parseFloat(this.commissionStats.availableBalanceRmb || this.commissionStats.availableBalance || '0')
|
||||
}
|
||||
|
||||
if (amount > availableBalance) {
|
||||
uni.showToast({
|
||||
title: this.$t('invite.amountExceedsBalance') || '超出可提现金额',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user