From bd4d9dbf0db17f6d6284f0b23845cc5062b77930 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Tue, 15 Apr 2025 14:54:33 +0330 Subject: [PATCH] chore: add referral logic to first payment --- src/modules/auth/DTO/complete-register.dto.ts | 11 ++- src/modules/auth/auth.module.ts | 3 +- src/modules/auth/providers/auth.service.ts | 11 ++- .../blogs/repositories/blogs.repository.ts | 2 + src/modules/payments/payments.module.ts | 2 + .../payments/providers/payments.service.ts | 68 ++++++++++++++++++- src/modules/users/providers/users.service.ts | 3 +- 7 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/modules/auth/DTO/complete-register.dto.ts b/src/modules/auth/DTO/complete-register.dto.ts index 8394f45..8a0db56 100755 --- a/src/modules/auth/DTO/complete-register.dto.ts +++ b/src/modules/auth/DTO/complete-register.dto.ts @@ -1,8 +1,8 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsMobilePhone, IsNotEmpty, IsNumberString, IsString, Length, MinLength } from "class-validator"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsMobilePhone, IsNotEmpty, IsNumberString, IsOptional, IsString, Length, MinLength } from "class-validator"; import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator"; -import { AuthMessage } from "../../../common/enums/message.enum"; +import { AuthMessage, ReferralMessage } from "../../../common/enums/message.enum"; export class CompleteRegistrationDto { @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) @@ -46,4 +46,9 @@ export class CompleteRegistrationDto { @ApiProperty({ description: "password", example: "12S345SS678" }) @MinLength(8, { message: AuthMessage.PASSWORD_LENGTH }) password: string; + + @IsOptional() + @IsString({ message: ReferralMessage.INVALID_REFERRAL_CODE }) + @ApiPropertyOptional({ description: "Referral code", example: "abc123" }) + referralCode?: string; } diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 4621873..2e0d8eb 100755 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -10,9 +10,10 @@ import { AuthService } from "./providers/auth.service"; import { TokensService } from "./providers/tokens.service"; import { JwtStrategy } from "./strategies/jwt.strategy"; import { NotificationModule } from "../notifications/notifications.module"; +import { ReferralsModule } from "../referrals/referrals.module"; @Module({ - imports: [UtilsModule, UsersModule, PassportModule, JwtModule.registerAsync(jwtConfig()), NotificationModule], + imports: [UtilsModule, UsersModule, PassportModule, JwtModule.registerAsync(jwtConfig()), NotificationModule, ReferralsModule], controllers: [AuthController], providers: [AuthService, TokensService, JwtStrategy], exports: [AuthService], diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 43332ae..269f510 100755 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -4,6 +4,7 @@ import { DataSource } from "typeorm"; import { TokensService } from "./tokens.service"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; import { NotificationsService } from "../../notifications/providers/notifications.service"; +import { ReferralsService } from "../../referrals/providers/referrals.service"; import { Role } from "../../users/entities/role.entity"; import { UsersService } from "../../users/providers/users.service"; import { OTPService } from "../../utils/providers/otp.service"; @@ -25,6 +26,7 @@ export class AuthService { private readonly dataSource: DataSource, private readonly notificationService: NotificationsService, private readonly smsService: SmsService, + private readonly referralsService: ReferralsService, ) {} //****************** */ //****************** */ @@ -51,7 +53,7 @@ export class AuthService { //****************** */ //****************** */ async completeRegistration(completeRegistrationDto: CompleteRegistrationDto) { - const { phone, code } = completeRegistrationDto; + const { phone, code, referralCode } = completeRegistrationDto; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); @@ -65,6 +67,13 @@ export class AuthService { const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, queryRunner); + if (referralCode) { + await this.referralsService.useReferralCode({ + referralCode, + userId: user.id, + }); + } + const tokens = await this.tokensService.generateTokens(user, queryRunner); await queryRunner.commitTransaction(); diff --git a/src/modules/blogs/repositories/blogs.repository.ts b/src/modules/blogs/repositories/blogs.repository.ts index 6ac6771..1d0d4eb 100644 --- a/src/modules/blogs/repositories/blogs.repository.ts +++ b/src/modules/blogs/repositories/blogs.repository.ts @@ -31,6 +31,8 @@ export class BlogsRepository extends Repository { content: true, tags: true, viewCount: true, + isActive: true, + isPinned: true, category: { id: true, title: true, diff --git a/src/modules/payments/payments.module.ts b/src/modules/payments/payments.module.ts index fae25c2..cf6cb3c 100755 --- a/src/modules/payments/payments.module.ts +++ b/src/modules/payments/payments.module.ts @@ -19,6 +19,7 @@ import { PaymentGateway } from "./entities/payment-gateway.entity"; import { DepositRequestsRepository } from "./repositories/deposit-requests.repository"; import { PaymentGatewaysRepository } from "./repositories/payment-gateway.repository"; import { NotificationModule } from "../notifications/notifications.module"; +import { ReferralsModule } from "../referrals/referrals.module"; @Module({ imports: [ @@ -26,6 +27,7 @@ import { NotificationModule } from "../notifications/notifications.module"; BullModule.registerQueue({ name: PAYMENT.PAYMENT_QUEUE_NAME }), WalletsModule, NotificationModule, + ReferralsModule, ], controllers: [PaymentsController], providers: [ diff --git a/src/modules/payments/providers/payments.service.ts b/src/modules/payments/providers/payments.service.ts index e8b3d66..3562979 100755 --- a/src/modules/payments/providers/payments.service.ts +++ b/src/modules/payments/providers/payments.service.ts @@ -10,6 +10,11 @@ import { DataSource, Not, QueryRunner } from "typeorm"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { CommonMessage, PaymentMessage, UserMessage, WalletMessage } from "../../../common/enums/message.enum"; import { NotificationsService } from "../../notifications/providers/notifications.service"; +import { ReferralReward } from "../../referrals/entities/referral-reward.entity"; +import { Referral } from "../../referrals/entities/referral.entity"; +import { ReferralRewardStatus } from "../../referrals/enums/referral-reward-status.enum"; +import { ReferralStatus } from "../../referrals/enums/referral-status.enum"; +// import { ReferralsService } from "../../referrals/providers/referrals.service"; import { User } from "../../users/entities/user.entity"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { WalletsService } from "../../wallets/providers/wallets.service"; @@ -47,6 +52,7 @@ export class PaymentsService { private readonly walletsService: WalletsService, private readonly depositRequestsRepository: DepositRequestsRepository, private dataSource: DataSource, + // private readonly referralsService: ReferralsService, ) {} //*********************************** */ @@ -253,11 +259,69 @@ export class PaymentsService { async handleFailedPayment(paymentId: string, queryRunner: QueryRunner) { await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.CANCELLED }); } - //*********************************** */ + + private async isFirstPurchase(userId: string, queryRunner: QueryRunner): Promise { + const paymentCount = await queryRunner.manager.count(Payment, { + where: { user: { id: userId }, status: PaymentStatus.COMPLETED }, + }); + return paymentCount === 0; + } + //*********************************** */ + + private async getPendingReferral(userId: string, queryRunner: QueryRunner): Promise { + return queryRunner.manager.findOne(Referral, { + where: { + referredUser: { id: userId }, + status: ReferralStatus.PENDING, + }, + relations: { referrer: true }, + }); + } + //*********************************** */ + + private async handleReferralReward(payment: Payment, queryRunner: QueryRunner) { + const isFirstPurchase = await this.isFirstPurchase(payment.user.id, queryRunner); + if (!isFirstPurchase) return; + + const referral = await this.getPendingReferral(payment.user.id, queryRunner); + if (!referral) return; + + // Reward amount is 10% of the purchase amount + const rewardAmount = new Decimal(payment.amount).mul(0.1); + + // Create reward record + const reward = queryRunner.manager.create(ReferralReward, { + user: { id: referral.referrer.id }, + referral: { id: referral.id }, + amount: rewardAmount, + status: ReferralRewardStatus.PENDING, + }); + await queryRunner.manager.save(reward); + + // Charge referrer's wallet + await this.walletsService.createReferralRewardTransaction(referral.referrer.id, rewardAmount, queryRunner); + + // Update reward status + reward.status = ReferralRewardStatus.PAID; + reward.paidAt = new Date(); + await queryRunner.manager.save(reward); + + // Update referral status + referral.status = ReferralStatus.COMPLETED; + referral.completedAt = new Date(); + await queryRunner.manager.save(referral); + } + //*********************************** */ + async handleSuccessfulPayment(payment: Payment, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) { await queryRunner.manager.update(Payment, { id: payment.id }, { status: PaymentStatus.COMPLETED, transactionId }); - return this.walletsService.createPaymentTransaction(userId, amount, payment, queryRunner); + const result = await this.walletsService.createPaymentTransaction(userId, amount, payment, queryRunner); + + // Handle referral reward if applicable + await this.handleReferralReward(payment, queryRunner); + + return result; } //*********************************** */ diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 8a3849c..5ec0bdd 100755 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -246,8 +246,9 @@ export class UsersService { const userName = slugify(`${registerDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true }); + const { referralCode, ...userData } = registerDto; const user = queryRunner.manager.create(User, { - ...registerDto, + ...userData, userName, password: hashedPassword, roles: [role],