diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index a665d2e720..a07cfb5a1f 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -319,7 +319,11 @@ export class Billing { name: string, billingPlan: string, paymentMethodId: string, - billingAddressId: string = undefined + billingAddressId: string = undefined, + invites: string[] = undefined, + couponId: string = undefined, + taxId: string = undefined, + billingBudget: number = undefined ): Promise { const path = `/organizations`; const params = { @@ -327,7 +331,11 @@ export class Billing { name, billingPlan, paymentMethodId, - billingAddressId + billingAddressId, + invites, + couponId, + taxId, + billingBudget }; const uri = new URL(this.client.config.endpoint + path); return await this.client.call( @@ -384,14 +392,37 @@ export class Billing { organizationId: string, billingPlan: string, paymentMethodId: string, - billingAddressId: string = undefined + billingAddressId: string = undefined, + invites: string[] = undefined, + couponId: string = undefined, + taxId: string = undefined, + billingBudget: number = undefined ): Promise { const path = `/organizations/${organizationId}/plan`; const params = { organizationId, billingPlan, paymentMethodId, - billingAddressId + billingAddressId, + invites, + couponId, + taxId, + billingBudget + }; + const uri = new URL(this.client.config.endpoint + path); + return await this.client.call( + 'patch', + uri, + { + 'content-type': 'application/json' + }, + params + ); + } + async validateOrganization(organizationId: string): Promise { + const path = `/organizations/${organizationId}/validate`; + const params = { + organizationId }; const uri = new URL(this.client.config.endpoint + path); return await this.client.call( diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 21611761f9..08ea724ec5 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -470,3 +470,23 @@ export function calculateResourceSurplus(total: number, limit: number, limitUnit const realLimit = (limitUnit ? sizeToBytes(limit, limitUnit) : limit) || Infinity; return total > realLimit ? total - realLimit : 0; } + +export function buildOrgRedirectURL( + base: string, + name: string, + plan: string, + paymentMethodId: string, + id: string, + collaborators: string[] = null, + coupon: string = null +) { + const url = new URL(base); + url.searchParams.append('name', name); + url.searchParams.append('plan', plan); + url.searchParams.append('paymentMethod', paymentMethodId); + url.searchParams.append('validate', id); + if (collaborators?.length) url.searchParams.append('collaborators', collaborators.join(',')); + if (coupon) url.searchParams.append('coupon', coupon); + + return url.toString(); +} diff --git a/src/lib/stores/organization.ts b/src/lib/stores/organization.ts index f0f363d56f..6dd86828ae 100644 --- a/src/lib/stores/organization.ts +++ b/src/lib/stores/organization.ts @@ -20,6 +20,7 @@ export type Organization = Models.Team> & { amount: number; billingTaxId?: string; billingPlanDowngrade?: Tier; + client_secret?: string; }; export type OrganizationList = { diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index a621bff958..2fe375c46d 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -18,10 +18,12 @@ } from '$lib/layout'; import { type PaymentList } from '$lib/sdk/billing'; import { app } from '$lib/stores/app'; + import { buildOrgRedirectURL } from '$lib/stores/billing.js'; import { campaigns } from '$lib/stores/campaigns'; import { addNotification } from '$lib/stores/notifications'; import { organizationList, type Organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; + import { confirmPayment } from '$lib/stores/stripe.js'; import { ID } from '@appwrite.io/console'; import { onMount } from 'svelte'; import { writable } from 'svelte/store'; @@ -83,6 +85,19 @@ if (campaign?.plan) { billingPlan = campaign.plan; } + if ($page.url.searchParams.has('name')) { + name = $page.url.searchParams.get('name'); + } + if ($page.url.searchParams.has('paymentMethod')) { + paymentMethodId = $page.url.searchParams.get('paymentMethod'); + } + if ($page.url.searchParams.has('collaborators')) { + collaborators = $page.url.searchParams.get('collaborators')?.split(',') ?? []; + } + if ($page.url.searchParams.has('validate')) { + const id = $page.url.searchParams.get('validate'); + await sdk.forConsole.billing.validateOrganization(id); + } }); async function loadPaymentMethods() { @@ -105,7 +120,12 @@ newOrgId, name, billingPlan, - paymentMethodId + paymentMethodId, + null, + collaborators?.length ? collaborators : null, + couponData?.code ? couponData.code : null, + taxId ?? null, + billingBudget ?? null ); } // Upgrade existing org @@ -114,42 +134,58 @@ selectedOrg.$id, billingPlan, paymentMethodId, - null + null, + collaborators?.length ? collaborators : null, + couponData?.code ? couponData.code : null, + taxId ?? null, + billingBudget ?? null ); } // Existing pro org else { org = selectedOrg; - } - // Add coupon - if (couponData?.code) { - await sdk.forConsole.billing.addCredit(org.$id, couponData.code); - } + if (couponData?.code) { + await sdk.forConsole.billing.addCredit(org.$id, couponData.code); + } + if (collaborators?.length) { + collaborators.forEach(async (collaborator) => { + await sdk.forConsole.teams.createMembership( + org.$id, + ['owner'], + collaborator, + undefined, + undefined, + `${$page.url.origin}/${base}/organization-${org.$id}` + ); + }); + } - // Add budget - if (billingBudget) { - await sdk.forConsole.billing.updateBudget(org.$id, billingBudget, [75]); - } + if (billingBudget) { + await sdk.forConsole.billing.updateBudget(org.$id, billingBudget, [75]); + } - // Add collaborators - if (collaborators?.length) { - collaborators.forEach(async (collaborator) => { - await sdk.forConsole.teams.createMembership( - org.$id, - ['owner'], - collaborator, - undefined, - undefined, - `${$page.url.origin}/${base}/organization-${org.$id}` - ); - }); + if (taxId) { + await sdk.forConsole.billing.updateTaxId(org.$id, taxId); + } } - // Add tax ID - if (taxId) { - await sdk.forConsole.billing.updateTaxId(org.$id, taxId); + // Confirm payment if required + if (org?.client_secret) { + let redirectURL = buildOrgRedirectURL( + `${base}/apply-credit`, + name, + billingPlan, + paymentMethodId, + org.$id, + collaborators, + couponData?.code + ); + await confirmPayment(org.$id, org.client_secret, paymentMethodId, redirectURL); + + await sdk.forConsole.billing.validateOrganization(org.$id); } + trackEvent(Submit.CreditRedeem, { coupon: couponData.code, campaign: couponData?.campaign diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index 137ede330a..a9d661d83b 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -19,10 +19,11 @@ WizardSecondaryFooter } from '$lib/layout'; import type { Coupon, PaymentList } from '$lib/sdk/billing'; - import { tierToPlan } from '$lib/stores/billing'; + import { buildOrgRedirectURL, tierToPlan } from '$lib/stores/billing'; import { addNotification } from '$lib/stores/notifications'; import { organizationList, type Organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; + import { confirmPayment } from '$lib/stores/stripe'; import { ID } from '@appwrite.io/console'; import { onMount } from 'svelte'; import { writable } from 'svelte/store'; @@ -79,6 +80,16 @@ billingPlan = plan as BillingPlan; } } + if ($page.url.searchParams.has('paymentMethod')) { + paymentMethodId = $page.url.searchParams.get('paymentMethod'); + } + if ($page.url.searchParams.has('collaborators')) { + collaborators = $page.url.searchParams.get('collaborators')?.split(',') ?? []; + } + if ($page.url.searchParams.has('validate')) { + const id = $page.url.searchParams.get('validate'); + await sdk.forConsole.billing.validateOrganization(id); + } if (anyOrgFree) { billingPlan = BillingPlan.PRO; } @@ -107,44 +118,34 @@ name, billingPlan, paymentMethodId, - null + null, + collaborators?.length ? collaborators : null, + couponData?.code ?? null, + taxId ?? null, + billingBudget ?? null ); - //Add budget - if (billingBudget) { - await sdk.forConsole.billing.updateBudget(org.$id, billingBudget, [75]); - } - - //Add coupon - if (couponData?.code) { - await sdk.forConsole.billing.addCredit(org.$id, couponData.code); - trackEvent(Submit.CreditRedeem); - } - - //Add collaborators - if (collaborators?.length) { - collaborators.forEach(async (collaborator) => { - await sdk.forConsole.teams.createMembership( - org.$id, - ['developer'], - collaborator, - undefined, - undefined, - `${$page.url.origin}/${base}/organization-${org.$id}` - ); - }); - } - - // Add tax ID - if (taxId) { - await sdk.forConsole.billing.updateTaxId(org.$id, taxId); + if (org?.client_secret) { + let redirectURL = buildOrgRedirectURL( + `${base}/create-organization`, + name, + billingPlan, + paymentMethodId, + org.$id, + collaborators, + couponData?.code + ); + await confirmPayment(org.$id, org.client_secret, paymentMethodId, redirectURL); + + await sdk.forConsole.billing.validateOrganization(org.$id); } } trackEvent(Submit.OrganizationCreate, { plan: tierToPlan(billingPlan)?.name, budget_cap_enabled: !!billingBudget, - members_invited: collaborators?.length + members_invited: collaborators?.length, + coupon: couponData?.code ?? null }); await invalidate(Dependencies.ACCOUNT); diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index dbfb4fbc7e..a414984fa4 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -30,10 +30,11 @@ WizardSecondaryFooter } from '$lib/layout'; import { type Coupon, type PaymentList } from '$lib/sdk/billing'; - import { plansInfo, tierToPlan, type Tier } from '$lib/stores/billing'; + import { buildOrgRedirectURL, plansInfo, tierToPlan, type Tier } from '$lib/stores/billing'; import { addNotification } from '$lib/stores/notifications'; import { organization, organizationList, type Organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; + import { confirmPayment } from '$lib/stores/stripe.js'; import { user } from '$lib/stores/user'; import { VARS } from '$lib/system'; import { onMount } from 'svelte'; @@ -99,6 +100,17 @@ } else { billingPlan = BillingPlan.PRO; } + + if ($page.url.searchParams.has('paymentMethod')) { + paymentMethodId = $page.url.searchParams.get('paymentMethod'); + } + if ($page.url.searchParams.has('collaborators')) { + collaborators = $page.url.searchParams.get('collaborators')?.split(',') ?? []; + } + if ($page.url.searchParams.has('validate')) { + const id = $page.url.searchParams.get('validate'); + await sdk.forConsole.billing.validateOrganization(id); + } }); async function loadPaymentMethods() { @@ -173,41 +185,27 @@ $organization.$id, billingPlan, paymentMethodId, - null + null, + collaborators?.length ? collaborators : null, + couponData?.code ? couponData.code : null, + taxId ?? null, + billingBudget ?? null ); - //Add coupon - if (couponData?.code) { - await sdk.forConsole.billing.addCredit(org.$id, couponData.code); - trackEvent(Submit.CreditRedeem); - } - - //Add budget - if (billingBudget) { - await sdk.forConsole.billing.updateBudget(org.$id, billingBudget, [75]); - } - - //Add collaborators - if (collaborators?.length) { - const newCollaborators = collaborators.filter( - (collaborator) => - !data?.members?.memberships?.find((m) => m.userEmail === collaborator) + //If the response contains a client_secret, it means that validation is required + if (org?.client_secret) { + let redirectURL = buildOrgRedirectURL( + `${base}/create-organization`, + org.name, + billingPlan, + paymentMethodId, + org.$id, + collaborators, + couponData?.code ); - newCollaborators.forEach(async (collaborator) => { - await sdk.forConsole.teams.createMembership( - org.$id, - ['owner'], - collaborator, - undefined, - undefined, - `${$page.url.origin}/${base}/organization-${org.$id}` - ); - }); - } + await confirmPayment(org.$id, org.client_secret, paymentMethodId, redirectURL); - //Add tax ID - if (taxId) { - await sdk.forConsole.billing.updateTaxId(org.$id, taxId); + await sdk.forConsole.billing.validateOrganization(org.$id); } await invalidate(Dependencies.ACCOUNT);