diff --git a/src/lib/components/billing/alerts/missingPaymentMethod.svelte b/src/lib/components/billing/alerts/missingPaymentMethod.svelte
new file mode 100644
index 000000000..b39fd7508
--- /dev/null
+++ b/src/lib/components/billing/alerts/missingPaymentMethod.svelte
@@ -0,0 +1,25 @@
+
+
+{#if ($orgMissingPaymentMethod.billingPlan === BillingPlan.PRO || $orgMissingPaymentMethod.billingPlan === BillingPlan.SCALE) && !$orgMissingPaymentMethod.paymentMethodId && !$orgMissingPaymentMethod.backupPaymentMethodId && !$page.url.pathname.includes('/console/account')}
+
+
+ Add a payment method to {$orgMissingPaymentMethod.name} to avoid service interruptions to
+ your projects.
+
+
+
+
+
+{/if}
diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts
index 698f78377..66bab7a38 100644
--- a/src/lib/sdk/billing.ts
+++ b/src/lib/sdk/billing.ts
@@ -1,5 +1,5 @@
import type { Client, Models, Query } from '@appwrite.io/console';
-import type { Organization } from '../stores/organization';
+import type { Organization, OrganizationList } from '../stores/organization';
import type { PaymentMethod } from '@stripe/stripe-js';
import type { Tier } from '$lib/stores/billing';
@@ -274,6 +274,22 @@ export class Billing {
this.client = client;
}
+ async listOrganization(queries: Query[] = []): Promise {
+ const path = `/organizations`;
+ const params = {
+ queries
+ };
+ const uri = new URL(this.client.config.endpoint + path);
+ return await this.client.call(
+ 'GET',
+ uri,
+ {
+ 'content-type': 'application/json'
+ },
+ params
+ );
+ }
+
async createOrganization(
organizationId: string,
name: string,
diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts
index 22b873828..141b31f93 100644
--- a/src/lib/stores/billing.ts
+++ b/src/lib/stores/billing.ts
@@ -11,9 +11,10 @@ import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequi
import { addNotification, notifications } from './notifications';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
-import { activeHeaderAlert } from '$routes/console/store';
+import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/console/store';
import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.svelte';
import { BillingPlan } from '$lib/constants';
+import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2';
@@ -265,3 +266,20 @@ export function checkForMarkedForDeletion(org: Organization) {
});
}
}
+
+export async function checkForMissingPaymentMethod() {
+ const orgs = await sdk.forConsole.billing.listOrganization([
+ Query.notEqual('billingPlan', BillingPlan.STARTER),
+ Query.isNull('paymentMethodId'),
+ Query.isNull('backupPaymentMethodId')
+ ]);
+ if (orgs?.total) {
+ orgMissingPaymentMethod.set(orgs.teams[0]);
+ headerAlert.add({
+ id: 'missingPaymentMethod',
+ component: MissingPaymentMethod,
+ show: true,
+ importance: 8
+ });
+ }
+}
diff --git a/src/lib/stores/organization.ts b/src/lib/stores/organization.ts
index 38914e5b0..2beb13b03 100644
--- a/src/lib/stores/organization.ts
+++ b/src/lib/stores/organization.ts
@@ -22,6 +22,11 @@ export type Organization = Models.Team> & {
billingPlanDowngrade?: string;
};
+export type OrganizationList = {
+ teams: Organization[];
+ total: number;
+};
+
export type BillingLimits = {
bandwidth: number;
documents: number;
diff --git a/src/routes/console/+layout.svelte b/src/routes/console/+layout.svelte
index 7bbd71997..d4c3d7dc2 100644
--- a/src/routes/console/+layout.svelte
+++ b/src/routes/console/+layout.svelte
@@ -19,7 +19,8 @@
checkPaymentAuthorizationRequired,
calculateTrialDay,
paymentExpired,
- checkForMarkedForDeletion
+ checkForMarkedForDeletion,
+ checkForMissingPaymentMethod
} from '$lib/stores/billing';
import { goto } from '$app/navigation';
import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
@@ -247,6 +248,7 @@
if (isCloud && hasStripePublicKey) {
$stripe = await loadStripe(VARS.STRIPE_PUBLIC_KEY);
+ await checkForMissingPaymentMethod();
}
});
diff --git a/src/routes/console/onboarding/+page.svelte b/src/routes/console/onboarding/+page.svelte
index c4cda0b74..af2a1e2ba 100644
--- a/src/routes/console/onboarding/+page.svelte
+++ b/src/routes/console/onboarding/+page.svelte
@@ -25,16 +25,18 @@
let showCustomId = false;
let plan: Tier;
- const options = [
- {
- value: BillingPlan.STARTER,
- label: `Starter - ${formatCurrency($plansInfo.get(BillingPlan.STARTER).price)}/month`
- },
- {
- value: BillingPlan.PRO,
- label: `Pro - ${formatCurrency($plansInfo.get(BillingPlan.PRO).price)}/month + add-ons`
- }
- ];
+ const options = isCloud
+ ? [
+ {
+ value: BillingPlan.STARTER,
+ label: `Starter - ${formatCurrency($plansInfo.get(BillingPlan.STARTER).price)}/month`
+ },
+ {
+ value: BillingPlan.PRO,
+ label: `Pro - ${formatCurrency($plansInfo.get(BillingPlan.PRO).price)}/month + add-ons`
+ }
+ ]
+ : [];
onMount(() => {
if (isCloud) {
diff --git a/src/routes/console/store.ts b/src/routes/console/store.ts
index 3dabf7951..662572c6f 100644
--- a/src/routes/console/store.ts
+++ b/src/routes/console/store.ts
@@ -1,5 +1,6 @@
import { page } from '$app/stores';
import type { HeaderAlert } from '$lib/stores/headerAlert';
+import type { Organization } from '$lib/stores/organization';
import type { Models } from '@appwrite.io/console';
import { derived, writable } from 'svelte/store';
@@ -10,3 +11,4 @@ export const consoleVariables = derived(
);
export const activeHeaderAlert = writable(null);
+export const orgMissingPaymentMethod = writable(null);