|
|
@@ -13,6 +13,8 @@ import {
|
|
|
SingleListResponse
|
|
|
} from '../dto/payment.dto'
|
|
|
import axios from 'axios'
|
|
|
+import { TeamDomainService } from './team-domain.service'
|
|
|
+import { TeamMembersService } from './team-members.service'
|
|
|
import crypto from 'crypto'
|
|
|
import { randomInt } from 'crypto'
|
|
|
import Decimal from 'decimal.js'
|
|
|
@@ -35,6 +37,8 @@ export class PaymentService {
|
|
|
private userService: UserService
|
|
|
private teamService: TeamService
|
|
|
private sysConfigService: SysConfigService
|
|
|
+ private teamDomainService: TeamDomainService
|
|
|
+ private teamMembersService: TeamMembersService
|
|
|
|
|
|
private static readonly ORDER_TYPE_TO_VIP_LEVEL_MAP: Record<OrderType, VipLevel> = {
|
|
|
[OrderType.SINGLE_TIP]: VipLevel.FREE,
|
|
|
@@ -68,6 +72,8 @@ export class PaymentService {
|
|
|
this.userService = new UserService(app)
|
|
|
this.teamService = new TeamService(app)
|
|
|
this.sysConfigService = new SysConfigService(app)
|
|
|
+ this.teamDomainService = new TeamDomainService(app)
|
|
|
+ this.teamMembersService = new TeamMembersService(app)
|
|
|
}
|
|
|
|
|
|
private generateSign(params: Record<string, any>, key: string): string {
|
|
|
@@ -82,6 +88,189 @@ export class PaymentService {
|
|
|
return crypto.createHash('md5').update(strToSign, 'utf8').digest('hex')
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据member的domainId查找对应的域名绑定,确定分成对象和比例
|
|
|
+ * 支持团队和个人同时分润
|
|
|
+ */
|
|
|
+ private async getCommissionInfo(member: any): Promise<{
|
|
|
+ teamAgentId: number
|
|
|
+ teamCommissionRate: number
|
|
|
+ personalAgentId: number
|
|
|
+ personalCommissionRate: number
|
|
|
+ isPersonalCommission: boolean
|
|
|
+ }> {
|
|
|
+ try {
|
|
|
+ // 如果用户绑定teamId为0,无团队,直接返回无分成
|
|
|
+ if (!member.teamId || member.teamId === 0) {
|
|
|
+ return {
|
|
|
+ teamAgentId: 0,
|
|
|
+ teamCommissionRate: 0,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果domainId为0,说明是默认入口,直接走默认分成逻辑
|
|
|
+ if (!member.domainId || member.domainId === 0) {
|
|
|
+ return await this.getDefaultCommission(member)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据domainId查找域名绑定信息
|
|
|
+ const teamDomain = await this.teamDomainService.findById(member.domainId)
|
|
|
+ if (!teamDomain) {
|
|
|
+ // 域名绑定不存在,回退到默认分成
|
|
|
+ this.app.log.warn(`Domain binding not found for domainId: ${member.domainId}, using default commission`)
|
|
|
+ return await this.getDefaultCommission(member)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取团队分成信息
|
|
|
+ const teamCommission = await this.getTeamCommissionFromDomain(teamDomain, member)
|
|
|
+
|
|
|
+ // 如果teamDomain绑定teamMemberId为null,无绑定队员,只给团队分成
|
|
|
+ if (!teamDomain.teamMemberId) {
|
|
|
+ return {
|
|
|
+ teamAgentId: teamCommission.agentId,
|
|
|
+ teamCommissionRate: teamCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果域名绑定到个人,检查个人分成
|
|
|
+ try {
|
|
|
+ const teamMember = await this.teamMembersService.findById(teamDomain.teamMemberId)
|
|
|
+ if (teamMember?.commissionRate > 0) {
|
|
|
+ // 团队和个人同时分润
|
|
|
+ // 团队分成保持原比例,个人分成从团队分成中扣除
|
|
|
+ return {
|
|
|
+ teamAgentId: teamCommission.agentId,
|
|
|
+ teamCommissionRate: teamCommission.commissionRate, // 团队总分成比例
|
|
|
+ personalAgentId: teamMember.userId,
|
|
|
+ personalCommissionRate: teamMember.commissionRate, // 个人分成比例
|
|
|
+ isPersonalCommission: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (memberError) {
|
|
|
+ this.app.log.warn(`Failed to find team member ${teamDomain.teamMemberId}:`, memberError)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 个人分成配置失败,只给团队分成
|
|
|
+ return {
|
|
|
+ teamAgentId: teamCommission.agentId,
|
|
|
+ teamCommissionRate: teamCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.warn('Failed to get commission info for member:', member.id, error)
|
|
|
+ // 最终回退到默认分成逻辑
|
|
|
+ return await this.getDefaultCommission(member)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取默认分成信息(domainId为0时的逻辑)
|
|
|
+ */
|
|
|
+ private async getDefaultCommission(member: any): Promise<{
|
|
|
+ teamAgentId: number
|
|
|
+ teamCommissionRate: number
|
|
|
+ personalAgentId: number
|
|
|
+ personalCommissionRate: number
|
|
|
+ isPersonalCommission: boolean
|
|
|
+ }> {
|
|
|
+ const teamCommission = await this.getTeamCommission(member)
|
|
|
+ return {
|
|
|
+ teamAgentId: teamCommission.agentId,
|
|
|
+ teamCommissionRate: teamCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取团队分成信息(原有逻辑)
|
|
|
+ */
|
|
|
+ private async getTeamCommission(member: any): Promise<{
|
|
|
+ agentId: number
|
|
|
+ commissionRate: number
|
|
|
+ isPersonalCommission: boolean
|
|
|
+ }> {
|
|
|
+ try {
|
|
|
+ if (member.teamId && member.teamId > 0) {
|
|
|
+ const team = await this.teamService.findById(member.teamId)
|
|
|
+ if (team && team.commissionRate && team.commissionRate > 0) {
|
|
|
+ return {
|
|
|
+ agentId: team.userId,
|
|
|
+ commissionRate: team.commissionRate,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return { agentId: 0, commissionRate: 0, isPersonalCommission: false }
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.warn('Failed to get team commission:', error)
|
|
|
+ return { agentId: 0, commissionRate: 0, isPersonalCommission: false }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从域名绑定的团队获取分成信息
|
|
|
+ */
|
|
|
+ private async getTeamCommissionFromDomain(teamDomain: any, member: any): Promise<{
|
|
|
+ agentId: number
|
|
|
+ commissionRate: number
|
|
|
+ isPersonalCommission: boolean
|
|
|
+ }> {
|
|
|
+ try {
|
|
|
+ if (teamDomain.teamId) {
|
|
|
+ const team = await this.teamService.findById(teamDomain.teamId)
|
|
|
+ if (team && team.commissionRate && team.commissionRate > 0) {
|
|
|
+ return {
|
|
|
+ agentId: team.userId,
|
|
|
+ commissionRate: team.commissionRate,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 如果域名绑定的团队也没有分成比例,回退到member的团队
|
|
|
+ return await this.getTeamCommission(member)
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.warn('Failed to get team commission from domain:', error)
|
|
|
+ return await this.getTeamCommission(member)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 回退到原有的团队分成逻辑(基于user.parentId)
|
|
|
+ */
|
|
|
+ private async getFallbackCommission(user: any): Promise<{
|
|
|
+ agentId: number
|
|
|
+ commissionRate: number
|
|
|
+ isPersonalCommission: boolean
|
|
|
+ }> {
|
|
|
+ try {
|
|
|
+ if (user.parentId && user.parentId > 0) {
|
|
|
+ const team = await this.teamService.findByUserId(user.parentId)
|
|
|
+ if (team && team.commissionRate && team.commissionRate > 0) {
|
|
|
+ return {
|
|
|
+ agentId: user.parentId,
|
|
|
+ commissionRate: team.commissionRate,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return { agentId: 0, commissionRate: 0, isPersonalCommission: false }
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.warn('Failed to get fallback commission:', error)
|
|
|
+ return { agentId: 0, commissionRate: 0, isPersonalCommission: false }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private verifyNotifySign(params: PaymentNotifyParams): boolean {
|
|
|
const { sign: receivedSign, ...rest } = params
|
|
|
const calculatedSign = this.generateSign(rest, this.key)
|
|
|
@@ -185,30 +374,75 @@ export class PaymentService {
|
|
|
|
|
|
// 创建收入记录
|
|
|
try {
|
|
|
- // 计算佣金
|
|
|
- let agentId = user.parentId
|
|
|
- let incomeAmount = new Decimal(price)
|
|
|
-
|
|
|
- if (user.parentId && user.parentId > 0) {
|
|
|
- try {
|
|
|
- const team = await this.teamService.findByUserId(user.parentId)
|
|
|
- if (team && team.commissionRate && team.commissionRate > 0) {
|
|
|
- const commissionRate = new Decimal(team.commissionRate)
|
|
|
- const hundred = new Decimal(100)
|
|
|
- incomeAmount = new Decimal(price).mul(commissionRate).div(hundred)
|
|
|
+ // 获取member信息,如果失败则使用原有逻辑
|
|
|
+ let commissionInfo = {
|
|
|
+ teamAgentId: 0,
|
|
|
+ teamCommissionRate: 0,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const member = await this.memberService.findByUserId(user.id)
|
|
|
+ if (member) {
|
|
|
+ // 使用新的分成逻辑
|
|
|
+ commissionInfo = await this.getCommissionInfo(member)
|
|
|
+ } else {
|
|
|
+ // 如果没有member信息,回退到原有的团队分成逻辑
|
|
|
+ this.app.log.warn(`Member not found for user ${user.id}, using fallback commission logic`)
|
|
|
+ const fallbackCommission = await this.getFallbackCommission(user)
|
|
|
+ commissionInfo = {
|
|
|
+ teamAgentId: fallbackCommission.agentId,
|
|
|
+ teamCommissionRate: fallbackCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
}
|
|
|
- } catch (teamError) {
|
|
|
- this.app.log.warn('Failed to find team for user:', user.parentId, teamError)
|
|
|
- agentId = 0
|
|
|
}
|
|
|
- } else {
|
|
|
- agentId = 0
|
|
|
+ } catch (memberError) {
|
|
|
+ // 获取member信息失败,使用原有逻辑
|
|
|
+ this.app.log.warn(`Failed to get member info for user ${user.id}, using fallback commission logic:`, memberError)
|
|
|
+ const fallbackCommission = await this.getFallbackCommission(user)
|
|
|
+ commissionInfo = {
|
|
|
+ teamAgentId: fallbackCommission.agentId,
|
|
|
+ teamCommissionRate: fallbackCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算个人分成金额
|
|
|
+ let personalIncomeAmount = new Decimal(0)
|
|
|
+ if (commissionInfo.personalAgentId > 0 && commissionInfo.personalCommissionRate > 0) {
|
|
|
+ const commissionRate = new Decimal(commissionInfo.personalCommissionRate)
|
|
|
+ const hundred = new Decimal(100)
|
|
|
+ personalIncomeAmount = new Decimal(price).mul(commissionRate).div(hundred)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算团队分成金额
|
|
|
+ let teamIncomeAmount = new Decimal(0)
|
|
|
+ if (commissionInfo.teamAgentId > 0 && commissionInfo.teamCommissionRate > 0) {
|
|
|
+ const commissionRate = new Decimal(commissionInfo.teamCommissionRate)
|
|
|
+ const hundred = new Decimal(100)
|
|
|
+ const totalTeamIncome = new Decimal(price).mul(commissionRate).div(hundred)
|
|
|
+
|
|
|
+ // 如果有个人分成,团队实际分到的金额 = 团队总分成 - 个人分成
|
|
|
+ if (commissionInfo.isPersonalCommission && personalIncomeAmount.greaterThan(0)) {
|
|
|
+ teamIncomeAmount = totalTeamIncome.sub(personalIncomeAmount)
|
|
|
+ } else {
|
|
|
+ teamIncomeAmount = totalTeamIncome
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // 创建收入记录,包含团队和个人分成信息
|
|
|
await this.incomeRecordsService.create({
|
|
|
- agentId,
|
|
|
+ agentId: commissionInfo.teamAgentId,
|
|
|
userId: user.id,
|
|
|
- incomeAmount: incomeAmount.toNumber(),
|
|
|
+ personalAgentId: commissionInfo.personalAgentId,
|
|
|
+ personalIncomeAmount: personalIncomeAmount.toNumber(),
|
|
|
+ incomeAmount: teamIncomeAmount.toNumber(),
|
|
|
incomeType: IncomeType.COMMISSION,
|
|
|
orderType: this.getOrderTypeByType(params.type),
|
|
|
orderPrice: new Decimal(price).toNumber(),
|
|
|
@@ -268,29 +502,75 @@ export class PaymentService {
|
|
|
|
|
|
// 创建收入记录
|
|
|
try {
|
|
|
- // 计算佣金
|
|
|
- let agentId = user.parentId
|
|
|
- let incomeAmount = new Decimal(price)
|
|
|
- if (user.parentId && user.parentId > 0) {
|
|
|
- try {
|
|
|
- const team = await this.teamService.findByUserId(user.parentId)
|
|
|
- if (team && team.commissionRate && team.commissionRate > 0) {
|
|
|
- const commissionRate = new Decimal(team.commissionRate)
|
|
|
- const hundred = new Decimal(100)
|
|
|
- incomeAmount = new Decimal(price).mul(commissionRate).div(hundred)
|
|
|
+ // 获取member信息,如果失败则使用原有逻辑
|
|
|
+ let commissionInfo = {
|
|
|
+ teamAgentId: 0,
|
|
|
+ teamCommissionRate: 0,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const member = await this.memberService.findByUserId(user.id)
|
|
|
+ if (member) {
|
|
|
+ // 使用新的分成逻辑
|
|
|
+ commissionInfo = await this.getCommissionInfo(member)
|
|
|
+ } else {
|
|
|
+ // 如果没有member信息,回退到原有的团队分成逻辑
|
|
|
+ this.app.log.warn(`Member not found for user ${user.id}, using fallback commission logic`)
|
|
|
+ const fallbackCommission = await this.getFallbackCommission(user)
|
|
|
+ commissionInfo = {
|
|
|
+ teamAgentId: fallbackCommission.agentId,
|
|
|
+ teamCommissionRate: fallbackCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
}
|
|
|
- } catch (teamError) {
|
|
|
- this.app.log.warn('Failed to find team for user:', user.parentId, teamError)
|
|
|
- agentId = 0
|
|
|
}
|
|
|
- } else {
|
|
|
- agentId = 0
|
|
|
+ } catch (memberError) {
|
|
|
+ // 获取member信息失败,使用原有逻辑
|
|
|
+ this.app.log.warn(`Failed to get member info for user ${user.id}, using fallback commission logic:`, memberError)
|
|
|
+ const fallbackCommission = await this.getFallbackCommission(user)
|
|
|
+ commissionInfo = {
|
|
|
+ teamAgentId: fallbackCommission.agentId,
|
|
|
+ teamCommissionRate: fallbackCommission.commissionRate,
|
|
|
+ personalAgentId: 0,
|
|
|
+ personalCommissionRate: 0,
|
|
|
+ isPersonalCommission: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算个人分成金额
|
|
|
+ let personalIncomeAmount = new Decimal(0)
|
|
|
+ if (commissionInfo.personalAgentId > 0 && commissionInfo.personalCommissionRate > 0) {
|
|
|
+ const commissionRate = new Decimal(commissionInfo.personalCommissionRate)
|
|
|
+ const hundred = new Decimal(100)
|
|
|
+ personalIncomeAmount = new Decimal(price).mul(commissionRate).div(hundred)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算团队分成金额
|
|
|
+ let teamIncomeAmount = new Decimal(0)
|
|
|
+ if (commissionInfo.teamAgentId > 0 && commissionInfo.teamCommissionRate > 0) {
|
|
|
+ const commissionRate = new Decimal(commissionInfo.teamCommissionRate)
|
|
|
+ const hundred = new Decimal(100)
|
|
|
+ const totalTeamIncome = new Decimal(price).mul(commissionRate).div(hundred)
|
|
|
+
|
|
|
+ // 如果有个人分成,团队实际分到的金额 = 团队总分成 - 个人分成
|
|
|
+ if (commissionInfo.isPersonalCommission && personalIncomeAmount.greaterThan(0)) {
|
|
|
+ teamIncomeAmount = totalTeamIncome.sub(personalIncomeAmount)
|
|
|
+ } else {
|
|
|
+ teamIncomeAmount = totalTeamIncome
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // 创建收入记录,包含团队和个人分成信息
|
|
|
await this.incomeRecordsService.create({
|
|
|
- agentId,
|
|
|
+ agentId: commissionInfo.teamAgentId,
|
|
|
userId: user.id,
|
|
|
- incomeAmount: incomeAmount.toNumber(),
|
|
|
+ personalAgentId: commissionInfo.personalAgentId,
|
|
|
+ personalIncomeAmount: personalIncomeAmount.toNumber(),
|
|
|
+ incomeAmount: teamIncomeAmount.toNumber(),
|
|
|
incomeType: IncomeType.TIP,
|
|
|
orderType: OrderType.SINGLE_TIP,
|
|
|
orderPrice: new Decimal(price).toNumber(),
|