This commit is contained in:
18631081161 2025-12-24 01:01:17 +08:00
parent e5b0ae1c19
commit bbaf345509
11 changed files with 222 additions and 28 deletions

View 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');
},
};

View File

@ -59,6 +59,12 @@ const Commission = sequelize.define('Commission', {
field: 'commission_amount', field: 'commission_amount',
comment: '佣金金额', comment: '佣金金额',
}, },
currency: {
type: DataTypes.ENUM('RMB', 'PHP'),
defaultValue: 'RMB',
allowNull: false,
comment: '佣金货币类型',
},
status: { status: {
type: DataTypes.ENUM('credited', 'cancelled'), type: DataTypes.ENUM('credited', 'cancelled'),
defaultValue: 'credited', defaultValue: 'credited',

View File

@ -63,6 +63,14 @@ const User = sequelize.define('User', {
type: DataTypes.DECIMAL(10, 2), type: DataTypes.DECIMAL(10, 2),
defaultValue: 0.00, defaultValue: 0.00,
allowNull: false, allowNull: false,
comment: '人民币余额',
},
balancePeso: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0.00,
allowNull: false,
field: 'balance_peso',
comment: '比索余额',
}, },
invitationCode: { invitationCode: {
type: DataTypes.STRING(20), type: DataTypes.STRING(20),

View File

@ -30,6 +30,12 @@ const Withdrawal = sequelize.define('Withdrawal', {
type: DataTypes.DECIMAL(10, 2), type: DataTypes.DECIMAL(10, 2),
allowNull: false, allowNull: false,
}, },
currency: {
type: DataTypes.ENUM('CNY', 'PHP'),
defaultValue: 'CNY',
allowNull: false,
comment: '提现货币类型',
},
paymentMethod: { paymentMethod: {
type: DataTypes.ENUM('wechat', 'alipay', 'bank'), type: DataTypes.ENUM('wechat', 'alipay', 'bank'),
allowNull: false, allowNull: false,

View 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();

View File

@ -37,8 +37,22 @@ const calculateCommission = async (paymentOrderId) => {
// Get current commission rate // Get current commission rate
const commissionRate = await commissionConfigService.getCommissionRate(); const commissionRate = await commissionConfigService.getCommissionRate();
// Calculate commission amount - use amountRmb if available, otherwise use amount // Calculate commission amount - prioritize RMB, then Peso, then legacy amount
const paymentAmount = parseFloat(paymentOrder.amountRmb || paymentOrder.amount || 0); // 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) { if (paymentAmount <= 0) {
return null; // No valid payment amount return null; // No valid payment amount
} }
@ -48,7 +62,7 @@ const calculateCommission = async (paymentOrderId) => {
const transaction = await sequelize.transaction(); const transaction = await sequelize.transaction();
try { try {
// Create commission record // Create commission record with currency
const commission = await Commission.create({ const commission = await Commission.create({
inviterId: inviteeUser.invitedBy, inviterId: inviteeUser.invitedBy,
inviteeId: inviteeUser.id, inviteeId: inviteeUser.id,
@ -56,13 +70,18 @@ const calculateCommission = async (paymentOrderId) => {
paymentAmount: paymentAmount, paymentAmount: paymentAmount,
commissionRate: commissionRate, commissionRate: commissionRate,
commissionAmount: commissionAmount, commissionAmount: commissionAmount,
currency: currency,
status: 'credited', status: 'credited',
}, { transaction }); }, { transaction });
// Update inviter's balance // Update inviter's balance based on currency
const inviter = await User.findByPk(inviteeUser.invitedBy, { transaction }); const inviter = await User.findByPk(inviteeUser.invitedBy, { transaction });
if (inviter) { 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 }); await inviter.save({ transaction });
} }
@ -172,16 +191,24 @@ const getCommissionStats = async (inviterId) => {
where: { invitedBy: inviterId }, where: { invitedBy: inviterId },
}); });
// Get commission statistics // Get commission statistics by currency
const commissions = await Commission.findAll({ const commissions = await Commission.findAll({
where: { inviterId, status: 'credited' }, where: { inviterId, status: 'credited' },
attributes: ['commissionAmount'], attributes: ['commissionAmount', 'currency'],
}); });
const totalCommission = commissions.reduce( // Calculate total commission by currency
(sum, c) => sum + parseFloat(c.commissionAmount), let totalCommissionRmb = 0;
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) // Get paid invites count (invitees who have made payments)
const paidInvitesCount = await Commission.count({ const paidInvitesCount = await Commission.count({
@ -193,9 +220,13 @@ const getCommissionStats = async (inviterId) => {
return { return {
totalInvites, totalInvites,
paidInvites: paidInvitesCount, paidInvites: paidInvitesCount,
totalCommission: totalCommission.toFixed(2), totalCommission: totalCommissionRmb.toFixed(2),
totalCommissionRmb: totalCommissionRmb.toFixed(2),
totalCommissionPeso: totalCommissionPeso.toFixed(2),
commissionCount: commissions.length, commissionCount: commissions.length,
availableBalance: parseFloat(user.balance).toFixed(2), availableBalance: parseFloat(user.balance).toFixed(2),
availableBalanceRmb: parseFloat(user.balance).toFixed(2),
availableBalancePeso: parseFloat(user.balancePeso || 0).toFixed(2),
invitationCode: invitationCode, invitationCode: invitationCode,
}; };
}; };

View File

@ -28,7 +28,7 @@ const generateWithdrawalNo = () => {
* @returns {Object} Created withdrawal * @returns {Object} Created withdrawal
*/ */
const createWithdrawal = async (userId, withdrawalData) => { const createWithdrawal = async (userId, withdrawalData) => {
const { amount, paymentMethod, paymentDetails } = withdrawalData; const { amount, currency = 'CNY', paymentMethod, paymentDetails } = withdrawalData;
// Validate amount // Validate amount
const withdrawalAmount = parseFloat(amount); const withdrawalAmount = parseFloat(amount);
@ -41,6 +41,12 @@ const createWithdrawal = async (userId, withdrawalData) => {
throw new Error(`Minimum withdrawal amount is ${MIN_WITHDRAWAL_AMOUNT}`); 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 // Validate payment method
const validMethods = ['wechat', 'alipay', 'bank']; const validMethods = ['wechat', 'alipay', 'bank'];
if (!validMethods.includes(paymentMethod)) { if (!validMethods.includes(paymentMethod)) {
@ -66,8 +72,14 @@ const createWithdrawal = async (userId, withdrawalData) => {
throw new Error('User not found'); throw new Error('User not found');
} }
// Check if user has sufficient balance // Check if user has sufficient balance based on currency
const userBalance = parseFloat(user.balance); let userBalance;
if (currency === 'PHP') {
userBalance = parseFloat(user.balancePeso || 0);
} else {
userBalance = parseFloat(user.balance);
}
if (userBalance < withdrawalAmount) { if (userBalance < withdrawalAmount) {
throw new Error('Insufficient balance'); throw new Error('Insufficient balance');
} }
@ -90,6 +102,7 @@ const createWithdrawal = async (userId, withdrawalData) => {
userId, userId,
withdrawalNo, withdrawalNo,
amount: withdrawalAmount, amount: withdrawalAmount,
currency,
paymentMethod, paymentMethod,
paymentDetails, paymentDetails,
status: 'waiting', status: 'waiting',
@ -139,6 +152,7 @@ const getWithdrawals = async (userId, options = {}) => {
id: withdrawal.id, id: withdrawal.id,
withdrawalNo: withdrawal.withdrawalNo, withdrawalNo: withdrawal.withdrawalNo,
amount: parseFloat(withdrawal.amount).toFixed(2), amount: parseFloat(withdrawal.amount).toFixed(2),
currency: withdrawal.currency || 'CNY',
paymentMethod: withdrawal.paymentMethod, paymentMethod: withdrawal.paymentMethod,
paymentDetails: withdrawal.paymentDetails, paymentDetails: withdrawal.paymentDetails,
status: withdrawal.status, status: withdrawal.status,
@ -181,6 +195,7 @@ const getWithdrawalById = async (userId, withdrawalId) => {
id: withdrawal.id, id: withdrawal.id,
withdrawalNo: withdrawal.withdrawalNo, withdrawalNo: withdrawal.withdrawalNo,
amount: parseFloat(withdrawal.amount).toFixed(2), amount: parseFloat(withdrawal.amount).toFixed(2),
currency: withdrawal.currency || 'CNY',
paymentMethod: withdrawal.paymentMethod, paymentMethod: withdrawal.paymentMethod,
paymentDetails: withdrawal.paymentDetails, paymentDetails: withdrawal.paymentDetails,
status: withdrawal.status, status: withdrawal.status,

View File

@ -239,6 +239,8 @@ If you have privacy questions, please contact us through the application.`
paidYes: '¥ 12', paidYes: '¥ 12',
paidNo: '¥ 4', paidNo: '¥ 4',
statusWaiting: 'Waiting', statusWaiting: 'Waiting',
statusWaitingRmb: 'CNY Pending',
statusWaitingPeso: 'PHP Pending',
statusProcessing: 'Processing', statusProcessing: 'Processing',
statusCompleted: 'Completed', statusCompleted: 'Completed',
ruleTitle: 'Rules', ruleTitle: 'Rules',

View File

@ -239,6 +239,8 @@ Si tiene preguntas sobre privacidad, contáctenos a través de la aplicación.`
paidYes: '¥ 12', paidYes: '¥ 12',
paidNo: '¥ 4', paidNo: '¥ 4',
statusWaiting: 'Esperando', statusWaiting: 'Esperando',
statusWaitingRmb: 'Yuan Pendiente',
statusWaitingPeso: 'Peso Pendiente',
statusProcessing: 'Procesando', statusProcessing: 'Procesando',
statusCompleted: 'Completado', statusCompleted: 'Completado',
ruleTitle: 'Reglas', ruleTitle: 'Reglas',

View File

@ -255,6 +255,8 @@ export default {
paidYes: '¥ 12', paidYes: '¥ 12',
paidNo: '¥ 4', paidNo: '¥ 4',
statusWaiting: '待提现', statusWaiting: '待提现',
statusWaitingRmb: '人民币待提现',
statusWaitingPeso: '比索待提现',
statusProcessing: '提现中', statusProcessing: '提现中',
statusCompleted: '已提现', statusCompleted: '已提现',
ruleTitle: '活动规则', ruleTitle: '活动规则',

View File

@ -86,9 +86,13 @@
<image src="/static/new_bg2.png" class="record-bg-image" mode="scaleToFill"></image> <image src="/static/new_bg2.png" class="record-bg-image" mode="scaleToFill"></image>
<view class="reward-cards"> <view class="reward-cards">
<view class="reward-card" v-for="(item, index) in withdrawRecords" :key="index"> <view class="reward-card">
<text class="reward-amount">{{ item.amount }}{{ $t('common.currency') }}</text> <text class="reward-amount">¥{{ commissionStats.availableBalanceRmb || '0.00' }}</text>
<text class="reward-status">{{ item.status }}</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>
</view> </view>
@ -150,7 +154,7 @@
<view class="modal-table-body"> <view class="modal-table-body">
<view class="modal-table-row" v-for="(item, index) in withdrawDetailList" :key="index"> <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.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> <text class="modal-table-cell">{{ item.status }}</text>
</view> </view>
<view v-if="withdrawDetailList.length === 0" class="modal-empty-state"> <view v-if="withdrawDetailList.length === 0" class="modal-empty-state">
@ -335,7 +339,6 @@
cardholderName: '', cardholderName: '',
bankName: '', bankName: '',
swiftCode: '', swiftCode: '',
withdrawRecords: [],
withdrawDetailList: [], withdrawDetailList: [],
inviteRecords: [], inviteRecords: [],
// //
@ -483,6 +486,7 @@
this.withdrawDetailList = res.data.records.map(item => ({ this.withdrawDetailList = res.data.records.map(item => ({
time: this.formatDate(item.createdAt), time: this.formatDate(item.createdAt),
amount: item.amount, amount: item.amount,
currency: item.currency || 'CNY',
status: this.getWithdrawStatusText(item.status) status: this.getWithdrawStatusText(item.status)
})) }))
} }
@ -491,13 +495,10 @@
} }
}, },
// // - 使 commissionStats
updateWithdrawDisplay() { updateWithdrawDisplay() {
// // commissionStats
this.withdrawRecords = [{ // availableBalanceRmb availableBalancePeso
amount: this.commissionStats.availableBalance || '0',
status: this.$t('invite.statusWaiting')
}]
}, },
// //
@ -752,8 +753,14 @@
return 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) { if (amount > availableBalance) {
uni.showToast({ uni.showToast({
title: this.$t('invite.amountExceedsBalance') || '超出可提现金额', title: this.$t('invite.amountExceedsBalance') || '超出可提现金额',