Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
ef1435c
remove: hardcoded `Plan` type and use billing models from sdk.
ItzNotABug Dec 18, 2025
c462360
remove: credits manual type.
ItzNotABug Dec 18, 2025
c3a5a3a
remove: paymentMethodList & paymentMethodData manual types.
ItzNotABug Dec 18, 2025
36f11f3
remove: `invoice`, `invoiceList` and `estimationDeleteOrganization` m…
ItzNotABug Dec 18, 2025
2f7bb0c
remove: `Tier` type, ugh!
ItzNotABug Dec 18, 2025
ce1c088
remove: `Estimation` type!
ItzNotABug Dec 18, 2025
f2fd84b
remove: `Coupon` type!
ItzNotABug Dec 18, 2025
c18713e
remove: `Campaign` type!
ItzNotABug Dec 18, 2025
387fca1
remove: `AggregationTeam` type!
ItzNotABug Dec 18, 2025
183f821
remove: `AggregationBreakdown`, `InvoiceUsage` and `AggregationList` …
ItzNotABug Dec 18, 2025
38b814a
remove: `OrganizationUsage` type.
ItzNotABug Dec 18, 2025
8aad7cf
remove: `Address` and `AddressList` type.
ItzNotABug Dec 18, 2025
facf695
remove: `Roles` type.
ItzNotABug Dec 18, 2025
529d8b1
replace: `Organization` type with `Models.Organizations`.
ItzNotABug Dec 18, 2025
67dff43
clear: tests.
ItzNotABug Dec 18, 2025
a7b7b77
remove: `validateOrganization`.
ItzNotABug Dec 18, 2025
b4e08b5
remove: `estimationCreateOrganization`, `deleteOrganization`, `estima…
ItzNotABug Dec 18, 2025
8382911
remove: `listPlans`.
ItzNotABug Dec 19, 2025
60af829
remove: `getPlan`.
ItzNotABug Dec 19, 2025
dddadc3
remove: projects, plans, payment methods hardcoded methods.
ItzNotABug Dec 19, 2025
91aaa34
remove: invoices, payments, usage, aggregation hardcoded methods.
ItzNotABug Dec 19, 2025
9a0059c
remove: credits hardcoded methods.
ItzNotABug Dec 19, 2025
3b14de3
remove: campaigns hardcoded methods.
ItzNotABug Dec 19, 2025
4216f3b
remove: `setMembership` hardcoded methods.
ItzNotABug Dec 19, 2025
0597d21
refactor: remove `getCouponAccount` hardcoded method and its usages.
ItzNotABug Dec 19, 2025
7fc29ae
refactor: remove `billingAddress`, `tax`, `orgPayment` hardcoded meth…
ItzNotABug Dec 19, 2025
b3e040b
refactor: remove `accounts`, `console`, `regions` based hardcoded met…
ItzNotABug Dec 19, 2025
7710e55
Merge branch 'main' into billing-sdk-refactor
ItzNotABug Dec 19, 2025
deb74f9
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Dec 23, 2025
79dec66
fix: temporary hot fix for group.
ItzNotABug Dec 23, 2025
5b0fd91
remove: BillingPlan hardcoded constant.
ItzNotABug Dec 24, 2025
69baca8
remove: `isGitHubEducationPlan` method and implement proper checks.
ItzNotABug Dec 25, 2025
36368fe
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Dec 28, 2025
cfd4636
cleanups.
ItzNotABug Dec 29, 2025
05cfdd2
Merge branch 'billing-sdk-refactor' into 'remove-billing-plan'.
ItzNotABug Dec 29, 2025
4c47118
remove: programs endpoint api call.
ItzNotABug Dec 30, 2025
0804f64
remove: hardcoded plan.
ItzNotABug Dec 30, 2025
8f05b75
address rabbit comments.
ItzNotABug Dec 31, 2025
9b11552
remove: unused things.
ItzNotABug Dec 31, 2025
9e07b4b
fix: invalid plan var.
ItzNotABug Dec 31, 2025
5cc88f6
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Jan 12, 2026
5b652af
Merge branch 'billing-sdk-refactor' into 'remove-billing-plan'.
ItzNotABug Jan 12, 2026
aa06130
address comments.
ItzNotABug Jan 12, 2026
54b00a6
fix: condition for badge.
ItzNotABug Jan 12, 2026
654db2e
Merge branch 'main' into billing-sdk-refactor
ItzNotABug Jan 18, 2026
cf68297
Merge branch 'billing-sdk-refactor' into remove-billing-plan
ItzNotABug Jan 18, 2026
20c785a
fix: urls and payment post routes.
ItzNotABug Jan 18, 2026
985b9e5
update: address comments.
ItzNotABug Jan 18, 2026
e66898f
update: address comments.
ItzNotABug Jan 18, 2026
06dcdc4
update: address comments.
ItzNotABug Jan 18, 2026
67d3fab
fix: e2e.
ItzNotABug Jan 18, 2026
f3ca152
fix: e2e.
ItzNotABug Jan 18, 2026
f9e2510
Merge branch 'billing-sdk-refactor' into 'remove-billing-plan'.
ItzNotABug Jan 18, 2026
b21f3c0
address comments.
ItzNotABug Jan 18, 2026
ea6084f
Merge branch 'billing-sdk-refactor' into remove-billing-plan
ItzNotABug Jan 18, 2026
caf2f6e
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Jan 19, 2026
d5e5b93
Merge branch 'billing-sdk-refactor' into remove-billing-plan
ItzNotABug Jan 19, 2026
9a0b4c1
remove: empty file.
ItzNotABug Jan 21, 2026
c4e77db
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Jan 21, 2026
b51353b
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Jan 23, 2026
b2e8849
Merge branch 'billing-sdk-refactor' into 'remove-billing-plan'.
ItzNotABug Jan 26, 2026
cc8811b
update: address comments.
ItzNotABug Jan 26, 2026
f209eee
fix: lint.
ItzNotABug Jan 26, 2026
6eb2269
fix: tests.
ItzNotABug Jan 26, 2026
fe09e90
lint.
ItzNotABug Jan 26, 2026
ef59b70
Merge pull request #2737 from appwrite/remove-billing-plan
ItzNotABug Jan 26, 2026
18d3a9b
update: split check into readable format; address comment.
ItzNotABug Jan 27, 2026
8fdda44
update: remove conditional guards on `billingPlanDetails`.
ItzNotABug Jan 27, 2026
085cd23
update: use explicit cloud check.
ItzNotABug Jan 27, 2026
945ae62
address comments.
ItzNotABug Jan 27, 2026
66577cc
address comment.
ItzNotABug Jan 27, 2026
33fedf7
address comments.
ItzNotABug Jan 27, 2026
067d10b
update: send back `null` on `billingIdToPlan` if none is found.
ItzNotABug Jan 27, 2026
a8ea415
fix: logic breaking on self-hosted instances.
ItzNotABug Jan 27, 2026
0e921a6
update: better params for short-circuiting.
ItzNotABug Jan 27, 2026
28210cd
Merge branch 'main' into 'billing-sdk-refactor'.
ItzNotABug Jan 27, 2026
88333cd
fix: tests.
ItzNotABug Jan 27, 2026
4decc15
Merge branch 'main' into billing-sdk-refactor
ItzNotABug Jan 27, 2026
b458be4
fix: regions endpoint call.
ItzNotABug Jan 27, 2026
2bd17e8
remove: legacy todos.
ItzNotABug Jan 27, 2026
ef14ea3
address: todo.
ItzNotABug Jan 27, 2026
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
12 changes: 9 additions & 3 deletions e2e/steps/free-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ export async function createFreeProject(page: Page): Promise<Metadata> {
const regionPicker = dialog.locator('button[role="combobox"]');
if (await regionPicker.isVisible()) {
await regionPicker.click();
await page.getByRole('option', { name: /New York/i }).click();

region = 'nyc';
const firstEnabledOption = page
.locator('[role="option"]:not([data-disabled="true"])')
.first();

if ((await firstEnabledOption.count()) > 0) {
const selectedRegion = await firstEnabledOption.getAttribute('data-value');
await firstEnabledOption.click();
region = selectedRegion?.replace(/"/g, '') || 'fra';
}
}

await dialog.getByRole('button', { name: 'create' }).click();
Expand Down
10 changes: 8 additions & 2 deletions e2e/steps/pro-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,15 @@ export async function createProProject(page: Page): Promise<Metadata> {
const regionPicker = dialog.locator('button[role="combobox"]');
if (await regionPicker.isVisible()) {
await regionPicker.click();
await page.getByRole('option', { name: /New York/i }).click();
const firstEnabledOption = page
.locator('[role="option"]:not([data-disabled="true"])')
.first();

region = 'nyc';
if ((await firstEnabledOption.count()) > 0) {
const selectedRegion = await firstEnabledOption.getAttribute('data-value');
await firstEnabledOption.click();
region = selectedRegion?.replace(/"/g, '') || 'fra';
}
}

await dialog.getByRole('button', { name: 'create' }).click();
Expand Down
18 changes: 8 additions & 10 deletions src/lib/commandCenter/searchers/organizations.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { resolve } from '$app/paths';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { sdk } from '$lib/stores/sdk';
import type { Searcher } from '../commands';
import { isCloud } from '$lib/system';
import { Platform, Query } from '@appwrite.io/console';
import { getTeamOrOrganizationList } from '$lib/stores/organization';

export const orgSearcher = (async (query: string) => {
const { teams } = !isCloud
? await sdk.forConsole.teams.list()
: await sdk.forConsole.billing.listOrganization([
Query.equal('platform', Platform.Appwrite)
]);
const { teams } = await getTeamOrOrganizationList();

return teams
.filter((organization) => organization.name.toLowerCase().includes(query.toLowerCase()))
.map((organization) => {
return {
label: organization.name,
callback: () => {
goto(`${base}/organization-${organization.$id}`);
goto(
resolve('/(console)/organization-[organization]', {
organization: organization.$id
})
);
},
group: 'organizations'
} as const;
Expand Down
10 changes: 4 additions & 6 deletions src/lib/components/archiveProject.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,21 @@
import { isSmallViewport } from '$lib/stores/viewport';
import { isCloud } from '$lib/system';
import { regions as regionsStore } from '$lib/stores/organization';
import type { Organization } from '$lib/stores/organization';
import type { Plan } from '$lib/sdk/billing';

// props
interface Props {
currentPlan: Models.BillingPlan;
organization: Models.Organization;
projectsToArchive: Models.Project[];
organization: Organization;
currentPlan: Plan;
archivedTotalOverall: number;
archivedOffset: number;
limit: number;
}

let {
projectsToArchive,
organization,
currentPlan,
organization,
projectsToArchive,
archivedTotalOverall,
archivedOffset,
limit
Expand Down
11 changes: 5 additions & 6 deletions src/lib/components/backupDatabaseAlert.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { page } from '$app/state';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { organization } from '$lib/stores/organization';
import { HeaderAlert } from '$lib/layout';
Expand All @@ -18,14 +17,14 @@
</script>

{#if $showPolicyAlert && isCloud && $organization?.$id && page.url.pathname.match(/\/databases\/database-[^/]+$/)}
{@const isFreePlan = $organization?.billingPlan === BillingPlan.FREE}
{@const areBackupsAvailable = $organization?.billingPlanDetails.backupsEnabled}

{@const subtitle = isFreePlan
{@const subtitle = !areBackupsAvailable
? 'Upgrade your plan to ensure your data stays safe and backed up'
: 'Protect your data by quickly adding a backup policy'}

{@const ctaText = isFreePlan ? 'Upgrade plan' : 'Create policy'}
{@const ctaURL = isFreePlan ? $upgradeURL : `${page.url.pathname}/backups`}
{@const ctaText = !areBackupsAvailable ? 'Upgrade plan' : 'Create policy'}
{@const ctaURL = !areBackupsAvailable ? $upgradeURL : `${page.url.pathname}/backups`}

<HeaderAlert type="warning" title="Your database has no backup policy">
<svelte:fragment>{subtitle}</svelte:fragment>
Expand All @@ -35,7 +34,7 @@
href={ctaURL}
secondary
fullWidthMobile
event={isFreePlan ? 'backup_banner_upgrade' : 'backup_banner_add'}>
event={!areBackupsAvailable ? 'backup_banner_upgrade' : 'backup_banner_add'}>
<span class="text">{ctaText}</span>
</Button>

Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/backupRestoreBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { onMount } from 'svelte';
import { isCloud, isSelfHosted } from '$lib/system';
import { organization } from '$lib/stores/organization';
import { BillingPlan, Dependencies } from '$lib/constants';
import { Dependencies } from '$lib/constants';
import { goto, invalidate } from '$app/navigation';
import { page } from '$app/state';
import { addNotification } from '$lib/stores/notifications';
Expand Down Expand Up @@ -125,8 +125,8 @@
}

onMount(() => {
// fast path: don't subscribe if org is on a free plan or is self-hosted.
if (isSelfHosted || (isCloud && $organization?.billingPlan === BillingPlan.FREE)) return;
// fast path: don't subscribe if org doesn't support backups or is self-hosted.
if (isSelfHosted || (isCloud && !$organization?.billingPlanDetails.backupsEnabled)) return;

return realtime.forProject(page.params.region, 'console', (response) => {
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
Expand Down
12 changes: 5 additions & 7 deletions src/lib/components/billing/alerts/limitReached.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@
import { base } from '$app/paths';
import { page } from '$app/state';
import { Click, trackEvent } from '$lib/actions/analytics';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { HeaderAlert } from '$lib/layout';
import { hideBillingHeaderRoutes, readOnly, tierToPlan, upgradeURL } from '$lib/stores/billing';
import { hideBillingHeaderRoutes, readOnly, upgradeURL } from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
</script>

{#if $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)}
{#if $organization?.$id && !$organization?.billingPlanDetails.usage && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)}
<HeaderAlert
type="error"
title={`${$organization.name} usage has reached the ${tierToPlan($organization.billingPlan).name} plan limit`}>
title={`${$organization.name} usage has reached the ${$organization.billingPlanDetails.name} plan limit`}>
<svelte:fragment>
Usage for the <b>{$organization.name}</b> organization has reached the limits of the {tierToPlan(
$organization.billingPlan
).name}
Usage for the <b>{$organization.name}</b> organization has reached the limits of the {$organization
.billingPlanDetails.name}
plan. Consider upgrading to increase your resource usage.
</svelte:fragment>
<svelte:fragment slot="buttons">
Expand Down
26 changes: 24 additions & 2 deletions src/lib/components/billing/alerts/missingPaymentMethod.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { HeaderAlert } from '$lib/layout';
import { hideBillingHeaderRoutes } from '$lib/stores/billing';
import { orgMissingPaymentMethod } from '$routes/(console)/store';

// exists
$: hasOrgBillingContext = !!$orgMissingPaymentMethod;

// needs any methods
$: requiresPaymentMethod =
hasOrgBillingContext && $orgMissingPaymentMethod.billingPlanDetails.requiresPaymentMethod;

// has any methods
$: hasAnyPaymentMethod =
hasOrgBillingContext &&
(!!$orgMissingPaymentMethod.paymentMethodId ||
!!$orgMissingPaymentMethod.backupPaymentMethodId);

// is url excluded
$: isBillingHeaderHidden = hideBillingHeaderRoutes.includes(page.url.pathname);

// should show header
$: shouldShowBillingHeader =
hasOrgBillingContext &&
requiresPaymentMethod &&
!hasAnyPaymentMethod &&
!isBillingHeaderHidden;
</script>

{#if ($orgMissingPaymentMethod.billingPlan === BillingPlan.PRO || $orgMissingPaymentMethod.billingPlan === BillingPlan.SCALE) && !$orgMissingPaymentMethod.paymentMethodId && !$orgMissingPaymentMethod.backupPaymentMethodId && !hideBillingHeaderRoutes.includes(page.url.pathname)}
{#if shouldShowBillingHeader}
<HeaderAlert
type="error"
title={`Payment method required for ${$orgMissingPaymentMethod.name}`}>
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/billing/alerts/newDevUpgradePro.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { base } from '$app/paths';
import { page } from '$app/state';
import { Click, trackEvent } from '$lib/actions/analytics';
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
import { NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { organization } from '$lib/stores/organization';
import { activeHeaderAlert } from '$routes/(console)/store';
Expand All @@ -23,7 +23,7 @@
}
</script>

{#if show && $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && !page.url.pathname.includes(base + '/account')}
{#if show && $organization?.$id && !$organization?.billingPlanDetails.supportsCredits && !page.url.pathname.includes(base + '/account')}
<GradientBanner on:close={handleClose}>
<Layout.Stack
gap="m"
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/billing/alerts/selectProjectCloud.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@

async function updateSelected() {
try {
await sdk.forConsole.billing.updateSelectedProjects(
projects[0].teamId,
selectedProjects
);
await sdk.forConsole.organizations.updateProjects({
organizationId,
projects: selectedProjects
});

showSelectProject = false;
invalidate(Dependencies.ORGANIZATION);
Expand Down
16 changes: 9 additions & 7 deletions src/lib/components/billing/couponInput.svelte
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
<script lang="ts">
import { Button, InputText } from '$lib/elements/forms';
import { formatCurrency } from '$lib/helpers/numbers';
import type { Coupon } from '$lib/sdk/billing';
import { sdk } from '$lib/stores/sdk';
import { Layout } from '@appwrite.io/pink-svelte';
import { createEventDispatcher } from 'svelte';
import { Layout } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
import { formatCurrency } from '$lib/helpers/numbers';
import { Button, InputText } from '$lib/elements/forms';

const dispatch = createEventDispatcher();

export let required = false;
export let coupon: string = '';
export let couponData: Partial<Coupon> = {
export let couponData: Partial<Models.Coupon> = {
code: null,
status: null,
credits: null
};

async function addCoupon() {
try {
const response = await sdk.forConsole.billing.getCouponAccount(coupon);
couponData = response;
couponData = await sdk.forConsole.account.getCoupon({
couponId: coupon
});

dispatch('validation', couponData);
coupon = null;
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/billing/creditsApplied.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script lang="ts">
import { Button } from '$lib/elements/forms';
import type { Coupon } from '$lib/sdk/billing';
import type { Models } from '@appwrite.io/console';
import { formatCurrency } from '$lib/helpers/numbers';
import { IconTag, IconX } from '@appwrite.io/pink-icons-svelte';
import { Badge, Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';

export let couponData: Partial<Coupon> = {
export let couponData: Partial<Models.Coupon> = {
code: null,
status: null,
credits: null
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/billing/discountsApplied.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<script lang="ts">
import type { Coupon } from '$lib/sdk/billing';
import type { Models } from '@appwrite.io/console';
import { formatCurrency } from '$lib/helpers/numbers';
import { IconTag } from '@appwrite.io/pink-icons-svelte';
import { Badge, Icon, Layout, Typography } from '@appwrite.io/pink-svelte';

export let label: string;
export let value: number;
export let couponData: Partial<Coupon> = {
export let couponData: Partial<Models.Coupon> = {
code: null,
status: null,
credits: null
Expand Down
30 changes: 21 additions & 9 deletions src/lib/components/billing/emptyCardCloud.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
<script lang="ts">
import { Click, trackEvent } from '$lib/actions/analytics';
import { BillingPlan } from '$lib/constants';
import { Card } from '..';
import type { Snippet } from 'svelte';
import { Button } from '$lib/elements/forms';
import { tierToPlan, upgradeURL } from '$lib/stores/billing';
import { BillingPlanGroup } from '@appwrite.io/console';
import { Click, trackEvent } from '$lib/actions/analytics';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
import { Card } from '..';
import { getBasePlanFromGroup, upgradeURL } from '$lib/stores/billing';

let {
service,
eventSource,
children = null
}: {
service: string;
eventSource: string;
children?: Snippet;
} = $props();

export let service: string;
export let eventSource: string;
const proPlanName = getBasePlanFromGroup(BillingPlanGroup.Pro).name;
</script>

<Card>
<slot>
{#if children}
{@render children()}
{:else}
<Layout.Stack alignItems="center">
<Typography.Text variant="m-600">Upgrade to add {service}</Typography.Text>
<Typography.Text>
Upgrade to a {tierToPlan(BillingPlan.PRO).name} plan to add {service} to your organization
Upgrade to a {proPlanName} plan to add {service} to your organization
</Typography.Text>

<Button
Expand All @@ -31,5 +43,5 @@
Upgrade
</Button>
</Layout.Stack>
</slot>
{/if}
</Card>
Loading