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}