feat: 支持分享邀请好友功能

This commit is contained in:
richarjiang
2026-04-19 14:12:25 +08:00
parent b02f38dcc7
commit 9575210b06
34 changed files with 1101 additions and 8 deletions

View File

@@ -5,6 +5,7 @@ import { MembershipStatus, OrderStatus } from '@mp-pilates/shared'
import { PaymentService } from '../payment.service'
import { WechatPayService } from '../wechat-pay.service'
import { PrismaService } from '../../prisma/prisma.service'
import { InviteService } from '../../invite/invite.service'
// ─── Fixtures ─────────────────────────────────────────────────────────────────
@@ -35,6 +36,11 @@ const mockUser = {
updatedAt: new Date(),
}
const mockInviteService = {
validateInviterForTrialOrder: jest.fn(),
recordTrialOrderPaid: jest.fn(),
}
const buildMockOrder = (overrides: Partial<Record<string, unknown>> = {}) => ({
id: 'order-uuid-1',
userId: mockUser.id,
@@ -105,6 +111,7 @@ describe('PaymentService', () => {
PaymentService,
{ provide: PrismaService, useValue: prisma },
{ provide: WechatPayService, useValue: wechat },
{ provide: InviteService, useValue: mockInviteService },
],
}).compile()
@@ -161,6 +168,16 @@ describe('PaymentService', () => {
)
})
it('validates inviter relationship for trial card orders', async () => {
prisma.cardType.findUnique.mockResolvedValue({ ...mockCardType, type: 'TRIAL' })
prisma.user.findUnique.mockResolvedValue(mockUser)
prisma.order.create.mockResolvedValue(buildMockOrder())
await service.createOrder(mockUser.id, mockCardType.id, 'inviter-001')
expect(mockInviteService.validateInviterForTrialOrder).toHaveBeenCalledWith(mockUser.id, 'inviter-001')
})
it('throws NotFoundException when cardType does not exist', async () => {
prisma.cardType.findUnique.mockResolvedValue(null)
@@ -232,6 +249,7 @@ describe('PaymentService', () => {
// membership.create was called
expect(prisma.membership.create).toHaveBeenCalledTimes(1)
expect(mockInviteService.recordTrialOrderPaid).toHaveBeenCalledWith(pendingOrder.id)
expect(result).toContain('SUCCESS')
})

View File

@@ -1,6 +1,10 @@
import { IsUUID } from 'class-validator'
import { IsOptional, IsUUID } from 'class-validator'
export class CreateOrderDto {
@IsUUID()
cardTypeId!: string
@IsUUID()
@IsOptional()
inviterId?: string
}

View File

@@ -33,7 +33,7 @@ export class PaymentController {
@CurrentUser('sub') userId: string,
@Body(new ValidationPipe({ whitelist: true })) dto: CreateOrderDto,
) {
return this.paymentService.createOrder(userId, dto.cardTypeId)
return this.paymentService.createOrder(userId, dto.cardTypeId, dto.inviterId)
}
/**

View File

@@ -3,9 +3,10 @@ import { PrismaModule } from '../prisma/prisma.module'
import { PaymentService } from './payment.service'
import { PaymentController } from './payment.controller'
import { WechatPayService } from './wechat-pay.service'
import { InviteModule } from '../invite/invite.module'
@Module({
imports: [PrismaModule],
imports: [PrismaModule, InviteModule],
controllers: [PaymentController],
providers: [PaymentService, WechatPayService],
exports: [PaymentService, WechatPayService],

View File

@@ -8,6 +8,7 @@ import { CardType, Order } from '@prisma/client'
import { MembershipStatus, OrderStatus, FlashSaleOrderStatus } from '@mp-pilates/shared'
import { PrismaService } from '../prisma/prisma.service'
import { WechatPayService, WxPaymentParams } from './wechat-pay.service'
import { InviteService } from '../invite/invite.service'
export interface CreateOrderResult {
order: Order
@@ -28,11 +29,12 @@ export class PaymentService {
constructor(
private readonly prisma: PrismaService,
private readonly wechatPayService: WechatPayService,
private readonly inviteService: InviteService,
) {}
// ─── User: create order ────────────────────────────────────────────────────
async createOrder(userId: string, cardTypeId: string): Promise<CreateOrderResult> {
async createOrder(userId: string, cardTypeId: string, inviterId?: string): Promise<CreateOrderResult> {
const cardType = await this.prisma.cardType.findUnique({ where: { id: cardTypeId } })
if (!cardType) {
@@ -47,6 +49,10 @@ export class PaymentService {
throw new NotFoundException(`User ${userId} not found`)
}
if (cardType.type === 'TRIAL') {
await this.inviteService.validateInviterForTrialOrder(userId, inviterId)
}
const orderNo = `${Date.now()}${Math.random().toString(36).substring(2, 8)}`
const order = await this.prisma.order.create({
@@ -135,6 +141,8 @@ export class PaymentService {
}),
])
await this.inviteService.recordTrialOrderPaid(existingOrder.id)
this.logger.log(`Order PAID and Membership created: orderNo=${notification.orderNo}`)
// ── Flash sale order: mark as PAID ──