From 811cda0a9911999f10634f3174f508086e3344a6 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Sun, 20 Jun 2021 13:09:12 +0200 Subject: [PATCH] Set up stripe for subscriptions --- apps/api/src/app/app.module.ts | 2 + apps/api/src/app/info/info.service.ts | 80 ++++++++++++------- .../subscription/subscription.controller.ts | 54 +++++++++++++ .../app/subscription/subscription.module.ts | 13 +++ .../app/subscription/subscription.service.ts | 74 +++++++++++++++++ .../api/src/services/configuration.service.ts | 1 + .../interfaces/environment.interface.ts | 1 + apps/client/src/app/app.module.ts | 5 +- .../pages/account/account-page.component.ts | 29 ++++++- .../src/app/pages/account/account-page.html | 16 ++-- .../pages/accounts/accounts-page.module.ts | 4 +- .../create-or-update-account-dialog.html | 4 +- .../create-or-update-account-dialog.module.ts | 2 +- .../pages/pricing/pricing-page.component.ts | 14 +++- .../src/app/pages/pricing/pricing-page.html | 13 ++- apps/client/src/app/services/data.service.ts | 6 ++ .../src/environments/environment.prod.ts | 1 + apps/client/src/environments/environment.ts | 1 + .../src/lib/interfaces/info-item.interface.ts | 2 + .../lib/interfaces/subscription.interface.ts | 4 + package.json | 6 +- replace.build.js | 28 +++++-- yarn.lock | 41 ++++++++++ 23 files changed, 345 insertions(+), 56 deletions(-) create mode 100644 apps/api/src/app/subscription/subscription.controller.ts create mode 100644 apps/api/src/app/subscription/subscription.module.ts create mode 100644 apps/api/src/app/subscription/subscription.service.ts create mode 100644 libs/common/src/lib/interfaces/subscription.interface.ts diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index f9644b2a0..c0f4ff88a 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -27,6 +27,7 @@ import { InfoModule } from './info/info.module'; import { OrderModule } from './order/order.module'; import { PortfolioModule } from './portfolio/portfolio.module'; import { RedisCacheModule } from './redis-cache/redis-cache.module'; +import { SubscriptionModule } from './subscription/subscription.module'; import { SymbolModule } from './symbol/symbol.module'; import { UserModule } from './user/user.module'; @@ -59,6 +60,7 @@ import { UserModule } from './user/user.module'; rootPath: join(__dirname, '..', 'client'), exclude: ['/api*'] }), + SubscriptionModule, SymbolModule, UserModule ], diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 580d39f35..a293ef4e6 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -1,6 +1,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { InfoItem } from '@ghostfolio/common/interfaces'; +import { Subscription } from '@ghostfolio/common/interfaces/subscription.interface'; import { permissions } from '@ghostfolio/common/permissions'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @@ -44,37 +45,8 @@ export class InfoService { currencies: Object.values(Currency), demoAuthToken: this.getDemoAuthToken(), lastDataGathering: await this.getLastDataGathering(), - statistics: await this.getStatistics() - }; - } - - private getDemoAuthToken() { - return this.jwtService.sign({ - id: InfoService.DEMO_USER_ID - }); - } - - private async getLastDataGathering() { - const lastDataGathering = await this.prisma.property.findUnique({ - where: { key: 'LAST_DATA_GATHERING' } - }); - - return lastDataGathering?.value ? new Date(lastDataGathering.value) : null; - } - - private async getStatistics() { - if (!this.configurationService.get('ENABLE_FEATURE_STATISTICS')) { - return undefined; - } - - const activeUsers1d = await this.countActiveUsers(1); - const activeUsers30d = await this.countActiveUsers(30); - const gitHubStargazers = await this.countGitHubStargazers(); - - return { - activeUsers1d, - activeUsers30d, - gitHubStargazers + statistics: await this.getStatistics(), + subscriptions: await this.getSubscriptions() }; } @@ -124,4 +96,50 @@ export class InfoService { return undefined; } } + + private getDemoAuthToken() { + return this.jwtService.sign({ + id: InfoService.DEMO_USER_ID + }); + } + + private async getLastDataGathering() { + const lastDataGathering = await this.prisma.property.findUnique({ + where: { key: 'LAST_DATA_GATHERING' } + }); + + return lastDataGathering?.value ? new Date(lastDataGathering.value) : null; + } + + private async getStatistics() { + if (!this.configurationService.get('ENABLE_FEATURE_STATISTICS')) { + return undefined; + } + + const activeUsers1d = await this.countActiveUsers(1); + const activeUsers30d = await this.countActiveUsers(30); + const gitHubStargazers = await this.countGitHubStargazers(); + + return { + activeUsers1d, + activeUsers30d, + gitHubStargazers + }; + } + + private async getSubscriptions(): Promise { + if (!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + return undefined; + } + + const stripeConfig = await this.prisma.property.findUnique({ + where: { key: 'STRIPE_CONFIG' } + }); + + if (stripeConfig) { + return [JSON.parse(stripeConfig.value)]; + } + + return []; + } } diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/subscription/subscription.controller.ts new file mode 100644 index 000000000..131109d45 --- /dev/null +++ b/apps/api/src/app/subscription/subscription.controller.ts @@ -0,0 +1,54 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; +import { RequestWithUser } from '@ghostfolio/common/types'; +import { + Body, + Controller, + Get, + HttpException, + Inject, + Post, + Req, + Res, + UseGuards +} from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; +import { StatusCodes, getReasonPhrase } from 'http-status-codes'; + +import { SubscriptionService } from './subscription.service'; + +@Controller('subscription') +export class SubscriptionController { + public constructor( + private readonly configurationService: ConfigurationService, + @Inject(REQUEST) private readonly request: RequestWithUser, + private readonly subscriptionService: SubscriptionService + ) {} + + @Get('stripe/callback') + public async stripeCallback(@Req() req, @Res() res) { + await this.subscriptionService.createSubscription( + req.query.checkoutSessionId + ); + + res.redirect(`${this.configurationService.get('ROOT_URL')}/account`); + } + + @Post('stripe/checkout-session/create') + @UseGuards(AuthGuard('jwt')) + public async createCheckoutSession(@Body() { priceId }: { priceId: string }) { + try { + return await this.subscriptionService.createCheckoutSession({ + priceId, + userId: this.request.user.id + }); + } catch (error) { + console.error(error); + + throw new HttpException( + getReasonPhrase(StatusCodes.BAD_REQUEST), + StatusCodes.BAD_REQUEST + ); + } + } +} diff --git a/apps/api/src/app/subscription/subscription.module.ts b/apps/api/src/app/subscription/subscription.module.ts new file mode 100644 index 000000000..34591d55a --- /dev/null +++ b/apps/api/src/app/subscription/subscription.module.ts @@ -0,0 +1,13 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; +import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { Module } from '@nestjs/common'; + +import { SubscriptionController } from './subscription.controller'; +import { SubscriptionService } from './subscription.service'; + +@Module({ + imports: [], + controllers: [SubscriptionController], + providers: [ConfigurationService, PrismaService, SubscriptionService] +}) +export class SubscriptionModule {} diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts new file mode 100644 index 000000000..b063cbe44 --- /dev/null +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -0,0 +1,74 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; +import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { Injectable } from '@nestjs/common'; +import { addDays } from 'date-fns'; +import Stripe from 'stripe'; + +@Injectable() +export class SubscriptionService { + private stripe: Stripe; + + public constructor( + private readonly configurationService: ConfigurationService, + private prisma: PrismaService + ) { + this.stripe = new Stripe( + this.configurationService.get('STRIPE_SECRET_KEY'), + { + apiVersion: '2020-08-27' + } + ); + } + + public async createCheckoutSession({ + priceId, + userId + }: { + priceId: string; + userId: string; + }) { + const session = await this.stripe.checkout.sessions.create({ + cancel_url: `${this.configurationService.get('ROOT_URL')}/account`, + client_reference_id: userId, + line_items: [ + { + price: priceId, + quantity: 1 + } + ], + metadata: { + user_id: userId + }, + mode: 'subscription', + payment_method_types: ['card'], + success_url: `${this.configurationService.get( + 'ROOT_URL' + )}/api/subscription/stripe/callback?checkoutSessionId={CHECKOUT_SESSION_ID}` + }); + + return { + sessionId: session.id + }; + } + + public async createSubscription(aCheckoutSessionId: string) { + try { + const session = await this.stripe.checkout.sessions.retrieve( + aCheckoutSessionId + ); + + await this.prisma.subscription.create({ + data: { + expiresAt: addDays(new Date(), 365), + User: { + connect: { + id: session.client_reference_id + } + } + } + }); + } catch (error) { + console.error(error); + } + } +} diff --git a/apps/api/src/services/configuration.service.ts b/apps/api/src/services/configuration.service.ts index af17e9989..f605032e5 100644 --- a/apps/api/src/services/configuration.service.ts +++ b/apps/api/src/services/configuration.service.ts @@ -28,6 +28,7 @@ export class ConfigurationService { REDIS_HOST: str({ default: 'localhost' }), REDIS_PORT: port({ default: 6379 }), ROOT_URL: str({ default: 'http://localhost:4200' }), + STRIPE_SECRET_KEY: str({ default: '' }), WEB_AUTH_RP_ID: host({ default: 'localhost' }) }); } diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 8d344c60f..520682247 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -19,5 +19,6 @@ export interface Environment extends CleanedEnvAccessors { REDIS_HOST: string; REDIS_PORT: number; ROOT_URL: string; + STRIPE_SECRET_KEY: string; WEB_AUTH_RP_ID: string; } diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts index 552013e12..6ccb38c04 100644 --- a/apps/client/src/app/app.module.ts +++ b/apps/client/src/app/app.module.ts @@ -15,7 +15,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MaterialCssVarsModule } from 'angular-material-css-vars'; import { MarkdownModule } from 'ngx-markdown'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { NgxStripeModule } from 'ngx-stripe'; +import { environment } from '../environments/environment'; import { CustomDateAdapter } from './adapter/custom-date-adapter'; import { DateFormats } from './adapter/date-formats'; import { AppRoutingModule } from './app-routing.module'; @@ -43,7 +45,8 @@ import { LanguageService } from './core/language.service'; }), MatNativeDateModule, MatSnackBarModule, - NgxSkeletonLoaderModule + NgxSkeletonLoaderModule, + NgxStripeModule.forRoot(environment.stripePublicKey) ], providers: [ authInterceptorProviders, 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 85ca88611..61929185d 100644 --- a/apps/client/src/app/pages/account/account-page.component.ts +++ b/apps/client/src/app/pages/account/account-page.component.ts @@ -16,8 +16,9 @@ import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Currency } from '@prisma/client'; +import { StripeService } from 'ngx-stripe'; import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { catchError, switchMap, takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-account-page', @@ -33,6 +34,8 @@ export class AccountPageComponent implements OnDestroy, OnInit { public currencies: Currency[] = []; public defaultDateFormat = DEFAULT_DATE_FORMAT; public hasPermissionToUpdateUserSettings: boolean; + public price: number; + public priceId: string; public user: User; private unsubscribeSubject = new Subject(); @@ -43,14 +46,19 @@ export class AccountPageComponent implements OnDestroy, OnInit { public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private stripeService: StripeService, private userService: UserService, public webAuthnService: WebAuthnService ) { this.dataService .fetchInfo() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ currencies }) => { + .subscribe(({ currencies, subscriptions }) => { this.currencies = currencies; + this.price = subscriptions?.[0].price; + this.priceId = subscriptions?.[0].priceId; + + this.changeDetectorRef.markForCheck(); }); this.userService.stateChanged @@ -99,6 +107,23 @@ export class AccountPageComponent implements OnDestroy, OnInit { }); } + public onCheckout(priceId: string) { + this.dataService + .createCheckoutSession(priceId) + .pipe( + switchMap(({ sessionId }: { sessionId: string }) => { + return this.stripeService.redirectToCheckout({ + sessionId + }); + }) + ) + .subscribe((result) => { + if (result.error) { + alert(result.error.message); + } + }); + } + public onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) { if (aEvent.checked) { this.registerDevice(); diff --git a/apps/client/src/app/pages/account/account-page.html b/apps/client/src/app/pages/account/account-page.html index 82d077e46..042c1f611 100644 --- a/apps/client/src/app/pages/account/account-page.html +++ b/apps/client/src/app/pages/account/account-page.html @@ -1,10 +1,7 @@
-

- {{ user.alias }} - Account -

+

Account

@@ -26,9 +23,18 @@ defaultDateFormat }}
- +
+ {{ user.settings.baseCurrency }} {{ price }} + per year +
diff --git a/apps/client/src/app/pages/accounts/accounts-page.module.ts b/apps/client/src/app/pages/accounts/accounts-page.module.ts index ec0212ed5..b9de21ff8 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.module.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.module.ts @@ -6,7 +6,7 @@ import { GfAccountsTableModule } from '@ghostfolio/client/components/accounts-ta import { AccountsPageRoutingModule } from './accounts-page-routing.module'; import { AccountsPageComponent } from './accounts-page.component'; -import { CreateOrUpdateAccountDialogModule } from './create-or-update-account-dialog/create-or-update-account-dialog.module'; +import { GfCreateOrUpdateAccountDialogModule } from './create-or-update-account-dialog/create-or-update-account-dialog.module'; @NgModule({ declarations: [AccountsPageComponent], @@ -14,8 +14,8 @@ import { CreateOrUpdateAccountDialogModule } from './create-or-update-account-di imports: [ AccountsPageRoutingModule, CommonModule, - CreateOrUpdateAccountDialogModule, GfAccountsTableModule, + GfCreateOrUpdateAccountDialogModule, MatButtonModule, RouterModule ], diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html index 5c0eef057..25c3fc430 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1,6 +1,6 @@
-

Update account

-

Add account

+

Update account

+

Add account

diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.module.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.module.ts index 4d7b0da77..ce5fed226 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.module.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.module.ts @@ -24,4 +24,4 @@ import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.c ], providers: [] }) -export class CreateOrUpdateAccountDialogModule {} +export class GfCreateOrUpdateAccountDialogModule {} 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 fe4a4df8b..b34ff6ace 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { baseCurrency } from '@ghostfolio/common/config'; import { User } from '@ghostfolio/common/interfaces'; @@ -13,6 +14,7 @@ import { takeUntil } from 'rxjs/operators'; export class PricingPageComponent implements OnInit { public baseCurrency = baseCurrency; public isLoggedIn: boolean; + public price: number; public user: User; private unsubscribeSubject = new Subject(); @@ -22,8 +24,18 @@ export class PricingPageComponent implements OnInit { */ public constructor( private changeDetectorRef: ChangeDetectorRef, + private dataService: DataService, private userService: UserService - ) {} + ) { + this.dataService + .fetchInfo() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ subscriptions }) => { + this.price = subscriptions?.[0]?.price; + + this.changeDetectorRef.markForCheck(); + }); + } /** * Initializes the controller diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index b912e6d1a..74479a839 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -176,11 +176,11 @@

Fully managed Ghostfolio cloud offering.

-

+

{{ user?.settings.baseCurrency || baseCurrency }} - 0.00 - 3.99 / Month{{ price }} + per year

@@ -188,6 +188,13 @@
+
diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index bf41116dd..0661b7c30 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -43,6 +43,12 @@ export class DataService { private settingsStorageService: SettingsStorageService ) {} + public createCheckoutSession(priceId) { + return this.http.post('/api/subscription/stripe/checkout-session/create', { + priceId + }); + } + public fetchAccounts() { return this.http.get('/api/account'); } diff --git a/apps/client/src/environments/environment.prod.ts b/apps/client/src/environments/environment.prod.ts index 41d27ee54..01ed05423 100644 --- a/apps/client/src/environments/environment.prod.ts +++ b/apps/client/src/environments/environment.prod.ts @@ -1,5 +1,6 @@ export const environment = { lastPublish: '{BUILD_TIMESTAMP}', production: true, + stripePublicKey: '{STRIPE_PUBLIC_KEY}', version: `v${require('../../../../package.json').version}` }; diff --git a/apps/client/src/environments/environment.ts b/apps/client/src/environments/environment.ts index 488a3b58e..58a0fe1cf 100644 --- a/apps/client/src/environments/environment.ts +++ b/apps/client/src/environments/environment.ts @@ -5,6 +5,7 @@ export const environment = { lastPublish: null, production: false, + stripePublicKey: '', version: 'dev' }; diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index cef64e5f6..e8a794840 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,6 +1,7 @@ import { Currency } from '@prisma/client'; import { Statistics } from './statistics.interface'; +import { Subscription } from './subscription.interface'; export interface InfoItem { currencies: Currency[]; @@ -13,4 +14,5 @@ export interface InfoItem { }; platforms: { id: string; name: string }[]; statistics: Statistics; + subscriptions: Subscription[]; } diff --git a/libs/common/src/lib/interfaces/subscription.interface.ts b/libs/common/src/lib/interfaces/subscription.interface.ts new file mode 100644 index 000000000..a414068f1 --- /dev/null +++ b/libs/common/src/lib/interfaces/subscription.interface.ts @@ -0,0 +1,4 @@ +export interface Subscription { + price: number; + priceId: string; +} diff --git a/package.json b/package.json index 01a0f42ad..e7a203936 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "affected:lint": "nx affected:lint", "affected:test": "nx affected:test", "angular": "node --max_old_space_size=32768 ./node_modules/@angular/cli/bin/ng", - "build:all": "ng build --prod api && ng build --prod client && yarn replace-placeholders-in-build", + "build:all": "ng build --configuration production api && ng build --configuration production client && yarn replace-placeholders-in-build", "clean": "rimraf dist", "database:format-schema": "prisma format", "database:generate-typings": "prisma generate", @@ -69,6 +69,7 @@ "@simplewebauthn/browser": "3.0.0", "@simplewebauthn/server": "3.0.0", "@simplewebauthn/typescript-types": "3.0.0", + "@stripe/stripe-js": "1.15.0", "@types/lodash": "4.14.168", "alphavantage": "2.2.0", "angular-material-css-vars": "1.2.0", @@ -92,6 +93,7 @@ "ngx-device-detector": "2.1.1", "ngx-markdown": "12.0.1", "ngx-skeleton-loader": "2.9.1", + "ngx-stripe": "12.0.2", "passport": "0.4.1", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.0", @@ -99,6 +101,7 @@ "reflect-metadata": "0.1.13", "round-to": "5.0.0", "rxjs": "6.6.7", + "stripe": "8.156.0", "svgmap": "2.1.1", "uuid": "8.3.2", "yahoo-finance": "0.3.6", @@ -129,6 +132,7 @@ "@typescript-eslint/parser": "4.27.0", "codelyzer": "6.0.1", "cypress": "6.2.1", + "dotenv": "8.2.0", "eslint": "7.28.0", "eslint-config-prettier": "8.3.0", "eslint-plugin-import": "2.23.4", diff --git a/replace.build.js b/replace.build.js index 2e4afd9ed..90cc3ff5c 100644 --- a/replace.build.js +++ b/replace.build.js @@ -1,4 +1,11 @@ +const dotenv = require('dotenv'); +const path = require('path'); const replace = require('replace-in-file'); + +dotenv.config({ + path: path.resolve(__dirname, '.env') +}); + const now = new Date(); const buildTimestamp = `${formatWithTwoDigits( now.getDate() @@ -7,17 +14,24 @@ const buildTimestamp = `${formatWithTwoDigits( )}.${now.getFullYear()} ${formatWithTwoDigits( now.getHours() )}:${formatWithTwoDigits(now.getMinutes())}`; -const options = { - files: './dist/apps/client/main.*.js', - from: /{BUILD_TIMESTAMP}/g, - to: buildTimestamp, - allowEmptyPaths: false -}; try { - const changedFiles = replace.sync(options); + let changedFiles = replace.sync({ + files: './dist/apps/client/main.*.js', + from: /{BUILD_TIMESTAMP}/g, + to: buildTimestamp, + allowEmptyPaths: false + }); console.log('Build version set: ' + buildTimestamp); console.log(changedFiles); + + changedFiles = replace.sync({ + files: './dist/apps/client/main.*.js', + from: /{STRIPE_PUBLIC_KEY}/g, + to: process.env.STRIPE_PUBLIC_KEY ?? '', + allowEmptyPaths: false + }); + console.log(changedFiles); } catch (error) { console.error('Error occurred:', error); } diff --git a/yarn.lock b/yarn.lock index 0dd28ea5c..12db72e43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2454,6 +2454,11 @@ resolved "https://registry.yarnpkg.com/@stencil/core/-/core-2.5.2.tgz#ad00d495ee37bbed4044524d59c7f22de15ab4a7" integrity sha512-bgjPXkSzzg1WnTgVUm6m5ZzpKt602WmA/QljODAW1xVN40OHJdbGblzF/F6MFzqv2c5Cy30CB41arc8qADIdcQ== +"@stripe/stripe-js@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.15.0.tgz#86178cfbe66151910b09b03595e60048ab4c698e" + integrity sha512-KQsNPc+uVQkc8dewwz1A6uHOWeU2cWoZyNIbsx5mtmperr5TPxw4u8M20WOa22n6zmIOh/zLdzEe8DYK/0IjBw== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2656,6 +2661,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.33.tgz#9e4f8c64345522e4e8ce77b334a8aaa64e2b6c78" integrity sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g== +"@types/node@>=8.1.0": + version "15.12.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.3.tgz#2817bf5f25bc82f56579018c53f7d41b1830b1af" + integrity sha512-SNt65CPCXvGNDZ3bvk1TQ0Qxoe3y1RKH88+wZ2Uf05dduBCqqFQ76ADP9pbT+Cpvj60SkRppMCh2Zo8tDixqjQ== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -9623,6 +9633,13 @@ ngx-skeleton-loader@2.9.1: perf-marks "^1.13.4" tslib "^1.10.0" +ngx-stripe@12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/ngx-stripe/-/ngx-stripe-12.0.2.tgz#b250acc2a08dc96dac035fc0a67b4a8cbeca3efb" + integrity sha512-/arfIi996yv3EpzqjYsb20TUdQ9t+GVMNVIx1mdsiWcpiNjL36tO3lG45T0hyiBJNAds87Ag40Fm8PfsuHFCUw== + dependencies: + tslib "^2.1.0" + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -11329,6 +11346,13 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.6.0: + version "6.10.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -12185,6 +12209,15 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -12692,6 +12725,14 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +stripe@8.156.0: + version "8.156.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.156.0.tgz#040de551df88d71ef670a8c8d4df114c3fa6eb4b" + integrity sha512-q+bixlhaxnSI/Htk/iB1i5LhuZ557hL0pFgECBxQNhso1elxIsOsPOIXEuo3tSLJEb8CJSB7t/+Fyq6KP69tAQ== + dependencies: + "@types/node" ">=8.1.0" + qs "^6.6.0" + style-loader@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"