mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat: refresh tax preview on address change (#44470)
## Summary
- Passes billing address and tax ID from the payment form to the
subscription preview endpoint, so taxes are recalculated live as the
user updates their details
- Debounces address/tax ID changes (1s) in `NewPaymentMethodElement` to
avoid excessive API calls while typing
- Decouples the preview refetch from the payment form's mount state -
uses `keepPreviousData` so the form stays mounted and the breakdown dims
with `opacity-50` instead of unmounting/remounting during refetches.
Shimmer skeleton only shows on initial load.
- Disables the "Confirm upgrade" button while a refetch is in progress
to prevent submitting with stale tax data
- Respects the "Use address as my org's billing address" checkbox: the
preview should mirror what will actually happen - if the checkbox is
unchecked, the address won't be saved to Orb, so it shouldn't be used
for the tax estimate either. Otherwise the user sees one price and gets
charged another. The logic for this lives in `PlanUpdateSidePanel`
``` mermaid
flowchart TD
A[User opens upgrade dialog] --> B[Fetch subscription preview]
B --> C[Show payment form + price breakdown]
C --> D[User edits address or tax ID]
D -->|1s debounce| E{Use as billing address?}
E -->|Yes| F[Re-fetch preview with new address]
E -->|No| G[Re-fetch preview without address override]
F --> H[Update breakdown — form stays mounted]
G --> H
H --> C
```
## Test plan
- [x] Open the plan upgrade dialog, verify the payment form and
breakdown load normally on first open
- [x] Change the billing address country - verify the breakdown dims
briefly and updates with new tax amounts without the payment form
unmounting
- [x] Toggle the tax ID on/off and change its value - verify the
preview refreshes after ~1s debounce
- [x] Confirm the upgrade button is disabled while the preview is
refetching
- [x] Uncheck "Use address as my org's billing address" - verify the
preview refetches without address/tax overrides
- [x] Re-check the checkbox - verify the preview refetches again with
the current form address
- [x] Assert that adding a new billing address in the CreditTopUp form
works and saves the address
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Payment and subscription flows now propagate billing address and tax
ID via new callbacks; subscription preview requests include these values
and preserve prior results while fetching.
* Preview updates are debounced to reduce noise; loading state disables
confirm actions and visually dims charge breakdown.
* **UX**
* Address input emits complete normalized address updates (empty second
line cleared).
* Tax ID input emits updates and explicit clears (null).
* “Use address as my org's billing address” is now controlled and
reports changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
99253068c3
commit
f6eb9a024e
7 files changed
+137
-25
No files matched your search
+26
-2
@@ -85,12 +85,16 @@ export const NewPaymentMethodElement = forwardRef(
|
||||
currentAddress,
|
||||
currentTaxId,
|
||||
customerName,
|
||||
onAddressChange,
|
||||
onTaxIdChange,
|
||||
}: {
|
||||
email?: string | null | undefined
|
||||
readOnly: boolean
|
||||
currentAddress?: CustomerAddress | null
|
||||
currentTaxId?: CustomerTaxId | null
|
||||
customerName?: string | undefined
|
||||
onAddressChange?: (address: CustomerAddress) => void
|
||||
onTaxIdChange?: (taxId: CustomerTaxId | null) => void
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -123,13 +127,25 @@ export const NewPaymentMethodElement = forwardRef(
|
||||
form.setValue('tax_id_name', name)
|
||||
}
|
||||
|
||||
const { tax_id_name } = form.watch()
|
||||
const { tax_id_name, tax_id_value } = form.watch()
|
||||
const selectedTaxId = TAX_IDS.find((option) => option.name === tax_id_name)
|
||||
|
||||
const [purchasingAsBusiness, setPurchasingAsBusiness] = useState(currentTaxId != null)
|
||||
const [stripeAddress, setStripeAddress] = useState<
|
||||
StripeAddressElementChangeEvent['value'] | undefined
|
||||
>(undefined)
|
||||
useEffect(() => {
|
||||
if (!onTaxIdChange) return
|
||||
if (purchasingAsBusiness && selectedTaxId && tax_id_value) {
|
||||
onTaxIdChange({
|
||||
country: getEffectiveTaxCountry(selectedTaxId),
|
||||
type: selectedTaxId.type,
|
||||
value: tax_id_value,
|
||||
})
|
||||
} else {
|
||||
onTaxIdChange(null)
|
||||
}
|
||||
}, [purchasingAsBusiness, selectedTaxId, tax_id_value, onTaxIdChange])
|
||||
|
||||
const availableTaxIds = useMemo(() => {
|
||||
const country = stripeAddress?.address.country || null
|
||||
@@ -272,7 +288,15 @@ export const NewPaymentMethodElement = forwardRef(
|
||||
options={addressOptions}
|
||||
// Force reload after changing purchasingAsBusiness setting, it seems like the element does not reload otherwise
|
||||
key={`address-elements-${purchasingAsBusiness}`}
|
||||
onChange={(evt) => setStripeAddress(evt.value)}
|
||||
onChange={(evt) => {
|
||||
setStripeAddress(evt.value)
|
||||
if (onAddressChange && evt.complete) {
|
||||
onAddressChange({
|
||||
...evt.value.address,
|
||||
line2: evt.value.address.line2 || undefined,
|
||||
})
|
||||
}
|
||||
}}
|
||||
onReady={() => setFullyLoaded(true)}
|
||||
/>
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
|
||||
})
|
||||
|
||||
const [topUpModalVisible, setTopUpModalVisible] = useState(false)
|
||||
const [useAsDefaultBillingAddress, setUseAsDefaultBillingAddress] = useState(true)
|
||||
const [paymentConfirmationLoading, setPaymentConfirmationLoading] = useState(false)
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
|
||||
const [captchaRef, setCaptchaRef] = useState<HCaptcha | null>(null)
|
||||
@@ -281,6 +282,8 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
|
||||
onSelectPaymentMethod={(pm) => form.setValue('paymentMethod', pm)}
|
||||
selectedPaymentMethod={form.getValues('paymentMethod')}
|
||||
readOnly={executingTopUp || paymentConfirmationLoading}
|
||||
useAsDefaultBillingAddress={useAsDefaultBillingAddress}
|
||||
onUseAsDefaultBillingAddressChange={setUseAsDefaultBillingAddress}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
+14
-2
@@ -26,6 +26,7 @@ import { useOrganizationCustomerProfileQuery } from '@/data/organizations/organi
|
||||
import { useOrganizationPaymentMethodSetupIntent } from '@/data/organizations/organization-payment-method-setup-intent-mutation'
|
||||
import { useOrganizationPaymentMethodsQuery } from '@/data/organizations/organization-payment-methods-query'
|
||||
import { useOrganizationTaxIdQuery } from '@/data/organizations/organization-tax-id-query'
|
||||
import type { CustomerAddress, CustomerTaxId } from '@/data/organizations/types'
|
||||
import { SetupIntentResponse } from '@/data/stripe/setup-intent-mutation'
|
||||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||||
import { BASE_PATH, STRIPE_PUBLIC_KEY } from '@/lib/constants'
|
||||
@@ -37,6 +38,10 @@ export interface PaymentMethodSelectionProps {
|
||||
onSelectPaymentMethod: (id: string) => void
|
||||
layout?: 'vertical' | 'horizontal'
|
||||
readOnly: boolean
|
||||
onAddressChange?: (address: CustomerAddress) => void
|
||||
onTaxIdChange?: (taxId: CustomerTaxId | null) => void
|
||||
useAsDefaultBillingAddress: boolean
|
||||
onUseAsDefaultBillingAddressChange: (useAsDefault: boolean) => void
|
||||
}
|
||||
|
||||
const PaymentMethodSelection = forwardRef(function PaymentMethodSelection(
|
||||
@@ -45,6 +50,10 @@ const PaymentMethodSelection = forwardRef(function PaymentMethodSelection(
|
||||
onSelectPaymentMethod,
|
||||
layout = 'vertical',
|
||||
readOnly,
|
||||
onAddressChange,
|
||||
onTaxIdChange,
|
||||
useAsDefaultBillingAddress,
|
||||
onUseAsDefaultBillingAddressChange,
|
||||
}: PaymentMethodSelectionProps,
|
||||
ref
|
||||
) {
|
||||
@@ -53,7 +62,6 @@ const PaymentMethodSelection = forwardRef(function PaymentMethodSelection(
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
|
||||
const [captchaRef, setCaptchaRef] = useState<HCaptcha | null>(null)
|
||||
const [setupIntent, setSetupIntent] = useState<SetupIntentResponse | undefined>(undefined)
|
||||
const [useAsDefaultBillingAddress, setUseAsDefaultBillingAddress] = useState(true)
|
||||
const { resolvedTheme } = useTheme()
|
||||
const paymentRef = useRef<PaymentMethodElementRef | null>(null)
|
||||
const [setupNewPaymentMethod, setSetupNewPaymentMethod] = useState<boolean | null>(null)
|
||||
@@ -281,6 +289,8 @@ const PaymentMethodSelection = forwardRef(function PaymentMethodSelection(
|
||||
customerName={customerProfile?.billing_name}
|
||||
currentAddress={customerProfile?.address}
|
||||
currentTaxId={taxId}
|
||||
onAddressChange={onAddressChange}
|
||||
onTaxIdChange={onTaxIdChange}
|
||||
/>
|
||||
</Elements>
|
||||
|
||||
@@ -290,7 +300,9 @@ const PaymentMethodSelection = forwardRef(function PaymentMethodSelection(
|
||||
<Checkbox_Shadcn_
|
||||
id="defaultBillingAddress"
|
||||
checked={useAsDefaultBillingAddress}
|
||||
onCheckedChange={() => setUseAsDefaultBillingAddress(!useAsDefaultBillingAddress)}
|
||||
onCheckedChange={() => {
|
||||
onUseAsDefaultBillingAddressChange(!useAsDefaultBillingAddress)
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor="defaultBillingAddress"
|
||||
|
||||
+38
-2
@@ -1,10 +1,11 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useDebounce } from '@uidotdev/usehooks'
|
||||
import { useParams } from 'common'
|
||||
import { StudioPricingSidePanelOpenedEvent } from 'common/telemetry-constants'
|
||||
import { isArray } from 'lodash'
|
||||
import { Check, ExternalLink } from 'lucide-react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { plans as subscriptionsPlans } from 'shared-data/plans'
|
||||
import { Button, cn, SidePanel } from 'ui'
|
||||
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
@@ -22,6 +23,7 @@ import { RequestUpgradeToBillingOwners } from '@/components/ui/RequestUpgradeToB
|
||||
import { useFreeProjectLimitCheckQuery } from '@/data/organizations/free-project-limit-check-query'
|
||||
import { useOrganizationBillingSubscriptionPreview } from '@/data/organizations/organization-billing-subscription-preview'
|
||||
import { useOrganizationQuery } from '@/data/organizations/organization-query'
|
||||
import type { CustomerAddress, CustomerTaxId } from '@/data/organizations/types'
|
||||
import { useOrgProjectsInfiniteQuery } from '@/data/projects/org-projects-infinite-query'
|
||||
import { useOrgPlansQuery } from '@/data/subscriptions/org-plans-query'
|
||||
import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
|
||||
@@ -61,6 +63,26 @@ export const PlanUpdateSidePanel = () => {
|
||||
const [showUpgradeSurvey, setShowUpgradeSurvey] = useState(false)
|
||||
const [showDowngradeError, setShowDowngradeError] = useState(false)
|
||||
const [selectedTier, setSelectedTier] = useState<'tier_free' | 'tier_pro' | 'tier_team'>()
|
||||
const [latestAddress, setLatestAddress] = useState<CustomerAddress>()
|
||||
const [latestTaxId, setLatestTaxId] = useState<CustomerTaxId | null>()
|
||||
const [useAsDefaultBillingAddress, setUseAsDefaultBillingAddress] = useState(true)
|
||||
|
||||
const billingAddress = useAsDefaultBillingAddress ? latestAddress : undefined
|
||||
const billingTaxId = useAsDefaultBillingAddress ? latestTaxId : null
|
||||
const debouncedAddress = useDebounce(billingAddress, 1000)
|
||||
const debouncedTaxId = useDebounce(billingTaxId, 1000)
|
||||
|
||||
const handleAddressChange = useCallback(
|
||||
(address: CustomerAddress) => setLatestAddress(address),
|
||||
[]
|
||||
)
|
||||
|
||||
const handleTaxIdChange = useCallback((taxId: CustomerTaxId | null) => setLatestTaxId(taxId), [])
|
||||
|
||||
const handleUseAsDefaultBillingAddressChange = useCallback(
|
||||
(useAsDefault: boolean) => setUseAsDefaultBillingAddress(useAsDefault),
|
||||
[]
|
||||
)
|
||||
|
||||
const { can: canUpdateSubscription } = useAsyncCheckPermissions(
|
||||
PermissionAction.BILLING_WRITE,
|
||||
@@ -104,8 +126,14 @@ export const PlanUpdateSidePanel = () => {
|
||||
data: subscriptionPreview,
|
||||
error: subscriptionPreviewError,
|
||||
isPending: subscriptionPreviewIsLoading,
|
||||
isFetching: subscriptionPreviewIsFetching,
|
||||
isSuccess: subscriptionPreviewInitialized,
|
||||
} = useOrganizationBillingSubscriptionPreview({ tier: selectedTier, organizationSlug: slug })
|
||||
} = useOrganizationBillingSubscriptionPreview({
|
||||
tier: selectedTier,
|
||||
organizationSlug: slug,
|
||||
address: debouncedAddress,
|
||||
taxId: debouncedTaxId ?? undefined,
|
||||
})
|
||||
|
||||
const availablePlans: OrgPlan[] = plans?.plans ?? []
|
||||
const hasMembersExceedingFreeTierLimit =
|
||||
@@ -118,6 +146,9 @@ export const PlanUpdateSidePanel = () => {
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setSelectedTier(undefined)
|
||||
setLatestAddress(undefined)
|
||||
setLatestTaxId(undefined)
|
||||
setUseAsDefaultBillingAddress(true)
|
||||
const source = Array.isArray(router.query.source)
|
||||
? router.query.source[0]
|
||||
: router.query.source
|
||||
@@ -342,6 +373,7 @@ export const PlanUpdateSidePanel = () => {
|
||||
planMeta={planMeta}
|
||||
subscriptionPreviewError={subscriptionPreviewError}
|
||||
subscriptionPreviewIsLoading={subscriptionPreviewIsLoading}
|
||||
subscriptionPreviewIsFetching={subscriptionPreviewIsFetching}
|
||||
subscriptionPreviewInitialized={subscriptionPreviewInitialized}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
subscription={subscription}
|
||||
@@ -352,6 +384,10 @@ export const PlanUpdateSidePanel = () => {
|
||||
subscriptionsPlans.find((plan) => plan.id === `tier_${subscription?.plan?.id}`)
|
||||
?.features || [],
|
||||
}}
|
||||
onAddressChange={handleAddressChange}
|
||||
onTaxIdChange={handleTaxIdChange}
|
||||
useAsDefaultBillingAddress={useAsDefaultBillingAddress}
|
||||
onUseAsDefaultBillingAddressChange={handleUseAsDefaultBillingAddressChange}
|
||||
/>
|
||||
|
||||
<MembersExceedLimitModal
|
||||
|
||||
+35
-13
@@ -6,7 +6,7 @@ import Link from 'next/link'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { plans as subscriptionsPlans } from 'shared-data/plans'
|
||||
import { toast } from 'sonner'
|
||||
import { Button, Dialog, DialogContent, Table, TableBody, TableCell, TableRow } from 'ui'
|
||||
import { Button, cn, Dialog, DialogContent, Table, TableBody, TableCell, TableRow } from 'ui'
|
||||
import { Admonition } from 'ui-patterns'
|
||||
import { InfoTooltip } from 'ui-patterns/info-tooltip'
|
||||
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '@/components/interfaces/Billing/Subscription/Subscription.utils'
|
||||
import AlertError from '@/components/ui/AlertError'
|
||||
import { OrganizationBillingSubscriptionPreviewData } from '@/data/organizations/organization-billing-subscription-preview'
|
||||
import type { CustomerAddress, CustomerTaxId } from '@/data/organizations/types'
|
||||
import { OrgProject } from '@/data/projects/org-projects-infinite-query'
|
||||
import { useConfirmPendingSubscriptionChangeMutation } from '@/data/subscriptions/org-subscription-confirm-pending-change'
|
||||
import { useOrgSubscriptionUpdateMutation } from '@/data/subscriptions/org-subscription-update-mutation'
|
||||
@@ -64,11 +65,16 @@ interface Props {
|
||||
planMeta: any
|
||||
subscriptionPreviewError: any
|
||||
subscriptionPreviewIsLoading: boolean
|
||||
subscriptionPreviewIsFetching: boolean
|
||||
subscriptionPreviewInitialized: boolean
|
||||
subscriptionPreview: OrganizationBillingSubscriptionPreviewData | undefined
|
||||
subscription: any
|
||||
currentPlanMeta: any
|
||||
projects: OrgProject[]
|
||||
onAddressChange?: (address: CustomerAddress) => void
|
||||
onTaxIdChange?: (taxId: CustomerTaxId | null) => void
|
||||
useAsDefaultBillingAddress: boolean
|
||||
onUseAsDefaultBillingAddressChange: (useAsDefault: boolean) => void
|
||||
}
|
||||
|
||||
export const SubscriptionPlanUpdateDialog = ({
|
||||
@@ -77,11 +83,16 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
planMeta,
|
||||
subscriptionPreviewError,
|
||||
subscriptionPreviewIsLoading,
|
||||
subscriptionPreviewIsFetching,
|
||||
subscriptionPreviewInitialized,
|
||||
subscriptionPreview,
|
||||
subscription,
|
||||
currentPlanMeta,
|
||||
projects,
|
||||
onAddressChange,
|
||||
onTaxIdChange,
|
||||
useAsDefaultBillingAddress,
|
||||
onUseAsDefaultBillingAddressChange,
|
||||
}: Props) => {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
||||
@@ -325,16 +336,22 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
<div className="p-8 pb-8 flex flex-col xl:col-span-3">
|
||||
<div className="flex-1">
|
||||
<div>
|
||||
{!billingViaPartner && subscriptionPreview != null && changeType === 'upgrade' && (
|
||||
<div className="space-y-2 mb-4">
|
||||
<PaymentMethodSelection
|
||||
ref={paymentMethodSelectionRef}
|
||||
selectedPaymentMethod={selectedPaymentMethod}
|
||||
onSelectPaymentMethod={(pm) => setSelectedPaymentMethod(pm)}
|
||||
readOnly={paymentConfirmationLoading || isConfirming || isUpdating}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!billingViaPartner &&
|
||||
subscriptionPreviewInitialized &&
|
||||
changeType === 'upgrade' && (
|
||||
<div className="space-y-2 mb-4">
|
||||
<PaymentMethodSelection
|
||||
ref={paymentMethodSelectionRef}
|
||||
selectedPaymentMethod={selectedPaymentMethod}
|
||||
onSelectPaymentMethod={(pm) => setSelectedPaymentMethod(pm)}
|
||||
readOnly={paymentConfirmationLoading || isConfirming || isUpdating}
|
||||
onAddressChange={onAddressChange}
|
||||
onTaxIdChange={onTaxIdChange}
|
||||
useAsDefaultBillingAddress={useAsDefaultBillingAddress}
|
||||
onUseAsDefaultBillingAddressChange={onUseAsDefaultBillingAddressChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{billingViaPartner && (
|
||||
<div className="mb-4">
|
||||
@@ -368,7 +385,12 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
)}
|
||||
{subscriptionPreviewInitialized && (
|
||||
<>
|
||||
<div className="mt-2 mb-4 text-foreground-light text-sm">
|
||||
<div
|
||||
className={cn(
|
||||
'mt-2 mb-4 text-foreground-light text-sm transition-opacity',
|
||||
subscriptionPreviewIsFetching && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
{breakdownItems.map((item, i) =>
|
||||
item.type === 'amount' ? (
|
||||
<div
|
||||
@@ -668,7 +690,7 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
loading={isUpdating || paymentConfirmationLoading || isConfirming}
|
||||
disabled={subscriptionPreviewIsLoading}
|
||||
disabled={subscriptionPreviewIsLoading || subscriptionPreviewIsFetching}
|
||||
type="primary"
|
||||
onClick={onUpdateSubscription}
|
||||
className="flex-1"
|
||||
|
||||
@@ -14,8 +14,11 @@ export const organizationKeys = {
|
||||
slug: string | undefined,
|
||||
{ date_start, date_end }: { date_start: string | undefined; date_end: string | undefined }
|
||||
) => ['organizations', slug, 'audit-logs', { date_start, date_end }] as const,
|
||||
subscriptionPreview: (slug: string | undefined, tier: string | undefined) =>
|
||||
['organizations', slug, 'subscription', 'preview', tier] as const,
|
||||
subscriptionPreview: (
|
||||
slug: string | undefined,
|
||||
tier: string | undefined,
|
||||
params?: { address?: Record<string, unknown>; taxId?: Record<string, unknown> }
|
||||
) => ['organizations', slug, 'subscription', 'preview', tier, params] as const,
|
||||
taxId: (slug: string | undefined) => ['organizations', slug, 'tax-ids'] as const,
|
||||
tokenValidation: (slug: string | undefined, token: string | undefined) =>
|
||||
['organizations', slug, 'validate-token', token] as const,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { organizationKeys } from './keys'
|
||||
import type { CustomerAddress, CustomerTaxId } from './types'
|
||||
import { handleError, post } from '@/data/fetchers'
|
||||
import type { SubscriptionTier } from '@/data/subscriptions/types'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
@@ -8,11 +9,15 @@ import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
export type OrganizationBillingSubscriptionPreviewVariables = {
|
||||
organizationSlug?: string
|
||||
tier?: SubscriptionTier
|
||||
address?: CustomerAddress
|
||||
taxId?: CustomerTaxId
|
||||
}
|
||||
|
||||
export async function previewOrganizationBillingSubscription({
|
||||
organizationSlug,
|
||||
tier,
|
||||
address,
|
||||
taxId,
|
||||
}: OrganizationBillingSubscriptionPreviewVariables) {
|
||||
if (!organizationSlug) throw new Error('organizationSlug is required')
|
||||
if (!tier) throw new Error('tier is required')
|
||||
@@ -23,6 +28,8 @@ export async function previewOrganizationBillingSubscription({
|
||||
params: { path: { slug: organizationSlug } },
|
||||
body: {
|
||||
tier,
|
||||
...(address && { address }),
|
||||
...(taxId && { tax_id: taxId }),
|
||||
},
|
||||
headers: {
|
||||
Version: '2',
|
||||
@@ -42,15 +49,20 @@ export type OrganizationBillingSubscriptionPreviewData = Awaited<
|
||||
export const useOrganizationBillingSubscriptionPreview = <
|
||||
TData = OrganizationBillingSubscriptionPreviewData,
|
||||
>(
|
||||
{ organizationSlug, tier }: OrganizationBillingSubscriptionPreviewVariables,
|
||||
{ organizationSlug, tier, address, taxId }: OrganizationBillingSubscriptionPreviewVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseCustomQueryOptions<OrganizationBillingSubscriptionPreviewData, ResponseError, TData> = {}
|
||||
) =>
|
||||
useQuery<OrganizationBillingSubscriptionPreviewData, ResponseError, TData>({
|
||||
queryKey: organizationKeys.subscriptionPreview(organizationSlug, tier),
|
||||
queryFn: () => previewOrganizationBillingSubscription({ organizationSlug, tier }),
|
||||
queryKey: organizationKeys.subscriptionPreview(organizationSlug, tier, {
|
||||
address: address as Record<string, unknown> | undefined,
|
||||
taxId: taxId as Record<string, unknown> | undefined,
|
||||
}),
|
||||
queryFn: () =>
|
||||
previewOrganizationBillingSubscription({ organizationSlug, tier, address, taxId }),
|
||||
enabled: enabled && typeof organizationSlug !== 'undefined' && typeof tier !== 'undefined',
|
||||
placeholderData: keepPreviousData,
|
||||
...options,
|
||||
})
|
||||
Reference in new issue
Block a user