mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
158 lines
4.7 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
}
|