Merge pull request #2706 from appwrite/improve-payment-modal

This commit is contained in:
Darshan
2025-12-13 16:34:33 +05:30
committed by GitHub
7 changed files with 57 additions and 33 deletions
+18 -10
View File
@@ -1,8 +1,13 @@
<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';
@@ -10,16 +15,16 @@
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() {
@@ -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'
@@ -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'
@@ -89,6 +94,8 @@
};
});
onDestroy(unmountPaymentElement);
$: if (element) {
observer.observe(element, { childList: true });
}
@@ -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 />
@@ -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]
};
}
@@ -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"
+15 -12
View File
@@ -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;
@@ -15,12 +15,14 @@
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
return;
};
export let title = '';
/**
* needed when using `StatePicker`
*/
export let skipEnterOnBackdrop = false;
let backdrop: HTMLDivElement;
onMount(async () => {});
function handleBLur(event: MouseEvent) {
if (event.target === backdrop) {
trackEvent(Click.ModalCloseClick, {
@@ -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'}
@@ -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}
+16 -2
View File
@@ -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();
@@ -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) {
@@ -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}
@@ -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 = '';
@@ -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