Überarbeitung des Stripe Prozesses
This commit is contained in:
parent
7a286e3519
commit
74d5f92aba
|
|
@ -31,9 +31,8 @@ export const users = pgTable('users', {
|
||||||
updated: timestamp('updated'),
|
updated: timestamp('updated'),
|
||||||
latitude: doublePrecision('latitude'),
|
latitude: doublePrecision('latitude'),
|
||||||
longitude: doublePrecision('longitude'),
|
longitude: doublePrecision('longitude'),
|
||||||
stripeCustomerId: text('stripeCustomerId'),
|
|
||||||
subscriptionId: text('subscriptionId'),
|
subscriptionId: text('subscriptionId'),
|
||||||
subscriptionPlan: subscriptionTypeEnum('subscriptionType'),
|
subscriptionPlan: subscriptionTypeEnum('subscriptionPlan'),
|
||||||
// embedding: vector('embedding', { dimensions: 1536 }),
|
// embedding: vector('embedding', { dimensions: 1536 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
|
|
||||||
<p>Your subscription details are as follows:</p>
|
<p>Your subscription details are as follows:</p>
|
||||||
|
|
||||||
<p><span class="plan-info">{{#if (eq subscriptionPlan "professional")}}Professional (CPA, Attorney, Title Company) Plan{{else if (eq subscriptionPlan "broker")}}Business Broker Plan{{/if}}</span></p>
|
<p><span class="plan-info">{{#if (eq subscriptionPlan "professional")}}Professional Plan (CPA, Attorney, Title Company, Surveyor, Appraiser){{else if (eq subscriptionPlan "broker")}}Business Broker Plan{{/if}}</span></p>
|
||||||
|
|
||||||
<p>If you have any questions or need further assistance, please feel free to contact our support team at any time.</p>
|
<p>If you have any questions or need further assistance, please feel free to contact our support team at any time.</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ async function bootstrap() {
|
||||||
origin: '*',
|
origin: '*',
|
||||||
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App
|
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
allowedHeaders: 'Content-Type, Accept, Authorization',
|
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
|
||||||
});
|
});
|
||||||
//origin: 'http://localhost:4200',
|
//origin: 'http://localhost:4200',
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
|
|
|
||||||
|
|
@ -157,10 +157,7 @@ export const UserSchema = z
|
||||||
customerSubType: CustomerSubTypeEnum.optional().nullable(),
|
customerSubType: CustomerSubTypeEnum.optional().nullable(),
|
||||||
created: z.date().optional().nullable(),
|
created: z.date().optional().nullable(),
|
||||||
updated: z.date().optional().nullable(),
|
updated: z.date().optional().nullable(),
|
||||||
stripeCustomerId: z.string().optional().nullable(),
|
|
||||||
subscriptionId: z.string().optional().nullable(),
|
subscriptionId: z.string().optional().nullable(),
|
||||||
planActive: z.boolean().optional().nullable(),
|
|
||||||
planExpires: z.date().optional().nullable(),
|
|
||||||
subscriptionPlan: SubscriptionTypeEnum.optional().nullable(),
|
subscriptionPlan: SubscriptionTypeEnum.optional().nullable(),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
|
|
|
||||||
|
|
@ -322,10 +322,7 @@ export function createDefaultUser(email: string, firstname: string, lastname: st
|
||||||
customerSubType: null,
|
customerSubType: null,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
updated: new Date(),
|
updated: new Date(),
|
||||||
stripeCustomerId: null,
|
|
||||||
subscriptionId: null,
|
subscriptionId: null,
|
||||||
planActive: false,
|
|
||||||
planExpires: null,
|
|
||||||
subscriptionPlan: subscriptionPlan,
|
subscriptionPlan: subscriptionPlan,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ export class PaymentController {
|
||||||
const signature = req.headers['stripe-signature'] as string;
|
const signature = req.headers['stripe-signature'] as string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const event = await this.paymentService.constructEvent(req.body, signature);
|
// Konvertieren Sie den req.body Buffer in einen lesbaren String
|
||||||
|
const payload = req.body instanceof Buffer ? req.body.toString('utf8') : req.body;
|
||||||
|
const event = await this.paymentService.constructEvent(payload, signature);
|
||||||
|
// const event = await this.paymentService.constructEvent(req.body, signature);
|
||||||
|
|
||||||
if (event.type === 'checkout.session.completed') {
|
if (event.type === 'checkout.session.completed') {
|
||||||
await this.paymentService.handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session);
|
await this.paymentService.handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session);
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ export class PaymentService {
|
||||||
const newCustomer = await this.stripe.customers.create({
|
const newCustomer = await this.stripe.customers.create({
|
||||||
email: checkout.email,
|
email: checkout.email,
|
||||||
name: checkout.name,
|
name: checkout.name,
|
||||||
|
shipping: {
|
||||||
|
name: checkout.name,
|
||||||
|
address: {
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
country: 'US',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
customerId = newCustomer.id;
|
customerId = newCustomer.id;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +68,6 @@ export class PaymentService {
|
||||||
],
|
],
|
||||||
success_url: `${process.env.WEB_HOST}/success`,
|
success_url: `${process.env.WEB_HOST}/success`,
|
||||||
cancel_url: `${process.env.WEB_HOST}/pricing`,
|
cancel_url: `${process.env.WEB_HOST}/pricing`,
|
||||||
// customer_email: checkout.email,
|
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
shipping_address_collection: {
|
shipping_address_collection: {
|
||||||
allowed_countries: ['US'],
|
allowed_countries: ['US'],
|
||||||
|
|
@ -84,25 +91,29 @@ export class PaymentService {
|
||||||
return this.stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
|
return this.stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
|
||||||
}
|
}
|
||||||
async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise<void> {
|
async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise<void> {
|
||||||
this.logger.info(JSON.stringify(session));
|
try {
|
||||||
const keycloakUsers = await this.authService.getUsers();
|
const keycloakUsers = await this.authService.getUsers();
|
||||||
const keycloakUser = keycloakUsers.find(u => u.email === session.customer_details.email);
|
const keycloakUser = keycloakUsers.find(u => u.email === session.customer_details.email);
|
||||||
const user = await this.userService.getUserByMail(session.customer_details.email, {
|
const user = await this.userService.getUserByMail(session.customer_details.email, {
|
||||||
userId: keycloakUser.id,
|
userId: keycloakUser.id,
|
||||||
firstname: keycloakUser.firstName,
|
firstname: keycloakUser.firstName,
|
||||||
lastname: keycloakUser.lastName,
|
lastname: keycloakUser.lastName,
|
||||||
username: keycloakUser.email,
|
username: keycloakUser.email,
|
||||||
roles: [],
|
roles: [],
|
||||||
});
|
});
|
||||||
user.stripeCustomerId = session.customer as string;
|
this.logger.info(JSON.stringify(session));
|
||||||
user.subscriptionId = session.subscription as string;
|
user.subscriptionId = session.subscription as string;
|
||||||
user.customerType = 'professional';
|
const subscription = await this.stripe.subscriptions.retrieve(user.subscriptionId);
|
||||||
if (session.metadata['plan'] === 'Broker Plan') {
|
user.customerType = 'professional';
|
||||||
user.customerSubType = 'broker';
|
if (subscription.metadata['plan'] === 'Broker Plan') {
|
||||||
|
user.customerSubType = 'broker';
|
||||||
|
}
|
||||||
|
user.subscriptionPlan = subscription.metadata['plan'] === 'Broker Plan' ? 'broker' : 'professional'; //session.metadata['subscriptionPlan'] as 'free' | 'professional' | 'broker';
|
||||||
|
await this.userService.saveUser(user, false);
|
||||||
|
await this.mailService.sendSubscriptionConfirmation(user);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
}
|
}
|
||||||
user.subscriptionPlan = session.metadata['plan'] === 'Broker Plan' ? 'broker' : 'professional'; //session.metadata['subscriptionPlan'] as 'free' | 'professional' | 'broker';
|
|
||||||
this.userService.saveUser(user);
|
|
||||||
this.mailService.sendSubscriptionConfirmation(user);
|
|
||||||
}
|
}
|
||||||
async getSubscription(email: string): Promise<Stripe.Subscription[]> {
|
async getSubscription(email: string): Promise<Stripe.Subscription[]> {
|
||||||
const existingCustomers = await this.stripe.customers.list({
|
const existingCustomers = await this.stripe.customers.list({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Body, Controller, Get, Inject, Param, Post, Query, Request, UseGuards } from '@nestjs/common';
|
import { BadRequestException, Body, Controller, Get, Inject, Param, Post, Query, Request, UseGuards } from '@nestjs/common';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
|
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
|
||||||
import { User } from '../models/db.model';
|
import { User } from '../models/db.model';
|
||||||
|
|
@ -31,13 +32,32 @@ export class UserController {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@Post()
|
@Post()
|
||||||
save(@Body() user: any): Promise<User> {
|
async save(@Body() user: any): Promise<User> {
|
||||||
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
|
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
|
||||||
const savedUser = this.userService.saveUser(user);
|
try {
|
||||||
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
|
const savedUser = await this.userService.saveUser(user);
|
||||||
|
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
|
||||||
|
return savedUser;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const filteredErrors = error.errors
|
||||||
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
field: item.path[0],
|
||||||
|
}))
|
||||||
|
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
||||||
|
throw new BadRequestException(filteredErrors);
|
||||||
|
}
|
||||||
|
throw error; // Andere Fehler einfach durchreichen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Post('guaranteed')
|
||||||
|
async saveGuaranteed(@Body() user: any): Promise<User> {
|
||||||
|
this.logger.info(`Saving user guaranteed: ${JSON.stringify(user)}`);
|
||||||
|
const savedUser = await this.userService.saveUser(user, false);
|
||||||
|
this.logger.info(`User persisted guaranteed: ${JSON.stringify(savedUser)}`);
|
||||||
return savedUser;
|
return savedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('search')
|
@Post('search')
|
||||||
find(@Body() criteria: UserListingCriteria): any {
|
find(@Body() criteria: UserListingCriteria): any {
|
||||||
this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`);
|
this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { and, count, eq, ilike, inArray, or, SQL, sql } from 'drizzle-orm';
|
import { and, count, eq, ilike, inArray, or, SQL, sql } from 'drizzle-orm';
|
||||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { ZodError } from 'zod';
|
|
||||||
import * as schema from '../drizzle/schema.js';
|
import * as schema from '../drizzle/schema.js';
|
||||||
import { customerSubTypeEnum, PG_CONNECTION } from '../drizzle/schema.js';
|
import { customerSubTypeEnum, PG_CONNECTION } from '../drizzle/schema.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
|
|
@ -97,7 +96,7 @@ export class UserService {
|
||||||
.where(sql`email = ${email}`)) as User[];
|
.where(sql`email = ${email}`)) as User[];
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
const user: User = { id: undefined, customerType: 'buyer', ...createDefaultUser(email, jwtuser.firstname, jwtuser.lastname, null) };
|
const user: User = { id: undefined, customerType: 'buyer', ...createDefaultUser(email, jwtuser.firstname, jwtuser.lastname, null) };
|
||||||
const u = await this.saveUser(user);
|
const u = await this.saveUser(user, false);
|
||||||
return convertDrizzleUserToUser(u);
|
return convertDrizzleUserToUser(u);
|
||||||
} else {
|
} else {
|
||||||
const user = users[0];
|
const user = users[0];
|
||||||
|
|
@ -117,7 +116,7 @@ export class UserService {
|
||||||
user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email));
|
user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email));
|
||||||
return convertDrizzleUserToUser(user);
|
return convertDrizzleUserToUser(user);
|
||||||
}
|
}
|
||||||
async saveUser(user: User): Promise<User> {
|
async saveUser(user: User, processValidation = true): Promise<User> {
|
||||||
try {
|
try {
|
||||||
user.updated = new Date();
|
user.updated = new Date();
|
||||||
if (user.id) {
|
if (user.id) {
|
||||||
|
|
@ -125,7 +124,10 @@ export class UserService {
|
||||||
} else {
|
} else {
|
||||||
user.created = new Date();
|
user.created = new Date();
|
||||||
}
|
}
|
||||||
const validatedUser = UserSchema.parse(user);
|
let validatedUser = user;
|
||||||
|
if (processValidation) {
|
||||||
|
validatedUser = UserSchema.parse(user);
|
||||||
|
}
|
||||||
const drizzleUser = convertUserToDrizzleUser(validatedUser);
|
const drizzleUser = convertUserToDrizzleUser(validatedUser);
|
||||||
if (user.id) {
|
if (user.id) {
|
||||||
const [updateUser] = await this.conn.update(schema.users).set(drizzleUser).where(eq(schema.users.id, user.id)).returning();
|
const [updateUser] = await this.conn.update(schema.users).set(drizzleUser).where(eq(schema.users.id, user.id)).returning();
|
||||||
|
|
@ -135,15 +137,6 @@ export class UserService {
|
||||||
return convertDrizzleUserToUser(newUser) as User;
|
return convertDrizzleUserToUser(newUser) as User;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ZodError) {
|
|
||||||
const filteredErrors = error.errors
|
|
||||||
.map(item => ({
|
|
||||||
...item,
|
|
||||||
field: item.path[0],
|
|
||||||
}))
|
|
||||||
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
|
||||||
throw new BadRequestException(filteredErrors);
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,22 @@ import { LoadingService } from '../services/loading.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoadingInterceptor implements HttpInterceptor {
|
export class LoadingInterceptor implements HttpInterceptor {
|
||||||
constructor(private loadingService: LoadingService) {}
|
constructor(private loadingService: LoadingService) {}
|
||||||
|
|
||||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
|
const hideLoading = request.headers.get('X-Hide-Loading') === 'true';
|
||||||
const requestId = `HTTP-${v4()}`;
|
const requestId = `HTTP-${v4()}`;
|
||||||
this.loadingService.startLoading(requestId, request.url);
|
|
||||||
|
if (!hideLoading) {
|
||||||
|
this.loadingService.startLoading(requestId, request.url);
|
||||||
|
}
|
||||||
|
|
||||||
return next.handle(request).pipe(
|
return next.handle(request).pipe(
|
||||||
tap({
|
tap({
|
||||||
finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist
|
finalize: () => {
|
||||||
// Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird,
|
if (!hideLoading) {
|
||||||
// egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde.
|
this.loadingService.stopLoading(requestId);
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
@if(user){
|
@if(user){
|
||||||
<a routerLink="/account" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Account</a>
|
<a routerLink="/account" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Account</a>
|
||||||
} @else {
|
} @else {
|
||||||
<a routerLink="/pricing" class="text-gray-800">Pricing</a>
|
<!-- <a routerLink="/pricing" class="text-gray-800">Pricing</a> -->
|
||||||
<a (click)="login()" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Log In</a>
|
<a (click)="login()" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Log In</a>
|
||||||
<a routerLink="/pricing" class="text-white bg-blue-600 px-4 py-2 rounded">Register</a>
|
<a routerLink="/pricing" class="text-white bg-blue-600 px-4 py-2 rounded">Register</a>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,9 @@ export class PricingComponent {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (priceId) {
|
if (priceId) {
|
||||||
this.keycloakService.register({ redirectUri: `${window.location.origin}/pricing/${btoa(priceId)}` });
|
this.keycloakService.register({
|
||||||
|
redirectUri: `${window.location.origin}/pricing/${btoa(priceId)}`,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
|
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,9 +243,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-start">
|
|
||||||
<button routerLink="/pricing" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Upgrade Subscription Plan</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-8 sm:hidden">
|
<div class="mt-8 sm:hidden">
|
||||||
<h3 class="text-lg font-medium text-gray-700 mb-1">Membership Level</h3>
|
<h3 class="text-lg font-medium text-gray-700 mb-1">Membership Level</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
|
@ -279,6 +277,16 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if(user.subscriptionPlan==='free'){
|
||||||
|
<div class="flex justify-start">
|
||||||
|
<button
|
||||||
|
routerLink="/pricing"
|
||||||
|
class="py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
Upgrade Subscription Plan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DatePipe, TitleCasePipe } from '@angular/common';
|
import { DatePipe, TitleCasePipe } from '@angular/common';
|
||||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { initFlowbite } from 'flowbite';
|
import { initFlowbite } from 'flowbite';
|
||||||
|
|
@ -32,7 +32,7 @@ import { SharedService } from '../../../services/shared.service';
|
||||||
import { SubscriptionsService } from '../../../services/subscriptions.service';
|
import { SubscriptionsService } from '../../../services/subscriptions.service';
|
||||||
import { UserService } from '../../../services/user.service';
|
import { UserService } from '../../../services/user.service';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
import { map2User } from '../../../utils/utils';
|
import { checkAndUpdate, map2User } from '../../../utils/utils';
|
||||||
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: 'app-account',
|
||||||
|
|
@ -93,6 +93,7 @@ export class AccountComponent {
|
||||||
private validationMessagesService: ValidationMessagesService,
|
private validationMessagesService: ValidationMessagesService,
|
||||||
private subscriptionService: SubscriptionsService,
|
private subscriptionService: SubscriptionsService,
|
||||||
private datePipe: DatePipe,
|
private datePipe: DatePipe,
|
||||||
|
private router: Router,
|
||||||
) {}
|
) {}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -108,9 +109,11 @@ export class AccountComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email));
|
this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email));
|
||||||
if (this.subscriptions.length === 0) {
|
await this.synchronizeSubscriptions(this.subscriptions);
|
||||||
this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }];
|
// if (this.subscriptions.length === 0) {
|
||||||
}
|
// this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }];
|
||||||
|
// }
|
||||||
|
|
||||||
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||||
this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||||
|
|
||||||
|
|
@ -128,6 +131,36 @@ export class AccountComponent {
|
||||||
label: this.titleCasePipe.transform(type.name),
|
label: this.titleCasePipe.transform(type.name),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
async synchronizeSubscriptions(subscriptions: StripeSubscription[]) {
|
||||||
|
let changed = false;
|
||||||
|
if (this.subscriptions.length === 0) {
|
||||||
|
if (!this.user.subscriptionPlan) {
|
||||||
|
this.router.navigate(['pricing']);
|
||||||
|
} else {
|
||||||
|
this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }];
|
||||||
|
changed = checkAndUpdate(changed, this.user.customerType !== 'buyer' && this.user.customerType !== 'seller', () => (this.user.customerType = 'buyer'));
|
||||||
|
changed = checkAndUpdate(changed, !!this.user.customerSubType, () => (this.user.customerSubType = null));
|
||||||
|
changed = checkAndUpdate(changed, this.user.subscriptionPlan !== 'free', () => (this.user.subscriptionPlan = 'free'));
|
||||||
|
changed = checkAndUpdate(changed, !!this.user.subscriptionId, () => (this.user.subscriptionId = null));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const subscription = subscriptions[0];
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional'));
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerSubType !== 'broker', () => (this.user.customerSubType = 'broker'));
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.subscriptionPlan !== 'broker', () => (this.user.subscriptionPlan = 'broker'));
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && !this.user.subscriptionId, () => (this.user.subscriptionId = subscription.id));
|
||||||
|
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional'));
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionPlan !== 'professional', () => (this.user.subscriptionPlan = 'professional'));
|
||||||
|
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionId !== 'professional', () => (this.user.subscriptionId = subscription.id));
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
await this.userService.saveGuaranteed(this.user);
|
||||||
|
this.cdref.detectChanges();
|
||||||
|
this.cdref.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
<div class="min-h-screen flex items-center justify-center">
|
<div class="min-h-screen flex items-center justify-center">
|
||||||
<div class="bg-white p-6 rounded-lg shadow-lg max-w-md">
|
<div class="bg-white p-6 rounded-lg shadow-lg max-w-md">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
@if(user && (user.subscriptionPlan==='professional' || user.subscriptionPlan==='broker')){
|
<!-- Neue Bedingung hinzufügen -->
|
||||||
|
@if(maxAttemptsReached) {
|
||||||
|
<h2 class="text-2xl font-bold text-red-800 mt-4">We're sorry!</h2>
|
||||||
|
<p class="text-gray-600 mt-2">
|
||||||
|
We regret to inform you that we have not yet received any response from our payment service provider regarding the status of your subscription. Please log in to the
|
||||||
|
<a href="https://www.bizmatch.net" class="text-blue-500 hover:underline">website</a> and check your subscription status under the Account menu. If you have any questions, please contact us at
|
||||||
|
<a href="mailto:support@bizmatch.net" class="text-blue-500 hover:underline">support@bizmatch.net</a>.
|
||||||
|
</p>
|
||||||
|
} @else if(user && (user.subscriptionPlan==='professional' || user.subscriptionPlan==='broker')) {
|
||||||
<svg class="mx-auto h-16 w-16 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="mx-auto h-16 w-16 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ import { map2User } from '../../utils/utils';
|
||||||
})
|
})
|
||||||
export class SuccessComponent {
|
export class SuccessComponent {
|
||||||
user: User;
|
user: User;
|
||||||
|
maxAttemptsReached: boolean = false; // Neue Variable hinzufügen
|
||||||
|
|
||||||
constructor(private keycloakService: KeycloakService, private userService: UserService, private logService: LogService, private router: Router) {}
|
constructor(private keycloakService: KeycloakService, private userService: UserService, private logService: LogService, private router: Router) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
let email = null;
|
let email = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -29,6 +32,7 @@ export class SuccessComponent {
|
||||||
this.checkSubscriptionPlan(email, e.message);
|
this.checkSubscriptionPlan(email, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkSubscriptionPlan(email: string, error?: string) {
|
async checkSubscriptionPlan(email: string, error?: string) {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
this.logService.log({ severity: 'error', text: `Unauthorized Access to Success Page ${error}` });
|
this.logService.log({ severity: 'error', text: `Unauthorized Access to Success Page ${error}` });
|
||||||
|
|
@ -44,12 +48,13 @@ export class SuccessComponent {
|
||||||
if (attempts >= maxAttempts) {
|
if (attempts >= maxAttempts) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
console.error('Max attempts reached');
|
console.error('Max attempts reached');
|
||||||
|
this.maxAttemptsReached = true; // Setze die Variable auf true, wenn die max. Versuche erreicht wurden
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
this.user = await this.userService.getByMail(email);
|
this.user = await this.userService.getByMail(email, true);
|
||||||
|
|
||||||
if (this.user && this.user.subscriptionPlan) {
|
if (this.user && this.user.subscriptionPlan) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { lastValueFrom, Observable } from 'rxjs';
|
import { lastValueFrom, Observable } from 'rxjs';
|
||||||
import urlcat from 'urlcat';
|
import urlcat from 'urlcat';
|
||||||
|
|
@ -20,12 +20,19 @@ export class UserService {
|
||||||
async save(user: User): Promise<User> {
|
async save(user: User): Promise<User> {
|
||||||
return await lastValueFrom(this.http.post<User>(`${this.apiBaseUrl}/bizmatch/user`, user));
|
return await lastValueFrom(this.http.post<User>(`${this.apiBaseUrl}/bizmatch/user`, user));
|
||||||
}
|
}
|
||||||
|
async saveGuaranteed(user: User): Promise<User> {
|
||||||
|
return await lastValueFrom(this.http.post<User>(`${this.apiBaseUrl}/bizmatch/user/guaranteed`, user));
|
||||||
|
}
|
||||||
async getById(id: string): Promise<User> {
|
async getById(id: string): Promise<User> {
|
||||||
return await lastValueFrom(this.http.get<User>(`${this.apiBaseUrl}/bizmatch/user/${id}`));
|
return await lastValueFrom(this.http.get<User>(`${this.apiBaseUrl}/bizmatch/user/${id}`));
|
||||||
}
|
}
|
||||||
async getByMail(mail: string): Promise<User> {
|
async getByMail(mail: string, hideLoading: boolean = true): Promise<User> {
|
||||||
const url = urlcat(`${this.apiBaseUrl}/bizmatch/user`, { mail });
|
const url = urlcat(`${this.apiBaseUrl}/bizmatch/user`, { mail });
|
||||||
return await lastValueFrom(this.http.get<User>(url));
|
let headers = new HttpHeaders();
|
||||||
|
if (hideLoading) {
|
||||||
|
headers = headers.set('X-Hide-Loading', 'true');
|
||||||
|
}
|
||||||
|
return await lastValueFrom(this.http.get<User>(url, { headers }));
|
||||||
}
|
}
|
||||||
async search(criteria?: UserListingCriteria): Promise<ResponseUsersArray> {
|
async search(criteria?: UserListingCriteria): Promise<ResponseUsersArray> {
|
||||||
return await lastValueFrom(this.http.post<ResponseUsersArray>(`${this.apiBaseUrl}/bizmatch/user/search`, criteria));
|
return await lastValueFrom(this.http.post<ResponseUsersArray>(`${this.apiBaseUrl}/bizmatch/user/search`, criteria));
|
||||||
|
|
|
||||||
|
|
@ -287,6 +287,12 @@ export function assignProperties(target, source) {
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
export function checkAndUpdate(changed: boolean, condition: boolean, assignment: () => any): boolean {
|
||||||
|
if (condition) {
|
||||||
|
assignment();
|
||||||
|
}
|
||||||
|
return changed || condition;
|
||||||
|
}
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Criteria Proxy
|
// Criteria Proxy
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue