Skip to content
Merged
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
28 changes: 18 additions & 10 deletions src/lib/components/billing/paymentModal.svelte
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
<script lang="ts">
import { FakeModal } from '$lib/components';
import { InputText, Button } from '$lib/elements/forms';
import { createEventDispatcher, onMount } from 'svelte';
import { initializeStripe, setPaymentMethod, submitStripeCard } from '$lib/stores/stripe';
import { onMount, onDestroy } from 'svelte';
import {
initializeStripe,
setPaymentMethod,
submitStripeCard,
unmountPaymentElement
} from '$lib/stores/stripe';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { addNotification } from '$lib/stores/notifications';
import { page } from '$app/state';
import { Spinner } from '@appwrite.io/pink-svelte';
import type { PaymentMethod } from '@stripe/stripe-js';
import StatePicker from './statePicker.svelte';
import type { PaymentMethodData } from '$lib/sdk/billing';

export let show = false;
export let onCardSubmit: ((card: PaymentMethodData) => void) | null = null;

const dispatch = createEventDispatcher();

let name: string;
let error: string;
let modal: FakeModal;
let showState: boolean = false;
let name: string;
let state: string = '';
let error: string = null;
let showState: boolean = false;
let paymentMethod: PaymentMethod | null = null;

async function handleSubmit() {
Expand All @@ -32,7 +37,7 @@
const card = await setPaymentMethod(paymentMethod.id, name, state);
modal.closeModal();
await invalidate(Dependencies.PAYMENT_METHODS);
dispatch('submit', card);
onCardSubmit?.(card);
addNotification({
type: 'success',
message: 'A new payment method has been added to your account'
Expand All @@ -50,7 +55,7 @@
}
modal.closeModal();
await invalidate(Dependencies.PAYMENT_METHODS);
dispatch('submit', card);
onCardSubmit?.(card as PaymentMethodData);
addNotification({
type: 'success',
message: 'A new payment method has been added to your account'
Expand Down Expand Up @@ -89,6 +94,8 @@
};
});

onDestroy(unmountPaymentElement);

$: if (element) {
observer.observe(element, { childList: true });
}
Expand All @@ -99,7 +106,8 @@
bind:show
title="Add payment method"
bind:error
onSubmit={handleSubmit}>
onSubmit={handleSubmit}
skipEnterOnBackdrop={showState}>
<slot />
{#if showState}
<StatePicker card={paymentMethod} bind:state />
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/billing/selectPaymentMethod.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
let showTaxId = false;
let showPaymentModal = false;

async function cardSaved(event: CustomEvent<PaymentMethodData>) {
value = event.detail.$id;
async function cardSaved(card: PaymentMethodData) {
value = card.$id;

if (value) {
methods = {
...methods,
total: methods.total + 1,
paymentMethods: [...methods.paymentMethods, event.detail]
paymentMethods: [...methods.paymentMethods, card]
};
}

Expand Down Expand Up @@ -98,7 +98,7 @@
</Layout.Stack>

{#if showPaymentModal && isCloud && hasStripePublicKey}
<PaymentModal bind:show={showPaymentModal} on:submit={cardSaved}>
<PaymentModal bind:show={showPaymentModal} onCardSubmit={cardSaved}>
<svelte:fragment slot="end">
<Selector.Checkbox
id="taxIdCheck"
Expand Down
27 changes: 15 additions & 12 deletions src/lib/components/fakeModal.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import { Alert } from '@appwrite.io/pink-svelte';
import { onMount } from 'svelte';
import { clickOnEnter } from '$lib/helpers/a11y';
import Form from '$lib/elements/forms/form.svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
import { clickOnEnter } from '$lib/helpers/a11y';

export let title = '';
export let show = false;
export let size: 'small' | 'big' = 'big';
export let icon: string = null;
Expand All @@ -15,11 +15,13 @@
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
return;
};
export let title = '';

let backdrop: HTMLDivElement;
/**
* needed when using `StatePicker`
*/
export let skipEnterOnBackdrop = false;

onMount(async () => {});
let backdrop: HTMLDivElement;

function handleBLur(event: MouseEvent) {
if (event.target === backdrop) {
Expand Down Expand Up @@ -60,12 +62,12 @@
<svelte:window on:keydown={handleKeydown} />

{#if show}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={backdrop}
onclick={handleBLur}
class="payment-modal-backdrop"
on:keyup={clickOnEnter}
on:click={handleBLur}
bind:this={backdrop}>
onkeyup={skipEnterOnBackdrop ? undefined : clickOnEnter}>
<div
class="modal"
class:is-small={size === 'small'}
Expand Down Expand Up @@ -99,11 +101,12 @@
style="--button-size:1.5rem;"
aria-label="Close Modal"
title="Close Modal"
on:click={() =>
onclick={() => {
closeModal();
trackEvent(Click.ModalCloseClick, {
from: 'button'
})}
on:click={closeModal}>
});
}}>
<span class="icon-x" aria-hidden="true"></span>
</button>
{/if}
Expand Down
18 changes: 16 additions & 2 deletions src/lib/stores/stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const isStripeInitialized = writable(false);

export async function initializeStripe(node: HTMLElement) {
if (!get(stripe)) return;

// cleanup any existing state
await unmountPaymentElement();

isStripeInitialized.set(true);

const methods = await sdk.forConsole.billing.listPaymentMethods();
Expand Down Expand Up @@ -54,10 +58,20 @@ export async function initializeStripe(node: HTMLElement) {

export async function unmountPaymentElement() {
isStripeInitialized.set(false);
paymentElement?.unmount();

if (paymentElement) {
try {
paymentElement.unmount();
paymentElement.destroy();
} catch (e) {
console.debug('Payment element cleanup:', e.message);
}
}

elements = null;
clientSecret = null;
paymentMethod = null;
elements = null;
paymentElement = null;
}

export async function submitStripeCard(name: string, organizationId?: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,11 @@
{#if showPayment && isCloud && hasStripePublicKey}
<PaymentModal
bind:show={showPayment}
on:submit={(e) => {
onCardSubmit={(card) => {
if (isSelectedBackup) {
addBackupPaymentMethod(e.detail.$id);
addBackupPaymentMethod(card.$id);
} else {
addPaymentMethod(e.detail.$id);
addPaymentMethod(card.$id);
}
}} />
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
export let methods: PaymentList;

let name: string;
let error: string;
let error: string = null;
let selectedPaymentMethodId: string;
let showState: boolean = false;
let state: string = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@
onSubmit={handleSubmit}
size="big"
title="Retry payment">
<!-- TODO: format currency -->
<p class="text">
Your payment of <span class="inline-tag">{formatCurrency(invoice.grossAmount)}</span> due on {toLocaleDate(
invoice.dueAt
Expand Down