Skip to content

Commit 0afd6d2

Browse files
Merge pull request #1331 from appwrite/poc-invoice-cycle-ref
Feat: invoice cycle changes
2 parents 1f1e845 + cfe49e0 commit 0afd6d2

34 files changed

+1091
-488
lines changed

playwright.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const config: PlaywrightTestConfig = {
1212
webServer: {
1313
timeout: 120000,
1414
env: {
15-
PUBLIC_APPWRITE_ENDPOINT: 'https://console-testing-2.appwrite.org/v1',
15+
PUBLIC_APPWRITE_ENDPOINT: 'https://dlbillingic.appwrite.org/v1',
1616
PUBLIC_CONSOLE_MODE: 'cloud',
1717
PUBLIC_STRIPE_KEY:
1818
'pk_test_51LT5nsGYD1ySxNCyd7b304wPD8Y1XKKWR6hqo6cu3GIRwgvcVNzoZv4vKt5DfYXL1gRGw4JOqE19afwkJYJq1g3K004eVfpdWn'

src/lib/commandCenter/searchers/organizations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { sdk } from '$lib/stores/sdk';
44
import type { Searcher } from '../commands';
55

66
export const orgSearcher = (async (query: string) => {
7-
const { teams } = await sdk.forConsole.teams.list();
7+
const { teams } = await sdk.forConsole.billing.listOrganization();
88
return teams
99
.filter((organization) => organization.name.toLowerCase().includes(query.toLowerCase()))
1010
.map((organization) => {

src/lib/components/billing/alerts/newDevUpgradePro.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { base } from '$app/paths';
33
import { page } from '$app/stores';
44
import { trackEvent } from '$lib/actions/analytics';
5-
import { BillingPlan } from '$lib/constants';
5+
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
66
import { Button } from '$lib/elements/forms';
77
import { organization } from '$lib/stores/organization';
88
import { activeHeaderAlert } from '$routes/(console)/store';
@@ -29,7 +29,7 @@
2929
secondary
3030
fullWidthMobile
3131
class="u-line-height-1"
32-
href={`${base}/apply-credit?code=appw50&org=${$organization.$id}`}
32+
href={`${base}/apply-credit?code=${NEW_DEV_PRO_UPGRADE_COUPON}&org=${$organization.$id}`}
3333
on:click={() => {
3434
trackEvent('click_credits_redeem', {
3535
from: 'button',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script lang="ts">
2+
import { formatCurrency } from '$lib/helpers/numbers';
3+
import type { Coupon } from '$lib/sdk/billing';
4+
5+
export let label: string;
6+
export let value: number;
7+
export let couponData: Partial<Coupon> = {
8+
code: null,
9+
status: null,
10+
credits: null
11+
};
12+
export let fixedCoupon = false;
13+
</script>
14+
15+
{#if value > 0}
16+
<span class="u-flex u-main-space-between">
17+
<div class="u-flex u-cross-center u-gap-4">
18+
<p class="text">
19+
<span class="icon-tag u-color-text-success" aria-hidden="true" />
20+
<span>
21+
{label}
22+
</span>
23+
</p>
24+
{#if !fixedCoupon && label.toLowerCase() === 'credits'}
25+
<button
26+
type="button"
27+
class="button is-text is-only-icon"
28+
style="--button-size:1.5rem;"
29+
aria-label="Close"
30+
title="Close"
31+
on:click={() =>
32+
(couponData = {
33+
code: null,
34+
status: null,
35+
credits: null
36+
})}>
37+
<span class="icon-x" aria-hidden="true" />
38+
</button>
39+
{/if}
40+
</div>
41+
{#if value >= 100}
42+
<p class="inline-tag">Credits applied</p>
43+
{:else}
44+
<span class="u-color-text-success">-{formatCurrency(value)}</span>
45+
{/if}
46+
</span>
47+
{/if}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<script lang="ts">
2+
import { FormList, InputChoice, InputNumber } from '$lib/elements/forms';
3+
import { formatCurrency } from '$lib/helpers/numbers';
4+
import type { Coupon, Estimation } from '$lib/sdk/billing';
5+
import { sdk } from '$lib/stores/sdk';
6+
import { AppwriteException } from '@appwrite.io/console';
7+
import Card from '../card.svelte';
8+
import DiscountsApplied from './discountsApplied.svelte';
9+
import { addNotification } from '$lib/stores/notifications';
10+
11+
export let organizationId: string | undefined = undefined;
12+
export let billingPlan: string;
13+
export let collaborators: string[];
14+
export let fixedCoupon = false;
15+
export let couponData: Partial<Coupon>;
16+
17+
export let billingBudget: number;
18+
19+
let budgetEnabled = false;
20+
let estimation: Estimation;
21+
22+
async function getEstimate(
23+
billingPlan: string,
24+
collaborators: string[],
25+
couponId: string | undefined
26+
) {
27+
try {
28+
estimation = await sdk.forConsole.billing.estimationCreateOrganization(
29+
billingPlan,
30+
couponId === '' ? null : couponId,
31+
collaborators ?? []
32+
);
33+
} catch (e) {
34+
if (e instanceof AppwriteException) {
35+
if (
36+
e.type === 'billing_coupon_not_found' ||
37+
e.type === 'billing_coupon_already_used' ||
38+
e.type === 'billing_credit_unsupported'
39+
) {
40+
couponData = {
41+
code: null,
42+
status: null,
43+
credits: null
44+
};
45+
}
46+
}
47+
addNotification({
48+
type: 'error',
49+
isHtml: false,
50+
message: e.message
51+
});
52+
}
53+
}
54+
55+
async function getUpdatePlanEstimate(
56+
organizationId: string,
57+
billingPlan: string,
58+
collaborators: string[],
59+
couponId: string | undefined
60+
) {
61+
try {
62+
estimation = await sdk.forConsole.billing.estimationUpdatePlan(
63+
organizationId,
64+
billingPlan,
65+
couponId && couponId.length > 0 ? couponId : null,
66+
collaborators ?? []
67+
);
68+
} catch (e) {
69+
if (e instanceof AppwriteException) {
70+
if (
71+
e.type === 'billing_coupon_not_found' ||
72+
e.type === 'billing_coupon_already_used' ||
73+
e.type === 'billing_credit_unsupported'
74+
) {
75+
couponData = {
76+
code: null,
77+
status: null,
78+
credits: null
79+
};
80+
}
81+
}
82+
addNotification({
83+
type: 'error',
84+
isHtml: false,
85+
message: e.message
86+
});
87+
}
88+
}
89+
90+
$: organizationId
91+
? getUpdatePlanEstimate(organizationId, billingPlan, collaborators, couponData?.code)
92+
: getEstimate(billingPlan, collaborators, couponData?.code);
93+
</script>
94+
95+
{#if estimation}
96+
<Card class="u-flex u-flex-vertical u-gap-8">
97+
<slot />
98+
{#if estimation}
99+
{#each estimation.items ?? [] as item}
100+
{#if item.value > 0}
101+
<span class="u-flex u-main-space-between">
102+
<p class="text">{item.label}</p>
103+
<p class="text">{formatCurrency(item.value)}</p>
104+
</span>
105+
{/if}
106+
{/each}
107+
{#each estimation.discounts ?? [] as item}
108+
<DiscountsApplied {fixedCoupon} bind:couponData {...item} />
109+
{/each}
110+
<div class="u-sep-block-start" />
111+
<span class="u-flex u-main-space-between">
112+
<p class="text">Total due</p>
113+
<p class="text">
114+
{formatCurrency(estimation.grossAmount)}
115+
</p>
116+
</span>
117+
118+
<p class="text u-margin-block-start-16">
119+
You'll pay <span class="u-bold">{formatCurrency(estimation.grossAmount)}</span> now.
120+
{#if couponData?.code}Once your credits run out,{:else}Then{/if} you'll be charged
121+
<span class="u-bold">{formatCurrency(estimation.amount)}</span> every 30 days.
122+
</p>
123+
{/if}
124+
125+
<FormList class="u-margin-block-start-24">
126+
<InputChoice
127+
type="switchbox"
128+
id="budget"
129+
label="Enable budget cap"
130+
tooltip="If enabled, you will be notified when your spending reaches 75% of the set cap. Update cap alerts in your organization settings."
131+
fullWidth
132+
bind:value={budgetEnabled}>
133+
{#if budgetEnabled}
134+
<div class="u-margin-block-start-16">
135+
<InputNumber
136+
id="budget"
137+
label="Budget cap (USD)"
138+
placeholder="0"
139+
min={0}
140+
bind:value={billingBudget} />
141+
</div>
142+
{/if}
143+
</InputChoice>
144+
</FormList>
145+
</Card>
146+
{/if}

src/lib/components/billing/estimatedTotalBox.svelte

-99
This file was deleted.

src/lib/components/billing/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ export { default as PaymentBoxes } from './paymentBoxes.svelte';
22
export { default as CouponInput } from './couponInput.svelte';
33
export { default as SelectPaymentMethod } from './selectPaymentMethod.svelte';
44
export { default as UsageRates } from './usageRates.svelte';
5-
export { default as EstimatedTotalBox } from './estimatedTotalBox.svelte';
65
export { default as PlanComparisonBox } from './planComparisonBox.svelte';
76
export { default as EmptyCardCloud } from './emptyCardCloud.svelte';
87
export { default as CreditsApplied } from './creditsApplied.svelte';
98
export { default as PlanSelection } from './planSelection.svelte';
9+
export { default as EstimatedTotal } from './estimatedTotal.svelte';
10+
export { default as SelectPlan } from './selectPlan.svelte';

0 commit comments

Comments
 (0)