228 lines
5.4 KiB
TypeScript
228 lines
5.4 KiB
TypeScript
import 'server-only';
|
|
|
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
|
|
import { createBillingGatewayService } from '@kit/billing-gateway';
|
|
import { getLogger } from '@kit/shared/logger';
|
|
import { Database } from '@kit/supabase/database';
|
|
|
|
export function createAccountPerSeatBillingService(
|
|
client: SupabaseClient<Database>,
|
|
) {
|
|
return new AccountPerSeatBillingService(client);
|
|
}
|
|
|
|
/**
|
|
* @name AccountPerSeatBillingService
|
|
* @description Service for managing per-seat billing for accounts.
|
|
*/
|
|
class AccountPerSeatBillingService {
|
|
private readonly namespace = 'accounts.per-seat-billing';
|
|
|
|
constructor(private readonly client: SupabaseClient<Database>) {}
|
|
|
|
/**
|
|
* @name getPerSeatSubscriptionItem
|
|
* @description Retrieves the per-seat subscription item for an account.
|
|
* @param accountId
|
|
*/
|
|
async getPerSeatSubscriptionItem(accountId: string) {
|
|
const logger = await getLogger();
|
|
const ctx = { accountId, name: this.namespace };
|
|
|
|
logger.info(
|
|
ctx,
|
|
`Retrieving per-seat subscription item for account ${accountId}...`,
|
|
);
|
|
|
|
const { data, error } = await this.client
|
|
.from('subscriptions')
|
|
.select(
|
|
`
|
|
provider: billing_provider,
|
|
id,
|
|
subscription_items !inner (
|
|
quantity,
|
|
id,
|
|
type
|
|
)
|
|
`,
|
|
)
|
|
.eq('account_id', accountId)
|
|
.eq('subscription_items.type', 'per_seat')
|
|
.maybeSingle();
|
|
|
|
if (error) {
|
|
logger.error(
|
|
{
|
|
...ctx,
|
|
error,
|
|
},
|
|
`Failed to get per-seat subscription item for account ${accountId}`,
|
|
);
|
|
|
|
throw error;
|
|
}
|
|
|
|
if (!data?.subscription_items) {
|
|
logger.info(
|
|
ctx,
|
|
`Account is not subscribed to a per-seat subscription. Exiting...`,
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
logger.info(
|
|
ctx,
|
|
`Per-seat subscription item found for account ${accountId}. Will update...`,
|
|
);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @name increaseSeats
|
|
* @description Increases the number of seats for an account.
|
|
* @param accountId
|
|
*/
|
|
async increaseSeats(accountId: string) {
|
|
const logger = await getLogger();
|
|
const subscription = await this.getPerSeatSubscriptionItem(accountId);
|
|
|
|
if (!subscription) {
|
|
return;
|
|
}
|
|
|
|
const subscriptionItems = subscription.subscription_items.filter((item) => {
|
|
return item.type === 'per_seat';
|
|
});
|
|
|
|
if (!subscriptionItems.length) {
|
|
return;
|
|
}
|
|
|
|
const billingGateway = createBillingGatewayService(subscription.provider);
|
|
|
|
const ctx = {
|
|
name: this.namespace,
|
|
accountId,
|
|
subscriptionItems,
|
|
};
|
|
|
|
logger.info(ctx, `Increasing seats for account ${accountId}...`);
|
|
|
|
const promises = subscriptionItems.map(async (item) => {
|
|
try {
|
|
logger.info(
|
|
{
|
|
name: this.namespace,
|
|
accountId,
|
|
subscriptionItemId: item.id,
|
|
quantity: item.quantity + 1,
|
|
},
|
|
`Updating subscription item...`,
|
|
);
|
|
|
|
await billingGateway.updateSubscriptionItem({
|
|
subscriptionId: subscription.id,
|
|
subscriptionItemId: item.id,
|
|
quantity: item.quantity + 1,
|
|
});
|
|
|
|
logger.info(
|
|
{
|
|
name: this.namespace,
|
|
accountId,
|
|
subscriptionItemId: item.id,
|
|
quantity: item.quantity + 1,
|
|
},
|
|
`Subscription item updated successfully`,
|
|
);
|
|
} catch (error) {
|
|
logger.error(
|
|
{
|
|
...ctx,
|
|
error,
|
|
},
|
|
`Failed to increase seats for account ${accountId}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* @name decreaseSeats
|
|
* @description Decreases the number of seats for an account.
|
|
* @param accountId
|
|
*/
|
|
async decreaseSeats(accountId: string) {
|
|
const logger = await getLogger();
|
|
const subscription = await this.getPerSeatSubscriptionItem(accountId);
|
|
|
|
if (!subscription) {
|
|
return;
|
|
}
|
|
|
|
const subscriptionItems = subscription.subscription_items.filter((item) => {
|
|
return item.type === 'per_seat';
|
|
});
|
|
|
|
if (!subscriptionItems.length) {
|
|
return;
|
|
}
|
|
|
|
const ctx = {
|
|
name: this.namespace,
|
|
accountId,
|
|
subscriptionItems,
|
|
};
|
|
|
|
logger.info(ctx, `Decreasing seats for account ${accountId}...`);
|
|
|
|
const billingGateway = createBillingGatewayService(subscription.provider);
|
|
|
|
const promises = subscriptionItems.map(async (item) => {
|
|
try {
|
|
logger.info(
|
|
{
|
|
name: this.namespace,
|
|
accountId,
|
|
subscriptionItemId: item.id,
|
|
quantity: item.quantity - 1,
|
|
},
|
|
`Updating subscription item...`,
|
|
);
|
|
|
|
await billingGateway.updateSubscriptionItem({
|
|
subscriptionId: subscription.id,
|
|
subscriptionItemId: item.id,
|
|
quantity: item.quantity - 1,
|
|
});
|
|
|
|
logger.info(
|
|
{
|
|
name: this.namespace,
|
|
accountId,
|
|
subscriptionItemId: item.id,
|
|
quantity: item.quantity - 1,
|
|
},
|
|
`Subscription item updated successfully`,
|
|
);
|
|
} catch (error) {
|
|
logger.error(
|
|
{
|
|
...ctx,
|
|
error,
|
|
},
|
|
`Failed to decrease seats for account ${accountId}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
}
|