chore: add referral logic to first payment
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
//*********************************** */
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user