Refactor billing services with new AccountsApi

The billing services have been refactored to use the new AccountsApi and TeamAccountsApi. All methods that were previously defined in each billing service, including getting customer ID, getting permissions, etc., have been transferred to these APIs. This change improves the modularity and organization of the code.
This commit is contained in:
giancarlo
2024-04-22 01:35:15 +08:00
parent 75c438a5f2
commit 866b9888f7
17 changed files with 324 additions and 323 deletions

View File

@@ -12,7 +12,8 @@
"./personal-account-dropdown": "./src/components/personal-account-dropdown.tsx",
"./account-selector": "./src/components/account-selector.tsx",
"./personal-account-settings": "./src/components/personal-account-settings/index.ts",
"./hooks/*": "./src/hooks/*.ts"
"./hooks/*": "./src/hooks/*.ts",
"./api": "./src/server/api.ts"
},
"dependencies": {
"@tanstack/react-table": "^8.16.0",

View File

@@ -0,0 +1,107 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database';
/**
* Class representing an API for interacting with user accounts.
* @constructor
* @param {SupabaseClient<Database>} client - The Supabase client instance.
*/
class AccountsApi {
constructor(private readonly client: SupabaseClient<Database>) {}
async getAccountWorkspace() {
const { data, error } = await this.client
.from('user_account_workspace')
.select(`*`)
.single();
if (error) {
throw error;
}
return data;
}
async loadUserAccounts() {
const { data: accounts, error } = await this.client
.from('user_accounts')
.select(`name, slug, picture_url`);
if (error) {
throw error;
}
return accounts.map(({ name, slug, picture_url }) => {
return {
label: name,
value: slug,
image: picture_url,
};
});
}
/**
* @name getSubscriptionData
* Get the subscription data for the given user.
* @param accountId
*/
getSubscriptionData(accountId: string) {
return this.client
.from('subscriptions')
.select('*, items: subscription_items !inner (*)')
.eq('account_id', accountId)
.maybeSingle()
.then((response) => {
if (response.error) {
throw response.error;
}
return response.data;
});
}
/**
* Get the orders data for the given account.
* @param accountId
*/
getOrdersData(accountId: string) {
return this.client
.from('orders')
.select('*, items: order_items !inner (*)')
.eq('account_id', accountId)
.maybeSingle()
.then((response) => {
if (response.error) {
throw response.error;
}
return response.data;
});
}
/**
* @name getBillingCustomerId
* Get the billing customer ID for the given user.
* If the user does not have a billing customer ID, it will return null.
* @param accountId
*/
getBillingCustomerId(accountId: string) {
return this.client
.from('billing_customers')
.select('customer_id')
.eq('account_id', accountId)
.maybeSingle()
.then((response) => {
if (response.error) {
throw response.error;
}
return response.data?.customer_id;
});
}
}
export function createAccountsApi(client: SupabaseClient<Database>) {
return new AccountsApi(client);
}

View File

@@ -9,6 +9,7 @@
"typecheck": "tsc --noEmit"
},
"exports": {
"./api": "./src/server/api.ts",
"./components": "./src/components/index.ts",
"./webhooks": "./src/server/services/webhooks/index.ts"
},

View File

@@ -0,0 +1,142 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database';
/**
* Class representing an API for interacting with team accounts.
* @constructor
* @param {SupabaseClient<Database>} client - The Supabase client instance.
*/
export class TeamAccountsApi {
constructor(private readonly client: SupabaseClient<Database>) {}
/**
* @name getAccountWorkspace
* @description Get the account workspace data.
* @param slug
*/
async getAccountWorkspace(slug: string) {
const accountPromise = this.client.rpc('team_account_workspace', {
account_slug: slug,
});
const accountsPromise = this.client.from('user_accounts').select('*');
const [
accountResult,
accountsResult,
{
data: { user },
},
] = await Promise.all([
accountPromise,
accountsPromise,
this.client.auth.getUser(),
]);
if (accountResult.error) {
return {
error: accountResult.error,
data: null,
};
}
if (accountsResult.error) {
return {
error: accountsResult.error,
data: null,
};
}
if (!user) {
return {
error: new Error('User is not logged in'),
data: null,
};
}
const accountData = accountResult.data[0];
if (!accountData) {
return {
error: new Error('Account data not found'),
data: null,
};
}
return {
data: {
account: accountData,
accounts: accountsResult.data,
user,
},
error: null,
};
}
/**
* @name hasPermission
* @description Check if the user has permission to manage billing for the account.
*/
async hasPermission(params: {
accountId: string;
userId: string;
permission: Database['public']['Enums']['app_permissions'];
}) {
const { data, error } = await this.client.rpc('has_permission', {
account_id: params.accountId,
user_id: params.userId,
permission_name: params.permission,
});
if (error) {
throw error;
}
return data;
}
/**
* @name getMembersCount
* @description Get the number of members in the account.
* @param accountId
*/
async getMembersCount(accountId: string) {
const { count, error } = await this.client
.from('accounts_memberships')
.select('*', {
head: true,
count: 'exact',
})
.eq('account_id', accountId);
if (error) {
throw error;
}
return count;
}
/**
* @name getCustomerId
* @description Get the billing customer ID for the given account.
* @param accountId
*/
async getCustomerId(accountId: string) {
const { data, error } = await this.client
.from('billing_customers')
.select('customer_id')
.eq('account_id', accountId)
.maybeSingle();
if (error) {
throw error;
}
return data?.customer_id;
}
}
export function createTeamAccountsApi(client: SupabaseClient<Database>) {
return new TeamAccountsApi(client);
}