diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/subscription/subscription.controller.ts index 131109d45..ecd326ead 100644 --- a/apps/api/src/app/subscription/subscription.controller.ts +++ b/apps/api/src/app/subscription/subscription.controller.ts @@ -34,11 +34,14 @@ export class SubscriptionController { res.redirect(`${this.configurationService.get('ROOT_URL')}/account`); } - @Post('stripe/checkout-session/create') + @Post('stripe/checkout-session') @UseGuards(AuthGuard('jwt')) - public async createCheckoutSession(@Body() { priceId }: { priceId: string }) { + public async createCheckoutSession( + @Body() { couponId, priceId }: { couponId: string; priceId: string } + ) { try { return await this.subscriptionService.createCheckoutSession({ + couponId, priceId, userId: this.request.user.id }); diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index b063cbe44..fa77a55fc 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -21,13 +21,15 @@ export class SubscriptionService { } public async createCheckoutSession({ + couponId, priceId, userId }: { + couponId?: string; priceId: string; userId: string; }) { - const session = await this.stripe.checkout.sessions.create({ + const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = { cancel_url: `${this.configurationService.get('ROOT_URL')}/account`, client_reference_id: userId, line_items: [ @@ -44,7 +46,19 @@ export class SubscriptionService { success_url: `${this.configurationService.get( 'ROOT_URL' )}/api/subscription/stripe/callback?checkoutSessionId={CHECKOUT_SESSION_ID}` - }); + }; + + if (couponId) { + checkoutSessionCreateParams.discounts = [ + { + coupon: couponId + } + ]; + } + + const session = await this.stripe.checkout.sessions.create( + checkoutSessionCreateParams + ); return { sessionId: session.id diff --git a/apps/api/src/app/user/interfaces/user-settings-params.interface.ts b/apps/api/src/app/user/interfaces/user-settings-params.interface.ts new file mode 100644 index 000000000..e1e369e11 --- /dev/null +++ b/apps/api/src/app/user/interfaces/user-settings-params.interface.ts @@ -0,0 +1,7 @@ +import { Currency, ViewMode } from '@prisma/client'; + +export interface UserSettingsParams { + currency?: Currency; + userId: string; + viewMode?: ViewMode; +} diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index f7e5d275b..fd07beaec 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -25,6 +25,7 @@ import { User as UserModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { UserItem } from './interfaces/user-item.interface'; +import { UserSettingsParams } from './interfaces/user-settings-params.interface'; import { UpdateUserSettingsDto } from './update-user-settings.dto'; import { UserService } from './user.service'; @@ -92,10 +93,20 @@ export class UserController { ); } - return await this.userService.updateUserSettings({ + const userSettings: UserSettingsParams = { currency: data.baseCurrency, - userId: this.request.user.id, - viewMode: data.viewMode - }); + userId: this.request.user.id + }; + + if ( + hasPermission( + getPermissions(this.request.user.role), + permissions.updateViewMode + ) + ) { + userSettings.viewMode = data.viewMode; + } + + return await this.userService.updateUserSettings(userSettings); } } diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index ff018ed69..d2a5e8895 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -1,13 +1,13 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { locale } from '@ghostfolio/common/config'; -import { resetHours } from '@ghostfolio/common/helper'; import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces'; import { getPermissions, permissions } from '@ghostfolio/common/permissions'; import { SubscriptionType } from '@ghostfolio/common/types/subscription.type'; import { Injectable } from '@nestjs/common'; import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client'; -import { add, isBefore } from 'date-fns'; +import { isBefore } from 'date-fns'; +import { UserSettingsParams } from './interfaces/user-settings-params.interface'; const crypto = require('crypto'); @@ -24,7 +24,7 @@ export class UserService { Account, alias, id, - role, + permissions, Settings, subscription }: UserWithSettings): Promise { @@ -36,15 +36,10 @@ export class UserService { where: { GranteeUser: { id } } }); - const currentPermissions = getPermissions(role); - - if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { - currentPermissions.push(permissions.accessFearAndGreedIndex); - } - return { alias, id, + permissions, subscription, access: access.map((accessItem) => { return { @@ -53,7 +48,6 @@ export class UserService { }; }), accounts: Account, - permissions: currentPermissions, settings: { locale, baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY, @@ -72,6 +66,14 @@ export class UserService { const user: UserWithSettings = userFromDatabase; + const currentPermissions = getPermissions(userFromDatabase.role); + + if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { + currentPermissions.push(permissions.accessFearAndGreedIndex); + } + + user.permissions = currentPermissions; + if (userFromDatabase?.Settings) { if (!userFromDatabase.Settings.currency) { // Set default currency if needed @@ -106,6 +108,13 @@ export class UserService { type: SubscriptionType.Basic }; } + + if (user.subscription.type === SubscriptionType.Basic) { + user.permissions = user.permissions.filter((permission) => { + return permission !== permissions.updateViewMode; + }); + user.Settings.viewMode = ViewMode.ZEN; + } } return user; @@ -213,11 +222,7 @@ export class UserService { currency, userId, viewMode - }: { - currency?: Currency; - userId: string; - viewMode?: ViewMode; - }) { + }: UserSettingsParams) { await this.prisma.settings.upsert({ create: { currency, diff --git a/apps/client/src/app/pages/account/account-page.component.ts b/apps/client/src/app/pages/account/account-page.component.ts index 61929185d..3cca89d56 100644 --- a/apps/client/src/app/pages/account/account-page.component.ts +++ b/apps/client/src/app/pages/account/account-page.component.ts @@ -31,8 +31,11 @@ export class AccountPageComponent implements OnDestroy, OnInit { public accesses: Access[]; public baseCurrency: Currency; + public coupon: number; + public couponId: string; public currencies: Currency[] = []; public defaultDateFormat = DEFAULT_DATE_FORMAT; + public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateUserSettings: boolean; public price: number; public priceId: string; @@ -54,9 +57,11 @@ export class AccountPageComponent implements OnDestroy, OnInit { .fetchInfo() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ currencies, subscriptions }) => { + this.coupon = subscriptions?.[0]?.coupon; + this.couponId = subscriptions?.[0]?.couponId; this.currencies = currencies; - this.price = subscriptions?.[0].price; - this.priceId = subscriptions?.[0].priceId; + this.price = subscriptions?.[0]?.price; + this.priceId = subscriptions?.[0]?.priceId; this.changeDetectorRef.markForCheck(); }); @@ -72,6 +77,11 @@ export class AccountPageComponent implements OnDestroy, OnInit { permissions.updateUserSettings ); + this.hasPermissionToUpdateViewMode = hasPermission( + this.user.permissions, + permissions.updateViewMode + ); + this.changeDetectorRef.markForCheck(); } }); @@ -107,9 +117,9 @@ export class AccountPageComponent implements OnDestroy, OnInit { }); } - public onCheckout(priceId: string) { + public onCheckout() { this.dataService - .createCheckoutSession(priceId) + .createCheckoutSession({ couponId: this.couponId, priceId: this.priceId }) .pipe( switchMap(({ sessionId }: { sessionId: string }) => { return this.stripeService.redirectToCheckout({ diff --git a/apps/client/src/app/pages/account/account-page.html b/apps/client/src/app/pages/account/account-page.html index 042c1f611..0662d78a9 100644 --- a/apps/client/src/app/pages/account/account-page.html +++ b/apps/client/src/app/pages/account/account-page.html @@ -32,8 +32,13 @@ Upgrade
- {{ user.settings.baseCurrency }} {{ price }} - per year + {{ user.settings.baseCurrency }} + {{ price - coupon }} + {{ user.settings.baseCurrency }} {{ price }} + + {{ price }} + per year
@@ -61,7 +66,7 @@ View Mode diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index b34ff6ace..5f17ce229 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -13,6 +13,7 @@ import { takeUntil } from 'rxjs/operators'; }) export class PricingPageComponent implements OnInit { public baseCurrency = baseCurrency; + public coupon: number; public isLoggedIn: boolean; public price: number; public user: User; @@ -31,6 +32,7 @@ export class PricingPageComponent implements OnInit { .fetchInfo() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ subscriptions }) => { + this.coupon = this.price = subscriptions?.[0]?.coupon; this.price = subscriptions?.[0]?.price; this.changeDetectorRef.markForCheck(); diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index 74479a839..ffd86fbb2 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -179,8 +179,14 @@

{{ user?.settings.baseCurrency || baseCurrency }} - {{ price }} - per year{{ price - coupon }} + {{ user.settings.baseCurrency }} {{ price }} + + {{ price }} + per year

diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 0661b7c30..4057a7e12 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -43,8 +43,15 @@ export class DataService { private settingsStorageService: SettingsStorageService ) {} - public createCheckoutSession(priceId) { - return this.http.post('/api/subscription/stripe/checkout-session/create', { + public createCheckoutSession({ + couponId, + priceId + }: { + couponId?: string; + priceId: string; + }) { + return this.http.post('/api/subscription/stripe/checkout-session', { + couponId, priceId }); } diff --git a/libs/common/src/lib/interfaces/subscription.interface.ts b/libs/common/src/lib/interfaces/subscription.interface.ts index a414068f1..29f5d3aba 100644 --- a/libs/common/src/lib/interfaces/subscription.interface.ts +++ b/libs/common/src/lib/interfaces/subscription.interface.ts @@ -1,4 +1,6 @@ export interface Subscription { + coupon?: number; + couponId?: string; price: number; priceId: string; } diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts index 01d3aa5be..a63394e0d 100644 --- a/libs/common/src/lib/interfaces/user-settings.interface.ts +++ b/libs/common/src/lib/interfaces/user-settings.interface.ts @@ -1,7 +1,7 @@ import { Currency, ViewMode } from '@prisma/client'; export interface UserSettings { - baseCurrency: Currency; + baseCurrency?: Currency; locale: string; - viewMode: ViewMode; + viewMode?: ViewMode; } diff --git a/libs/common/src/lib/interfaces/user-with-settings.ts b/libs/common/src/lib/interfaces/user-with-settings.ts index eae7789ad..8c6fce1fb 100644 --- a/libs/common/src/lib/interfaces/user-with-settings.ts +++ b/libs/common/src/lib/interfaces/user-with-settings.ts @@ -3,6 +3,7 @@ import { Account, Settings, User } from '@prisma/client'; export type UserWithSettings = User & { Account: Account[]; + permissions?: string[]; Settings: Settings; subscription?: { expiresAt?: Date; diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index 4d8f3a719..fe6c0d066 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -21,7 +21,8 @@ export const permissions = { updateAccount: 'updateAccount', updateAuthDevice: 'updateAuthDevice', updateOrder: 'updateOrder', - updateUserSettings: 'updateUserSettings' + updateUserSettings: 'updateUserSettings', + updateViewMode: 'updateViewMode' }; export function hasPermission( @@ -46,7 +47,8 @@ export function getPermissions(aRole: Role): string[] { permissions.updateAccount, permissions.updateAuthDevice, permissions.updateOrder, - permissions.updateUserSettings + permissions.updateUserSettings, + permissions.updateViewMode ]; case 'DEMO': @@ -62,7 +64,8 @@ export function getPermissions(aRole: Role): string[] { permissions.updateAccount, permissions.updateAuthDevice, permissions.updateOrder, - permissions.updateUserSettings + permissions.updateUserSettings, + permissions.updateViewMode ]; default: