cleanup = Dockerfile
This commit is contained in:
parent
f3bf6ff9af
commit
5619007b0f
|
|
@ -1,4 +0,0 @@
|
||||||
REALM=bizmatch-dev
|
|
||||||
usersURL=/admin/realms/bizmatch-dev/users
|
|
||||||
WEB_HOST=https://dev.bizmatch.net
|
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_w2yvJY8qFMfO5wJgyNHCn6oYT7o2J5pS
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
REALM=bizmatch
|
|
||||||
WEB_HOST=https://www.bizmatch.net
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Build Stage
|
||||||
|
FROM node:18-alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Runtime Stage
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/dist /app/dist
|
||||||
|
COPY --from=build /app/package*.json /app/
|
||||||
|
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
|
|
@ -3,7 +3,6 @@ export default defineConfig({
|
||||||
schema: './src/drizzle/schema.ts',
|
schema: './src/drizzle/schema.ts',
|
||||||
out: './src/drizzle/migrations',
|
out: './src/drizzle/migrations',
|
||||||
dialect: 'postgresql',
|
dialect: 'postgresql',
|
||||||
// driver: 'pg',
|
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: process.env.DATABASE_URL,
|
url: process.env.DATABASE_URL,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,6 @@ export const users = pgTable(
|
||||||
subscriptionPlan: subscriptionTypeEnum('subscriptionPlan'),
|
subscriptionPlan: subscriptionTypeEnum('subscriptionPlan'),
|
||||||
location: jsonb('location'),
|
location: jsonb('location'),
|
||||||
showInDirectory: boolean('showInDirectory').default(true),
|
showInDirectory: boolean('showInDirectory').default(true),
|
||||||
// city: varchar('city', { length: 255 }),
|
|
||||||
// state: char('state', { length: 2 }),
|
|
||||||
// latitude: doublePrecision('latitude'),
|
|
||||||
// longitude: doublePrecision('longitude'),
|
|
||||||
},
|
},
|
||||||
table => ({
|
table => ({
|
||||||
locationUserCityStateIdx: index('idx_user_location_city_state').on(
|
locationUserCityStateIdx: index('idx_user_location_city_state').on(
|
||||||
|
|
@ -98,14 +94,6 @@ export const commercials = pgTable(
|
||||||
created: timestamp('created'),
|
created: timestamp('created'),
|
||||||
updated: timestamp('updated'),
|
updated: timestamp('updated'),
|
||||||
location: jsonb('location'),
|
location: jsonb('location'),
|
||||||
// city: varchar('city', { length: 255 }),
|
|
||||||
// state: char('state', { length: 2 }),
|
|
||||||
// zipCode: integer('zipCode'),
|
|
||||||
// county: varchar('county', { length: 255 }),
|
|
||||||
// street: varchar('street', { length: 255 }),
|
|
||||||
// housenumber: varchar('housenumber', { length: 10 }),
|
|
||||||
// latitude: doublePrecision('latitude'),
|
|
||||||
// longitude: doublePrecision('longitude'),
|
|
||||||
},
|
},
|
||||||
table => ({
|
table => ({
|
||||||
locationCommercialsCityStateIdx: index('idx_commercials_location_city_state').on(
|
locationCommercialsCityStateIdx: index('idx_commercials_location_city_state').on(
|
||||||
|
|
@ -113,18 +101,7 @@ export const commercials = pgTable(
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// export const geo = pgTable('geo', {
|
|
||||||
// id: uuid('id').primaryKey().defaultRandom().notNull(),
|
|
||||||
// country: varchar('country', { length: 255 }).default('us'),
|
|
||||||
// state: char('state', { length: 2 }),
|
|
||||||
// city: varchar('city', { length: 255 }),
|
|
||||||
// zipCode: integer('zipCode'),
|
|
||||||
// county: varchar('county', { length: 255 }),
|
|
||||||
// street: varchar('street', { length: 255 }),
|
|
||||||
// housenumber: varchar('housenumber', { length: 10 }),
|
|
||||||
// latitude: doublePrecision('latitude'),
|
|
||||||
// longitude: doublePrecision('longitude'),
|
|
||||||
// });
|
|
||||||
export const listingEvents = pgTable('listing_events', {
|
export const listingEvents = pgTable('listing_events', {
|
||||||
id: uuid('id').primaryKey().defaultRandom().notNull(),
|
id: uuid('id').primaryKey().defaultRandom().notNull(),
|
||||||
listingId: varchar('listing_id', { length: 255 }), // Assuming listings are referenced by UUID, adjust as necessary
|
listingId: varchar('listing_id', { length: 255 }), // Assuming listings are referenced by UUID, adjust as necessary
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
import { Body, Controller, Get, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common';
|
|
||||||
import { Request, Response } from 'express';
|
|
||||||
import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard';
|
|
||||||
import { Checkout } from 'src/models/main.model';
|
|
||||||
import Stripe from 'stripe';
|
|
||||||
import { PaymentService } from './payment.service';
|
|
||||||
|
|
||||||
@Controller('payment')
|
|
||||||
export class PaymentController {
|
|
||||||
constructor(private readonly paymentService: PaymentService) {}
|
|
||||||
|
|
||||||
// @Post()
|
|
||||||
// async createSubscription(@Body() subscriptionData: any) {
|
|
||||||
// return this.paymentService.createSubscription(subscriptionData);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @UseGuards(AdminAuthGuard)
|
|
||||||
// @Get('user/all')
|
|
||||||
// async getAllStripeCustomer(): Promise<Stripe.Customer[]> {
|
|
||||||
// return await this.paymentService.getAllStripeCustomer();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @UseGuards(AdminAuthGuard)
|
|
||||||
// @Get('subscription/all')
|
|
||||||
// async getAllStripeSubscriptions(): Promise<Stripe.Subscription[]> {
|
|
||||||
// return await this.paymentService.getAllStripeSubscriptions();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @UseGuards(AdminAuthGuard)
|
|
||||||
// @Get('paymentmethod/:email')
|
|
||||||
// async getStripePaymentMethods(@Param('email') email: string): Promise<Stripe.PaymentMethod[]> {
|
|
||||||
// return await this.paymentService.getStripePaymentMethod(email);
|
|
||||||
// }
|
|
||||||
|
|
||||||
@UseGuards(OptionalAuthGuard)
|
|
||||||
@Post('create-checkout-session')
|
|
||||||
async createCheckoutSession(@Body() checkout: Checkout) {
|
|
||||||
return await this.paymentService.createCheckoutSession(checkout);
|
|
||||||
}
|
|
||||||
@Post('webhook')
|
|
||||||
async handleWebhook(@Req() req: Request, @Res() res: Response): Promise<void> {
|
|
||||||
const signature = req.headers['stripe-signature'] as string;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send('Webhook received');
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Webhook Error: ${error.message}`);
|
|
||||||
throw new HttpException('Webhook Error', HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UseGuards(OptionalAuthGuard)
|
|
||||||
@Get('subscriptions/:email')
|
|
||||||
async findSubscriptionsById(@Param('email') email: string): Promise<any> {
|
|
||||||
return await this.paymentService.getSubscription(email);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Endpoint zum Löschen eines Stripe-Kunden.
|
|
||||||
* Beispiel: DELETE /stripe/customer/cus_12345
|
|
||||||
*/
|
|
||||||
// @UseGuards(AdminAuthGuard)
|
|
||||||
// @Delete('customer/:id')
|
|
||||||
// @HttpCode(HttpStatus.NO_CONTENT)
|
|
||||||
// async deleteCustomer(@Param('id') customerId: string): Promise<void> {
|
|
||||||
// await this.paymentService.deleteCustomerCompletely(customerId);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { AuthModule } from '../auth/auth.module';
|
|
||||||
|
|
||||||
import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module';
|
|
||||||
import { DrizzleModule } from '../drizzle/drizzle.module';
|
|
||||||
import { FileService } from '../file/file.service';
|
|
||||||
import { GeoService } from '../geo/geo.service';
|
|
||||||
import { MailModule } from '../mail/mail.module';
|
|
||||||
import { MailService } from '../mail/mail.service';
|
|
||||||
import { UserModule } from '../user/user.module';
|
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
import { PaymentController } from './payment.controller';
|
|
||||||
import { PaymentService } from './payment.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [DrizzleModule, UserModule, MailModule, AuthModule,FirebaseAdminModule],
|
|
||||||
providers: [PaymentService, UserService, MailService, FileService, GeoService],
|
|
||||||
controllers: [PaymentController],
|
|
||||||
})
|
|
||||||
export class PaymentModule {}
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
import { BadRequestException, Inject, Injectable, InternalServerErrorException } from '@nestjs/common';
|
|
||||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver';
|
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
||||||
import Stripe from 'stripe';
|
|
||||||
import { Logger } from 'winston';
|
|
||||||
import * as schema from '../drizzle/schema';
|
|
||||||
import { PG_CONNECTION } from '../drizzle/schema';
|
|
||||||
import { MailService } from '../mail/mail.service';
|
|
||||||
import { Checkout } from '../models/main.model';
|
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
export interface BillingAddress {
|
|
||||||
country: string;
|
|
||||||
state: string;
|
|
||||||
}
|
|
||||||
@Injectable()
|
|
||||||
export class PaymentService {
|
|
||||||
private stripe: Stripe;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
|
||||||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
|
||||||
private readonly userService: UserService,
|
|
||||||
private readonly mailService: MailService,
|
|
||||||
) {
|
|
||||||
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
|
||||||
apiVersion: '2024-06-20',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
shipping: {
|
|
||||||
name: checkout.name,
|
|
||||||
address: {
|
|
||||||
city: '',
|
|
||||||
state: '',
|
|
||||||
country: 'US',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
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'],
|
|
||||||
line_items: [
|
|
||||||
{
|
|
||||||
price: checkout.priceId,
|
|
||||||
quantity: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
success_url: `${process.env.WEB_HOST}/success`,
|
|
||||||
cancel_url: `${process.env.WEB_HOST}/pricing`,
|
|
||||||
customer: customerId,
|
|
||||||
shipping_address_collection: {
|
|
||||||
allowed_countries: ['US'],
|
|
||||||
},
|
|
||||||
client_reference_id: btoa(checkout.name),
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async constructEvent(body: string | Buffer, signature: string) {
|
|
||||||
return this.stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
|
|
||||||
}
|
|
||||||
async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise<void> {
|
|
||||||
// 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: [],
|
|
||||||
// });
|
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
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 [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Ruft alle Stripe-Kunden ab, indem die Paginierung gehandhabt wird.
|
|
||||||
* @returns Ein Array von Stripe.Customer Objekten.
|
|
||||||
*/
|
|
||||||
async getAllStripeCustomer(): Promise<Stripe.Customer[]> {
|
|
||||||
const allCustomers: Stripe.Customer[] = [];
|
|
||||||
let hasMore = true;
|
|
||||||
let startingAfter: string | undefined = undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (hasMore) {
|
|
||||||
const response = await this.stripe.customers.list({
|
|
||||||
limit: 100, // Maximale Anzahl pro Anfrage
|
|
||||||
starting_after: startingAfter,
|
|
||||||
});
|
|
||||||
|
|
||||||
allCustomers.push(...response.data);
|
|
||||||
hasMore = response.has_more;
|
|
||||||
|
|
||||||
if (hasMore && response.data.length > 0) {
|
|
||||||
startingAfter = response.data[response.data.length - 1].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allCustomers;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Abrufen der Stripe-Kunden:', error);
|
|
||||||
throw new Error('Kunden konnten nicht abgerufen werden.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async getAllStripeSubscriptions(): Promise<Stripe.Subscription[]> {
|
|
||||||
const allSubscriptions: Stripe.Subscription[] = [];
|
|
||||||
const response = await this.stripe.subscriptions.list({
|
|
||||||
limit: 100,
|
|
||||||
});
|
|
||||||
allSubscriptions.push(...response.data);
|
|
||||||
return allSubscriptions;
|
|
||||||
}
|
|
||||||
async getStripePaymentMethod(email: string): Promise<Stripe.PaymentMethod[]> {
|
|
||||||
const existingCustomers = await this.stripe.customers.list({
|
|
||||||
email: email,
|
|
||||||
limit: 1,
|
|
||||||
});
|
|
||||||
const allPayments: Stripe.PaymentMethod[] = [];
|
|
||||||
if (existingCustomers.data.length > 0) {
|
|
||||||
const response = await this.stripe.paymentMethods.list({
|
|
||||||
customer: existingCustomers.data[0].id,
|
|
||||||
limit: 10,
|
|
||||||
});
|
|
||||||
allPayments.push(...response.data);
|
|
||||||
}
|
|
||||||
return allPayments;
|
|
||||||
}
|
|
||||||
async deleteCustomerCompletely(customerId: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
// 1. Abonnements kündigen und löschen
|
|
||||||
const subscriptions = await this.stripe.subscriptions.list({
|
|
||||||
customer: customerId,
|
|
||||||
limit: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const subscription of subscriptions.data) {
|
|
||||||
await this.stripe.subscriptions.cancel(subscription.id);
|
|
||||||
this.logger.info(`Abonnement ${subscription.id} gelöscht.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Zahlungsmethoden entfernen
|
|
||||||
const paymentMethods = await this.stripe.paymentMethods.list({
|
|
||||||
customer: customerId,
|
|
||||||
type: 'card',
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const paymentMethod of paymentMethods.data) {
|
|
||||||
await this.stripe.paymentMethods.detach(paymentMethod.id);
|
|
||||||
this.logger.info(`Zahlungsmethode ${paymentMethod.id} entfernt.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Kunden löschen
|
|
||||||
await this.stripe.customers.del(customerId);
|
|
||||||
this.logger.info(`Kunde ${customerId} erfolgreich gelöscht.`);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Fehler beim Löschen des Kunden ${customerId}:`, error);
|
|
||||||
throw new InternalServerErrorException('Fehler beim Löschen des Stripe-Kunden.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,12 +5,6 @@ export const environment_base = {
|
||||||
imageBaseUrl: 'https://dev.bizmatch.net',
|
imageBaseUrl: 'https://dev.bizmatch.net',
|
||||||
buildVersion: '<BUILD_VERSION>',
|
buildVersion: '<BUILD_VERSION>',
|
||||||
mailinfoUrl: 'https://dev.bizmatch.net',
|
mailinfoUrl: 'https://dev.bizmatch.net',
|
||||||
keycloak: {
|
|
||||||
url: 'https://auth.bizmatch.net',
|
|
||||||
realm: 'bizmatch-dev',
|
|
||||||
clientId: 'bizmatch-dev',
|
|
||||||
redirectUri: 'https://dev.bizmatch.net',
|
|
||||||
},
|
|
||||||
ipinfo_token: '7029590fb91214',
|
ipinfo_token: '7029590fb91214',
|
||||||
firebaseConfig: {
|
firebaseConfig: {
|
||||||
apiKey: 'AIzaSyBqVutQqdgUzwD9tKiKJrJq2Q6rD1hNdzw',
|
apiKey: 'AIzaSyBqVutQqdgUzwD9tKiKJrJq2Q6rD1hNdzw',
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,3 @@ import { environment_base } from './environment.base';
|
||||||
export const environment = environment_base;
|
export const environment = environment_base;
|
||||||
environment.mailinfoUrl = 'http://localhost:4200';
|
environment.mailinfoUrl = 'http://localhost:4200';
|
||||||
environment.imageBaseUrl = 'http://localhost:4200';
|
environment.imageBaseUrl = 'http://localhost:4200';
|
||||||
environment.keycloak.clientId = 'dev';
|
|
||||||
environment.keycloak.realm = 'dev';
|
|
||||||
environment.keycloak.redirectUri = 'http://192.168.178.24:4200';
|
|
||||||
// environment.keycloak.redirectUri = 'http://localhost:4200';
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue