File

apps/recallassess/recallassess-api/src/api/client/promo-code/promo-code.service.ts

Description

CLPromoCodeService

Client portal API — Promo Code (Promo Code). Business Logic Service for RecallAssess.

Index

Methods

Constructor

constructor(prisma: BNestPrismaService)
Parameters :
Name Type Optional
prisma BNestPrismaService No

Methods

calculateDiscount
calculateDiscount(originalAmount: number, validation: PromoCodeValidationDto)

Calculate discount amount based on promo code validation and original amount

Parameters :
Name Type Optional
originalAmount number No
validation PromoCodeValidationDto No
Returns : number
Private Async findByCode
findByCode(code: string)

Promo codes are stored uppercase; lookup is case-insensitive for legacy rows.

Parameters :
Name Type Optional
code string No
Returns : unknown
Async getPromoCodeDetails
getPromoCodeDetails(code: string)

Get detailed info about a valid promo code (Only returns data if code is valid)

Parameters :
Name Type Optional
code string No
Async incrementUsage
incrementUsage(code: string)

Increment usage count when a promo code is applied This should be called after a successful purchase/subscription

Parameters :
Name Type Optional
code string No
Returns : Promise<void>
Private toDiscountPayload
toDiscountPayload(promoCode: literal type)
Parameters :
Name Type Optional
promoCode literal type No
Returns : { discount_type: any; discount_percentage: any; discount_amount: any; }
Async validatePromoCode
validatePromoCode(code: string)

Validate a promo code Checks if code exists, is active, not expired, and not sold out

Parameters :
Name Type Optional
code string No
import { bnestPlainToDto } from "@bish-nest/core";
import { BNestPrismaService } from "@bish-nest/core/services/database/prisma/prisma.service";
import { Injectable } from "@nestjs/common";
import { PromoCodeDiscountType } from "@prisma/client";
import { calculatePromoDiscountAmount } from "@recallassess/shared-ng";
import { PromoCodeValidationDto, ValidPromoCodeDto } from "./dto";

/**
 * CLPromoCodeService
 *
 * Client portal API — Promo Code (Promo Code). Business Logic Service for RecallAssess.
 */


@Injectable()
export class CLPromoCodeService {
  constructor(private readonly prisma: BNestPrismaService) {}

  /** Promo codes are stored uppercase; lookup is case-insensitive for legacy rows. */
  private async findByCode(code: string) {
    const normalized = code.trim();
    if (!normalized) {
      return null;
    }

    return this.prisma.client.promoCode.findFirst({
      where: {
        promo_code: {
          equals: normalized,
          mode: "insensitive",
        },
      },
    });
  }

  private toDiscountPayload(promoCode: {
    discount_type: PromoCodeDiscountType;
    discount_percentage: { toNumber?: () => number } | number | null;
    discount_amount: { toNumber?: () => number } | number | null;
  }) {
    const discountType = promoCode.discount_type ?? PromoCodeDiscountType.PERCENTAGE;
    const discountPercentage =
      promoCode.discount_percentage != null ? Number(promoCode.discount_percentage) : undefined;
    const discountAmount =
      promoCode.discount_amount != null ? Number(promoCode.discount_amount) : undefined;

    return {
      discount_type: discountType,
      discount_percentage:
        discountType === PromoCodeDiscountType.PERCENTAGE ||
        discountType === PromoCodeDiscountType.PERCENTAGE_UP_TO
          ? discountPercentage
          : undefined,
      discount_amount:
        discountType === PromoCodeDiscountType.FIXED_AMOUNT ||
        discountType === PromoCodeDiscountType.PERCENTAGE_UP_TO
          ? discountAmount
          : undefined,
    };
  }

  /**
   * Validate a promo code
   * Checks if code exists, is active, not expired, and not sold out
   */
  async validatePromoCode(code: string): Promise<PromoCodeValidationDto> {
    const promoCode = await this.findByCode(code);

    // Code doesn't exist
    if (!promoCode) {
      return bnestPlainToDto(
        {
          is_valid: false,
          message: "Promo code not found",
          error_code: "NOT_FOUND",
        },
        PromoCodeValidationDto,
      );
    }

    // Code is inactive
    if (!promoCode.is_active) {
      return bnestPlainToDto(
        {
          is_valid: false,
          promo_code: promoCode.promo_code,
          message: "This promo code is no longer active",
          error_code: "INACTIVE",
        },
        PromoCodeValidationDto,
      );
    }

    const now = new Date();

    // Code hasn't started yet
    if (promoCode.valid_from && new Date(promoCode.valid_from) > now) {
      return bnestPlainToDto(
        {
          is_valid: false,
          promo_code: promoCode.promo_code,
          message: "This promo code is not yet valid",
          error_code: "NOT_STARTED",
        },
        PromoCodeValidationDto,
      );
    }

    // Code has expired
    if (promoCode.valid_until && new Date(promoCode.valid_until) < now) {
      return bnestPlainToDto(
        {
          is_valid: false,
          promo_code: promoCode.promo_code,
          message: "This promo code has expired",
          error_code: "EXPIRED",
        },
        PromoCodeValidationDto,
      );
    }

    // Code usage limit reached (sold out)
    if (promoCode.usage_limit && promoCode.usage_count >= promoCode.usage_limit) {
      return bnestPlainToDto(
        {
          is_valid: false,
          promo_code: promoCode.promo_code,
          message: "This promo code has reached its usage limit",
          error_code: "SOLD_OUT",
        },
        PromoCodeValidationDto,
      );
    }

    // Code is valid!
    return bnestPlainToDto(
      {
        is_valid: true,
        promo_code: promoCode.promo_code,
        ...this.toDiscountPayload(promoCode),
        message: "Promo code is valid",
      },
      PromoCodeValidationDto,
    );
  }

  /**
   * Get detailed info about a valid promo code
   * (Only returns data if code is valid)
   */
  async getPromoCodeDetails(code: string): Promise<ValidPromoCodeDto | null> {
    const validation = await this.validatePromoCode(code);

    if (!validation.is_valid) {
      return null;
    }

    const promoCode = await this.findByCode(code);

    if (!promoCode) {
      return null;
    }

    const remaining_uses = promoCode.usage_limit
      ? Math.max(0, promoCode.usage_limit - promoCode.usage_count)
      : null;

    return bnestPlainToDto(
      {
        promo_code: promoCode.promo_code,
        title: promoCode.title,
        ...this.toDiscountPayload(promoCode),
        valid_until: promoCode.valid_until,
        remaining_uses,
      },
      ValidPromoCodeDto,
    );
  }

  /**
   * Increment usage count when a promo code is applied
   * This should be called after a successful purchase/subscription
   */
  async incrementUsage(code: string): Promise<void> {
    const promoCode = await this.findByCode(code);
    if (!promoCode) {
      return;
    }

    await this.prisma.client.promoCode.update({
      where: { id: promoCode.id },
      data: {
        usage_count: {
          increment: 1,
        },
      },
    });
  }

  /**
   * Calculate discount amount based on promo code validation and original amount
   */
  calculateDiscount(originalAmount: number, validation: PromoCodeValidationDto): number {
    return calculatePromoDiscountAmount(originalAmount, {
      discount_type: validation.discount_type,
      discount_percentage: validation.discount_percentage,
      discount_amount: validation.discount_amount,
    });
  }
}

results matching ""

    No results matching ""