Überarbeitung des Stripe Prozesses

This commit is contained in:
2024-08-23 19:54:55 +02:00
parent 7a286e3519
commit 74d5f92aba
18 changed files with 164 additions and 68 deletions

View File

@@ -31,9 +31,8 @@ export const users = pgTable('users', {
updated: timestamp('updated'),
latitude: doublePrecision('latitude'),
longitude: doublePrecision('longitude'),
stripeCustomerId: text('stripeCustomerId'),
subscriptionId: text('subscriptionId'),
subscriptionPlan: subscriptionTypeEnum('subscriptionType'),
subscriptionPlan: subscriptionTypeEnum('subscriptionPlan'),
// embedding: vector('embedding', { dimensions: 1536 }),
});

View File

@@ -60,7 +60,7 @@
<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>

View File

@@ -12,7 +12,7 @@ async function bootstrap() {
origin: '*',
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: 'Content-Type, Accept, Authorization',
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
});
//origin: 'http://localhost:4200',
await app.listen(3000);

View File

@@ -157,10 +157,7 @@ export const UserSchema = z
customerSubType: CustomerSubTypeEnum.optional().nullable(),
created: z.date().optional().nullable(),
updated: z.date().optional().nullable(),
stripeCustomerId: z.string().optional().nullable(),
subscriptionId: z.string().optional().nullable(),
planActive: z.boolean().optional().nullable(),
planExpires: z.date().optional().nullable(),
subscriptionPlan: SubscriptionTypeEnum.optional().nullable(),
})
.superRefine((data, ctx) => {

View File

@@ -322,10 +322,7 @@ export function createDefaultUser(email: string, firstname: string, lastname: st
customerSubType: null,
created: new Date(),
updated: new Date(),
stripeCustomerId: null,
subscriptionId: null,
planActive: false,
planExpires: null,
subscriptionPlan: subscriptionPlan,
};
}

View File

@@ -21,7 +21,10 @@ export class PaymentController {
const signature = req.headers['stripe-signature'] as string;
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') {
await this.paymentService.handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session);

View File

@@ -43,6 +43,14 @@ export class PaymentService {
const newCustomer = await this.stripe.customers.create({
email: checkout.email,
name: checkout.name,
shipping: {
name: checkout.name,
address: {
city: '',
state: '',
country: 'US',
},
},
});
customerId = newCustomer.id;
}
@@ -60,7 +68,6 @@ export class PaymentService {
],
success_url: `${process.env.WEB_HOST}/success`,
cancel_url: `${process.env.WEB_HOST}/pricing`,
// customer_email: checkout.email,
customer: customerId,
shipping_address_collection: {
allowed_countries: ['US'],
@@ -84,25 +91,29 @@ export class PaymentService {
return this.stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
}
async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise<void> {
this.logger.info(JSON.stringify(session));
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.customerType = 'professional';
if (session.metadata['plan'] === 'Broker Plan') {
user.customerSubType = 'broker';
try {
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: [],
});
this.logger.info(JSON.stringify(session));
user.subscriptionId = session.subscription as string;
const subscription = await this.stripe.subscriptions.retrieve(user.subscriptionId);
user.customerType = 'professional';
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[]> {
const existingCustomers = await this.stripe.customers.list({

View File

@@ -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 { Logger } from 'winston';
import { ZodError } from 'zod';
import { FileService } from '../file/file.service.js';
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
import { User } from '../models/db.model';
@@ -31,13 +32,32 @@ export class UserController {
return user;
}
@Post()
save(@Body() user: any): Promise<User> {
async save(@Body() user: any): Promise<User> {
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
const savedUser = this.userService.saveUser(user);
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
try {
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;
}
@Post('search')
find(@Body() criteria: UserListingCriteria): any {
this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`);

View File

@@ -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 { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { ZodError } from 'zod';
import * as schema from '../drizzle/schema.js';
import { customerSubTypeEnum, PG_CONNECTION } from '../drizzle/schema.js';
import { FileService } from '../file/file.service.js';
@@ -97,7 +96,7 @@ export class UserService {
.where(sql`email = ${email}`)) as User[];
if (users.length === 0) {
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);
} else {
const user = users[0];
@@ -117,7 +116,7 @@ export class UserService {
user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email));
return convertDrizzleUserToUser(user);
}
async saveUser(user: User): Promise<User> {
async saveUser(user: User, processValidation = true): Promise<User> {
try {
user.updated = new Date();
if (user.id) {
@@ -125,7 +124,10 @@ export class UserService {
} else {
user.created = new Date();
}
const validatedUser = UserSchema.parse(user);
let validatedUser = user;
if (processValidation) {
validatedUser = UserSchema.parse(user);
}
const drizzleUser = convertUserToDrizzleUser(validatedUser);
if (user.id) {
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;
}
} 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;
}
}