Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Stripe from 'stripe';
import fastifyCors from '@fastify/cors';
import Fastify, { FastifyInstance } from 'fastify';
import { AppConfig } from './config';
Expand All @@ -13,8 +12,8 @@ import CacheService from './services/cache.service';
import { PaymentService } from './services/payment.service';
import { StorageService } from './services/storage.service';
import { UsersService } from './services/users.service';
import webhook from './webhooks';
import cryptoWebhook from './webhooks/providers/bit2me/index';
import { providerWebhooksEvents } from './webhooks';
import { cryptoProviderWebhooks } from './webhooks/providers/bit2me/index';
import { LicenseCodesService } from './services/licenseCodes.service';
import { ObjectStorageService } from './services/objectStorage.service';
import { TiersService } from './services/tiers.service';
Expand All @@ -34,7 +33,6 @@ interface AppDependencies {
objectStorageService: ObjectStorageService;
productsService: ProductsService;
userFeaturesOverridesService: UserFeaturesOverridesService;
stripe: Stripe;
config: AppConfig;
}

Expand All @@ -48,7 +46,6 @@ export async function buildApp({
objectStorageService,
productsService,
userFeaturesOverridesService,
stripe,
config,
}: AppDependencies): Promise<FastifyInstance> {
const fastify = Fastify({
Expand Down Expand Up @@ -81,20 +78,19 @@ export async function buildApp({
fastify.register(controllerMigration(paymentService, usersService, config));

fastify.register(
webhook(
stripe,
providerWebhooksEvents({
storageService,
usersService,
paymentService,
config,
cacheService,
objectStorageService,
tiersService,
),
}),
);

fastify.register(
cryptoWebhook({
cryptoProviderWebhooks({
storageService,
usersService,
paymentService,
Expand Down
9 changes: 1 addition & 8 deletions src/cli/determine-lifetime-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,7 @@ async function main() {
const usersTiersRepository = new MongoDBUsersTiersRepository(mongoClient);
const storageService = new StorageService(envVariablesConfig, axios);

const tiersService = new TiersService(
usersService,
paymentService,
tiersRepository,
usersTiersRepository,
storageService,
envVariablesConfig,
);
const tiersService = new TiersService(usersService, tiersRepository, usersTiersRepository, storageService);

const determineLifetimeUserCondition = new DetermineLifetimeConditions(paymentService, tiersService);
const user = await usersService.findUserByCustomerID(customerId);
Expand Down
6 changes: 2 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import axios from 'axios';
import { MongoClient } from 'mongodb';
import Stripe from 'stripe';
import { FastifyInstance } from 'fastify';

import { StorageService } from './services/storage.service';
Expand Down Expand Up @@ -35,6 +34,7 @@ import {
MongoDBUserFeatureOverridesRepository,
UserFeatureOverridesRepository,
} from './core/users/MongoDBUserFeatureOverridesRepository';
import { stripePaymentsAdapter } from './infrastructure/adapters/stripe.adapter';

const start = async (mongoTestClient?: MongoClient): Promise<FastifyInstance> => {
const mongoClient = mongoTestClient ?? (await new MongoClient(envVariablesConfig.MONGO_URI).connect());
Expand All @@ -50,9 +50,8 @@ const start = async (mongoTestClient?: MongoClient): Promise<FastifyInstance> =>
mongoClient,
);

const stripe = new Stripe(envVariablesConfig.STRIPE_SECRET_KEY, { apiVersion: '2025-02-24.acacia' });
const bit2MeService = new Bit2MeService(envVariablesConfig, axios);
const paymentService = new PaymentService(stripe, productsRepository, bit2MeService);
const paymentService = new PaymentService(stripePaymentsAdapter.getInstance(), productsRepository, bit2MeService);
const storageService = new StorageService(envVariablesConfig, axios);
const usersService = new UsersService(
usersRepository,
Expand Down Expand Up @@ -84,7 +83,6 @@ const start = async (mongoTestClient?: MongoClient): Promise<FastifyInstance> =>
objectStorageService,
productsService,
userFeaturesOverridesService,
stripe,
config: envVariablesConfig,
});

Expand Down
17 changes: 8 additions & 9 deletions src/services/tiers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { StorageService } from './storage.service';
import { Service, Tier } from '../core/users/Tier';
import { UsersTiersRepository } from '../core/users/MongoDBUsersTiersRepository';
import Stripe from 'stripe';
import { FastifyBaseLogger } from 'fastify';
import axios, { isAxiosError } from 'axios';
import { Customer } from '../infrastructure/domain/entities/customer';
import Logger from '../Logger';

export class TierNotFoundError extends Error {
constructor(message: string) {
Expand Down Expand Up @@ -99,7 +99,7 @@ export class TiersService {
return tier;
}

async removeTier(userWithEmail: User & { email: string }, productId: string, log: FastifyBaseLogger): Promise<void> {
async removeTier(userWithEmail: User & { email: string }, productId: string): Promise<void> {
const tier = await this.tiersRepository.findByProductId({ productId });
const { uuid: userUuid } = userWithEmail;

Expand All @@ -116,7 +116,7 @@ export class TiersService {

switch (s) {
case Service.Drive:
await this.removeDriveFeatures(userUuid, tier, log);
await this.removeDriveFeatures(userUuid, tier);
break;
case Service.Vpn:
await this.removeVPNFeatures(userUuid, tier.featuresPerService['vpn']);
Expand All @@ -133,7 +133,6 @@ export class TiersService {
customer: Customer,
subscriptionSeats: Stripe.InvoiceLineItem['quantity'],
tier: Tier,
log: FastifyBaseLogger,
customMaxSpaceBytes?: number,
): Promise<void> {
const features = tier.featuresPerService[Service.Drive];
Expand All @@ -154,10 +153,10 @@ export class TiersService {
seats: subscriptionSeats,
tierId: driveTierId,
});
log.info(`[DRIVE/WORKSPACES]: The workspace for user ${userWithEmail.uuid} has been updated`);
Logger.info(`[DRIVE/WORKSPACES]: The workspace for user ${userWithEmail.uuid} has been updated`);
} catch (err) {
if (isAxiosError(err) && err.response?.status === 404) {
log.info(
Logger.info(
`[DRIVE/WORKSPACES]: User with customer Id: ${customer.id} - uuid: ${userWithEmail.uuid} - email: ${customer.email} does not have a workspace. Creating a new one...`,
);
await this.usersService.initializeWorkspace(userWithEmail.uuid, {
Expand All @@ -184,7 +183,7 @@ export class TiersService {
);
}

async removeDriveFeatures(userUuid: User['uuid'], tier: Tier, log: FastifyBaseLogger): Promise<void> {
async removeDriveFeatures(userUuid: User['uuid'], tier: Tier): Promise<void> {
const freeTier = await this.getTierProductsByProductsId('free');
const features = tier.featuresPerService[Service.Drive];

Expand All @@ -195,12 +194,12 @@ export class TiersService {
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
const { status, data } = error.response;
log.error(
Logger.error(
`Failed to delete workspace for user ${userUuid}. Status: ${status}, Response: ${JSON.stringify(data)}`,
);
throw data;
} else {
log.error(`Unexpected error deleting workspace for user ${userUuid}: ${error}`);
Logger.error(`Unexpected error deleting workspace for user ${userUuid}: ${error}`);
throw error;
}
}
Expand Down
8 changes: 1 addition & 7 deletions src/webhooks/events/invoices/InvoiceCompletedHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Stripe from 'stripe';
import { DetermineLifetimeConditions } from '../../../core/users/DetermineLifetimeConditions';
import { FastifyBaseLogger } from 'fastify';
import { PaymentService } from '../../../services/payment.service';
import { PriceMetadata } from '../../../types/stripe';
import { User, UserType } from '../../../core/users/User';
Expand All @@ -23,7 +22,6 @@ export interface InvoiceCompletedHandlerPayload {
}

export class InvoiceCompletedHandler {
private readonly logger: FastifyBaseLogger;
private readonly determineLifetimeConditions: DetermineLifetimeConditions;
private readonly objectStorageWebhookHandler: ObjectStorageWebhookHandler;
private readonly paymentService: PaymentService;
Expand All @@ -33,7 +31,6 @@ export class InvoiceCompletedHandler {
private readonly cacheService: CacheService;

constructor({
logger,
determineLifetimeConditions,
objectStorageWebhookHandler,
paymentService,
Expand All @@ -42,7 +39,6 @@ export class InvoiceCompletedHandler {
usersService,
cacheService,
}: {
logger: FastifyBaseLogger;
determineLifetimeConditions: DetermineLifetimeConditions;
objectStorageWebhookHandler: ObjectStorageWebhookHandler;
paymentService: PaymentService;
Expand All @@ -51,7 +47,6 @@ export class InvoiceCompletedHandler {
usersService: UsersService;
cacheService: CacheService;
}) {
this.logger = logger;
this.determineLifetimeConditions = determineLifetimeConditions;
this.objectStorageWebhookHandler = objectStorageWebhookHandler;
this.paymentService = paymentService;
Expand Down Expand Up @@ -133,7 +128,7 @@ export class InvoiceCompletedHandler {
const localUser = await this.usersService.findUserByUuid(userUuid);

await this.handleNewProduct({
user: { ...localUser, email: email as string },
user: { ...localUser, email: email },
isLifetimePlan,
productId,
customer,
Expand Down Expand Up @@ -344,7 +339,6 @@ export class InvoiceCompletedHandler {
customer,
totalQuantity,
tierToApply,
this.logger,
lifetimeMaxSpaceBytesToApply,
);
Logger.info(`Drive features applied for user ${user.uuid} with customerId ${customer.id}`);
Expand Down
37 changes: 9 additions & 28 deletions src/webhooks/handleDisputeResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import Stripe from 'stripe';
import { UsersService } from '../services/users.service';
import { StorageService } from '../services/storage.service';
import CacheService from '../services/cache.service';
import { AppConfig } from '../config';
import handleLifetimeRefunded from './handleLifetimeRefunded';
import { PaymentService } from '../services/payment.service';
import { FastifyBaseLogger } from 'fastify';
import { TiersService } from '../services/tiers.service';

interface HandleDisputeResultProps {
Expand All @@ -16,8 +14,6 @@ interface HandleDisputeResultProps {
storageService: StorageService;
cacheService: CacheService;
tiersService: TiersService;
log: FastifyBaseLogger;
config: AppConfig;
}

export async function handleDisputeResult({
Expand All @@ -28,37 +24,22 @@ export async function handleDisputeResult({
storageService,
cacheService,
tiersService,
log,
config,
}: HandleDisputeResultProps) {
if (dispute.status !== 'lost') {
return;
}

const chargeId = dispute.charge as string;
try {
const charge = await stripe.charges.retrieve(chargeId);
const customerId = typeof charge.customer === 'string' ? charge.customer : (charge.customer?.id as string);
const invoiceId = typeof charge.invoice === 'string' ? charge.invoice : (charge.invoice?.id as string);
const charge = await stripe.charges.retrieve(chargeId);
const customerId = typeof charge.customer === 'string' ? charge.customer : (charge.customer?.id as string);
const invoiceId = typeof charge.invoice === 'string' ? charge.invoice : (charge.invoice?.id as string);

const { subscription: subscriptionId } = await stripe.invoices.retrieve(invoiceId as string);
const { lifetime } = await usersService.findUserByCustomerID(customerId);
const { subscription: subscriptionId } = await stripe.invoices.retrieve(invoiceId);
const { lifetime } = await usersService.findUserByCustomerID(customerId);

if (lifetime) {
await handleLifetimeRefunded(
storageService,
usersService,
charge,
cacheService,
paymentService,
log,
tiersService,
config,
);
} else {
await paymentService.cancelSubscription(subscriptionId as string);
}
} catch (error) {
throw error;
if (lifetime) {
await handleLifetimeRefunded(storageService, usersService, charge, cacheService, paymentService, tiersService);
} else {
await paymentService.cancelSubscription(subscriptionId as string);
}
}
12 changes: 4 additions & 8 deletions src/webhooks/handleLifetimeRefunded.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { FastifyLoggerInstance } from 'fastify';
import CacheService from '../services/cache.service';
import { StorageService } from '../services/storage.service';
import { UsersService } from '../services/users.service';
import { AppConfig } from '../config';
import { TierNotFoundError, TiersService } from '../services/tiers.service';
import { handleCancelPlan } from './utils/handleCancelPlan';
import Stripe from 'stripe';
import { PaymentService } from '../services/payment.service';
import { Service } from '../core/users/Tier';
import Logger from '../Logger';

export default async function handleLifetimeRefunded(
storageService: StorageService,
usersService: UsersService,
charge: Stripe.Charge,
cacheService: CacheService,
paymentsService: PaymentService,
log: FastifyLoggerInstance,
tiersService: TiersService,
config: AppConfig,
): Promise<void> {
const customerId = charge.customer as string;
const userEmail = charge.receipt_email;
Expand All @@ -35,7 +32,7 @@ export default async function handleLifetimeRefunded(
productId = product.id;
}

log.info(
Logger.info(
`[LIFETIME REFUNDED]: User with customerId ${customerId} found. The uuid of the user is: ${uuid} and productId: ${productId}`,
);

Expand All @@ -44,7 +41,7 @@ export default async function handleLifetimeRefunded(
await cacheService.clearUsedUserPromoCodes(customerId);
await cacheService.clearUserTier(uuid);
} catch (err) {
log.error(`Error in handleLifetimeRefunded after trying to clear ${customerId} subscription`);
Logger.error(`Error in handleLifetimeRefunded after trying to clear ${customerId} subscription`);
}

try {
Expand All @@ -55,11 +52,10 @@ export default async function handleLifetimeRefunded(
productId,
usersService,
tiersService,
log,
});
} catch (error) {
const err = error as Error;
log.error(`[LIFETIME REFUNDED/ERROR]: Error canceling tier product. ERROR: ${err.stack ?? err.message}`);
Logger.error(`[LIFETIME REFUNDED/ERROR]: Error canceling tier product. ERROR: ${err.stack ?? err.message}`);
if (!(error instanceof TierNotFoundError)) {
throw error;
}
Expand Down
Loading
Loading