mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 02:45:07 +03:00
Shift update Tax ID in Org billing V1 to RQ mutation, and shift retrieving Tax ID in Org Billing V1 to RQ
This commit is contained in:
1 parent
256252dc09
commit
53405cdbea
2 files changed
+92
-108
No files matched your search
@@ -30,9 +30,6 @@ const BillingSettings = () => {
|
||||
const [paymentMethods, setPaymentMethods] = useState<any>(null)
|
||||
const [isLoadingPaymentMethods, setIsLoadingPaymentMethods] = useState(false)
|
||||
|
||||
const [taxIds, setTaxIds] = useState<any>(null)
|
||||
const [isLoadingTaxIds, setIsLoadingTaxIds] = useState(false)
|
||||
|
||||
const defaultPaymentMethod = customer?.invoice_settings?.default_payment_method ?? ''
|
||||
const customerBalance = customer && customer.balance ? customer.balance / 100 : 0
|
||||
const isCredit = customerBalance < 0
|
||||
@@ -79,26 +76,9 @@ const BillingSettings = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getTaxIds = async () => {
|
||||
try {
|
||||
setIsLoadingTaxIds(true)
|
||||
const { data: taxIds, error } = await get(`${API_URL}/organizations/${slug}/tax-ids`)
|
||||
if (error) throw error
|
||||
setTaxIds(taxIds)
|
||||
setIsLoadingTaxIds(false)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
error,
|
||||
category: 'error',
|
||||
message: `Failed to get organization tax IDs: ${error.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getCustomerProfile()
|
||||
getPaymentMethods()
|
||||
getTaxIds()
|
||||
}, [slug])
|
||||
|
||||
return (
|
||||
@@ -124,11 +104,7 @@ const BillingSettings = () => {
|
||||
address={customer?.address ?? {}}
|
||||
onAddressUpdated={(address: any) => setCustomer({ ...customer, address })}
|
||||
/>
|
||||
<TaxID
|
||||
loading={isLoadingTaxIds}
|
||||
taxIds={taxIds || []}
|
||||
onTaxIdsUpdated={(ids: any) => setTaxIds(ids)}
|
||||
/>
|
||||
<TaxID />
|
||||
</ScaffoldContainerLegacy>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,54 +1,92 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useParams } from 'common'
|
||||
import { isEqual } from 'lodash'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, IconPlus, IconX, Input, Listbox } from 'ui'
|
||||
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
|
||||
import { delete_, post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { TaxId, useOrganizationTaxIDsQuery } from 'data/organizations/organization-tax-ids-query'
|
||||
import {
|
||||
TaxIdValue,
|
||||
useOrganizationTaxIDsUpdateMutation,
|
||||
} from 'data/organizations/organization-tax-ids-update-mutation'
|
||||
import { useCheckPermissions, useStore } from 'hooks'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { StripeTaxId, TAX_IDS } from './TaxID.constants'
|
||||
import { sanitizeTaxID } from './TaxID.utils'
|
||||
|
||||
export interface TaxIDProps {
|
||||
loading: boolean
|
||||
taxIds: any[]
|
||||
onTaxIdsUpdated: (taxIds: any) => void
|
||||
}
|
||||
|
||||
// Stripe recommends to delete tax ids and create new ones to update
|
||||
// https://stripe.com/docs/billing/customer/tax-ids
|
||||
|
||||
const TaxID = ({ loading, taxIds, onTaxIdsUpdated }: TaxIDProps) => {
|
||||
const TaxID = () => {
|
||||
const { ui } = useStore()
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const slug = selectedOrganization?.slug ?? ''
|
||||
const { slug } = useParams()
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [errors, setErrors] = useState<string[]>([])
|
||||
const [taxIdValues, setTaxIdValues] = useState<StripeTaxId[]>(taxIds)
|
||||
const formattedTaxIds: StripeTaxId[] = taxIds.map((taxId: StripeTaxId) => {
|
||||
return {
|
||||
id: taxId.id,
|
||||
type: taxId.type,
|
||||
value: taxId.value,
|
||||
name:
|
||||
taxId.type === 'eu_vat'
|
||||
? `${taxId.country} VAT`
|
||||
: TAX_IDS.find((option) => option.code === taxId.type)?.name ?? '',
|
||||
}
|
||||
const {
|
||||
data: taxIds,
|
||||
error,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useOrganizationTaxIDsQuery({ slug })
|
||||
|
||||
const { mutate: updateTaxIDs, isLoading: isUpdating } = useOrganizationTaxIDsUpdateMutation({
|
||||
onSuccess: (res) => {
|
||||
const { created, errors } = res
|
||||
setErrors(errors?.map((taxId) => taxId.id) ?? [])
|
||||
const updatedTaxIds =
|
||||
created?.map((x) => {
|
||||
return {
|
||||
id: x.id,
|
||||
type: x.type,
|
||||
value: x.value,
|
||||
name:
|
||||
x.type === 'eu_vat'
|
||||
? `${x.country} VAT`
|
||||
: TAX_IDS.find((option) => option.code === x.type)?.name ?? '',
|
||||
}
|
||||
}) ?? []
|
||||
setTaxIdValues(updatedTaxIds)
|
||||
|
||||
if (errors !== undefined && errors.length > 0) {
|
||||
errors.forEach((taxId: any) => {
|
||||
ui.setNotification({ category: 'error', message: taxId.result.error.message })
|
||||
})
|
||||
} else {
|
||||
ui.setNotification({ category: 'success', message: 'Successfully updated tax IDs' })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [errors, setErrors] = useState<string[]>([])
|
||||
const [taxIdValues, setTaxIdValues] = useState<TaxIdValue[]>([])
|
||||
|
||||
const formattedTaxIds =
|
||||
taxIds?.map((taxId: TaxId) => {
|
||||
return {
|
||||
id: taxId.id,
|
||||
type: taxId.type,
|
||||
value: taxId.value,
|
||||
name:
|
||||
taxId.type === 'eu_vat'
|
||||
? `${taxId.country} VAT`
|
||||
: TAX_IDS.find((option) => option.code === taxId.type)?.name ?? '',
|
||||
}
|
||||
}) ?? []
|
||||
|
||||
useEffect(() => {
|
||||
if (taxIdValues.length === 0) {
|
||||
setTaxIdValues(formattedTaxIds)
|
||||
} else {
|
||||
const erroredTaxIds = taxIdValues.filter((taxId: any) => errors.includes(taxId.id))
|
||||
setTaxIdValues(formattedTaxIds.concat(erroredTaxIds))
|
||||
if (isSuccess) {
|
||||
if (taxIdValues.length === 0) {
|
||||
setTaxIdValues(formattedTaxIds)
|
||||
} else {
|
||||
const erroredTaxIds = taxIdValues.filter((taxId: any) => errors.includes(taxId.id))
|
||||
setTaxIdValues(formattedTaxIds.concat(erroredTaxIds))
|
||||
}
|
||||
}
|
||||
}, [taxIds])
|
||||
}, [isSuccess])
|
||||
|
||||
const hasChanges = !isEqual(taxIdValues, formattedTaxIds)
|
||||
const canReadTaxIds = useCheckPermissions(PermissionAction.BILLING_READ, 'stripe.tax_ids')
|
||||
@@ -87,47 +125,11 @@ const TaxID = ({ loading, taxIds, onTaxIdsUpdated }: TaxIDProps) => {
|
||||
}
|
||||
|
||||
const onSaveTaxIds = async () => {
|
||||
// To make things simple we delete all existing ones and create new ones from this session
|
||||
setIsSaving(true)
|
||||
try {
|
||||
const deletedIds = await Promise.all(
|
||||
taxIds.map(async (taxId: StripeTaxId) => {
|
||||
return await delete_(`${API_URL}/organizations/${slug}/tax-ids`, { id: taxId.id })
|
||||
})
|
||||
)
|
||||
if (!slug) return console.error('Slug is required')
|
||||
if (taxIds === undefined) return console.error('Tax IDs are required')
|
||||
|
||||
const newIds = await Promise.all(
|
||||
taxIdValues.map(async (taxId: StripeTaxId) => {
|
||||
const sanitizedID = sanitizeTaxID(taxId)
|
||||
const result = await post(`${API_URL}/organizations/${slug}/tax-ids`, {
|
||||
type: sanitizedID.type,
|
||||
value: sanitizedID.value,
|
||||
})
|
||||
return { id: sanitizedID.id, result }
|
||||
})
|
||||
)
|
||||
const taxIdsWithErrors = newIds.filter((taxId: any) => {
|
||||
if (taxId.result.error) return taxId
|
||||
})
|
||||
setErrors(taxIdsWithErrors.map((taxId: any) => taxId.id))
|
||||
|
||||
if (taxIdsWithErrors.length > 0) {
|
||||
taxIdsWithErrors.forEach((taxId: any) => {
|
||||
ui.setNotification({ category: 'error', message: taxId.result.error.message })
|
||||
})
|
||||
} else {
|
||||
ui.setNotification({ category: 'success', message: 'Successfully updated tax IDs' })
|
||||
}
|
||||
const idsCreated = newIds
|
||||
.filter((taxId: any) => !taxId.result.error)
|
||||
.map((taxId: any) => taxId.result)
|
||||
|
||||
onTaxIdsUpdated(idsCreated)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({ category: 'error', message: 'Failed to save tax IDs' })
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
const newIds = taxIdValues.map((x) => sanitizeTaxID(x))
|
||||
updateTaxIDs({ slug, existingIds: taxIds, newIds })
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -146,9 +148,9 @@ const TaxID = ({ loading, taxIds, onTaxIdsUpdated }: TaxIDProps) => {
|
||||
</Panel>
|
||||
) : (
|
||||
<Panel
|
||||
loading={loading}
|
||||
loading={isLoading}
|
||||
footer={
|
||||
!loading && (
|
||||
!isLoading && (
|
||||
<div className="flex w-full justify-between">
|
||||
{!canUpdateTaxIds ? (
|
||||
<p className="text-sm text-scale-1000">
|
||||
@@ -161,7 +163,7 @@ const TaxID = ({ loading, taxIds, onTaxIdsUpdated }: TaxIDProps) => {
|
||||
<Button
|
||||
type="default"
|
||||
htmlType="reset"
|
||||
disabled={!hasChanges || isSaving}
|
||||
disabled={!hasChanges || isUpdating}
|
||||
onClick={() => onResetTaxIds()}
|
||||
>
|
||||
Cancel
|
||||
@@ -169,8 +171,8 @@ const TaxID = ({ loading, taxIds, onTaxIdsUpdated }: TaxIDProps) => {
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={isSaving}
|
||||
disabled={!hasChanges || isSaving}
|
||||
loading={isUpdating}
|
||||
disabled={!hasChanges || isUpdating}
|
||||
onClick={() => onSaveTaxIds()}
|
||||
>
|
||||
Save
|
||||
@@ -180,13 +182,19 @@ const TaxID = ({ loading, taxIds, onTaxIdsUpdated }: TaxIDProps) => {
|
||||
)
|
||||
}
|
||||
>
|
||||
{loading && taxIdValues.length === 0 ? (
|
||||
<div className="flex flex-col justify-between space-y-2 py-4 px-4">
|
||||
<div className="shimmering-loader mx-1 w-2/3 rounded py-3" />
|
||||
<div className="shimmering-loader mx-1 w-1/2 rounded py-3" />
|
||||
<div className="shimmering-loader mx-1 w-1/3 rounded py-3" />
|
||||
</div>
|
||||
) : (
|
||||
{isLoading && taxIdValues.length === 0 && (
|
||||
<Panel.Content>
|
||||
<GenericSkeletonLoader />
|
||||
</Panel.Content>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<Panel.Content>
|
||||
<AlertError error={error} subject="Failed to retrieve organization tax IDs" />
|
||||
</Panel.Content>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<Panel.Content className="w-8/12 space-y-4">
|
||||
{taxIdValues.length >= 1 ? (
|
||||
<div className="w-full space-y-2">
|
||||
|
||||
Reference in new issue
Block a user