diff --git a/src/lib/components/billing/updateStateModal.svelte b/src/lib/components/billing/updateStateModal.svelte new file mode 100644 index 0000000000..603b09bf4b --- /dev/null +++ b/src/lib/components/billing/updateStateModal.svelte @@ -0,0 +1,112 @@ + + + + + State information is required for US payment methods to apply correct taxes and meet U.S. + legal requirements. + + + + {#if paymentMethod} + + + + ending in {paymentMethod.last4} + + + {paymentMethod.country} + + + {/if} + + + + To complete the billing information, select your state so we can apply the correct + taxes and meet U.S. legal requirements. + + + + ({ + label: stateOption.name, + value: stateOption.abbreviation, + id: stateOption.abbreviation.toLowerCase() + }))} /> + + + + + + diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 5ee5232633..048dbb3d12 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -22,6 +22,7 @@ export type PaymentMethodData = { name: string; mandateId?: string; lastError?: string; + state?: string; }; export type PaymentList = { diff --git a/src/routes/(console)/account/payments/paymentMethods.svelte b/src/routes/(console)/account/payments/paymentMethods.svelte index 8e53a8d3da..9caea2e1f5 100644 --- a/src/routes/(console)/account/payments/paymentMethods.svelte +++ b/src/routes/(console)/account/payments/paymentMethods.svelte @@ -9,6 +9,7 @@ import DeletePaymentModal from './deletePaymentModal.svelte'; import { hasStripePublicKey, isCloud } from '$lib/system'; import PaymentModal from '$lib/components/billing/paymentModal.svelte'; + import UpdateStateModal from '$lib/components/billing/updateStateModal.svelte'; import { IconDotsHorizontal, IconInfo, @@ -33,6 +34,8 @@ let selectedLinkedOrgs: Organization[] = []; let showDelete = false; let showEdit = false; + let showUpdateState = false; + let paymentMethodNeedingState: PaymentMethodData | null = null; let isLinked = false; $: orgList = $organizationList.teams as unknown as Organization[]; @@ -49,6 +52,27 @@ ); $: hasLinkedOrgs = filteredMethods.some((method) => linkedMethodIds.has(method.$id)); $: hasPaymentError = filteredMethods.some((method) => method?.lastError || method?.expired); + + // Check for US payment methods without state + $: { + if ($paymentMethods?.paymentMethods && !showUpdateState && !paymentMethodNeedingState) { + const usMethodWithoutState = $paymentMethods.paymentMethods.find( + (method: PaymentMethodData) => + method?.country?.toLowerCase() === 'us' && + (!method.state || method.state.trim() === '') && + !!method.last4 + ); + if (usMethodWithoutState) { + paymentMethodNeedingState = usMethodWithoutState; + showUpdateState = true; + } + } + } + + // Reset when modal is closed + $: if (!showUpdateState && paymentMethodNeedingState) { + paymentMethodNeedingState = null; + } @@ -170,3 +194,6 @@ bind:showDelete linkedOrgs={selectedLinkedOrgs} /> {/if} +{#if showUpdateState && paymentMethodNeedingState && isCloud && hasStripePublicKey} + +{/if} diff --git a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte index 445c65d107..449f165d48 100644 --- a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte +++ b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte @@ -13,6 +13,7 @@ import ReplaceCard from './replaceCard.svelte'; import EditPaymentModal from '$routes/(console)/account/payments/editPaymentModal.svelte'; import PaymentModal from '$lib/components/billing/paymentModal.svelte'; + import UpdateStateModal from '$lib/components/billing/updateStateModal.svelte'; import { user } from '$lib/stores/user'; import { ActionMenu, @@ -44,6 +45,8 @@ let showEdit = false; let showDelete = false; let showReplace = false; + let showUpdateState = false; + let paymentMethodNeedingState: PaymentMethodData | null = null; let isSelectedBackup = false; async function addPaymentMethod(paymentMethodId: string) { @@ -96,6 +99,27 @@ primaryMethod?.expired || backupMethod?.lastError || backupMethod?.expired; + + // Check for US payment methods without state + $: { + if (methods?.paymentMethods && !showUpdateState && !paymentMethodNeedingState) { + const usMethodWithoutState = methods.paymentMethods.find( + (method: PaymentMethodData) => + method?.country?.toLowerCase() === 'us' && + (!method.state || method.state.trim() === '') && + !!method.last4 + ); + if (usMethodWithoutState) { + paymentMethodNeedingState = usMethodWithoutState; + showUpdateState = true; + } + } + } + + // Reset when modal is closed + $: if (!showUpdateState && paymentMethodNeedingState) { + paymentMethodNeedingState = null; + } @@ -323,3 +347,6 @@ isBackup={isSelectedBackup} disabled={organization?.billingPlan !== BillingPlan.FREE && !hasOtherMethod} /> {/if} +{#if showUpdateState && paymentMethodNeedingState && isCloud && hasStripePublicKey} + +{/if}