feat: payment failed

This commit is contained in:
Arman
2023-08-01 20:49:10 +02:00
parent bbe341f68b
commit e237a68806
8 changed files with 105 additions and 6 deletions
+18
View File
@@ -19,6 +19,9 @@
import CreateOrganizationCloud from '$routes/console/createOrganizationCloud.svelte';
import { feedback } from '$lib/stores/feedback';
import { Feedback } from '$lib/components/feedback';
import { Pill } from '$lib/elements';
import { failedInvoice } from '$lib/stores/billing';
import { diffDays } from '$lib/helpers/date';
let showDropdown = false;
let showSupport = false;
@@ -73,6 +76,21 @@
<div class="main-header-end">
<nav class="u-flex is-only-desktop u-cross-center">
{#if $failedInvoice}
{@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())}
{#if daysPassed >= 15}
<Pill danger>
<span class="icon-exclamation-circle" aria-hidden="true" />
<span>Organization at risk</span>
</Pill>
{:else if daysPassed >= 30}
<Pill danger>
<span class="icon-exclamation-circle" aria-hidden="true" />
<span>read-only access</span>
</Pill>
{/if}
{/if}
{#if $feedback.notification}
<div class="u-flex u-cross-center">
<div class="pulse-notification" />
+5 -1
View File
@@ -42,7 +42,11 @@
<main
class:grid-with-side={showSideNavigation}
class:is-open={isOpen}
class:u-hide={$wizard.show || $log.show}>
class:u-hide={$wizard.show || $log.show}
class:is-fixed-layout={$$slots.alert}>
{#if $$slots.alert}
<slot name="alert" />
{/if}
<header class="main-header u-padding-inline-end-0">
<button
class:u-hide={!showSideNavigation}
+24 -1
View File
@@ -3,7 +3,9 @@ import { derived, get } from 'svelte/store';
import { sdk } from './sdk';
import { limitRates } from '$lib/constants';
import { organization } from './organization';
import type { PaymentList } from '$lib/sdk/billing';
import type { Invoice, PaymentList } from '$lib/sdk/billing';
import { isCloud } from '$lib/system';
import { cachedStore } from '$lib/helpers/cache';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2';
@@ -31,6 +33,27 @@ export function getServiceLimit(serviceId: string) {
return limitRates?.[get(organization)?.billingPlan]?.find((l) => l.id === serviceId);
}
export const failedInvoice = cachedStore<
false | Invoice,
{
load: (orgId: string) => Promise<void>;
}
>('failedInvoice', function ({ set }) {
return {
load: async (orgId) => {
if (!isCloud) set(false);
const invoices = await sdk.forConsole.billing.listInvoices(orgId);
const failedInvoices = invoices.invoices.filter((i) => i.status === 'failed');
// const failedInvoices = invoices.invoices;
if (failedInvoices.length > 0) {
const firstFailed = failedInvoices[0];
//TODO: if firstFailed is older than 30 days, set readonly to true!
set(firstFailed);
} else set(false);
}
};
});
export const tierFree = {
price: 0,
name: 'Free',
+35 -1
View File
@@ -14,12 +14,14 @@
import { onMount } from 'svelte';
import { loading } from '../store';
import Create from './createOrganization.svelte';
import { failedInvoice } from '$lib/stores/billing';
import { diffDays, toLocaleDate } from '$lib/helpers/date';
import { base } from '$app/paths';
let isOpen = false;
onMount(() => {
loading.set(false);
setInterval(() => {
checkForFeedback(INTERVAL);
}, INTERVAL);
@@ -54,6 +56,38 @@
!$page?.params.organization &&
!$page.url.pathname.includes('/console/account') &&
!$page.url.pathname.includes('/console/onboarding')}>
<svelte:fragment slot="alert">
{#if $failedInvoice}
{@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())}
<section class="alert is-action is-action-and-top-sticky is-danger u-sep-block-end">
<div class="alert-grid">
<span class="icon-info" aria-hidden="true" />
<div class="alert-content">
<h6 class="alert-title">Your projects are at risk</h6>
<p class="alert-message">
{#if daysPassed > 30}
Your scheduled payment on <b
>{toLocaleDate($failedInvoice.dueAt)}</b> failed. To resume write
access of your organization, please update your billing details.
{:else}
Your scheduled payment on <b
>{toLocaleDate($failedInvoice.dueAt)}</b> failed. Access to The paid
projects within this organization will be disabled if no action is taken
within 30 days.
{/if}
</p>
</div>
<div class="alert-buttons u-flex u-gap-16 u-cross-child-center">
<a
href={`${base}/console/organization-${$failedInvoice.teamId}/billing`}
class="button is-secondary is-full-width-mobile">
<span class="text">Update billing details</span>
</a>
</div>
</div>
</section>
{/if}
</svelte:fragment>
<Header slot="header" />
<SideNavigation slot="side" bind:isOpen />
<slot />
@@ -4,11 +4,17 @@ import { sdk } from '$lib/stores/sdk';
import type { LayoutLoad } from './$types';
import { error } from '@sveltejs/kit';
import { Dependencies } from '$lib/constants';
import { failedInvoice } from '$lib/stores/billing';
import { isCloud } from '$lib/system';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.ORGANIZATION);
depends(Dependencies.PAYMENT_METHODS);
if (isCloud) {
await failedInvoice.load(params.organization);
}
try {
return {
header: Header,
@@ -9,7 +9,7 @@
import AvailableCredit from './availableCredit.svelte';
import PaymentHistory from './paymentHistory.svelte';
import { Alert, Heading } from '$lib/components';
import { paymentMethods } from '$lib/stores/billing';
import { failedInvoice, paymentMethods } from '$lib/stores/billing';
import type { PaymentMethodData } from '$lib/sdk/billing';
$: defaultPaymentMethod = $paymentMethods.paymentMethods.find(
@@ -18,7 +18,15 @@
</script>
<Container>
{#if defaultPaymentMethod.failed}
{#if $failedInvoice}
<Alert type="error">
<svelte:fragment slot="title">
The scheduled payment for {$organization.name} failed
</svelte:fragment>
To avoid service disruptions in your projects, please verify your payment details and update
them if necessary.
</Alert>
{:else if defaultPaymentMethod.failed}
<Alert type="error">
<svelte:fragment slot="title">
The default payment method for {$organization.name} has expired
@@ -2,7 +2,7 @@ import { Dependencies } from '$lib/constants';
import { sdk } from '$lib/stores/sdk';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params, parent, depends }) => {
export const load: PageLoad = async ({ parent, depends }) => {
await parent();
depends(Dependencies.PAYMENT_METHODS);
depends(Dependencies.ORGANIZATION);
@@ -3,6 +3,8 @@ import { sdk } from '$lib/stores/sdk';
import { error } from '@sveltejs/kit';
import type { LayoutLoad } from './$types';
import { preferences } from '$lib/stores/preferences';
import { failedInvoice } from '$lib/stores/billing';
import { isCloud } from '$lib/system';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.PROJECT);
@@ -13,6 +15,10 @@ export const load: LayoutLoad = async ({ params, depends }) => {
localStorage.setItem('organization', project.teamId);
preferences.loadTeamPrefs(project.teamId);
if (isCloud) {
await failedInvoice.load(project.teamId);
}
return {
project,
organization: await sdk.forConsole.teams.get(project.teamId)