Add payment methods to org billing v2

This commit is contained in:
Joshen Lim committed 2023-07-03 12:00:18 +08:00
1 parent 0ed5e40c4c
commit cc32db3281
9 files changed
+779 -3

No files matched your search

@@ -5,6 +5,7 @@ import CreditBalance from './CreditBalance'
import { LayoutWrapper } from './Scaffold'
import BillingAddress from './BillingAddress/BillingAddress'
import TaxID from './TaxID/TaxID'
import PaymentMethods from './PaymentMethods/PaymentMethods'
// [Joshen TODO] Last one: payment method details management
// Either as part of the subscription plan component like project, or its own section like the old design
@@ -21,6 +22,9 @@ const BillingSettings = () => {
<LayoutWrapper>
<CreditBalance />
</LayoutWrapper>
<LayoutWrapper>
<PaymentMethods />
</LayoutWrapper>
<LayoutWrapper>
<BillingEmail />
</LayoutWrapper>
@@ -0,0 +1,80 @@
import { useParams } from 'common'
import { useOrganizationPaymentMethodDeleteMutation } from 'data/organizations/organization-payment-method-delete-mutation'
import { OrganizationPaymentMethod } from 'data/organizations/organization-payment-methods-query'
import { useStore } from 'hooks'
import { Alert, Button, Modal } from 'ui'
export interface DeletePaymentMethodModalProps {
selectedPaymentMethod?: OrganizationPaymentMethod
onClose: () => void
}
const DeletePaymentMethodModal = ({
selectedPaymentMethod,
onClose,
}: DeletePaymentMethodModalProps) => {
const { ui } = useStore()
const { slug } = useParams()
const { mutateAsync: deletePayment, isLoading: isDeleting } =
useOrganizationPaymentMethodDeleteMutation()
const onConfirmDelete = async () => {
if (!slug) return console.error('Slug is required')
if (!selectedPaymentMethod) return console.error('Card ID is required')
try {
await deletePayment({ slug, cardId: selectedPaymentMethod.id })
ui.setNotification({
category: 'success',
message: `Successfully removed payment method ending with ${selectedPaymentMethod.card.last4}`,
})
onClose()
} catch (error: any) {
ui.setNotification({
category: 'error',
message: `Failed to delete payment method: ${error.message}`,
})
}
}
return (
<Modal
visible={selectedPaymentMethod !== undefined}
size="medium"
header={`Confirm to delete payment method ending with ${selectedPaymentMethod?.card.last4}`}
onCancel={() => onClose()}
customFooter={
<div className="flex items-center gap-2">
<Button type="default" disabled={isDeleting} onClick={() => onClose()}>
Cancel
</Button>
<Button
type="primary"
disabled={isDeleting}
loading={isDeleting}
onClick={onConfirmDelete}
>
Confirm
</Button>
</div>
}
>
<div className="py-4">
<Modal.Content>
<Alert
withIcon
variant="warning"
title="This will permanently delete your payment method."
>
<p className="">
Any subscription currently charging this payment method will start charging your
default payment method.
</p>
</Alert>
</Modal.Content>
</div>
</Modal>
)
}
export default DeletePaymentMethodModal
@@ -0,0 +1,180 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import { useParams } from 'common'
import { AddNewPaymentMethodModal } from 'components/interfaces/Billing'
import { FormPanel, FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms'
import NoPermission from 'components/ui/NoPermission'
import { organizationKeys } from 'data/organizations/keys'
import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query'
import { useCheckPermissions, useStore } from 'hooks'
import { getURL } from 'lib/helpers'
import { Badge, Button, Dropdown, IconCreditCard, IconMoreHorizontal, IconPlus, Input } from 'ui'
import { BASE_PATH } from 'lib/constants'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import DeletePaymentMethodModal from './DeletePaymentMethodModal'
// [TODO] Implement change credit card for org subscription logic
const PaymentMethods = () => {
const { ui } = useStore()
const { slug } = useParams()
const queryClient = useQueryClient()
const [selectedMethodForDefault, setSelectedMethodForDefault] = useState<any>()
const [selectedMethodToDelete, setSelectedMethodToDelete] = useState<any>()
const [showAddPaymentMethodModal, setShowAddPaymentMethodModal] = useState(false)
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: slug })
const {
data: paymentMethods,
isLoading,
isError,
isSuccess,
} = useOrganizationPaymentMethodsQuery({ slug })
const canReadPaymentMethods = useCheckPermissions(
PermissionAction.BILLING_READ,
'stripe.payment_methods'
)
const canUpdatePaymentMethods = useCheckPermissions(
PermissionAction.BILLING_WRITE,
'stripe.payment_methods'
)
return (
<>
<FormSection
id="payment-methods"
header={
<FormSectionLabel>
<div className="sticky space-y-6 top-16">
<div>
<p className="text-base">Payment methods</p>
<p className="text-sm text-scale-1000 mb-2">
This copy might need to change, when adding a new payment method, either remove
the old one or go to your projects' subscription to explicitly update the payment
method.
</p>
</div>
</div>
</FormSectionLabel>
}
>
<FormSectionContent loading={isLoading}>
{!canReadPaymentMethods ? (
<NoPermission resourceText="view this organization's payment methods" />
) : (
<FormPanel
footer={
<div className="flex items-center justify-between py-4 px-8">
{!canUpdatePaymentMethods ? (
<p className="text-sm text-scale-1000">
You need additional permissions to manage payment methods
</p>
) : (
<div />
)}
<Button
type="default"
icon={<IconPlus />}
disabled={!canUpdatePaymentMethods}
onClick={() => setShowAddPaymentMethodModal(true)}
>
Add new card
</Button>
</div>
}
>
<FormSection>
<FormSectionContent fullWidth loading={false}>
{(paymentMethods?.length ?? 0) === 0 ? (
<div className="flex items-center space-x-2 opacity-50">
<IconCreditCard />
<p className="text-sm">No payment methods</p>
</div>
) : (
<div className="space-y-3">
{paymentMethods?.map((paymentMethod) => {
const activePaymentMethod =
subscription?.payment_method_id === paymentMethod.id
return (
<div key={paymentMethod.id} className="flex items-center justify-between">
<div className="flex items-center space-x-8">
<img
alt="Credit card brand"
src={`${BASE_PATH}/img/payment-methods/${paymentMethod.card.brand
.replace(' ', '-')
.toLowerCase()}.png`}
width="32"
/>
<p className="prose text-sm font-mono">
**** **** **** {paymentMethod.card.last4}
</p>
<p className="text-sm tabular-nums">
Expires: {paymentMethod.card.exp_month}/
{paymentMethod.card.exp_year}
</p>
{activePaymentMethod && <Badge color="green">Active</Badge>}
</div>
{canUpdatePaymentMethods ? (
<Dropdown
size="tiny"
align="end"
overlay={[
<Dropdown.Item
key="make-default"
onClick={() => setSelectedMethodForDefault(paymentMethod)}
>
Make default
</Dropdown.Item>,
<Dropdown.Item
key="delete-method"
onClick={() => setSelectedMethodToDelete(paymentMethod)}
>
Delete
</Dropdown.Item>,
]}
>
<Button
type="outline"
icon={<IconMoreHorizontal />}
className="hover:border-gray-500 px-1"
/>
</Dropdown>
) : null}
</div>
)
})}
</div>
)}
</FormSectionContent>
</FormSection>
</FormPanel>
)}
</FormSectionContent>
</FormSection>
<AddNewPaymentMethodModal
visible={showAddPaymentMethodModal}
returnUrl={`${getURL()}/org/${slug}/billing`}
onCancel={() => setShowAddPaymentMethodModal(false)}
onConfirm={async () => {
setShowAddPaymentMethodModal(false)
ui.setNotification({
category: 'success',
message: 'Successfully added new payment method',
})
await queryClient.invalidateQueries(organizationKeys.paymentMethods(slug))
}}
/>
<DeletePaymentMethodModal
selectedPaymentMethod={selectedMethodToDelete}
onClose={() => setSelectedMethodToDelete(undefined)}
/>
</>
)
}
export default PaymentMethods
@@ -0,0 +1,181 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'common'
import { useOrganizationCustomerProfileQuery } from 'data/organizations/organization-customer-profile-query'
import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query'
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import { BASE_PATH } from 'lib/constants'
import { getURL } from 'lib/helpers'
import { Button, IconAlertCircle, IconCreditCard, IconLoader, IconPlus, Listbox } from 'ui'
import { AddNewPaymentMethodModal } from 'components/interfaces/Billing'
export interface PaymentMethodSelectionProps {
selectedPaymentMethod?: string
onSelectPaymentMethod: (id: string) => void
}
const PaymentMethodSelection = ({
selectedPaymentMethod,
onSelectPaymentMethod,
}: PaymentMethodSelectionProps) => {
const { ui } = useStore()
const { ref: projectRef } = useParams()
const selectedOrganization = useSelectedOrganization()
const slug = selectedOrganization?.slug
const [showAddNewPaymentMethodModal, setShowAddNewPaymentMethodModal] = useState(false)
const {
data,
isLoading,
isSuccess: loadedPaymentMethods,
refetch: refetchPaymentMethods,
} = useOrganizationPaymentMethodsQuery({ slug })
const paymentMethods = useMemo(() => data ?? [], [data])
const { data: customerProfile, isSuccess: loadedCustomerProfile } =
useOrganizationCustomerProfileQuery({ slug })
const canUpdatePaymentMethods = useCheckPermissions(
PermissionAction.BILLING_WRITE,
'stripe.payment_methods'
)
useEffect(() => {
if (loadedPaymentMethods && loadedCustomerProfile && paymentMethods.length > 0) {
const selectedPaymentMethodExists = paymentMethods.some(
(it) => it.id === selectedPaymentMethod
)
if (!selectedPaymentMethod || !selectedPaymentMethodExists) {
const defaultPaymentMethod = paymentMethods.find(
(method) => method.id === customerProfile.invoice_settings.default_payment_method
)
if (defaultPaymentMethod !== undefined) {
onSelectPaymentMethod(defaultPaymentMethod.id)
} else {
onSelectPaymentMethod(paymentMethods[0].id)
}
}
}
}, [
loadedPaymentMethods,
loadedCustomerProfile,
selectedPaymentMethod,
customerProfile,
paymentMethods,
onSelectPaymentMethod,
])
return (
<>
<div className="space-y-2">
<p className="text-sm">Select payment method</p>
{isLoading ? (
<div className="flex items-center px-4 py-2 space-x-4 border rounded-md border-scale-700 bg-scale-400">
<IconLoader className="animate-spin" size={14} />
<p className="text-sm text-scale-1100">Retrieving payment methods</p>
</div>
) : paymentMethods.length === 0 ? (
<div className="flex items-center justify-between px-4 py-2 border border-dashed rounded-md bg-scale-100">
<div className="flex items-center space-x-4 text-scale-1100">
<IconAlertCircle size={16} strokeWidth={1.5} />
<p className="text-sm">No saved payment methods</p>
</div>
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<Button
type="default"
disabled={!canUpdatePaymentMethods}
icon={<IconCreditCard />}
onClick={() => setShowAddNewPaymentMethodModal(true)}
>
Add new
</Button>
</Tooltip.Trigger>
{!canUpdatePaymentMethods && (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'w-48 border border-scale-200 text-center', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add new payment methods to this
organization
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
) : (
<Listbox value={selectedPaymentMethod} onChange={onSelectPaymentMethod}>
{paymentMethods.map((method: any) => {
const label = `•••• •••• •••• ${method.card.last4}`
return (
<Listbox.Option
key={method.id}
label={label}
value={method.id}
addOnBefore={() => {
return (
<img
alt="Credit Card Brand"
src={`${BASE_PATH}/img/payment-methods/${method.card.brand
.replace(' ', '-')
.toLowerCase()}.png`}
width="32"
/>
)
}}
>
<div>{label}</div>
</Listbox.Option>
)
})}
<div
className="flex items-center px-3 py-2 space-x-2 transition cursor-pointer group hover:bg-scale-500"
onClick={() => setShowAddNewPaymentMethodModal(true)}
>
<IconPlus size={16} />
<p className="transition text-scale-1000 group-hover:text-scale-1200">
Add new payment method
</p>
</div>
</Listbox>
)}
</div>
<AddNewPaymentMethodModal
visible={showAddNewPaymentMethodModal}
returnUrl={`${getURL()}/project/${projectRef}/settings/billing/subscription?panel=subscriptionPlan`}
onCancel={() => setShowAddNewPaymentMethodModal(false)}
onConfirm={async () => {
setShowAddNewPaymentMethodModal(false)
ui.setNotification({
category: 'success',
message: 'Successfully added new payment method',
})
const { data } = await refetchPaymentMethods()
if (data?.length) {
// Preselect the card that was just added
const mostRecentPaymentMethod = data.reduce(
(prev, current) => (prev.created > current.created ? prev : current),
data[0]
)
onSelectPaymentMethod(mostRecentPaymentMethod.id)
}
}}
/>
</>
)
}
export default PaymentMethodSelection
@@ -9,6 +9,7 @@ import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-que
import { useFlag } from 'hooks'
import { Alert, Button, IconExternalLink } from 'ui'
import ProjectUpdateDisabledTooltip from '../../BillingSettings/ProjectUpdateDisabledTooltip'
import SubscriptionPaymentMethod from './SubscriptionPaymentMethod'
const Subscription = () => {
const { slug } = useParams()
@@ -158,6 +159,10 @@ const Subscription = () => {
labelBottomClass="!text-scale-1000 pb-1"
labelTop={`${daysToCycleEnd} Days left`}
/>
{subscription !== undefined && (
<SubscriptionPaymentMethod subscription={subscription} />
)}
</div>
)}
</FormSectionContent>
@@ -0,0 +1,216 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useState } from 'react'
import { useParams } from 'common'
import Panel from 'components/ui/Panel'
import {
SubscriptionTier,
updateSubscriptionTier,
} from 'data/subscriptions/project-subscription-update-mutation'
import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query'
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import { BASE_PATH } from 'lib/constants'
import Link from 'next/link'
import { Button, IconCreditCard, Modal } from 'ui'
import PaymentMethodSelection from './PaymentMethodSelection'
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
export interface SubscriptionTierProps {
subscription: ProjectSubscriptionResponse
}
const SubscriptionPaymentMethod = ({ subscription }: SubscriptionTierProps) => {
const { ui } = useStore()
const { slug } = useParams()
const selectedOrganization = useSelectedOrganization()
const currentOrgSlug = selectedOrganization?.slug
const { mutateAsync: updateOrgSubscription } = useOrgSubscriptionUpdateMutation()
const canUpdatePaymentMethod = useCheckPermissions(
PermissionAction.BILLING_WRITE,
'stripe.subscriptions'
)
const [isSubmitting, setIsSubmitting] = useState(false)
const [showPaymentEditModal, setShowPaymentEditModal] = useState(false)
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>(
subscription.payment_method_id || ''
)
const onConfirmPaymentMethod = async () => {
setIsSubmitting(true)
if (selectedPaymentMethod) {
if (!slug) return console.error('Slug is required')
if (!selectedPaymentMethod) {
return ui.setNotification({ category: 'error', message: 'Please select a payment method' })
}
const selectedTier =
subscription.plan.id === 'pro' && subscription.usage_billing_enabled
? 'tier_payg'
: `tier_${subscription.plan.id}`
try {
setIsSubmitting(true)
await updateOrgSubscription({
slug,
tier: selectedTier as SubscriptionTier,
paymentMethod: selectedPaymentMethod,
})
setShowPaymentEditModal(false)
ui.setNotification({ category: 'success', message: `Successfully updated payment method!` })
} catch (error: any) {
ui.setNotification({
error,
category: 'error',
message: `Unable to update subscription: ${error.message}`,
})
} finally {
setIsSubmitting(false)
}
}
setIsSubmitting(false)
}
if (subscription.payment_method_type === 'none') return null
return (
<>
<Panel>
<Panel.Content className="flex justify-between lg:items-center flex-col md:flex-row space-y-6 md:space-y-0">
{subscription.payment_method_type === 'card' && (
<>
<div className="flex space-x-3 items-center font-mono tracking-wide text-scale-1000">
{subscription.payment_method_card_details?.brand ? (
<img
alt="Credit card brand"
src={`${BASE_PATH}/img/payment-methods/${subscription.payment_method_card_details?.brand
.replace(' ', '-')
.toLowerCase()}.png`}
width="24"
/>
) : (
<IconCreditCard />
)}
<span className="text-sm">
**** **** ****{' '}
{subscription?.payment_method_card_details?.last_4_digits || '****'}
</span>
</div>
<div className="flex flex-row space-x-3 text-scale-1000 text-sm">
<span>
Expires {subscription?.payment_method_card_details?.expiry_month || '-'}/
{subscription?.payment_method_card_details?.expiry_year?.toString()?.slice(-2) ||
'-'}
</span>
</div>
</>
)}
{subscription?.payment_method_type === 'invoice' && (
<>
<div className="flex space-x-3">
<div>
<p className="text-scale-1100">Payment via invoice</p>
<p className="text-sm text-scale-1000">
You get a monthly invoice and payment link via email.
</p>
</div>
</div>
</>
)}
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger asChild>
<div>
<Button
type="default"
disabled={subscription?.payment_method_type !== 'card' || !canUpdatePaymentMethod}
onClick={() => setShowPaymentEditModal(true)}
>
Change
</Button>
</div>
</Tooltip.Trigger>
{subscription?.payment_method_type !== 'card' ? (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
Please email support@supbase.io to change your payment method.
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : !canUpdatePaymentMethod ? (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You do not have permission to change the payment method
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : null}
</Tooltip.Root>
</Panel.Content>
</Panel>
<Modal
loading={isSubmitting}
alignFooter="right"
className="!w-[450px]"
visible={showPaymentEditModal}
onCancel={() => setShowPaymentEditModal(false)}
onConfirm={onConfirmPaymentMethod}
overlayClassName="pointer-events-none"
header={`Change payment method`}
>
<Modal.Content>
<div className="py-6 space-y-2">
<p className="text-sm">
Upon clicking confirm, all future charges will be deducted from the selected payment
method. There are no immediate charges. Changing the payment method for this project
does not affect the payment method for other projects.
</p>
<p className="text-sm text-scale-1000">
To remove unused or expired payment methods, head to your{' '}
<Link href={`/org/${currentOrgSlug || '_'}/billing`} passHref>
<a target="_blank" className="text-green-900 transition hover:text-green-1000">
organization's billing settings
</a>
</Link>
.
</p>
<div className="!mt-6">
<PaymentMethodSelection
selectedPaymentMethod={selectedPaymentMethod}
onSelectPaymentMethod={setSelectedPaymentMethod}
/>
</div>
</div>
</Modal.Content>
</Modal>
</>
)
}
export default SubscriptionPaymentMethod
@@ -0,0 +1,48 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { delete_ } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { organizationKeys } from './keys'
import { ResponseError } from 'types'
export type OrganizationPaymentMethodDeleteVariables = {
slug: string
cardId: string
}
export async function deleteOrganizationMember({
slug,
cardId,
}: OrganizationPaymentMethodDeleteVariables) {
const response = await delete_(`${API_URL}/organizations/${slug}/payments`, { card_id: cardId })
if (response.error) throw response.error
return response
}
type OrganizationPaymentMethodDeleteData = Awaited<ReturnType<typeof deleteOrganizationMember>>
export const useOrganizationPaymentMethodDeleteMutation = ({
onSuccess,
...options
}: Omit<
UseMutationOptions<
OrganizationPaymentMethodDeleteData,
unknown,
OrganizationPaymentMethodDeleteVariables
>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<
OrganizationPaymentMethodDeleteData,
unknown,
OrganizationPaymentMethodDeleteVariables
>((vars) => deleteOrganizationMember(vars), {
async onSuccess(data, variables, context) {
const { slug } = variables
await queryClient.invalidateQueries(organizationKeys.paymentMethods(slug))
await onSuccess?.(data, variables, context)
},
...options,
})
}
@@ -5,7 +5,7 @@ import { useCallback } from 'react'
import { organizationKeys } from './keys'
export type OrganizationPaymentMethodsVariables = { slug?: string }
export type OrganizationPaymentMethodsResponse = {
export type OrganizationPaymentMethod = {
id: string
customer: string
type: string
@@ -29,7 +29,7 @@ export type OrganizationPaymentMethodsResponse = {
name: string | null
phone: string | null
}
}[]
}
export async function getOrganizations(
{ slug }: OrganizationPaymentMethodsVariables,
@@ -40,7 +40,7 @@ export async function getOrganizations(
const response = await get(`${API_URL}/organizations/${slug}/payments`, { signal })
if (response.error) throw response.error
return response.data as OrganizationPaymentMethodsResponse
return response.data as OrganizationPaymentMethod[]
}
export type OrganizationPaymentMethodsData = Awaited<ReturnType<typeof getOrganizations>>
@@ -0,0 +1,62 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { put } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { subscriptionKeys } from './keys'
export type SubscriptionTier =
| 'tier_free'
| 'tier_pro'
| 'tier_payg'
| 'tier_team'
| 'tier_enterprise'
export type OrgSubscriptionUpdateVariables = {
slug: string
paymentMethod?: string
tier: SubscriptionTier
}
export type OrgSubscriptionUpdateResponse = {
error?: any
}
export async function updateOrgSubscription({
slug,
tier,
paymentMethod,
}: OrgSubscriptionUpdateVariables) {
if (!slug) throw new Error('slug is required')
if (!tier) throw new Error('tier is required')
const payload: { tier: string; payment_method?: string } = { tier }
if (paymentMethod !== undefined) payload.payment_method = paymentMethod
const response = await put(`${API_URL}/organizations/${slug}/billing/subscription`, payload)
if (response.error) throw response.error
return response as OrgSubscriptionUpdateResponse
}
type OrgSubscriptionUpdateData = Awaited<ReturnType<typeof updateOrgSubscription>>
export const useOrgSubscriptionUpdateMutation = ({
onSuccess,
...options
}: Omit<
UseMutationOptions<OrgSubscriptionUpdateData, unknown, OrgSubscriptionUpdateVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<OrgSubscriptionUpdateData, unknown, OrgSubscriptionUpdateVariables>(
(vars) => updateOrgSubscription(vars),
{
async onSuccess(data, variables, context) {
const { slug } = variables
await queryClient.invalidateQueries(subscriptionKeys.orgSubscription(slug))
await onSuccess?.(data, variables, context)
},
...options,
}
)
}