Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated pricing estimates #1565

Open
wants to merge 6 commits into
base: poc-invoice-cycle-ref
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
103 changes: 76 additions & 27 deletions src/lib/components/billing/estimatedTotalBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import type { Coupon } from '$lib/sdk/billing';
import { plansInfo, type Tier } from '$lib/stores/billing';
import { CreditsApplied } from '.';
import { BillingPlan } from '$lib/constants';
import { tooltip } from '$lib/actions/tooltip';

// undefined as we only need this on `change-plan`
export let currentTier: Tier | undefined = undefined;
export let billingPlan: Tier;
export let collaborators: string[];
export let couponData: Partial<Coupon>;
Expand All @@ -14,21 +18,29 @@
export let isDowngrade = false;

const today = new Date();
const isScaleDowngrade = isDowngrade && billingPlan === BillingPlan.PRO;
const isScaleUpgrade = !isDowngrade && billingPlan === BillingPlan.SCALE;
const billingPayDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);

let budgetEnabled = false;

$: currentPlan = $plansInfo.get(billingPlan);
$: extraSeatsCost = 0; // 0 untile trial period later replace (collaborators?.length ?? 0) * (currentPlan?.addons?.member?.price ?? 0);
$: grossCost = currentPlan.price + extraSeatsCost;
$: selectedPlan = $plansInfo.get(billingPlan);
$: currentOrgPlan = $plansInfo.get(currentTier);
$: unUsedBalances = isScaleUpgrade
? currentOrgPlan.price +
(collaborators?.length ?? 0) * (currentOrgPlan?.addons?.member?.price ?? 0)
: isScaleDowngrade
? currentOrgPlan.price
: 0;

$: extraSeatsCost = (collaborators?.length ?? 0) * (selectedPlan?.addons?.member?.price ?? 0);
$: grossCost = isScaleUpgrade
? selectedPlan.price + extraSeatsCost - unUsedBalances
: selectedPlan.price + extraSeatsCost;
$: estimatedTotal =
couponData?.status === 'active'
? grossCost - couponData.credits >= 0
? grossCost - couponData.credits
: 0
: grossCost;
couponData?.status === 'active' ? Math.max(0, grossCost - couponData.credits) : grossCost;
$: trialEndDate = new Date(
billingPayDate.getTime() + currentPlan.trialDays * 24 * 60 * 60 * 1000
billingPayDate.getTime() + selectedPlan.trialDays * 24 * 60 * 60 * 1000
);
</script>

Expand All @@ -37,41 +49,78 @@
style:--p-card-padding="1.5rem"
style:--p-card-border-radius="var(--border-radius-small)">
<slot />
<span class="u-flex u-main-space-between">
<p class="text">{currentPlan.name} plan</p>
<p class="text">{formatCurrency(currentPlan.price)}</p>
</span>
<span class="u-flex u-main-space-between">
<div class="u-flex u-main-space-between">
<p class="text">{selectedPlan.name} plan</p>
<p class="text">{formatCurrency(selectedPlan.price)}</p>
</div>

<div class="u-flex u-main-space-between">
<p class="text" class:u-bold={isDowngrade}>Additional seats ({collaborators?.length})</p>
<p class="text" class:u-bold={isDowngrade}>
{formatCurrency(extraSeatsCost)}
{formatCurrency(
isScaleDowngrade
? (collaborators?.length ?? 0) * (selectedPlan?.addons?.member?.price ?? 0)
: extraSeatsCost
)}
</p>
</span>
</div>

{#if isScaleUpgrade}
{@const currentPlanName = currentOrgPlan.name}
<div class="u-flex u-main-space-between">
<div class="text">
<span>Unused {currentPlanName} plan balance</span>
<span
use:tooltip={{
placement: 'bottom',
content: `This discount reflects the unused portion of your ${currentPlanName} plan and add-ons. Future credits for extra seats and features will apply automatically.`
}}
class="icon-info">
</span>
</div>
<p class="text">-{formatCurrency(unUsedBalances)}</p>
</div>
{/if}

{#if couponData?.status === 'active'}
<CreditsApplied bind:couponData {fixedCoupon} />
{/if}
<div class="u-sep-block-start" />
<span class="u-flex u-main-space-between">
<div class="u-flex u-main-space-between">
<p class="text">
Upcoming charge<br /><span class="u-color-text-gray"
>Due on {!currentPlan.trialDays
>Due on {!selectedPlan.trialDays
? toLocaleDate(billingPayDate.toString())
: toLocaleDate(trialEndDate.toString())}</span>
</p>
<p class="text">
{formatCurrency(estimatedTotal)}
</p>
</span>
</div>

<p class="text u-margin-block-start-16">
You'll pay <span class="u-bold">{formatCurrency(estimatedTotal)}</span> now, with our first
billing cycle starting on
<span class="u-bold"
>{!currentPlan.trialDays
? toLocaleDate(billingPayDate.toString())
: toLocaleDate(trialEndDate.toString())}</span
>. Once your credits run out, you'll be charged
<span class="u-bold">{formatCurrency(currentPlan.price)}</span> plus usage fees every 30 days.
{#if isScaleDowngrade}
You'll continue using the {currentOrgPlan.name} plan until your current cycle ends on
<span class="u-bold"
>{!selectedPlan.trialDays
? toLocaleDate(billingPayDate.toString())
: toLocaleDate(trialEndDate.toString())}</span
>. Starting the next cycle, your plan will switch to {selectedPlan.name}, and you'll be
charged
<span class="u-bold">{formatCurrency(grossCost)}</span>
every 30 days.
{:else}
You'll pay
<span class="u-bold">{formatCurrency(estimatedTotal)}</span>
now, with our first billing cycle starting on
<span class="u-bold"
>{!selectedPlan.trialDays
? toLocaleDate(billingPayDate.toString())
: toLocaleDate(trialEndDate.toString())}</span
>. Once your credits run out, you'll be charged
<span class="u-bold">{formatCurrency(selectedPlan.price)}</span> plus usage fees every 30
days.
{/if}
</p>

<FormList class="u-margin-block-start-24">
Expand Down
6 changes: 1 addition & 5 deletions src/lib/components/billing/planSelection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,7 @@
</LabelCard>
</li>
<li>
<LabelCard
name="plan"
bind:group={billingPlan}
value={BillingPlan.SCALE}
padding={1.5}>
<LabelCard name="plan" bind:group={billingPlan} value={BillingPlan.SCALE} padding={1.5}>
<svelte:fragment slot="custom">
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
<h4 class="body-text-2 u-bold">
Expand Down
7 changes: 2 additions & 5 deletions src/lib/sdk/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,11 @@ export class Billing {
);
}

async validateOrganization(
organizationId: string,
invites: string[],
): Promise<Organization> {
async validateOrganization(organizationId: string, invites: string[]): Promise<Organization> {
const path = `/organizations/${organizationId}/validate`;
const params = {
organizationId,
invites,
invites
};
const uri = new URL(this.client.config.endpoint + path);
return await this.client.call(
Expand Down
4 changes: 2 additions & 2 deletions src/lib/stores/stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export async function confirmPayment(
clientSecret: clientSecret,
confirmParams: {
return_url: url,
payment_method: paymentMethod.providerMethodId,
},
payment_method: paymentMethod.providerMethodId
}
});
if (error) {
if (returnError) {
Expand Down
10 changes: 4 additions & 6 deletions src/routes/(console)/account/organizations/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ export const load: PageLoad = async ({ url, route }) => {
const limit = getLimit(url, route, CARD_LIMIT);
const offset = pageToOffset(page, limit);

const queries = [
Query.offset(offset),
Query.limit(limit),
Query.orderDesc('')
];
const queries = [Query.offset(offset), Query.limit(limit), Query.orderDesc('')];

const organizations = isCloud ? await sdk.forConsole.billing.listOrganization(queries) : sdk.forConsole.teams.list(queries);
const organizations = isCloud
? await sdk.forConsole.billing.listOrganization(queries)
: sdk.forConsole.teams.list(queries);

return {
offset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
InputTextarea,
Label
} from '$lib/elements/forms';
import { formatCurrency } from '$lib/helpers/numbers.js';
import { formatCurrency } from '$lib/helpers/numbers';
import {
WizardSecondaryContainer,
WizardSecondaryContent,
Expand All @@ -40,7 +40,7 @@
type Organization
} from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
import { confirmPayment } from '$lib/stores/stripe.js';
import { confirmPayment } from '$lib/stores/stripe';
import { user } from '$lib/stores/user';
import { VARS } from '$lib/system';
import { onMount } from 'svelte';
Expand Down Expand Up @@ -212,7 +212,7 @@
async function upgrade() {
try {
//Add collaborators
var newCollaborators = [];
let newCollaborators = [];
if (collaborators?.length) {
newCollaborators = collaborators.filter(
(collaborator) =>
Expand Down Expand Up @@ -371,11 +371,12 @@
<svelte:fragment slot="aside">
{#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== billingPlan && $organization.billingPlan !== BillingPlan.CUSTOM}
<EstimatedTotalBox
{isDowngrade}
{billingPlan}
{collaborators}
bind:couponData
bind:billingBudget
{isDowngrade} />
currentTier={$organization.billingPlan} />
{:else if $organization.billingPlan !== BillingPlan.CUSTOM}
<PlanComparisonBox downgrade={isDowngrade} />
{/if}
Expand Down
4 changes: 3 additions & 1 deletion src/routes/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
if (account) {
return {
account,
organizations: isCloud ? await sdk.forConsole.billing.listOrganization() : await sdk.forConsole.teams.list()
organizations: isCloud
? await sdk.forConsole.billing.listOrganization()
: await sdk.forConsole.teams.list()
};
}

Expand Down
Loading