| 
						
						
						
					 | 
					@ -1,8 +1,16 @@ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; | 
					 | 
					 | 
					import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; | 
					 | 
					 | 
					import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; | 
					 | 
					 | 
					import { PropertyService } from '@ghostfolio/api/services/property/property.service'; | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					import { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					  DEFAULT_LANGUAGE_CODE, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					  PROPERTY_STRIPE_CONFIG | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					} from '@ghostfolio/common/config'; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import { parseDate } from '@ghostfolio/common/helper'; | 
					 | 
					 | 
					import { parseDate } from '@ghostfolio/common/helper'; | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					import { SubscriptionOffer, UserWithSettings } from '@ghostfolio/common/types'; | 
					 | 
					 | 
					import { SubscriptionOffer } from '@ghostfolio/common/interfaces'; | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					import { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					  SubscriptionOfferKey, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					  UserWithSettings | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					} from '@ghostfolio/common/types'; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; | 
					 | 
					 | 
					import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import { Injectable, Logger } from '@nestjs/common'; | 
					 | 
					 | 
					import { Injectable, Logger } from '@nestjs/common'; | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -17,7 +25,8 @@ export class SubscriptionService { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  public constructor( | 
					 | 
					 | 
					  public constructor( | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    private readonly configurationService: ConfigurationService, | 
					 | 
					 | 
					    private readonly configurationService: ConfigurationService, | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					    private readonly prismaService: PrismaService | 
					 | 
					 | 
					    private readonly prismaService: PrismaService, | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    private readonly propertyService: PropertyService | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  ) { | 
					 | 
					 | 
					  ) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    this.stripe = new Stripe( | 
					 | 
					 | 
					    this.stripe = new Stripe( | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      this.configurationService.get('STRIPE_SECRET_KEY'), | 
					 | 
					 | 
					      this.configurationService.get('STRIPE_SECRET_KEY'), | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -36,6 +45,18 @@ export class SubscriptionService { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    priceId: string; | 
					 | 
					 | 
					    priceId: string; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    user: UserWithSettings; | 
					 | 
					 | 
					    user: UserWithSettings; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  }) { | 
					 | 
					 | 
					  }) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    const subscriptionOffers: { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      [offer in SubscriptionOfferKey]: SubscriptionOffer; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    } = | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      ((await this.propertyService.getByKey(PROPERTY_STRIPE_CONFIG)) as any) ?? | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      {}; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    const subscriptionOffer = Object.values(subscriptionOffers).find( | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      (subscriptionOffer) => { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        return subscriptionOffer.priceId === priceId; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    ); | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = { | 
					 | 
					 | 
					    const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      cancel_url: `${this.configurationService.get('ROOT_URL')}/${ | 
					 | 
					 | 
					      cancel_url: `${this.configurationService.get('ROOT_URL')}/${ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        user.Settings?.settings?.language ?? DEFAULT_LANGUAGE_CODE | 
					 | 
					 | 
					        user.Settings?.settings?.language ?? DEFAULT_LANGUAGE_CODE | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -47,6 +68,13 @@ export class SubscriptionService { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					          quantity: 1 | 
					 | 
					 | 
					          quantity: 1 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        } | 
					 | 
					 | 
					        } | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      ], | 
					 | 
					 | 
					      ], | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      locale: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        (user.Settings?.settings | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					          ?.language as Stripe.Checkout.SessionCreateParams.Locale) ?? | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        DEFAULT_LANGUAGE_CODE, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      metadata: subscriptionOffer | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        ? { subscriptionOffer: JSON.stringify(subscriptionOffer) } | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        : {}, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      mode: 'payment', | 
					 | 
					 | 
					      mode: 'payment', | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      payment_method_types: ['card'], | 
					 | 
					 | 
					      payment_method_types: ['card'], | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      success_url: `${this.configurationService.get( | 
					 | 
					 | 
					      success_url: `${this.configurationService.get( | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					@ -73,17 +101,25 @@ export class SubscriptionService { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  public async createSubscription({ | 
					 | 
					 | 
					  public async createSubscription({ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    duration = '1 year', | 
					 | 
					 | 
					    duration = '1 year', | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    durationExtension, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    price, | 
					 | 
					 | 
					    price, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    userId | 
					 | 
					 | 
					    userId | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  }: { | 
					 | 
					 | 
					  }: { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    duration?: StringValue; | 
					 | 
					 | 
					    duration?: StringValue; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    durationExtension?: StringValue; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    price: number; | 
					 | 
					 | 
					    price: number; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    userId: string; | 
					 | 
					 | 
					    userId: string; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  }) { | 
					 | 
					 | 
					  }) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    let expiresAt = addMilliseconds(new Date(), ms(duration)); | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    if (durationExtension) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      expiresAt = addMilliseconds(expiresAt, ms(durationExtension)); | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    } | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    await this.prismaService.subscription.create({ | 
					 | 
					 | 
					    await this.prismaService.subscription.create({ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      data: { | 
					 | 
					 | 
					      data: { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        expiresAt, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        price, | 
					 | 
					 | 
					        price, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        expiresAt: addMilliseconds(new Date(), ms(duration)), | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        User: { | 
					 | 
					 | 
					        User: { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					          connect: { | 
					 | 
					 | 
					          connect: { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            id: userId | 
					 | 
					 | 
					            id: userId | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -95,10 +131,21 @@ export class SubscriptionService { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					  public async createSubscriptionViaStripe(aCheckoutSessionId: string) { | 
					 | 
					 | 
					  public async createSubscriptionViaStripe(aCheckoutSessionId: string) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    try { | 
					 | 
					 | 
					    try { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      let durationExtension: StringValue; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      const session = | 
					 | 
					 | 
					      const session = | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        await this.stripe.checkout.sessions.retrieve(aCheckoutSessionId); | 
					 | 
					 | 
					        await this.stripe.checkout.sessions.retrieve(aCheckoutSessionId); | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      const subscriptionOffer: SubscriptionOffer = JSON.parse( | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        session.metadata.subscriptionOffer ?? '{}' | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      ); | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      if (subscriptionOffer) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        durationExtension = subscriptionOffer.durationExtension; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      await this.createSubscription({ | 
					 | 
					 | 
					      await this.createSubscription({ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        durationExtension, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        price: session.amount_total / 100, | 
					 | 
					 | 
					        price: session.amount_total / 100, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        userId: session.client_reference_id | 
					 | 
					 | 
					        userId: session.client_reference_id | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      }); | 
					 | 
					 | 
					      }); | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -121,7 +168,7 @@ export class SubscriptionService { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b; | 
					 | 
					 | 
					        return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b; | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      }); | 
					 | 
					 | 
					      }); | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					      let offer: SubscriptionOffer = price ? 'renewal' : 'default'; | 
					 | 
					 | 
					      let offer: SubscriptionOfferKey = price ? 'renewal' : 'default'; | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					      if (isBefore(createdAt, parseDate('2023-01-01'))) { | 
					 | 
					 | 
					      if (isBefore(createdAt, parseDate('2023-01-01'))) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        offer = 'renewal-early-bird-2023'; | 
					 | 
					 | 
					        offer = 'renewal-early-bird-2023'; | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					
  |