From bbaf34550992df807e47ccd47f511819fe31f9bc Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Wed, 24 Dec 2025 01:01:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=AF=94=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../013-add-dual-currency-balance.js | 42 +++++++++++ backend/src/models/Commission.js | 6 ++ backend/src/models/User.js | 8 ++ backend/src/models/Withdrawal.js | 6 ++ backend/src/scripts/addDualCurrencyFields.js | 73 +++++++++++++++++++ backend/src/services/commissionService.js | 55 +++++++++++--- backend/src/services/withdrawalService.js | 21 +++++- miniprogram/src/locale/en.js | 2 + miniprogram/src/locale/es.js | 2 + miniprogram/src/locale/zh.js | 2 + .../src/pages/me/invite-reward-page.vue | 33 +++++---- 11 files changed, 222 insertions(+), 28 deletions(-) create mode 100644 backend/src/migrations/013-add-dual-currency-balance.js create mode 100644 backend/src/scripts/addDualCurrencyFields.js diff --git a/backend/src/migrations/013-add-dual-currency-balance.js b/backend/src/migrations/013-add-dual-currency-balance.js new file mode 100644 index 0000000..10c8eb8 --- /dev/null +++ b/backend/src/migrations/013-add-dual-currency-balance.js @@ -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'); + }, +}; diff --git a/backend/src/models/Commission.js b/backend/src/models/Commission.js index 3297be2..8badde9 100644 --- a/backend/src/models/Commission.js +++ b/backend/src/models/Commission.js @@ -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', diff --git a/backend/src/models/User.js b/backend/src/models/User.js index b1ef4ed..b37ca0f 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -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), diff --git a/backend/src/models/Withdrawal.js b/backend/src/models/Withdrawal.js index 12490ac..01de3df 100644 --- a/backend/src/models/Withdrawal.js +++ b/backend/src/models/Withdrawal.js @@ -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, diff --git a/backend/src/scripts/addDualCurrencyFields.js b/backend/src/scripts/addDualCurrencyFields.js new file mode 100644 index 0000000..6aa4295 --- /dev/null +++ b/backend/src/scripts/addDualCurrencyFields.js @@ -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(); diff --git a/backend/src/services/commissionService.js b/backend/src/services/commissionService.js index e8e5442..423e273 100644 --- a/backend/src/services/commissionService.js +++ b/backend/src/services/commissionService.js @@ -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, }; }; diff --git a/backend/src/services/withdrawalService.js b/backend/src/services/withdrawalService.js index b8fa06c..478a978 100644 --- a/backend/src/services/withdrawalService.js +++ b/backend/src/services/withdrawalService.js @@ -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, diff --git a/miniprogram/src/locale/en.js b/miniprogram/src/locale/en.js index 61db884..35be164 100644 --- a/miniprogram/src/locale/en.js +++ b/miniprogram/src/locale/en.js @@ -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', diff --git a/miniprogram/src/locale/es.js b/miniprogram/src/locale/es.js index 4bf8dcd..af57ad7 100644 --- a/miniprogram/src/locale/es.js +++ b/miniprogram/src/locale/es.js @@ -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', diff --git a/miniprogram/src/locale/zh.js b/miniprogram/src/locale/zh.js index 7970049..50e7df7 100644 --- a/miniprogram/src/locale/zh.js +++ b/miniprogram/src/locale/zh.js @@ -255,6 +255,8 @@ export default { paidYes: '¥ 12', paidNo: '¥ 4', statusWaiting: '待提现', + statusWaitingRmb: '人民币待提现', + statusWaitingPeso: '比索待提现', statusProcessing: '提现中', statusCompleted: '已提现', ruleTitle: '活动规则', diff --git a/miniprogram/src/pages/me/invite-reward-page.vue b/miniprogram/src/pages/me/invite-reward-page.vue index 1f3b4d1..3f638a2 100644 --- a/miniprogram/src/pages/me/invite-reward-page.vue +++ b/miniprogram/src/pages/me/invite-reward-page.vue @@ -86,9 +86,13 @@ - - {{ item.amount }}{{ $t('common.currency') }} - {{ item.status }} + + ¥{{ commissionStats.availableBalanceRmb || '0.00' }} + {{ $t('invite.statusWaitingRmb') || '人民币待提现' }} + + + ₱{{ commissionStats.availableBalancePeso || '0.00' }} + {{ $t('invite.statusWaitingPeso') || '比索待提现' }} @@ -150,7 +154,7 @@ {{ item.time }} - ¥{{ item.amount }} + {{ item.currency === 'PHP' ? '₱' : '¥' }}{{ item.amount }} {{ item.status }} @@ -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') || '超出可提现金额',