From e2cc4f6f3a103862d90ed1683f130a0e47f1f3d3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 30 Jul 2025 05:21:11 +0000 Subject: [PATCH 01/16] Feat: State selector US --- .../components/billing/paymentModal.svelte | 58 ++++- src/lib/components/billing/statePicker.svelte | 42 ++++ src/lib/components/billing/states.js | 238 ++++++++++++++++++ src/lib/stores/stripe.ts | 43 +++- 4 files changed, 367 insertions(+), 14 deletions(-) create mode 100644 src/lib/components/billing/statePicker.svelte create mode 100644 src/lib/components/billing/states.js diff --git a/src/lib/components/billing/paymentModal.svelte b/src/lib/components/billing/paymentModal.svelte index c79602517..fda3229cb 100644 --- a/src/lib/components/billing/paymentModal.svelte +++ b/src/lib/components/billing/paymentModal.svelte @@ -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; @@ -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 && typeof card !== 'string') { + if ((card as PaymentMethod).card.country === 'US') { + paymentMethod = card as PaymentMethod; + showState = true; + return; + } + } modal.closeModal(); await invalidate(Dependencies.PAYMENT_METHODS); dispatch('submit', card); @@ -87,13 +115,19 @@ {/if} - -
- -
+ {#if showState} +
+ + +
+ {:else} +
+ +
+ {/if} @@ -107,6 +141,14 @@ display: flex; min-height: 245px; + .state-element { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + .stripe-element { width: 100%; } diff --git a/src/lib/components/billing/statePicker.svelte b/src/lib/components/billing/statePicker.svelte new file mode 100644 index 000000000..553397d0c --- /dev/null +++ b/src/lib/components/billing/statePicker.svelte @@ -0,0 +1,42 @@ + + +{#if card} + + + + ending in {card.card?.last4} + + + {card.card.country} + {card.billing_details.address.postal_code} + + +{/if} + + + + To complete the billing information, select your state so we can apply the correct taxes and + meet U.S. legal requirements. + + + + ({ + label: state.name, + value: state.abbreviation, + id: state.abbreviation.toLowerCase() + }))} /> diff --git a/src/lib/components/billing/states.js b/src/lib/components/billing/states.js new file mode 100644 index 000000000..b8ca0ec88 --- /dev/null +++ b/src/lib/components/billing/states.js @@ -0,0 +1,238 @@ +export const states = [ + { + "name": "Alabama", + "abbreviation": "AL" + }, + { + "name": "Alaska", + "abbreviation": "AK" + }, + { + "name": "American Samoa", + "abbreviation": "AS" + }, + { + "name": "Arizona", + "abbreviation": "AZ" + }, + { + "name": "Arkansas", + "abbreviation": "AR" + }, + { + "name": "California", + "abbreviation": "CA" + }, + { + "name": "Colorado", + "abbreviation": "CO" + }, + { + "name": "Connecticut", + "abbreviation": "CT" + }, + { + "name": "Delaware", + "abbreviation": "DE" + }, + { + "name": "District Of Columbia", + "abbreviation": "DC" + }, + { + "name": "Federated States Of Micronesia", + "abbreviation": "FM" + }, + { + "name": "Florida", + "abbreviation": "FL" + }, + { + "name": "Georgia", + "abbreviation": "GA" + }, + { + "name": "Guam", + "abbreviation": "GU" + }, + { + "name": "Hawaii", + "abbreviation": "HI" + }, + { + "name": "Idaho", + "abbreviation": "ID" + }, + { + "name": "Illinois", + "abbreviation": "IL" + }, + { + "name": "Indiana", + "abbreviation": "IN" + }, + { + "name": "Iowa", + "abbreviation": "IA" + }, + { + "name": "Kansas", + "abbreviation": "KS" + }, + { + "name": "Kentucky", + "abbreviation": "KY" + }, + { + "name": "Louisiana", + "abbreviation": "LA" + }, + { + "name": "Maine", + "abbreviation": "ME" + }, + { + "name": "Marshall Islands", + "abbreviation": "MH" + }, + { + "name": "Maryland", + "abbreviation": "MD" + }, + { + "name": "Massachusetts", + "abbreviation": "MA" + }, + { + "name": "Michigan", + "abbreviation": "MI" + }, + { + "name": "Minnesota", + "abbreviation": "MN" + }, + { + "name": "Mississippi", + "abbreviation": "MS" + }, + { + "name": "Missouri", + "abbreviation": "MO" + }, + { + "name": "Montana", + "abbreviation": "MT" + }, + { + "name": "Nebraska", + "abbreviation": "NE" + }, + { + "name": "Nevada", + "abbreviation": "NV" + }, + { + "name": "New Hampshire", + "abbreviation": "NH" + }, + { + "name": "New Jersey", + "abbreviation": "NJ" + }, + { + "name": "New Mexico", + "abbreviation": "NM" + }, + { + "name": "New York", + "abbreviation": "NY" + }, + { + "name": "North Carolina", + "abbreviation": "NC" + }, + { + "name": "North Dakota", + "abbreviation": "ND" + }, + { + "name": "Northern Mariana Islands", + "abbreviation": "MP" + }, + { + "name": "Ohio", + "abbreviation": "OH" + }, + { + "name": "Oklahoma", + "abbreviation": "OK" + }, + { + "name": "Oregon", + "abbreviation": "OR" + }, + { + "name": "Palau", + "abbreviation": "PW" + }, + { + "name": "Pennsylvania", + "abbreviation": "PA" + }, + { + "name": "Puerto Rico", + "abbreviation": "PR" + }, + { + "name": "Rhode Island", + "abbreviation": "RI" + }, + { + "name": "South Carolina", + "abbreviation": "SC" + }, + { + "name": "South Dakota", + "abbreviation": "SD" + }, + { + "name": "Tennessee", + "abbreviation": "TN" + }, + { + "name": "Texas", + "abbreviation": "TX" + }, + { + "name": "Utah", + "abbreviation": "UT" + }, + { + "name": "Vermont", + "abbreviation": "VT" + }, + { + "name": "Virgin Islands", + "abbreviation": "VI" + }, + { + "name": "Virginia", + "abbreviation": "VA" + }, + { + "name": "Washington", + "abbreviation": "WA" + }, + { + "name": "West Virginia", + "abbreviation": "WV" + }, + { + "name": "Wisconsin", + "abbreviation": "WI" + }, + { + "name": "Wyoming", + "abbreviation": "WY" + } +]; \ No newline at end of file diff --git a/src/lib/stores/stripe.ts b/src/lib/stores/stripe.ts index 0f71da642..162d6129b 100644 --- a/src/lib/stores/stripe.ts +++ b/src/lib/stores/stripe.ts @@ -1,4 +1,4 @@ -import type { Appearance, Stripe, StripeElement, StripeElements } from '@stripe/stripe-js'; +import type { Appearance, PaymentMethod, Stripe, StripeElement, StripeElements } from '@stripe/stripe-js'; import { sdk } from './sdk'; import { app } from './app'; import { get, writable } from 'svelte/store'; @@ -83,21 +83,28 @@ export async function submitStripeCard(name: string, organizationId?: string) { billing_details: { name } - } + }, + expand: ['payment_method'] }, redirect: 'if_required' }); + console.log(setupIntent); if (error) { const e = new Error(error.message); trackError(e, Submit.PaymentMethodCreate); throw e; } + if (setupIntent && setupIntent.status === 'succeeded') { + if ((setupIntent.payment_method as PaymentMethod).card.country === 'US') { + // need to get state + return setupIntent.payment_method as PaymentMethod; + } const method = await sdk.forConsole.billing.setPaymentMethod( paymentMethod.$id, - setupIntent.payment_method, + (setupIntent.payment_method as PaymentMethod).id, name ); paymentElement.destroy(); @@ -115,6 +122,31 @@ export async function submitStripeCard(name: string, organizationId?: string) { } } +export async function setPaymentMethod(providerMethodId: string, name: string, state: string) { + if(!paymentMethod) { + addNotification({ + title: 'Error', + message: 'No payment method found. Please try again.', + type: 'error' + }); + return; + } + try { + const method = await sdk.forConsole.billing.setPaymentMethod( + paymentMethod.$id, + providerMethodId, + name + ); + paymentElement.destroy(); + isStripeInitialized.set(false); + trackEvent(Submit.PaymentMethodCreate); + return method; + } catch (e) { + trackError(e, Submit.PaymentMethodCreate); + throw e; + } +} + export async function confirmPayment( orgId: string, clientSecret: string, @@ -156,9 +188,8 @@ export async function confirmSetup( const { setupIntent, error } = await get(stripe).confirmCardSetup(clientSecret, { payment_method: paymentMethodId, - return_url: `${baseUrl}${ - urlRoute ?? `organization-${get(organization).$id}/billing?clientSecret=${clientSecret}` - }` + return_url: `${baseUrl}${urlRoute ?? `organization-${get(organization).$id}/billing?clientSecret=${clientSecret}` + }` }); if (error) { From c3f746c04f39f8b1dedfc3441acdbff8685e7390 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 31 Jul 2025 04:14:20 +0000 Subject: [PATCH 02/16] fix update gaping and component --- .../components/billing/paymentModal.svelte | 43 ++++++------ src/lib/components/billing/statePicker.svelte | 65 +++++++++++-------- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/lib/components/billing/paymentModal.svelte b/src/lib/components/billing/paymentModal.svelte index fda3229cb..9c47b2b2f 100644 --- a/src/lib/components/billing/paymentModal.svelte +++ b/src/lib/components/billing/paymentModal.svelte @@ -101,34 +101,33 @@ bind:error onSubmit={handleSubmit}> - - -
- {#if isLoading} -
- -
- {/if} - {#if showState} -
- - -
- {:else} + {#if showState} +
+ + +
+ {:else} + +
+ {#if isLoading} +
+ +
+ {/if}
- {/if} -
+
+ {/if} diff --git a/src/lib/components/billing/statePicker.svelte b/src/lib/components/billing/statePicker.svelte index 553397d0c..d84d5c39d 100644 --- a/src/lib/components/billing/statePicker.svelte +++ b/src/lib/components/billing/statePicker.svelte @@ -2,41 +2,50 @@ import { states } from './states.js'; import { InputSelect } from '$lib/elements/forms'; import type { PaymentMethod } from '@stripe/stripe-js'; - import Card from '../card.svelte'; - import { Alert, Layout, Typography } from '@appwrite.io/pink-svelte'; + import { Alert, Card, Layout, Typography } from '@appwrite.io/pink-svelte'; import { CreditCardBrandImage } from '../index.js'; export let state: string = ''; export let card: PaymentMethod | null = null; -{#if card} - - - - ending in {card.card?.last4} - +
+ {#if card} + + + + ending in {card.card?.last4} + + + {card.card.country} + {card.billing_details.address.postal_code} + + + {/if} + + - {card.card.country} - {card.billing_details.address.postal_code} + To complete the billing information, select your state so we can apply the correct taxes + and meet U.S. legal requirements. - -{/if} + - - - To complete the billing information, select your state so we can apply the correct taxes and - meet U.S. legal requirements. - - + ({ + label: state.name, + value: state.abbreviation, + id: state.abbreviation.toLowerCase() + }))} /> +
- ({ - label: state.name, - value: state.abbreviation, - id: state.abbreviation.toLowerCase() - }))} /> + From 37b3b7262c09930f2b6ce6a858203cc445e1ac67 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 31 Jul 2025 04:53:59 +0000 Subject: [PATCH 03/16] updated to use Layout --- src/lib/components/billing/paymentModal.svelte | 6 ++---- src/lib/components/billing/statePicker.svelte | 13 +++---------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/lib/components/billing/paymentModal.svelte b/src/lib/components/billing/paymentModal.svelte index 9c47b2b2f..0ab20511a 100644 --- a/src/lib/components/billing/paymentModal.svelte +++ b/src/lib/components/billing/paymentModal.svelte @@ -102,10 +102,8 @@ onSubmit={handleSubmit}> {#if showState} -
- - -
+ + {:else} -
+ {#if card} @@ -32,6 +32,7 @@ -
- - + From 783d503c63145eea83832e6267b5e0f6a5076acd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 31 Jul 2025 04:55:03 +0000 Subject: [PATCH 04/16] update gap --- src/lib/components/billing/paymentModal.svelte | 8 -------- src/lib/components/billing/statePicker.svelte | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/lib/components/billing/paymentModal.svelte b/src/lib/components/billing/paymentModal.svelte index 0ab20511a..ef8e298ef 100644 --- a/src/lib/components/billing/paymentModal.svelte +++ b/src/lib/components/billing/paymentModal.svelte @@ -138,14 +138,6 @@ display: flex; min-height: 245px; - .state-element { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } - .stripe-element { width: 100%; } diff --git a/src/lib/components/billing/statePicker.svelte b/src/lib/components/billing/statePicker.svelte index 9123e6522..6419c5470 100644 --- a/src/lib/components/billing/statePicker.svelte +++ b/src/lib/components/billing/statePicker.svelte @@ -9,7 +9,7 @@ export let card: PaymentMethod | null = null; - + {#if card} From ed04bbe5b61a06ae3cfb281cfa8569ce8ad8a72a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 31 Jul 2025 04:55:13 +0000 Subject: [PATCH 05/16] remove comment --- src/lib/components/billing/paymentModal.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/components/billing/paymentModal.svelte b/src/lib/components/billing/paymentModal.svelte index ef8e298ef..06f14cde1 100644 --- a/src/lib/components/billing/paymentModal.svelte +++ b/src/lib/components/billing/paymentModal.svelte @@ -102,7 +102,6 @@ onSubmit={handleSubmit}> {#if showState} - {:else} Date: Thu, 31 Jul 2025 04:56:26 +0000 Subject: [PATCH 06/16] remove log --- src/lib/stores/stripe.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/stores/stripe.ts b/src/lib/stores/stripe.ts index 162d6129b..3fab2e59a 100644 --- a/src/lib/stores/stripe.ts +++ b/src/lib/stores/stripe.ts @@ -89,7 +89,6 @@ export async function submitStripeCard(name: string, organizationId?: string) { redirect: 'if_required' }); - console.log(setupIntent); if (error) { const e = new Error(error.message); trackError(e, Submit.PaymentMethodCreate); From bfc64944aa72768dcd11cb4f0a291dff75501d5c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 31 Jul 2025 04:58:19 +0000 Subject: [PATCH 07/16] fixes --- src/lib/components/billing/{states.js => state.ts} | 2 +- src/lib/components/billing/statePicker.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/lib/components/billing/{states.js => state.ts} (98%) diff --git a/src/lib/components/billing/states.js b/src/lib/components/billing/state.ts similarity index 98% rename from src/lib/components/billing/states.js rename to src/lib/components/billing/state.ts index b8ca0ec88..faa3f0786 100644 --- a/src/lib/components/billing/states.js +++ b/src/lib/components/billing/state.ts @@ -1,4 +1,4 @@ -export const states = [ +export const states: Array<{ name: string; abbreviation: string }> = [ { "name": "Alabama", "abbreviation": "AL" diff --git a/src/lib/components/billing/statePicker.svelte b/src/lib/components/billing/statePicker.svelte index 6419c5470..125487bcb 100644 --- a/src/lib/components/billing/statePicker.svelte +++ b/src/lib/components/billing/statePicker.svelte @@ -1,5 +1,5 @@