merged dev
This commit is contained in:
parent
7cc68600aa
commit
10348884c2
@ -59,7 +59,7 @@
|
|||||||
"file-type-checker": "^1.0.8",
|
"file-type-checker": "^1.0.8",
|
||||||
"fp-ts": "^2.16.1",
|
"fp-ts": "^2.16.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.123",
|
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v1.125",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"monocle-ts": "^2.3.13",
|
"monocle-ts": "^2.3.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
@ -70,6 +70,7 @@
|
|||||||
"prisma-query": "^2.0.0",
|
"prisma-query": "^2.0.0",
|
||||||
"puppeteer": "^21.3.4",
|
"puppeteer": "^21.3.4",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"stripe": "^14.22.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
"typedi": "^0.10.0",
|
"typedi": "^0.10.0",
|
||||||
|
35
src/app/api/admin/StripeController.ts
Normal file
35
src/app/api/admin/StripeController.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||||
|
import { Controller, Post } from "@ControllerPattern/index";
|
||||||
|
import StripeService from "@Services/common/StripeService/StripeService";
|
||||||
|
import { validateOrReject } from "class-validator";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Subscription } from "le-coffre-resources/dist/Admin";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
@Service()
|
||||||
|
export default class StripeController extends ApiController {
|
||||||
|
constructor(private stripeService: StripeService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a new checkout session
|
||||||
|
*/
|
||||||
|
@Post("/api/v1/admin/stripe", [])
|
||||||
|
protected async post(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
//init Subscription resource with request body values
|
||||||
|
const subscriptionEntity = Subscription.hydrate<Subscription>(req.body);
|
||||||
|
|
||||||
|
await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false });
|
||||||
|
|
||||||
|
const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity);
|
||||||
|
|
||||||
|
this.httpCreated(response, stripeSession);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -86,8 +86,8 @@ export default class SubscriptionsController extends ApiController {
|
|||||||
try {
|
try {
|
||||||
//init Subscription resource with request body values
|
//init Subscription resource with request body values
|
||||||
const subscriptionEntity = Subscription.hydrate<Subscription>(req.body);
|
const subscriptionEntity = Subscription.hydrate<Subscription>(req.body);
|
||||||
//validate user
|
//validate subscription
|
||||||
await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false });
|
await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false });
|
||||||
//call service to get prisma entity
|
//call service to get prisma entity
|
||||||
const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity);
|
const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity);
|
||||||
//Hydrate ressource with prisma entity
|
//Hydrate ressource with prisma entity
|
||||||
@ -124,7 +124,6 @@ export default class SubscriptionsController extends ApiController {
|
|||||||
//init Subscription resource with request body values
|
//init Subscription resource with request body values
|
||||||
const subscriptionEntity = Subscription.hydrate<Subscription>(req.body);
|
const subscriptionEntity = Subscription.hydrate<Subscription>(req.body);
|
||||||
|
|
||||||
|
|
||||||
//call service to get prisma entity
|
//call service to get prisma entity
|
||||||
const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity);
|
const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity);
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ import NotaryOfficeRibController from "./api/notary/OfficeRibController";
|
|||||||
import CustomerOfficeRibController from "./api/customer/OfficeRibController";
|
import CustomerOfficeRibController from "./api/customer/OfficeRibController";
|
||||||
import IdNotOfficeController from "./api/idnot/OfficeController";
|
import IdNotOfficeController from "./api/idnot/OfficeController";
|
||||||
import SubscriptionsController from "./api/admin/SubscriptionsController";
|
import SubscriptionsController from "./api/admin/SubscriptionsController";
|
||||||
|
import StripeController from "./api/admin/StripeController";
|
||||||
|
import StripeWebhooks from "@Common/webhooks/stripeWebhooks";
|
||||||
/**
|
/**
|
||||||
* @description This allow to declare all controllers used in the application
|
* @description This allow to declare all controllers used in the application
|
||||||
*/
|
*/
|
||||||
@ -107,5 +109,7 @@ export default {
|
|||||||
Container.get(CustomerOfficeRibController);
|
Container.get(CustomerOfficeRibController);
|
||||||
Container.get(IdNotOfficeController);
|
Container.get(IdNotOfficeController);
|
||||||
Container.get(SubscriptionsController);
|
Container.get(SubscriptionsController);
|
||||||
|
Container.get(StripeController);
|
||||||
|
Container.get(StripeWebhooks);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -142,6 +142,21 @@ export class BackendVariables {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
public readonly SCW_BUCKET_NAME!: string;
|
public readonly SCW_BUCKET_NAME!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly STRIPE_SECRET_KEY!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly STRIPE_PAYMENT_SUCCESS_URL!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly STRIPE_PAYMENT_CANCEL_URL!: string;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
||||||
@ -186,10 +201,15 @@ export class BackendVariables {
|
|||||||
this.OVH_CONSUMER_KEY = process.env["OVH_CONSUMER_KEY"]!;
|
this.OVH_CONSUMER_KEY = process.env["OVH_CONSUMER_KEY"]!;
|
||||||
this.OVH_SMS_SERVICE_NAME = process.env["OVH_SMS_SERVICE_NAME"]!;
|
this.OVH_SMS_SERVICE_NAME = process.env["OVH_SMS_SERVICE_NAME"]!;
|
||||||
this.SMS_FACTOR_TOKEN = process.env["SMS_FACTOR_TOKEN"]!;
|
this.SMS_FACTOR_TOKEN = process.env["SMS_FACTOR_TOKEN"]!;
|
||||||
this.SCW_ACCESS_KEY_ID = process.env["ACCESS_KEY_ID"]!;
|
this.SCW_ACCESS_KEY_ID = process.env["SCW_ACCESS_KEY_ID"]!;
|
||||||
this.SCW_ACCESS_KEY_SECRET = process.env["ACCESS_KEY_SECRET"]!;
|
this.SCW_ACCESS_KEY_SECRET = process.env["SCW_ACCESS_KEY_SECRET"]!;
|
||||||
this.SCW_BUCKET_ENDPOINT = process.env["BUCKET_ENDPOINT"]!;
|
this.SCW_BUCKET_ENDPOINT = process.env["SCW_BUCKET_ENDPOINT"]!;
|
||||||
this.SCW_BUCKET_NAME = process.env["BUCKET_NAME"]!;
|
this.SCW_BUCKET_NAME = process.env["SCW_BUCKET_NAME"]!;
|
||||||
|
this.STRIPE_SECRET_KEY = process.env["STRIPE_SECRET_KEY"]!;
|
||||||
|
this.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID"]!;
|
||||||
|
this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!;
|
||||||
|
this.STRIPE_PAYMENT_SUCCESS_URL = process.env["STRIPE_PAYMENT_SUCCESS_URL"]!;
|
||||||
|
this.STRIPE_PAYMENT_CANCEL_URL = process.env["STRIPE_PAYMENT_CANCEL_URL"]!;
|
||||||
}
|
}
|
||||||
public async validate(groups?: string[]) {
|
public async validate(groups?: string[]) {
|
||||||
const validationOptions = groups ? { groups } : undefined;
|
const validationOptions = groups ? { groups } : undefined;
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `priceId` on the `subscriptions` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `stripe_subscription_id` to the `subscriptions` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "subscriptions" DROP COLUMN "priceId",
|
||||||
|
ADD COLUMN "stripe_subscription_id" VARCHAR(255) NOT NULL;
|
@ -0,0 +1,5 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "ESubscriptionStatus" AS ENUM ('ACTIVE', 'INACTIVE');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "subscriptions" ADD COLUMN "status" "ESubscriptionStatus" NOT NULL DEFAULT 'INACTIVE';
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "subscriptions" ALTER COLUMN "status" SET DEFAULT 'ACTIVE';
|
@ -380,7 +380,8 @@ model TotpCodes {
|
|||||||
model Subscriptions {
|
model Subscriptions {
|
||||||
uid String @id @unique @default(uuid())
|
uid String @id @unique @default(uuid())
|
||||||
type ESubscriptionType
|
type ESubscriptionType
|
||||||
priceId String @db.VarChar(255)
|
status ESubscriptionStatus @default(ACTIVE)
|
||||||
|
stripe_subscription_id String @db.VarChar(255)
|
||||||
start_date DateTime @default(now())
|
start_date DateTime @default(now())
|
||||||
end_date DateTime
|
end_date DateTime
|
||||||
nb_seats Int
|
nb_seats Int
|
||||||
@ -399,6 +400,11 @@ model Seats {
|
|||||||
@@map("seats")
|
@@map("seats")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ESubscriptionStatus {
|
||||||
|
ACTIVE
|
||||||
|
INACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
enum ESubscriptionType {
|
enum ESubscriptionType {
|
||||||
STANDARD
|
STANDARD
|
||||||
UNLIMITED
|
UNLIMITED
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Database from "@Common/databases/database";
|
import Database from "@Common/databases/database";
|
||||||
import BaseRepository from "@Repositories/BaseRepository";
|
import BaseRepository from "@Repositories/BaseRepository";
|
||||||
import { Service } from "typedi";
|
import { Service } from "typedi";
|
||||||
import { ESubscriptionType, Prisma, Subscriptions } from "@prisma/client";
|
import { ESubscriptionStatus, ESubscriptionType, Prisma, Subscriptions } from "@prisma/client";
|
||||||
import { Subscription } from "le-coffre-resources/dist/Admin";
|
import { Subscription } from "le-coffre-resources/dist/Admin";
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@ -48,8 +48,9 @@ export default class SubscriptionsRepository extends BaseRepository {
|
|||||||
start_date: subscription.start_date,
|
start_date: subscription.start_date,
|
||||||
end_date: subscription.end_date,
|
end_date: subscription.end_date,
|
||||||
type: ESubscriptionType.STANDARD,
|
type: ESubscriptionType.STANDARD,
|
||||||
|
status: ESubscriptionStatus.ACTIVE,
|
||||||
nb_seats: subscription.nb_seats!,
|
nb_seats: subscription.nb_seats!,
|
||||||
priceId: subscription.priceId,
|
stripe_subscription_id: subscription.stripe_subscription_id || "",
|
||||||
office: {
|
office: {
|
||||||
connect: {
|
connect: {
|
||||||
uid: subscription.office!.uid,
|
uid: subscription.office!.uid,
|
||||||
@ -75,8 +76,9 @@ export default class SubscriptionsRepository extends BaseRepository {
|
|||||||
start_date: subscription.start_date,
|
start_date: subscription.start_date,
|
||||||
end_date: subscription.end_date,
|
end_date: subscription.end_date,
|
||||||
type: ESubscriptionType.UNLIMITED,
|
type: ESubscriptionType.UNLIMITED,
|
||||||
|
status: ESubscriptionStatus.ACTIVE,
|
||||||
nb_seats: 0,
|
nb_seats: 0,
|
||||||
priceId: subscription.priceId,
|
stripe_subscription_id: subscription.stripe_subscription_id || "",
|
||||||
office: {
|
office: {
|
||||||
connect: {
|
connect: {
|
||||||
uid: subscription.office!.uid,
|
uid: subscription.office!.uid,
|
||||||
@ -103,6 +105,7 @@ export default class SubscriptionsRepository extends BaseRepository {
|
|||||||
data: {
|
data: {
|
||||||
end_date: subscription.end_date,
|
end_date: subscription.end_date,
|
||||||
type: ESubscriptionType.STANDARD,
|
type: ESubscriptionType.STANDARD,
|
||||||
|
status: subscription.status as ESubscriptionStatus,
|
||||||
nb_seats: subscription.nb_seats!,
|
nb_seats: subscription.nb_seats!,
|
||||||
seats: {
|
seats: {
|
||||||
deleteMany: {},
|
deleteMany: {},
|
||||||
@ -127,6 +130,7 @@ export default class SubscriptionsRepository extends BaseRepository {
|
|||||||
data: {
|
data: {
|
||||||
end_date: subscription.end_date,
|
end_date: subscription.end_date,
|
||||||
type: ESubscriptionType.UNLIMITED,
|
type: ESubscriptionType.UNLIMITED,
|
||||||
|
status: subscription.status as ESubscriptionStatus,
|
||||||
nb_seats: 0,
|
nb_seats: 0,
|
||||||
seats: {
|
seats: {
|
||||||
deleteMany: {},
|
deleteMany: {},
|
||||||
|
64
src/common/webhooks/stripeWebhooks.ts
Normal file
64
src/common/webhooks/stripeWebhooks.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||||
|
import { Controller, Post } from "@ControllerPattern/index";
|
||||||
|
import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts";
|
||||||
|
import StripeService from "@Services/common/StripeService/StripeService";
|
||||||
|
import { validateOrReject } from "class-validator";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Subscription } from "le-coffre-resources/dist/Admin";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
@Service()
|
||||||
|
export default class StripeWebhooks extends ApiController {
|
||||||
|
constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a new checkout session
|
||||||
|
*/
|
||||||
|
@Post("/api/v1/webhooks/stripe")
|
||||||
|
protected async post(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
// const sig = req.headers["stripe-signature"];
|
||||||
|
// const endpointSecret = "whsec_c4088876914bc166ff5c39253207f84900820b67f7bba3b2669c0ff392cbc838";
|
||||||
|
// const stripe = this.stripeService.getClient();
|
||||||
|
// let event: Stripe.Event;
|
||||||
|
|
||||||
|
// if (!sig || !endpointSecret) {
|
||||||
|
// throw new Error("Signature verification failed");
|
||||||
|
// }
|
||||||
|
// event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
|
||||||
|
const event = req.body;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case "checkout.session.completed":
|
||||||
|
if (event.data.object.status !== "complete") break;
|
||||||
|
|
||||||
|
const subscription = JSON.parse(event.data.object.metadata.subscription);
|
||||||
|
subscription.stripe_subscription_id = event.data.object.subscription;
|
||||||
|
|
||||||
|
const subscriptionInfo = await this.stripeService
|
||||||
|
.getClient()
|
||||||
|
.subscriptions.retrieve(subscription.stripe_subscription_id);
|
||||||
|
|
||||||
|
subscription.start_date = new Date(subscriptionInfo.current_period_start * 1000);
|
||||||
|
subscription.end_date = new Date(subscriptionInfo.current_period_end * 1000);
|
||||||
|
|
||||||
|
const subscriptionEntity = Subscription.hydrate<Subscription>(subscription);
|
||||||
|
|
||||||
|
await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false });
|
||||||
|
|
||||||
|
await this.subscriptionsService.create(subscriptionEntity);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.json({ received: true });
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/services/common/StripeService/StripeService.ts
Normal file
36
src/services/common/StripeService/StripeService.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import { Subscription } from "le-coffre-resources/dist/Admin";
|
||||||
|
import Stripe from "stripe";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class StripeService {
|
||||||
|
private client: Stripe;
|
||||||
|
constructor(protected variables: BackendVariables) {
|
||||||
|
this.client = new Stripe(variables.STRIPE_SECRET_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getClient(): Stripe {
|
||||||
|
return this.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createCheckoutSession(subscription: Subscription) {
|
||||||
|
const priceId = subscription.type === "STANDARD" ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID;
|
||||||
|
return this.client.checkout.sessions.create({
|
||||||
|
mode: "subscription",
|
||||||
|
payment_method_types: ["card", "paypal"],
|
||||||
|
billing_address_collection: "auto",
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
price: priceId,
|
||||||
|
quantity: subscription.nb_seats,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
success_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL,
|
||||||
|
cancel_url: this.variables.STRIPE_PAYMENT_CANCEL_URL,
|
||||||
|
metadata: {
|
||||||
|
subscription: JSON.stringify(subscription),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user