Stripe Pricing + Subscriptions
This commit is contained in:
parent
48bff89526
commit
b4609d07ba
File diff suppressed because it is too large
Load Diff
|
|
@ -25,10 +25,6 @@ import { UserModule } from './user/user.module.js';
|
|||
// const __dirname = path.dirname(__filename);
|
||||
|
||||
function loadEnvFiles() {
|
||||
// Load the .env file
|
||||
dotenv.config();
|
||||
console.log('Loaded .env file');
|
||||
|
||||
// Determine which additional env file to load
|
||||
let envFilePath = '';
|
||||
const host = process.env.HOST_NAME || '';
|
||||
|
|
@ -48,6 +44,13 @@ function loadEnvFiles() {
|
|||
} else {
|
||||
console.log(`No additional .env file found for HOST_NAME: ${host}`);
|
||||
}
|
||||
|
||||
// Load the .env file
|
||||
dotenv.config();
|
||||
console.log('Loaded .env file');
|
||||
// Output all loaded environment variables
|
||||
console.log('Loaded environment variables:');
|
||||
console.log(JSON.stringify(process.env, null, 2));
|
||||
}
|
||||
|
||||
loadEnvFiles();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import ky from 'ky';
|
||||
import { KeycloakUser } from 'src/models/main.model';
|
||||
import urlcat from 'urlcat';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -37,7 +38,7 @@ export class AuthService {
|
|||
}
|
||||
}
|
||||
|
||||
public async getUsers() {
|
||||
public async getUsers(): Promise<KeycloakUser[]> {
|
||||
const token = await this.getAccessToken();
|
||||
const URL = `${process.env.host}${process.env.usersURL}`;
|
||||
const response = await ky
|
||||
|
|
@ -48,7 +49,7 @@ export class AuthService {
|
|||
},
|
||||
})
|
||||
.json();
|
||||
return response;
|
||||
return response as KeycloakUser[];
|
||||
}
|
||||
public async getUser(userid: string) {
|
||||
const token = await this.getAccessToken();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import Stripe from 'stripe';
|
||||
import { BusinessListing, CommercialPropertyListing, Sender, User } from './db.model.js';
|
||||
import { State } from './server.model.js';
|
||||
|
||||
|
|
@ -258,6 +259,7 @@ export interface ModalResult {
|
|||
export interface Checkout {
|
||||
priceId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
export function isEmpty(value: any): boolean {
|
||||
// Check for undefined or null
|
||||
|
|
@ -298,7 +300,7 @@ export interface ValidationMessage {
|
|||
field: string;
|
||||
message: string;
|
||||
}
|
||||
export function createDefaultUser(email: string, firstname: string, lastname: string, subscriptionPlan: 'free' | 'professional' | 'broker'): User {
|
||||
export function createDefaultUser(email: string, firstname: string, lastname: string, subscriptionPlan: 'professional' | 'broker'): User {
|
||||
return {
|
||||
id: undefined,
|
||||
email,
|
||||
|
|
@ -374,3 +376,4 @@ export function createDefaultBusinessListing(): BusinessListing {
|
|||
listingsCategory: 'business',
|
||||
};
|
||||
}
|
||||
export type StripeSubscription = Stripe.Subscription;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Body, Controller, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common';
|
||||
import { Body, Controller, Get, HttpException, HttpStatus, Param, Post, Req, Res } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { Checkout } from 'src/models/main.model.js';
|
||||
import Stripe from 'stripe';
|
||||
|
|
@ -13,8 +13,8 @@ export class PaymentController {
|
|||
// return this.paymentService.createSubscription(subscriptionData);
|
||||
// }
|
||||
@Post('create-checkout-session')
|
||||
async calculateTax(@Body() checkout: Checkout) {
|
||||
return this.paymentService.checkout(checkout);
|
||||
async createCheckoutSession(@Body() checkout: Checkout) {
|
||||
return this.paymentService.createCheckoutSession(checkout);
|
||||
}
|
||||
@Post('webhook')
|
||||
async handleWebhook(@Req() req: Request, @Res() res: Response): Promise<void> {
|
||||
|
|
@ -33,4 +33,8 @@ export class PaymentController {
|
|||
throw new HttpException('Webhook Error', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
@Get('subscriptions/:email')
|
||||
async findSubscriptionsById(@Param('email') email: string): Promise<any> {
|
||||
return await this.paymentService.getSubscription(email);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AuthModule } from '../auth/auth.module.js';
|
||||
import { AuthService } from '../auth/auth.service.js';
|
||||
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
|
|
@ -10,8 +12,8 @@ import { PaymentController } from './payment.controller.js';
|
|||
import { PaymentService } from './payment.service.js';
|
||||
|
||||
@Module({
|
||||
imports: [DrizzleModule, UserModule, MailModule],
|
||||
providers: [PaymentService, UserService, MailService, FileService, GeoService],
|
||||
imports: [DrizzleModule, UserModule, MailModule, AuthModule],
|
||||
providers: [PaymentService, UserService, MailService, FileService, GeoService, AuthService],
|
||||
controllers: [PaymentController],
|
||||
})
|
||||
export class PaymentModule {}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
|||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import Stripe from 'stripe';
|
||||
import { Logger } from 'winston';
|
||||
import { AuthService } from '../auth/auth.service.js';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { MailService } from '../mail/mail.service.js';
|
||||
|
|
@ -21,13 +22,33 @@ export class PaymentService {
|
|||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
||||
private readonly userService: UserService,
|
||||
private readonly mailService: MailService,
|
||||
private readonly authService: AuthService,
|
||||
) {
|
||||
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2024-06-20',
|
||||
});
|
||||
}
|
||||
async checkout(checkout: Checkout) {
|
||||
async createCheckoutSession(checkout: Checkout) {
|
||||
try {
|
||||
let customerId;
|
||||
const existingCustomers = await this.stripe.customers.list({
|
||||
email: checkout.email,
|
||||
limit: 1,
|
||||
});
|
||||
if (existingCustomers.data.length > 0) {
|
||||
// Kunde existiert
|
||||
customerId = existingCustomers.data[0].id;
|
||||
} else {
|
||||
// Kunde existiert nicht, neuen Kunden erstellen
|
||||
const newCustomer = await this.stripe.customers.create({
|
||||
email: checkout.email,
|
||||
name: checkout.name,
|
||||
});
|
||||
customerId = newCustomer.id;
|
||||
}
|
||||
const price = await this.stripe.prices.retrieve(checkout.priceId);
|
||||
if (price.product) {
|
||||
const product = await this.stripe.products.retrieve(price.product as string);
|
||||
const session = await this.stripe.checkout.sessions.create({
|
||||
mode: 'subscription',
|
||||
payment_method_types: ['card'],
|
||||
|
|
@ -39,15 +60,26 @@ export class PaymentService {
|
|||
],
|
||||
success_url: 'http://localhost:4200/success',
|
||||
cancel_url: 'http://localhost:4200/pricing',
|
||||
customer_email: checkout.email,
|
||||
// customer_email: checkout.email,
|
||||
customer: customerId,
|
||||
// customer_details:{
|
||||
// name: checkout.name, // Hier wird der Name des Kunden übergeben
|
||||
// },
|
||||
shipping_address_collection: {
|
||||
allowed_countries: ['US'],
|
||||
},
|
||||
|
||||
client_reference_id: '1234',
|
||||
locale: 'en',
|
||||
subscription_data: {
|
||||
trial_end: Math.floor(new Date().setMonth(new Date().getMonth() + 3) / 1000),
|
||||
},
|
||||
metadata: { plan: product.name },
|
||||
});
|
||||
|
||||
return session;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new BadRequestException(`error during checkout: ${e}`);
|
||||
}
|
||||
|
|
@ -57,8 +89,15 @@ export class PaymentService {
|
|||
}
|
||||
async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise<void> {
|
||||
this.logger.info(JSON.stringify(session));
|
||||
//const jwtUser:JwtUser = {firstname:,lastname:}
|
||||
const user = await this.userService.getUserByMail(session.customer_details.email);
|
||||
const keycloakUsers = await this.authService.getUsers();
|
||||
const keycloakUser = keycloakUsers.find(u => u.email === session.customer_details.email);
|
||||
const user = await this.userService.getUserByMail(session.customer_details.email, {
|
||||
userId: keycloakUser.id,
|
||||
firstname: keycloakUser.firstName,
|
||||
lastname: keycloakUser.lastName,
|
||||
username: keycloakUser.email,
|
||||
roles: [],
|
||||
});
|
||||
user.stripeCustomerId = session.customer as string;
|
||||
user.subscriptionId = session.subscription as string;
|
||||
user.planActive = true;
|
||||
|
|
@ -78,4 +117,20 @@ export class PaymentService {
|
|||
this.userService.saveUser(user);
|
||||
this.mailService.sendSubscriptionConfirmation(user);
|
||||
}
|
||||
async getSubscription(email: string): Promise<Stripe.Subscription[]> {
|
||||
const existingCustomers = await this.stripe.customers.list({
|
||||
email: email,
|
||||
limit: 1,
|
||||
});
|
||||
if (existingCustomers.data.length > 0) {
|
||||
const subscriptions = await this.stripe.subscriptions.list({
|
||||
customer: existingCustomers.data[0].id,
|
||||
status: 'all', // Optional: Gibt Abos in allen Status zurück, wie 'active', 'canceled', etc.
|
||||
limit: 20, // Optional: Begrenze die Anzahl der zurückgegebenen Abonnements
|
||||
});
|
||||
return subscriptions.data.filter(s => s.status === 'active' || s.status === 'trialing');
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export class UserService {
|
|||
.from(schema.users)
|
||||
.where(sql`email = ${email}`)) as User[];
|
||||
if (users.length === 0) {
|
||||
const user: User = { id: undefined, customerType: 'buyer', ...createDefaultUser(email, jwtuser.firstname, jwtuser.lastname, 'free') };
|
||||
const user: User = { id: undefined, customerType: 'buyer', ...createDefaultUser(email, jwtuser.firstname, jwtuser.lastname, null) };
|
||||
const u = await this.saveUser(user);
|
||||
return convertDrizzleUserToUser(u);
|
||||
} else {
|
||||
|
|
@ -147,6 +147,7 @@ export class UserService {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getStates(): Promise<any[]> {
|
||||
const query = sql`SELECT jsonb_array_elements(${schema.users.areasServed}) ->> 'state' AS state, COUNT(DISTINCT ${schema.users.id}) AS count FROM ${schema.users} GROUP BY state ORDER BY count DESC`;
|
||||
const result = await this.conn.execute(query);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { HomeComponent } from './pages/home/home.component';
|
|||
import { BrokerListingsComponent } from './pages/listings/broker-listings/broker-listings.component';
|
||||
import { BusinessListingsComponent } from './pages/listings/business-listings/business-listings.component';
|
||||
import { CommercialPropertyListingsComponent } from './pages/listings/commercial-property-listings/commercial-property-listings.component';
|
||||
import { LoginComponent } from './pages/login/login.component';
|
||||
import { PricingComponent } from './pages/pricing/pricing.component';
|
||||
import { AccountComponent } from './pages/subscription/account/account.component';
|
||||
import { EditBusinessListingComponent } from './pages/subscription/edit-business-listing/edit-business-listing.component';
|
||||
|
|
@ -56,6 +57,10 @@ export const routes: Routes = [
|
|||
canActivate: [ListingCategoryGuard],
|
||||
component: NotFoundComponent, // Dummy-Komponente, wird nie angezeigt, da der Guard weiterleitet
|
||||
},
|
||||
{
|
||||
path: 'login/:page',
|
||||
component: LoginComponent, // Dummy-Komponente, wird nie angezeigt, da der Guard weiterleitet
|
||||
},
|
||||
{
|
||||
path: 'notfound',
|
||||
component: NotFoundComponent,
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export class HeaderComponent {
|
|||
}
|
||||
login() {
|
||||
this.keycloakService.login({
|
||||
redirectUri: window.location.href,
|
||||
redirectUri: `${window.location.origin}/login${this.router.routerState.snapshot.url}`,
|
||||
});
|
||||
}
|
||||
register() {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export class HomeComponent {
|
|||
}
|
||||
login() {
|
||||
this.keycloakService.login({
|
||||
redirectUri: window.location.href,
|
||||
redirectUri: `${window.location.origin}/login${this.router.routerState.snapshot.url}`,
|
||||
});
|
||||
}
|
||||
register() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { SubscriptionsService } from '../../services/subscriptions.service';
|
||||
import { UserService } from '../../services/user.service';
|
||||
import { map2User } from '../../utils/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterModule],
|
||||
template: ``,
|
||||
})
|
||||
export class LoginComponent {
|
||||
page: string | undefined = this.activatedRoute.snapshot.params['page'] as string | undefined;
|
||||
constructor(public userService: UserService, private activatedRoute: ActivatedRoute, private keycloakService: KeycloakService, private router: Router, private subscriptionService: SubscriptionsService) {}
|
||||
async ngOnInit() {
|
||||
const token = await this.keycloakService.getToken();
|
||||
const keycloakUser = map2User(token);
|
||||
const email = keycloakUser.email;
|
||||
const user = await this.userService.getByMail(email);
|
||||
if (!user.subscriptionPlan) {
|
||||
//this.router.navigate(['/pricing']);
|
||||
const subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(user.email));
|
||||
const activeSubscription = subscriptions.filter(s => s.status === 'active');
|
||||
if (activeSubscription.length > 0) {
|
||||
user.subscriptionPlan = activeSubscription[0].metadata['plan'] === 'Broker Plan' ? 'broker' : 'professional';
|
||||
this.userService.save(user);
|
||||
} else {
|
||||
this.router.navigate([`/pricing`]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.router.navigate([`/${this.page}`]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { StripeService } from 'ngx-stripe';
|
||||
import { switchMap } from 'rxjs';
|
||||
import { Checkout, KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { UserService } from '../../services/user.service';
|
||||
import { SharedModule } from '../../shared/shared/shared.module';
|
||||
import { map2User } from '../../utils/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pricing',
|
||||
|
|
@ -17,26 +20,40 @@ import { SharedModule } from '../../shared/shared/shared.module';
|
|||
export class PricingComponent {
|
||||
private apiBaseUrl = environment.apiBaseUrl;
|
||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||
constructor(public keycloakService: KeycloakService, private http: HttpClient, private stripeService: StripeService, private activatedRoute: ActivatedRoute) {}
|
||||
keycloakUser: KeycloakUser;
|
||||
constructor(public keycloakService: KeycloakService, private http: HttpClient, private stripeService: StripeService, private activatedRoute: ActivatedRoute, private userService: UserService, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
const token = await this.keycloakService.getToken();
|
||||
this.keycloakUser = map2User(token);
|
||||
if (this.id) {
|
||||
this.checkout(atob(this.id));
|
||||
this.checkout({ priceId: atob(this.id), email: this.keycloakUser.email, name: `${this.keycloakUser.firstName} ${this.keycloakUser.lastName}` });
|
||||
}
|
||||
}
|
||||
|
||||
register(priceId?: string) {
|
||||
async register(priceId?: string) {
|
||||
if (this.keycloakUser) {
|
||||
if (!priceId) {
|
||||
const user = await this.userService.getByMail(this.keycloakUser.email);
|
||||
user.subscriptionPlan = 'free';
|
||||
await this.userService.save(user);
|
||||
this.router.navigate([`/account`]);
|
||||
} else {
|
||||
this.checkout({ priceId: atob(this.id), email: this.keycloakUser.email, name: `${this.keycloakUser.firstName} ${this.keycloakUser.lastName}` });
|
||||
}
|
||||
} else {
|
||||
if (priceId) {
|
||||
this.keycloakService.register({ redirectUri: `${window.location.origin}/pricing/${btoa(priceId)}` });
|
||||
} else {
|
||||
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkout(priceId) {
|
||||
checkout(checkout: Checkout) {
|
||||
// Check the server.js tab to see an example implementation
|
||||
this.http
|
||||
.post(`${this.apiBaseUrl}/bizmatch/payment/create-checkout-session`, { priceId })
|
||||
.post(`${this.apiBaseUrl}/bizmatch/payment/create-checkout-session`, checkout)
|
||||
.pipe(
|
||||
switchMap((session: any) => {
|
||||
return this.stripeService.redirectToCheckout({ sessionId: session.id });
|
||||
|
|
|
|||
|
|
@ -220,46 +220,42 @@
|
|||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Level</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Start Date</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date Modified</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">End Date</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Next Settlement</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@for (subscription of subscriptions; track subscriptions; let i=$index){
|
||||
<tr>
|
||||
@for (subscription of userSubscriptions; track userSubscriptions){
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ subscription.id }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ subscription.level }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ subscription.start | date }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ subscription.modified | date }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ subscription.end | date }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ subscription.status }}</td>
|
||||
}
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getLevel(i) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getStartDate(i) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getEndDate(i) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getNextSettlement(i) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getStatus(i) }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 sm:hidden">
|
||||
<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">
|
||||
<h3 class="text-lg font-medium text-gray-700 mb-1">Membership Level</h3>
|
||||
<div class="space-y-2">
|
||||
@for (subscription of userSubscriptions; track userSubscriptions){
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-2 sm:grid-cols-2">
|
||||
<div class="sm:col-span-1 flex">
|
||||
<dt class="text-sm font-bold text-gray-500 mr-2">ID</dt>
|
||||
<dd class="text-sm text-gray-900">{{ subscription.id }}</dd>
|
||||
</div>
|
||||
<div class="sm:col-span-1 flex">
|
||||
<dt class="text-sm font-bold text-gray-500 mr-2">Level</dt>
|
||||
<dd class="text-sm text-gray-900">{{ subscription.level }}</dd>
|
||||
<dd class="text-sm text-gray-900">{{ level }}</dd>
|
||||
</div>
|
||||
<div class="sm:col-span-1 flex">
|
||||
<dt class="text-sm font-bold text-gray-500 mr-2">Start Date</dt>
|
||||
|
|
@ -282,7 +278,7 @@
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TitleCasePipe } from '@angular/common';
|
||||
import { DatePipe, TitleCasePipe } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
|
|
@ -10,7 +10,7 @@ import { ImageCropperComponent } from 'ngx-image-cropper';
|
|||
import { QuillModule } from 'ngx-quill';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { AutoCompleteCompleteEvent, Invoice, Subscription, UploadParams, ValidationMessage, createDefaultUser, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { AutoCompleteCompleteEvent, Invoice, StripeSubscription, UploadParams, ValidationMessage, createDefaultUser, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component';
|
||||
import { ConfirmationService } from '../../../components/confirmation/confirmation.service';
|
||||
|
|
@ -53,15 +53,13 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
|||
TooltipComponent,
|
||||
ValidatedCountyComponent,
|
||||
],
|
||||
providers: [TitleCasePipe],
|
||||
providers: [TitleCasePipe, DatePipe],
|
||||
templateUrl: './account.component.html',
|
||||
styleUrl: './account.component.scss',
|
||||
})
|
||||
export class AccountComponent {
|
||||
id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||
user: User;
|
||||
subscriptions: Array<Subscription>;
|
||||
userSubscriptions: Array<Subscription> = [];
|
||||
companyLogoUrl: string;
|
||||
profileUrl: string;
|
||||
type: 'company' | 'profile';
|
||||
|
|
@ -79,9 +77,9 @@ export class AccountComponent {
|
|||
customerTypeOptions: Array<{ value: string; label: string }> = [];
|
||||
customerSubTypeOptions: Array<{ value: string; label: string }> = [];
|
||||
tooltipTarget = 'tooltip-areasServed';
|
||||
subscriptions: StripeSubscription[] | any[];
|
||||
constructor(
|
||||
public userService: UserService,
|
||||
private subscriptionService: SubscriptionsService,
|
||||
private geoService: GeoService,
|
||||
public selectOptions: SelectOptionsService,
|
||||
private cdref: ChangeDetectorRef,
|
||||
|
|
@ -95,6 +93,8 @@ export class AccountComponent {
|
|||
private sharedService: SharedService,
|
||||
private titleCasePipe: TitleCasePipe,
|
||||
private validationMessagesService: ValidationMessagesService,
|
||||
private subscriptionService: SubscriptionsService,
|
||||
private datePipe: DatePipe,
|
||||
) {}
|
||||
async ngOnInit() {
|
||||
setTimeout(() => {
|
||||
|
|
@ -109,7 +109,10 @@ export class AccountComponent {
|
|||
this.user = await this.userService.getByMail(email);
|
||||
}
|
||||
|
||||
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.id));
|
||||
this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email));
|
||||
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.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||
|
||||
|
|
@ -133,7 +136,7 @@ export class AccountComponent {
|
|||
const confirmed = await this.confirmationService.showConfirmation({ message: 'Are you sure you want to switch to Buyer ? All your listings as well as all your professionals informations will be deleted' });
|
||||
if (confirmed) {
|
||||
const id = this.user.id;
|
||||
this.user = createDefaultUser(this.user.email, this.user.firstname, this.user.lastname, 'free');
|
||||
this.user = createDefaultUser(this.user.email, this.user.firstname, this.user.lastname, null);
|
||||
this.user.customerType = 'buyer';
|
||||
this.user.id = id;
|
||||
this.imageService.deleteLogoImagesByMail(this.user.email);
|
||||
|
|
@ -244,4 +247,19 @@ export class AccountComponent {
|
|||
this.user.areasServed[index].county = null;
|
||||
}
|
||||
}
|
||||
getLevel(i: number) {
|
||||
return this.subscriptions[i].metadata.plan;
|
||||
}
|
||||
getStartDate(i: number) {
|
||||
return this.datePipe.transform(new Date(this.subscriptions[i].start_date * 1000));
|
||||
}
|
||||
getEndDate(i: number) {
|
||||
return this.subscriptions[i].status === 'trialing' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---';
|
||||
}
|
||||
getNextSettlement(i: number) {
|
||||
return this.subscriptions[i].status === 'active' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---';
|
||||
}
|
||||
getStatus(i: number) {
|
||||
return this.subscriptions[i].status ? this.subscriptions[i].status : '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Subscription } from '../../../../bizmatch-server/src/models/main.model';
|
||||
import { StripeSubscription } from '../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -11,7 +11,7 @@ export class SubscriptionsService {
|
|||
private apiBaseUrl = environment.apiBaseUrl;
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getAllSubscriptions(id: string): Observable<Subscription[]> {
|
||||
return this.http.get<Subscription[]>(`${this.apiBaseUrl}/bizmatch/user/subscriptions/${id}`);
|
||||
getAllSubscriptions(email: string): Observable<StripeSubscription[]> {
|
||||
return this.http.get<StripeSubscription[]>(`${this.apiBaseUrl}/bizmatch/payment/subscriptions/${email}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue