chore: add referral logic to first payment

This commit is contained in:
mahyargdz
2025-04-15 14:54:33 +03:30
parent a5976a3377
commit bd4d9dbf0d
7 changed files with 92 additions and 8 deletions
@@ -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;
}
+2 -1
View File
@@ -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],
+10 -1
View File
@@ -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();
@@ -31,6 +31,8 @@ export class BlogsRepository extends Repository<Blog> {
content: true,
tags: true,
viewCount: true,
isActive: true,
isPinned: true,
category: {
id: true,
title: true,
+2
View File
@@ -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: [
@@ -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<boolean> {
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<Referral | null> {
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;
}
//*********************************** */
+2 -1
View File
@@ -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],