Skip to content

Feat: State selector US #2164

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

Merged
merged 19 commits into from
Aug 5, 2025
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
3 changes: 3 additions & 0 deletions e2e/steps/pro-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export async function enterCreditCard(page: Page) {
await stripe.locator('id=Field-cvcInput').fill('123');
await stripe.locator('id=Field-countryInput').selectOption('DE');
await dialog.getByRole('button', { name: 'Add', exact: true }).click();
await page.locator('id=state-picker').click(); // open dropdown
await page.getByRole('option', { name: 'Alabama' }).click();
await dialog.getByRole('button', { name: 'Add', exact: true }).click();
await dialog.waitFor({
state: 'hidden'
});
Expand Down
43 changes: 26 additions & 17 deletions src/lib/components/billing/paymentBoxes.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import { initializeStripe, unmountPaymentElement } from '$lib/stores/stripe';
import { Badge, Card, Layout } from '@appwrite.io/pink-svelte';
import type { PaymentMethodData } from '$lib/sdk/billing';
import type { PaymentMethod } from '@stripe/stripe-js';
import StatePicker from './statePicker.svelte';

export let methods: PaymentMethodData[];
export let group: string;
Expand All @@ -14,6 +16,9 @@
export let disabledCondition: string = null;
export let setAsDefault = false;
export let showSetAsDefault = false;
export let showState = false;
export let paymentMethod: PaymentMethod | null = null;
export let state: string = '';

let element: HTMLDivElement;
let loader: HTMLDivElement;
Expand Down Expand Up @@ -79,25 +84,29 @@
{/each}
<Card.Selector title="Add new payment method" name="$new" bind:group value="$new" />
{#if group === '$new'}
<InputText
id="name"
label="Cardholder name"
placeholder="Cardholder name"
bind:value={name}
required
autofocus={true} />
{#if showState}
<StatePicker card={paymentMethod} bind:state />
{:else}
<InputText
id="name"
label="Cardholder name"
placeholder="Cardholder name"
bind:value={name}
required
autofocus={true} />

<div class="aw-stripe-container" data-private>
<div class="loader-container" bind:this={loader}>
<div class="loader"></div>
<div class="aw-stripe-container" data-private>
<div class="loader-container" bind:this={loader}>
<div class="loader"></div>
</div>
<div bind:this={element}></div>
</div>
<div bind:this={element}></div>
</div>
{#if showSetAsDefault}
<InputChoice
bind:value={setAsDefault}
id="default"
label="Set as default payment method for this organization" />
{#if showSetAsDefault}
<InputChoice
bind:value={setAsDefault}
id="default"
label="Set as default payment method for this organization" />
{/if}
{/if}
{/if}
</Layout.Stack>
Expand Down
72 changes: 51 additions & 21 deletions src/lib/components/billing/paymentModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import { FakeModal } from '$lib/components';
import { InputText, Button } from '$lib/elements/forms';
import { createEventDispatcher, onMount } from 'svelte';
import { initializeStripe, submitStripeCard } from '$lib/stores/stripe';
import { initializeStripe, setPaymentMethod, submitStripeCard } 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';

export let show = false;

Expand All @@ -16,10 +18,36 @@
let name: string;
let error: string;
let modal: FakeModal;
let showState: boolean = false;
let state: string = '';
let paymentMethod: PaymentMethod | null = null;

async function handleSubmit() {
try {
if (showState && !state) {
throw Error('Please select a state');
}

if (showState) {
const card = await setPaymentMethod(paymentMethod.id, name, state);
modal.closeModal();
await invalidate(Dependencies.PAYMENT_METHODS);
dispatch('submit', card);
addNotification({
type: 'success',
message: 'A new payment method has been added to your account'
});
return;
}

const card = await submitStripeCard(name, page?.params?.organization ?? null);
if (card && Object.hasOwn(card, 'id')) {
if ((card as PaymentMethod).card.country === 'US') {
paymentMethod = card as PaymentMethod;
showState = true;
return;
}
}
modal.closeModal();
await invalidate(Dependencies.PAYMENT_METHODS);
dispatch('submit', card);
Expand Down Expand Up @@ -73,28 +101,30 @@
bind:error
onSubmit={handleSubmit}>
<slot />
<InputText
id="name"
required
autofocus={true}
bind:value={name}
label="Cardholder name"
placeholder="Cardholder name" />

<div class="aw-stripe-container" data-private>
{#if isLoading}
<div class="loader-element">
<Spinner />
{#if showState}
<StatePicker card={paymentMethod} bind:state />
{:else}
<InputText
id="name"
required
autofocus={true}
bind:value={name}
label="Cardholder name"
placeholder="Cardholder name" />
<div class="aw-stripe-container" data-private>
{#if isLoading}
<div class="loader-element">
<Spinner />
</div>
{/if}
<div
style:display={isLoading ? 'none' : 'initial'}
class="stripe-element"
bind:this={element}>
<!-- Stripe will create form elements here -->
</div>
{/if}

<div
style:display={isLoading ? 'none' : 'initial'}
class="stripe-element"
bind:this={element}>
<!-- Stripe will create form elements here -->
</div>
</div>
{/if}
<slot name="end"></slot>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (show = false)}>Cancel</Button>
Expand Down
Loading