Stripe Pricing + Subscriptions

This commit is contained in:
2024-08-21 21:13:43 +02:00
parent 48bff89526
commit b4609d07ba
16 changed files with 969 additions and 1035 deletions

View File

@@ -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);
}
}

View File

@@ -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 {}

View File

@@ -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,33 +22,64 @@ 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 {
const session = await this.stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: checkout.priceId,
quantity: 1,
},
],
success_url: 'http://localhost:4200/success',
cancel_url: 'http://localhost:4200/pricing',
customer_email: checkout.email,
shipping_address_collection: {
allowed_countries: ['US'],
},
client_reference_id: '1234',
locale: 'en',
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'],
line_items: [
{
price: checkout.priceId,
quantity: 1,
},
],
success_url: 'http://localhost:4200/success',
cancel_url: 'http://localhost:4200/pricing',
// customer_email: checkout.email,
customer: customerId,
// customer_details:{
// name: checkout.name, // Hier wird der Name des Kunden übergeben
// },
shipping_address_collection: {
allowed_countries: ['US'],
},
return session;
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 [];
}
}
}