From bf2166bd37d0e317ba3afc7ec40edb47d31c843c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 24 Feb 2026 08:09:24 +0000 Subject: [PATCH 1/9] feat: implement BAA addon management --- bun.lock | 4 +- package.json | 2 +- src/lib/actions/analytics.ts | 2 + src/lib/constants.ts | 3 +- .../billing/+page.ts | 1 + .../billing/planSummary.svelte | 14 +- .../settings/+page.svelte | 38 ++++- .../settings/+page.ts | 13 +- .../settings/BAA.svelte | 145 +++++++++++++++--- .../settings/BAADisableModal.svelte | 54 +++++++ .../settings/BAAEnableModal.svelte | 77 ++++++++++ .../settings/BAAModal.svelte | 125 --------------- 12 files changed, 323 insertions(+), 155 deletions(-) create mode 100644 src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte create mode 100644 src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte delete mode 100644 src/routes/(console)/organization-[organization]/settings/BAAModal.svelte diff --git a/bun.lock b/bun.lock index 36d1113afb..083357f2ca 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@44b749b", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -108,7 +108,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@44b749b", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index 1905197e2f..db18e24996 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@44b749b", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 305bf33d95..48a0414e66 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -425,6 +425,8 @@ export enum Submit { MessagingTopicSubscriberDelete = 'submit_messaging_topic_subscriber_delete', ApplyQuickFilter = 'submit_apply_quick_filter', RequestBAA = 'submit_request_baa', + BAAAddonEnable = 'submit_baa_addon_enable', + BAAAddonDisable = 'submit_baa_addon_disable', RequestSoc2 = 'submit_request_soc2', SiteCreate = 'submit_site_create', SiteDelete = 'submit_site_delete', diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2cb52605e3..549c56fd61 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -85,7 +85,8 @@ export enum Dependencies { MESSAGING_TOPIC_SUBSCRIBERS = 'dependency:messaging_topic_subscribers', SITE = 'dependency:site', SITES = 'dependency:sites', - SITES_DOMAINS = 'dependency:sites_domains' + SITES_DOMAINS = 'dependency:sites_domains', + ADDONS = 'dependency:addons' } export const defaultScopes: string[] = [ diff --git a/src/routes/(console)/organization-[organization]/billing/+page.ts b/src/routes/(console)/organization-[organization]/billing/+page.ts index 4c10658caa..202a6ee25c 100644 --- a/src/routes/(console)/organization-[organization]/billing/+page.ts +++ b/src/routes/(console)/organization-[organization]/billing/+page.ts @@ -25,6 +25,7 @@ export const load: PageLoad = async ({ parent, depends, url, route }) => { depends(Dependencies.CREDIT); depends(Dependencies.INVOICES); depends(Dependencies.ADDRESS); + depends(Dependencies.ADDONS); // aggregation reloads on page param changes depends(Dependencies.BILLING_AGGREGATION); diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index afe13b5172..a268205750 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -247,8 +247,17 @@ }; // addons (additional members, projects, etc.) + const billingAddonNames: Record = { + addon_baa: 'BAA Agreement' + }; + const addons = (currentAggregation?.resources || []) - .filter((r) => r.amount > 0 && currentPlan?.addons?.[r.resourceId]?.price > 0) + .filter( + (r) => + r.amount > 0 && + (currentPlan?.addons?.[r.resourceId]?.price > 0 || + r.resourceId.startsWith('addon_')) + ) .map((addon) => ({ id: `addon-${addon.resourceId}`, expandable: false, @@ -258,7 +267,8 @@ ? 'Additional members' : addon.resourceId === 'projects' ? 'Additional projects' - : `${addon.resourceId} overage (${formatNum(addon.value)})`, + : billingAddonNames[addon.resourceId] ?? + `${addon.resourceId} overage (${formatNum(addon.value)})`, usage: '', price: formatCurrency(addon.amount) }, diff --git a/src/routes/(console)/organization-[organization]/settings/+page.svelte b/src/routes/(console)/organization-[organization]/settings/+page.svelte index b5bf05fc0d..e9a1581587 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.svelte +++ b/src/routes/(console)/organization-[organization]/settings/+page.svelte @@ -6,9 +6,11 @@ import { sdk } from '$lib/stores/sdk'; import { members, organization } from '$lib/stores/organization'; import { projects } from '../store'; - import { invalidate } from '$app/navigation'; + import { goto, invalidate } from '$app/navigation'; + import { resolve } from '$app/paths'; import { Dependencies } from '$lib/constants'; import { onMount } from 'svelte'; + import { page } from '$app/state'; import Delete from './deleteOrganizationModal.svelte'; import DownloadDPA from './downloadDPA.svelte'; import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; @@ -22,8 +24,38 @@ let name: string; let showDelete = false; - onMount(() => { + onMount(async () => { name = $organization.name; + + if (page.url.searchParams.get('type') === 'validate-addon') { + const addonId = page.url.searchParams.get('addonId'); + if (addonId) { + try { + await sdk.forConsole.organizations.validateToggleAddonPayment({ + organizationId: $organization.$id, + addonId + }); + await Promise.all([ + invalidate(Dependencies.ADDONS), + invalidate(Dependencies.ORGANIZATION) + ]); + addNotification({ + message: 'BAA addon has been enabled', + type: 'success' + }); + } catch (e) { + addNotification({ + message: e.message, + type: 'error' + }); + } + const settingsUrl = resolve( + '/(console)/organization-[organization]/settings', + { organization: $organization.$id } + ); + await goto(settingsUrl, { replaceState: true }); + } + } }); async function updateName() { @@ -75,7 +107,7 @@ {#if isCloud} - + {/if} diff --git a/src/routes/(console)/organization-[organization]/settings/+page.ts b/src/routes/(console)/organization-[organization]/settings/+page.ts index 1f377bfe33..8ec4edb17b 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.ts +++ b/src/routes/(console)/organization-[organization]/settings/+page.ts @@ -7,8 +7,9 @@ import { isCloud } from '$lib/system'; export const load: PageLoad = async ({ depends, params, parent }) => { const { countryList, locale } = await parent(); depends(Dependencies.ORGANIZATION); + depends(Dependencies.ADDONS); - const [projects, invoices] = await Promise.all([ + const [projects, invoices, addons] = await Promise.all([ sdk.forConsole.projects.list({ queries: [Query.equal('teamId', params.organization), Query.select(['$id', 'name'])] }), @@ -16,12 +17,20 @@ export const load: PageLoad = async ({ depends, params, parent }) => { ? sdk.forConsole.organizations.listInvoices({ organizationId: params.organization }) - : undefined + : undefined, + isCloud + ? sdk.forConsole.organizations + .listAddons({ + organizationId: params.organization + }) + .catch(() => null) + : null ]); return { projects, invoices, + addons, countryList, locale }; diff --git a/src/routes/(console)/organization-[organization]/settings/BAA.svelte b/src/routes/(console)/organization-[organization]/settings/BAA.svelte index 87b33da1f1..a4e4d47252 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAA.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAA.svelte @@ -1,38 +1,145 @@ BAA - After requesting a BAA, we will contact you via email for the next steps. + A Business Associate Agreement (BAA) is a HIPAA-required document ensuring outside services + handling patient information for a healthcare organization follow privacy rules.
Business Associate Agreement (BAA)
-

- A Business Associate Agreement (BAA) is a HIPAA-required document ensuring outside - services handling patient information for a healthcare organization follow privacy - rules. -

- + {#if !planSupportsBaa && canUpgradeToBaa} +

+ BAA is not available on your current plan. Upgrade your plan to enable BAA for + your organization. +

+ + {:else if !planSupportsBaa} +

+ BAA is not available on your current plan. +

+ {:else if isActive} +
+ {#if isScheduledForRemoval} + + {:else} + + {/if} +
+

+ BAA is enabled for your organization at $350/month. +

+ {#if isScheduledForRemoval} +

+ BAA will be removed at the end of your current billing cycle. +

+ + {:else} + + {/if} + {:else} +

+ Enable BAA for your organization to ensure HIPAA compliance. This addon costs + $350/month, prorated for your current billing cycle. +

+ + {/if}
- + + +{#if baaAddon} + +{/if} diff --git a/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte new file mode 100644 index 0000000000..df8d321b84 --- /dev/null +++ b/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte @@ -0,0 +1,54 @@ + + + +

+ Are you sure you want to disable the BAA addon? The addon will remain active until the end + of your current billing cycle and will not be renewed. +

+ + + + + +
diff --git a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte new file mode 100644 index 0000000000..b129f85c64 --- /dev/null +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -0,0 +1,77 @@ + + + +

+ By enabling BAA, a Business Associate Agreement will be activated for your organization to + ensure HIPAA compliance. +

+

+ This addon costs $350/month, prorated for the remainder of your current billing + cycle. +

+ + + + + +
diff --git a/src/routes/(console)/organization-[organization]/settings/BAAModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAModal.svelte deleted file mode 100644 index 1aaccfef2a..0000000000 --- a/src/routes/(console)/organization-[organization]/settings/BAAModal.svelte +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - From fa54499147784fed5b320e56b9c2a676ea82ae3e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 26 Feb 2026 08:43:59 +0000 Subject: [PATCH 2/9] update sdk --- bun.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 083357f2ca..b68b8e2629 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@44b749b", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@111f583", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -108,7 +108,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@44b749b", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@111f583", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index db18e24996..86ff31b62f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@44b749b", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@111f583", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", From 599ce487b675ef433baa7d08b16dc0b699346af3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 1 Mar 2026 06:20:48 +0000 Subject: [PATCH 3/9] Update BAA addon handling and pricing in console settings - Updated the version of @appwrite.io/console in bun.lock and package.json. - Enhanced BAA settings page to handle missing or invalid addonId gracefully. - Added functionality to cancel and retry payment for pending BAA addon. - Improved BAAEnableModal with prorated pricing calculation and updated agreement terms. --- bun.lock | 4 +- package.json | 2 +- .../settings/+page.svelte | 59 ++++++++-- .../settings/BAA.svelte | 38 ++++++- .../settings/BAAEnableModal.svelte | 104 ++++++++++++++++-- 5 files changed, 183 insertions(+), 24 deletions(-) diff --git a/bun.lock b/bun.lock index b68b8e2629..93a8c9993e 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@111f583", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6eabe7e", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -108,7 +108,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@111f583", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@6eabe7e", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index 86ff31b62f..b2b94ad21f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@111f583", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6eabe7e", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/routes/(console)/organization-[organization]/settings/+page.svelte b/src/routes/(console)/organization-[organization]/settings/+page.svelte index e9a1581587..a7065e46e5 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.svelte +++ b/src/routes/(console)/organization-[organization]/settings/+page.svelte @@ -28,7 +28,23 @@ name = $organization.name; if (page.url.searchParams.get('type') === 'validate-addon') { - const addonId = page.url.searchParams.get('addonId'); + let addonId = page.url.searchParams.get('addonId'); + + // Fall back to listing addons if addonId is missing or invalid + if (!addonId || addonId === 'undefined') { + try { + const addons = await sdk.forConsole.organizations.listAddons({ + organizationId: $organization.$id + }); + const pending = addons.addons.find( + (a) => a.key === 'baa' && a.status === 'pending' + ); + addonId = pending?.$id ?? null; + } catch { + addonId = null; + } + } + if (addonId) { try { await sdk.forConsole.organizations.validateToggleAddonPayment({ @@ -44,17 +60,40 @@ type: 'success' }); } catch (e) { - addNotification({ - message: e.message, - type: 'error' - }); + // If addon not found, payment webhook may have already activated it + if (e?.type === 'addon_not_found' || e?.code === 404) { + await Promise.all([ + invalidate(Dependencies.ADDONS), + invalidate(Dependencies.ORGANIZATION) + ]); + addNotification({ + message: 'BAA addon has been enabled', + type: 'success' + }); + } else { + addNotification({ + message: e.message, + type: 'error' + }); + } } - const settingsUrl = resolve( - '/(console)/organization-[organization]/settings', - { organization: $organization.$id } - ); - await goto(settingsUrl, { replaceState: true }); + } else { + // No pending addon found — likely already activated by webhook + await Promise.all([ + invalidate(Dependencies.ADDONS), + invalidate(Dependencies.ORGANIZATION) + ]); + addNotification({ + message: 'BAA addon has been enabled', + type: 'success' + }); } + + const settingsUrl = resolve( + '/(console)/organization-[organization]/settings', + { organization: $organization.$id } + ); + await goto(settingsUrl, { replaceState: true }); } }); diff --git a/src/routes/(console)/organization-[organization]/settings/BAA.svelte b/src/routes/(console)/organization-[organization]/settings/BAA.svelte index a4e4d47252..7ea289bb8c 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAA.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAA.svelte @@ -20,13 +20,15 @@ let showEnable = false; let showDisable = false; let reEnabling = false; + let cancelling = false; $: planSupportsBaa = $currentPlan?.supportedAddons?.baa === true; $: canUpgradeToBaa = !planSupportsBaa && hasUpgradeablePlanWithBaa($currentPlan); $: baaAddon = addons?.addons?.find( (a) => a.key === 'baa' && (a.status === 'active' || a.status === 'pending') ); - $: isActive = baaAddon?.status === 'active' && baaAddon?.currentValue === 1; + $: isPending = baaAddon?.status === 'pending'; + $: isActive = baaAddon?.status === 'active'; $: isScheduledForRemoval = isActive && baaAddon?.nextValue === 0; function hasUpgradeablePlanWithBaa(plan: Models.BillingPlan): boolean { @@ -40,6 +42,25 @@ return false; } + async function handleCancelAndRetry() { + cancelling = true; + try { + await sdk.forConsole.organizations.deleteToggleAddon({ + organizationId: $organization.$id, + addonId: baaAddon.$id + }); + await invalidate(Dependencies.ADDONS); + showEnable = true; + } catch (e) { + addNotification({ + message: e.message, + type: 'error' + }); + } finally { + cancelling = false; + } + } + async function handleReEnable() { reEnabling = true; try { @@ -92,6 +113,21 @@

BAA is not available on your current plan.

+ {:else if isPending} +
+ +
+

+ A payment is awaiting confirmation. If the payment was interrupted, you can + cancel and retry. +

+ {:else if isActive}
{#if isScheduledForRemoval} diff --git a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte index b129f85c64..a1a484351c 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -9,13 +9,44 @@ import { confirmPayment } from '$lib/stores/stripe'; import { organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; + import { formatCurrency } from '$lib/helpers/numbers'; import type { Models } from '@appwrite.io/console'; export let show = false; + const BAA_MONTHLY_PRICE = 350; + const BAA_AGREEMENT_URL = 'https://appwrite.io/legal/baa'; + let error: string = null; let submitting = false; + $: proratedAmount = calculateProratedAmount( + $organization?.billingCurrentInvoiceDate, + $organization?.billingNextInvoiceDate + ); + + function calculateProratedAmount( + currentInvoiceDate: string | undefined, + nextInvoiceDate: string | undefined + ): number { + if (!currentInvoiceDate || !nextInvoiceDate) return BAA_MONTHLY_PRICE; + + const today = new Date(); + const cycleStart = new Date(currentInvoiceDate); + const cycleEnd = new Date(nextInvoiceDate); + + const totalDays = Math.max( + 1, + Math.ceil((cycleEnd.getTime() - cycleStart.getTime()) / (1000 * 60 * 60 * 24)) + ); + const remainingDays = Math.max( + 0, + Math.ceil((cycleEnd.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)) + ); + + return Math.round(((BAA_MONTHLY_PRICE * remainingDays) / totalDays) * 100) / 100; + } + async function handleSubmit() { submitting = true; error = null; @@ -36,7 +67,7 @@ clientSecret: paymentAuth.clientSecret, paymentMethodId: $organization.paymentMethodId, orgId: $organization.$id, - route: `${settingsUrl}?type=validate-addon&addonId=${paymentAuth.invoiceId}` + route: `${settingsUrl}?type=validate-addon&addonId=${paymentAuth.addonId}` }); return; } @@ -52,26 +83,79 @@ trackEvent(Submit.BAAAddonEnable); show = false; } catch (e) { - error = e.message; - trackError(e, Submit.BAAAddonEnable); + // 409 means addon already exists (pending or active from prior attempt) + if (e?.code === 409) { + await Promise.all([ + invalidate(Dependencies.ADDONS), + invalidate(Dependencies.ORGANIZATION) + ]); + show = false; + } else { + error = e.message; + trackError(e, Submit.BAAAddonEnable); + } } finally { submitting = false; } } - +

- By enabling BAA, a Business Associate Agreement will be activated for your organization to - ensure HIPAA compliance. -

-

- This addon costs $350/month, prorated for the remainder of your current billing + By clicking Accept & Enable, the amount of + {formatCurrency(BAA_MONTHLY_PRICE)} will be added to your subscription and your + credit card will be charged + {formatCurrency(proratedAmount)} immediately for the remaining days in your billing cycle.

+

+ Your action confirms acceptance of Appwrite's + Business Associate Agreement + and related terms. +

+ +
+
+ HIPAA BAA + {formatCurrency(BAA_MONTHLY_PRICE)} / month +
+
+
+ Total + {formatCurrency(BAA_MONTHLY_PRICE)} / month +
+

+ * Plus applicable tax and fees +

+
- +
+ + From 12db4e6491fcba837c8d7ba6b4126bf39c4438f1 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 1 Mar 2026 07:21:57 +0000 Subject: [PATCH 4/9] typo --- .../organization-[organization]/settings/BAAEnableModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte index a1a484351c..a0fcdba5c2 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -104,7 +104,7 @@

By clicking Accept & Enable, the amount of {formatCurrency(BAA_MONTHLY_PRICE)} will be added to your subscription and your - credit card will be charged + payment method will be charged {formatCurrency(proratedAmount)} immediately for the remaining days in your billing cycle.

From 23dd014c09242d8306e9d7366096288ba88ebdc5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 1 Mar 2026 07:29:03 +0000 Subject: [PATCH 5/9] format --- .../billing/planSummary.svelte | 4 ++-- .../organization-[organization]/settings/+page.svelte | 7 +++---- .../organization-[organization]/settings/BAA.svelte | 4 ++-- .../settings/BAAEnableModal.svelte | 10 ++++------ 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index a268205750..4411e16543 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -267,8 +267,8 @@ ? 'Additional members' : addon.resourceId === 'projects' ? 'Additional projects' - : billingAddonNames[addon.resourceId] ?? - `${addon.resourceId} overage (${formatNum(addon.value)})`, + : (billingAddonNames[addon.resourceId] ?? + `${addon.resourceId} overage (${formatNum(addon.value)})`), usage: '', price: formatCurrency(addon.amount) }, diff --git a/src/routes/(console)/organization-[organization]/settings/+page.svelte b/src/routes/(console)/organization-[organization]/settings/+page.svelte index a7065e46e5..66abe77cab 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.svelte +++ b/src/routes/(console)/organization-[organization]/settings/+page.svelte @@ -89,10 +89,9 @@ }); } - const settingsUrl = resolve( - '/(console)/organization-[organization]/settings', - { organization: $organization.$id } - ); + const settingsUrl = resolve('/(console)/organization-[organization]/settings', { + organization: $organization.$id + }); await goto(settingsUrl, { replaceState: true }); } }); diff --git a/src/routes/(console)/organization-[organization]/settings/BAA.svelte b/src/routes/(console)/organization-[organization]/settings/BAA.svelte index 7ea289bb8c..8583475143 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAA.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAA.svelte @@ -91,8 +91,8 @@ BAA - A Business Associate Agreement (BAA) is a HIPAA-required document ensuring outside services - handling patient information for a healthcare organization follow privacy rules. + A Business Associate Agreement (BAA) is a HIPAA-required document ensuring outside services handling + patient information for a healthcare organization follow privacy rules.
diff --git a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte index a0fcdba5c2..925480dfcd 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -59,10 +59,9 @@ if ('clientSecret' in result) { const paymentAuth = result as unknown as Models.PaymentAuthentication; - const settingsUrl = resolve( - '/(console)/organization-[organization]/settings', - { organization: $organization.$id } - ); + const settingsUrl = resolve('/(console)/organization-[organization]/settings', { + organization: $organization.$id + }); await confirmPayment({ clientSecret: paymentAuth.clientSecret, paymentMethodId: $organization.paymentMethodId, @@ -105,8 +104,7 @@ By clicking Accept & Enable, the amount of {formatCurrency(BAA_MONTHLY_PRICE)} will be added to your subscription and your payment method will be charged - {formatCurrency(proratedAmount)} immediately for the remaining days in your billing - cycle. + {formatCurrency(proratedAmount)} immediately for the remaining days in your billing cycle.

Your action confirms acceptance of Appwrite's From dd159cf4d459ee1ff21b9c4f27da60e42fac6fd4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 1 Mar 2026 07:41:17 +0000 Subject: [PATCH 6/9] rename addon display name --- .../organization-[organization]/billing/planSummary.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 4411e16543..31291bf74e 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -248,7 +248,7 @@ // addons (additional members, projects, etc.) const billingAddonNames: Record = { - addon_baa: 'BAA Agreement' + addon_baa: 'HIPPA BAA' }; const addons = (currentAggregation?.resources || []) From 214ba5c98b2a33b6a88beab205206b53da2801c4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 3 Mar 2026 02:03:03 +0000 Subject: [PATCH 7/9] Update BAA addon methods and console dependency version --- bun.lock | 4 ++-- package.json | 2 +- .../organization-[organization]/settings/+page.svelte | 2 +- .../(console)/organization-[organization]/settings/BAA.svelte | 4 ++-- .../settings/BAADisableModal.svelte | 2 +- .../settings/BAAEnableModal.svelte | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 93a8c9993e..2d0120fad7 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6eabe7e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@86906d2", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -108,7 +108,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@6eabe7e", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@86906d2", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index b2b94ad21f..601b5bc4db 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6eabe7e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@86906d2", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/routes/(console)/organization-[organization]/settings/+page.svelte b/src/routes/(console)/organization-[organization]/settings/+page.svelte index 66abe77cab..e4ec7fa998 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.svelte +++ b/src/routes/(console)/organization-[organization]/settings/+page.svelte @@ -47,7 +47,7 @@ if (addonId) { try { - await sdk.forConsole.organizations.validateToggleAddonPayment({ + await sdk.forConsole.organizations.validateAddonPayment({ organizationId: $organization.$id, addonId }); diff --git a/src/routes/(console)/organization-[organization]/settings/BAA.svelte b/src/routes/(console)/organization-[organization]/settings/BAA.svelte index 8583475143..5a458da47a 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAA.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAA.svelte @@ -45,7 +45,7 @@ async function handleCancelAndRetry() { cancelling = true; try { - await sdk.forConsole.organizations.deleteToggleAddon({ + await sdk.forConsole.organizations.deleteAddon({ organizationId: $organization.$id, addonId: baaAddon.$id }); @@ -64,7 +64,7 @@ async function handleReEnable() { reEnabling = true; try { - await sdk.forConsole.organizations.createToggleAddon({ + await sdk.forConsole.organizations.createBaaAddon({ organizationId: $organization.$id, key: 'baa' }); diff --git a/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte index df8d321b84..a1a5817bce 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte @@ -18,7 +18,7 @@ submitting = true; error = null; try { - await sdk.forConsole.organizations.deleteToggleAddon({ + await sdk.forConsole.organizations.deleteAddon({ organizationId: $organization.$id, addonId }); diff --git a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte index 925480dfcd..70e268f9e4 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -52,7 +52,7 @@ error = null; try { const result: Models.Addon | Models.PaymentAuthentication = - await sdk.forConsole.organizations.createToggleAddon({ + await sdk.forConsole.organizations.createBaaAddon({ organizationId: $organization.$id, key: 'baa' }); From e50b1475f109b324c138b05d0bde4fefd99fb900 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 3 Mar 2026 02:24:18 +0000 Subject: [PATCH 8/9] Fix typo in BAA addon name and update type for error handling in BAAEnableModal --- .../billing/planSummary.svelte | 2 +- .../organization-[organization]/settings/+page.svelte | 10 ---------- .../settings/BAAEnableModal.svelte | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 31291bf74e..1acbebfdb7 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -248,7 +248,7 @@ // addons (additional members, projects, etc.) const billingAddonNames: Record = { - addon_baa: 'HIPPA BAA' + addon_baa: 'HIPAA BAA' }; const addons = (currentAggregation?.resources || []) diff --git a/src/routes/(console)/organization-[organization]/settings/+page.svelte b/src/routes/(console)/organization-[organization]/settings/+page.svelte index e4ec7fa998..a303b98f5e 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.svelte +++ b/src/routes/(console)/organization-[organization]/settings/+page.svelte @@ -77,16 +77,6 @@ }); } } - } else { - // No pending addon found — likely already activated by webhook - await Promise.all([ - invalidate(Dependencies.ADDONS), - invalidate(Dependencies.ORGANIZATION) - ]); - addNotification({ - message: 'BAA addon has been enabled', - type: 'success' - }); } const settingsUrl = resolve('/(console)/organization-[organization]/settings', { diff --git a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte index 70e268f9e4..6aa4fe7fee 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -17,7 +17,7 @@ const BAA_MONTHLY_PRICE = 350; const BAA_AGREEMENT_URL = 'https://appwrite.io/legal/baa'; - let error: string = null; + let error: string | null = null; let submitting = false; $: proratedAmount = calculateProratedAmount( From f63879aeccd320268ba5f71e6ba4760201a59870 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 3 Mar 2026 02:40:34 +0000 Subject: [PATCH 9/9] Fix migration changes --- src/lib/stores/migration.ts | 2 +- src/routes/(console)/(migration-wizard)/resource-form.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/stores/migration.ts b/src/lib/stores/migration.ts index 534cddc6a9..2dcb58df76 100644 --- a/src/lib/stores/migration.ts +++ b/src/lib/stores/migration.ts @@ -1,5 +1,5 @@ import { writable } from 'svelte/store'; -import { Resources } from '@appwrite.io/console'; +import { AppwriteMigrationResource as Resources } from '@appwrite.io/console'; import { includesAll } from '$lib/helpers/array'; const initialFormData = { diff --git a/src/routes/(console)/(migration-wizard)/resource-form.svelte b/src/routes/(console)/(migration-wizard)/resource-form.svelte index 54e3f23ba4..39ebbd1d51 100644 --- a/src/routes/(console)/(migration-wizard)/resource-form.svelte +++ b/src/routes/(console)/(migration-wizard)/resource-form.svelte @@ -11,7 +11,7 @@ } from '$lib/stores/migration'; import { Button } from '$lib/elements/forms'; import { wizard } from '$lib/stores/wizard'; - import { Resources, type Models } from '@appwrite.io/console'; + import { AppwriteMigrationResource as Resources, type Models } from '@appwrite.io/console'; import type { sdk } from '$lib/stores/sdk'; import ImportReport from '$routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte';