File

apps/recallassess/recallassess-api/src/api/client/subscription/subscription.controller.ts

Prefix

api/client/subscription

Index

Methods

Methods

Async confirmChange
confirmChange(auth: CLAuthData, dto: SubscriptionConfirmRequestDto)
Decorators :
@Post('confirm')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Confirm subscription change and create payment checkout'})
@ApiResponse({status: 200, description: 'Returns Stripe checkout URL for payment', type: SubscriptionConfirmResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
dto SubscriptionConfirmRequestDto No
Async createPaymentMethodPortal
createPaymentMethodPortal(auth: CLAuthData)
Decorators :
@Post('payment-method')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Create Stripe billing portal session for payment method update'})
@ApiResponse({status: 200, description: 'Returns Stripe billing portal URL for payment method updates', type: SubscriptionPaymentMethodResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
Async createStripeBillingSubscription
createStripeBillingSubscription(auth: CLAuthData)
Decorators :
@Post('stripe/create-billing-subscription')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Create or replace Stripe Billing subscription from local paid subscription'})
@ApiResponse({status: 200, description: 'Stripe subscription synced from local plan row', type: SubscriptionStripeBillingCreateResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
Async downloadInvoicePdf
downloadInvoicePdf(auth: CLAuthData, invoiceNumber: string, authorization: string | undefined, reply: FastifyReply)
Decorators :
@Get('invoice/:invoiceNumber/download-pdf')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Download invoice PDF'})
@ApiResponse({status: 200, description: 'Streams invoice PDF as attachment'})
Parameters :
Name Type Optional
auth CLAuthData No
invoiceNumber string No
authorization string | undefined No
reply FastifyReply No
Returns : Promise<FastifyReply>
Async getBillingHistory
getBillingHistory(auth: CLAuthData)
Decorators :
@Get('billing-history')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Get billing history'})
@ApiResponse({status: 200, description: 'Returns billing history for the authenticated company', type: SubscriptionBillingHistoryResponseDto})

Get billing history for the authenticated company

Parameters :
Name Type Optional
auth CLAuthData No
Async getInvoiceDownloadUrl
getInvoiceDownloadUrl(auth: CLAuthData, invoiceNumber: string, authorization: string | undefined)
Decorators :
@Get('invoice/:invoiceNumber')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Get invoice download URL'})
@ApiResponse({status: 200, description: 'Returns a signed URL for the invoice PDF', type: SubscriptionInvoiceDownloadResponseDto})

Get invoice download URL by invoice number (company-scoped)

Parameters :
Name Type Optional
auth CLAuthData No
invoiceNumber string No
authorization string | undefined No
Async getOverview
getOverview(auth: CLAuthData)
Decorators :
@Get()
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Get current subscription overview'})
@ApiResponse({status: 200, description: 'Returns current subscription overview for the authenticated company', type: SubscriptionOverviewResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
Async getPackages
getPackages(auth: CLAuthData)
Decorators :
@Get('packages')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'List subscription packages'})
@ApiResponse({status: 200, description: 'Returns all active subscription packages with current plan highlighted', type: undefined})
Parameters :
Name Type Optional
auth CLAuthData No
Async previewChange
previewChange(auth: CLAuthData, dto: SubscriptionPreviewRequestDto)
Decorators :
@Post('preview')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Preview subscription change impact'})
@ApiResponse({status: 200, description: 'Returns a preview of the financial impact for a subscription change', type: SubscriptionPreviewResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
dto SubscriptionPreviewRequestDto No
Async reactivateStripeSubscriptionAfterScheduledCancel
reactivateStripeSubscriptionAfterScheduledCancel(auth: CLAuthData)
Decorators :
@Post('stripe/reactivate-subscription')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Remove cancel-at-period-end so the existing Stripe subscription renews'})
@ApiResponse({status: 200, description: 'Stripe subscription updated; local cancel-at-period-end flag cleared', type: SubscriptionCancelAtPeriodEndResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
Async retryStripePayment
retryStripePayment(auth: CLAuthData)
Decorators :
@Post('stripe/retry-payment')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Retry paying the latest Stripe invoice for the existing subscription'})
@ApiResponse({status: 200, description: 'Attempts to pay the latest invoice off-session using the saved default card', type: SubscriptionRetryPaymentResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
Async scheduleCancelAtPeriodEnd
scheduleCancelAtPeriodEnd(auth: CLAuthData)
Decorators :
@Post('cancel-at-period-end')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Schedule subscription cancellation at end of current Stripe billing period'})
@ApiResponse({status: 200, description: 'Stripe subscription updated; local subscription flag set', type: SubscriptionCancelAtPeriodEndResponseDto})
Parameters :
Name Type Optional
auth CLAuthData No
Async syncBillingHistoryFromStripe
syncBillingHistoryFromStripe(auth: CLAuthData)
Decorators :
@Post('billing-history/sync-from-stripe')
@HttpCode(HttpStatus.OK)
@ApiOperation({summary: 'Import missing invoices and refunds from Stripe for the current company'})
@ApiResponse({status: 200, description: 'Stripe sync result; reload billing history to see new rows'})
Parameters :
Name Type Optional
auth CLAuthData No
Returns : Promise<literal type>
import { bnestPlainToDto } from "@bish-nest/core";
import { Body, Controller, Get, Headers, HttpCode, HttpStatus, Param, Post, Res } from "@nestjs/common";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { FastifyReply } from "fastify";
import { CLAuthData, ClientAuth } from "../../shared/decorators/client-auth.decorator";
import {
  SubscriptionBillingHistoryResponseDto,
  SubscriptionCancelAtPeriodEndResponseDto,
  SubscriptionConfirmRequestDto,
  SubscriptionConfirmResponseDto,
  SubscriptionInvoiceDownloadResponseDto,
  SubscriptionOverviewResponseDto,
  SubscriptionPackageDto,
  SubscriptionPaymentMethodResponseDto,
  SubscriptionPreviewRequestDto,
  SubscriptionPreviewResponseDto,
  SubscriptionRetryPaymentResponseDto,
  SubscriptionStripeBillingCreateResponseDto,
} from "./dto";
import { CLSubscriptionService } from "./subscription.service";

@ApiTags("Client - Subscription")
@Controller("api/client/subscription")
export class CLSubscriptionController {
  constructor(private readonly subscriptionService: CLSubscriptionService) {}

  @Get()
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Get current subscription overview" })
  @ApiResponse({
    status: 200,
    description: "Returns current subscription overview for the authenticated company",
    type: SubscriptionOverviewResponseDto,
  })
  async getOverview(@ClientAuth() auth: CLAuthData): Promise<SubscriptionOverviewResponseDto> {
    return this.subscriptionService.getSubscriptionOverview(auth.companyId);
  }

  @Get("packages")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "List subscription packages" })
  @ApiResponse({
    status: 200,
    description: "Returns all active subscription packages with current plan highlighted",
    type: [SubscriptionPackageDto],
  })
  async getPackages(@ClientAuth() auth: CLAuthData): Promise<SubscriptionPackageDto[]> {
    return this.subscriptionService.getAvailablePackages(auth.companyId);
  }

  @Post("preview")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Preview subscription change impact" })
  @ApiResponse({
    status: 200,
    description: "Returns a preview of the financial impact for a subscription change",
    type: SubscriptionPreviewResponseDto,
  })
  async previewChange(
    @ClientAuth() auth: CLAuthData,
    @Body() dto: SubscriptionPreviewRequestDto,
  ): Promise<SubscriptionPreviewResponseDto> {
    return this.subscriptionService.previewSubscriptionChange(auth.companyId, dto);
  }

  @Post("confirm")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Confirm subscription change and create payment checkout" })
  @ApiResponse({
    status: 200,
    description: "Returns Stripe checkout URL for payment",
    type: SubscriptionConfirmResponseDto,
  })
  async confirmChange(
    @ClientAuth() auth: CLAuthData,
    @Body() dto: SubscriptionConfirmRequestDto,
  ): Promise<SubscriptionConfirmResponseDto> {
    return this.subscriptionService.confirmSubscriptionChange(auth.companyId, dto);
  }

  @Post("payment-method")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Create Stripe billing portal session for payment method update" })
  @ApiResponse({
    status: 200,
    description: "Returns Stripe billing portal URL for payment method updates",
    type: SubscriptionPaymentMethodResponseDto,
  })
  async createPaymentMethodPortal(@ClientAuth() auth: CLAuthData): Promise<SubscriptionPaymentMethodResponseDto> {
    return this.subscriptionService.createPaymentMethodPortal(auth.companyId);
  }

  @Post("cancel-at-period-end")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Schedule subscription cancellation at end of current Stripe billing period" })
  @ApiResponse({
    status: 200,
    description: "Stripe subscription updated; local subscription flag set",
    type: SubscriptionCancelAtPeriodEndResponseDto,
  })
  async scheduleCancelAtPeriodEnd(
    @ClientAuth() auth: CLAuthData,
  ): Promise<SubscriptionCancelAtPeriodEndResponseDto> {
    return this.subscriptionService.scheduleCancelSubscriptionAtPeriodEnd(auth.companyId);
  }

  @Post("stripe/create-billing-subscription")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: "Create or replace Stripe Billing subscription from local paid subscription",
  })
  @ApiResponse({
    status: 200,
    description: "Stripe subscription synced from local plan row",
    type: SubscriptionStripeBillingCreateResponseDto,
  })
  async createStripeBillingSubscription(
    @ClientAuth() auth: CLAuthData,
  ): Promise<SubscriptionStripeBillingCreateResponseDto> {
    return this.subscriptionService.createStripeBillingSubscription(auth.companyId);
  }

  @Post("stripe/reactivate-subscription")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: "Remove cancel-at-period-end so the existing Stripe subscription renews",
  })
  @ApiResponse({
    status: 200,
    description: "Stripe subscription updated; local cancel-at-period-end flag cleared",
    type: SubscriptionCancelAtPeriodEndResponseDto,
  })
  async reactivateStripeSubscriptionAfterScheduledCancel(
    @ClientAuth() auth: CLAuthData,
  ): Promise<SubscriptionCancelAtPeriodEndResponseDto> {
    return this.subscriptionService.reactivateStripeSubscriptionAfterScheduledCancel(auth.companyId);
  }

  @Post("stripe/retry-payment")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: "Retry paying the latest Stripe invoice for the existing subscription",
  })
  @ApiResponse({
    status: 200,
    description: "Attempts to pay the latest invoice off-session using the saved default card",
    type: SubscriptionRetryPaymentResponseDto,
  })
  async retryStripePayment(@ClientAuth() auth: CLAuthData): Promise<SubscriptionRetryPaymentResponseDto> {
    return this.subscriptionService.retryStripeSubscriptionPayment(auth.companyId);
  }

  /**
   * Get billing history for the authenticated company
   */
  @Get("billing-history")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Get billing history" })
  @ApiResponse({
    status: 200,
    description: "Returns billing history for the authenticated company",
    type: SubscriptionBillingHistoryResponseDto,
  })
  async getBillingHistory(@ClientAuth() auth: CLAuthData): Promise<SubscriptionBillingHistoryResponseDto> {
    return this.subscriptionService.getBillingHistory(auth.companyId);
  }

  @Post("billing-history/sync-from-stripe")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: "Import missing invoices and refunds from Stripe for the current company",
  })
  @ApiResponse({
    status: 200,
    description: "Stripe sync result; reload billing history to see new rows",
  })
  async syncBillingHistoryFromStripe(
    @ClientAuth() auth: CLAuthData,
  ): Promise<{ success: boolean; message: string; invoices_created: number; refunds_created: number }> {
    return this.subscriptionService.syncBillingHistoryFromStripe(auth.companyId);
  }

  /**
   * Get invoice download URL by invoice number (company-scoped)
   */
  @Get("invoice/:invoiceNumber")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Get invoice download URL" })
  @ApiResponse({
    status: 200,
    description: "Returns a signed URL for the invoice PDF",
    type: SubscriptionInvoiceDownloadResponseDto,
  })
  async getInvoiceDownloadUrl(
    @ClientAuth() auth: CLAuthData,
    @Param("invoiceNumber") invoiceNumber: string,
    @Headers("authorization") authorization: string | undefined,
  ): Promise<SubscriptionInvoiceDownloadResponseDto> {
    const url = await this.subscriptionService.getInvoiceDownloadUrl(auth.companyId, invoiceNumber, authorization);
    return bnestPlainToDto({ url }, SubscriptionInvoiceDownloadResponseDto);
  }

  @Get("invoice/:invoiceNumber/download-pdf")
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: "Download invoice PDF" })
  @ApiResponse({
    status: 200,
    description: "Streams invoice PDF as attachment",
  })
  async downloadInvoicePdf(
    @ClientAuth() auth: CLAuthData,
    @Param("invoiceNumber") invoiceNumber: string,
    @Headers("authorization") authorization: string | undefined,
    @Res() reply: FastifyReply,
  ): Promise<FastifyReply> {
    const { buffer, filename } = await this.subscriptionService.getInvoicePdfBuffer(
      auth.companyId,
      invoiceNumber,
      authorization,
    );
    const safeName = filename.replace(/"/g, "%22");
    reply.header("Content-Type", "application/pdf");
    reply.header("Content-Disposition", `attachment; filename="${safeName}"`);
    return reply.send(buffer);
  }
}

results matching ""

    No results matching ""