File

apps/recallassess/recallassess-api/src/api/client/subscription/dto/subscription-overview-response.dto.ts

Description

GET /api/client/subscription — only @Expose() fields are serialized (global ClassSerializerInterceptor). Stripe-related fields stay on the wire for portal billing UI (cancel-at-period-end, upcoming invoice, etc.).

Index

Properties

Properties

auto_payment_enabled
Type : boolean
Decorators :
@Expose()
@ApiProperty()
billing_cycle
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
color
Type : string
Decorators :
@Expose()
@ApiProperty()
company_country
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

Company country (ISO or stored value); used for VAT display on portal invoice preview

has_paid_current_period
Type : boolean | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

DEFINITIVE paid-customer signal.

True iff the customer has paid a real (non-zero) invoice for the current subscription period. Replaces the duration-based heuristic for detecting "expired-recovery" trialing subscriptions — a Start-Up customer who has already paid will have this true regardless of trial_end value, even if the date was admin-edited or set by the recovery flow.

Computed from subscription.latest_invoice.amount_paid > 0 (or status === 'paid').

Frontend should treat true as "show Active plan", false as "real trial / unpaid period", and null as "data unavailable — fall back to heuristic".

has_stripe_customer
Type : boolean
Decorators :
@Expose()
@ApiProperty()

True when company has a Stripe customer id (required to create/sync billing subscription)

icon
Type : string
Decorators :
@Expose()
@ApiProperty()
is_subscription_expiry
Type : boolean
Decorators :
@Expose()
@ApiProperty()

Company flag: subscription access ended (e.g. expired plan)

license_count
Type : number
Decorators :
@Expose()
@ApiProperty()
monthly_subtotal
Type : number
Decorators :
@Expose()
@ApiProperty()
next_billing_amount
Type : number | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true, description: 'Amount charged on next_billing_date (monthly_subtotal × billing cycle multiplier)'})
next_billing_date
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
package_name
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
payment_method_brand
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
payment_method_exp_month
Type : number | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
payment_method_exp_year
Type : number | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
payment_method_last4
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
plan_type
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
price_per_license
Type : number | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
stripe_cancel_at_period_end
Type : boolean | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
stripe_collection_method
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

charge_automatically | send_invoice

stripe_current_period_end
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
stripe_current_period_start
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
stripe_next_charge_date
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

Convenience field — the date the customer's card will next be charged. Computed server-side: trial_end for trialing subs in the future, otherwise current_period_end. Frontend should prefer this over current_period_end for "next renewal / next charge" labelling.

stripe_next_invoice_amount
Type : number | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

Next Stripe invoice total (major units), from invoice preview; null if none

stripe_next_invoice_currency
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
stripe_subscription_id
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

Stripe Billing subscription id when synced; null if checkout-only / not created yet

stripe_subscription_status
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})
stripe_trial_end
Type : string | null
Decorators :
@Expose()
@ApiPropertyOptional({nullable: true})

If the subscription is on a trial, this is the trial cutoff date (ISO). For trialing subs the next charge happens at this date — not at stripe_current_period_end. Frontend should surface trial state when this is set + stripe_subscription_status === "trialing".

vip_first_invoice_offer_applies
Type : boolean
Decorators :
@Expose()

VIP offer flag (first Stripe invoice only): true when the current plan is billable and the previous local subscription was PRIVATE_VIP_TRIAL. The portal uses this to show a "VIP offer (one-time)" discount line in the billing details dialog.

import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { Exclude, Expose } from "class-transformer";

/**
 * GET /api/client/subscription — only @Expose() fields are serialized (global ClassSerializerInterceptor).
 * Stripe-related fields stay on the wire for portal billing UI (cancel-at-period-end, upcoming invoice, etc.).
 */
@Exclude()
export class SubscriptionOverviewResponseDto {
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  plan_type!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  package_name!: string | null;

  @Expose()
  @ApiProperty()
  license_count!: number;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  price_per_license!: number | null;

  @Expose()
  @ApiProperty()
  monthly_subtotal!: number;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  next_billing_date!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  payment_method_last4!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  payment_method_brand!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  payment_method_exp_month!: number | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  payment_method_exp_year!: number | null;

  @Expose()
  @ApiProperty()
  auto_payment_enabled!: boolean;

  @Expose()
  @ApiProperty()
  color!: string;

  @Expose()
  @ApiProperty()
  icon!: string;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  billing_cycle!: string | null;

  /** Company country (ISO or stored value); used for VAT display on portal invoice preview */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  company_country!: string | null;

  @Expose()
  @ApiPropertyOptional({
    nullable: true,
    description: "Amount charged on next_billing_date (monthly_subtotal × billing cycle multiplier)",
  })
  next_billing_amount!: number | null;

  /** Stripe Billing subscription id when synced; null if checkout-only / not created yet */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_subscription_id!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_subscription_status!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_current_period_start!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_current_period_end!: string | null;

  /**
   * If the subscription is on a trial, this is the trial cutoff date (ISO).
   * For trialing subs the *next charge* happens at this date — not at
   * stripe_current_period_end. Frontend should surface trial state when this
   * is set + stripe_subscription_status === "trialing".
   */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_trial_end!: string | null;

  /**
   * Convenience field — the date the customer's card will next be charged.
   * Computed server-side: trial_end for trialing subs in the future, otherwise
   * current_period_end. Frontend should prefer this over current_period_end
   * for "next renewal / next charge" labelling.
   */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_next_charge_date!: string | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_cancel_at_period_end!: boolean | null;

  /** charge_automatically | send_invoice */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_collection_method!: string | null;

  /** Next Stripe invoice total (major units), from invoice preview; null if none */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_next_invoice_amount!: number | null;

  @Expose()
  @ApiPropertyOptional({ nullable: true })
  stripe_next_invoice_currency!: string | null;

  /**
   * DEFINITIVE paid-customer signal.
   *
   * True iff the customer has paid a real (non-zero) invoice for the current
   * subscription period. Replaces the duration-based heuristic for detecting
   * "expired-recovery" trialing subscriptions — a Start-Up customer who has
   * already paid will have this true regardless of trial_end value, even if
   * the date was admin-edited or set by the recovery flow.
   *
   * Computed from subscription.latest_invoice.amount_paid > 0 (or status === 'paid').
   *
   * Frontend should treat true as "show Active plan", false as "real trial /
   * unpaid period", and null as "data unavailable — fall back to heuristic".
   */
  @Expose()
  @ApiPropertyOptional({ nullable: true })
  has_paid_current_period!: boolean | null;

  /** Company flag: subscription access ended (e.g. expired plan) */
  @Expose()
  @ApiProperty()
  is_subscription_expiry!: boolean;

  /** True when company has a Stripe customer id (required to create/sync billing subscription) */
  @Expose()
  @ApiProperty()
  has_stripe_customer!: boolean;

  /**
   * VIP offer flag (first Stripe invoice only):
   * true when the current plan is billable and the previous local subscription was PRIVATE_VIP_TRIAL.
   * The portal uses this to show a "VIP offer (one-time)" discount line in the billing details dialog.
   */
  @Expose()
  vip_first_invoice_offer_applies!: boolean;
}

results matching ""

    No results matching ""