console/packages/services/api/src/modules/commerce/providers/billing.provider.ts

158 lines
4.7 KiB
TypeScript

import { Inject, Injectable, Scope } from 'graphql-modules';
import { OrganizationBilling } from '../../../shared/entities';
import { AuditLogRecorder } from '../../audit-logs/providers/audit-log-recorder';
import { Session } from '../../auth/lib/authz';
import { IdTranslator } from '../../shared/providers/id-translator';
import { Logger } from '../../shared/providers/logger';
import { Storage } from '../../shared/providers/storage';
import {
COMMERCE_CONFIG,
COMMERCE_TRPC_CLIENT,
type CommerceConfig,
type CommerceTrpcClient,
type CommerceTrpcClientInputs,
} from './commerce-client';
type BillingInput = CommerceTrpcClientInputs['stripeBilling'];
@Injectable({
scope: Scope.Operation,
global: true,
})
export class BillingProvider {
private logger: Logger;
enabled = false;
constructor(
@Inject(COMMERCE_TRPC_CLIENT) private client: CommerceTrpcClient,
@Inject(COMMERCE_CONFIG) commerceConfig: CommerceConfig,
logger: Logger,
private auditLog: AuditLogRecorder,
private storage: Storage,
private idTranslator: IdTranslator,
private session: Session,
) {
this.logger = logger.child({ source: 'CommerceProvider' });
this.enabled = !!client && commerceConfig.billingEnabled;
}
async upgradeToPro(input: BillingInput['createSubscriptionForOrganization']) {
this.logger.debug('Upgrading to PRO (input=%o)', input);
if (!this.client) {
throw new Error(`Billing service is not configured!`);
}
const result = this.client.stripeBilling.createSubscriptionForOrganization.mutate(input);
await this.auditLog.record({
eventType: 'SUBSCRIPTION_CREATED',
organizationId: input.organizationId,
metadata: {
operations: input.reserved.operations,
paymentMethodId: input.paymentMethodId,
newPlan: 'PRO',
previousPlan: 'HOBBY',
},
});
return result;
}
syncOrganization(input: BillingInput['syncOrganizationToStripe']) {
if (!this.client || !this.enabled) {
throw new Error(`Billing service is not configured!`);
}
return this.client.stripeBilling.syncOrganizationToStripe.mutate(input);
}
async getAvailablePrices() {
this.logger.debug('Getting available prices');
if (!this.client || !this.enabled) {
return null;
}
return await this.client.stripeBilling.availablePrices.query();
}
async getOrganizationBillingParticipant(selector: {
organizationId: string;
}): Promise<OrganizationBilling | null> {
this.logger.debug('Fetching organization billing (selector=%o)', selector);
return this.storage.getOrganizationBilling({
organizationId: selector.organizationId,
});
}
getActiveSubscription(input: BillingInput['activeSubscription']) {
this.logger.debug('Fetching active subscription (input=%o)', input);
if (!this.client || !this.enabled) {
throw new Error(`Billing service is not configured!`);
}
return this.client.stripeBilling.activeSubscription.query(input);
}
invoices(input: BillingInput['invoices']) {
this.logger.debug('Fetching invoices (input=%o)', input);
if (!this.client || !this.enabled) {
throw new Error(`Billing service is not configured!`);
}
return this.client.stripeBilling.invoices.query(input);
}
upcomingInvoice(input: BillingInput['upcomingInvoice']) {
this.logger.debug('Fetching upcoming invoices (input=%o)', input);
if (!this.client || !this.enabled) {
throw new Error(`Billing service is not configured!`);
}
return this.client.stripeBilling.upcomingInvoice.query(input);
}
async downgradeToHobby(input: BillingInput['cancelSubscriptionForOrganization']) {
this.logger.debug('Downgrading to Hobby (input=%o)', input);
if (!this.client || !this.enabled) {
throw new Error(`Billing service is not configured!`);
}
await this.auditLog.record({
eventType: 'SUBSCRIPTION_CANCELED',
organizationId: input.organizationId,
metadata: {
newPlan: 'HOBBY',
previousPlan: 'PRO',
},
});
return await this.client.stripeBilling.cancelSubscriptionForOrganization.mutate(input);
}
async generateStripePortalLink(args: { organizationSlug: string }) {
this.logger.debug('Generating Stripe portal link for id:' + args.organizationSlug);
if (!this.client || !this.enabled) {
throw new Error(`Billing service is not configured!`);
}
const organizationId = await this.idTranslator.translateOrganizationId({
organizationSlug: args.organizationSlug,
});
await this.session.assertPerformAction({
action: 'billing:describe',
organizationId,
params: {
organizationId,
},
});
return await this.client.stripeBilling.generateStripePortalLink.mutate({
organizationId,
});
}
}