Files
supabase/apps/studio/data/subscriptions/org-subscription-confirm-pending-create.ts
T
Kevin Grüneberg dff6074d30 feat: support pending changes for plan upgrades (#36430)
This PR implements the new flow to confirm subscription upgrades using Orb pending changes. This is backwards compatible and based on a flag exposed by the backend (`subscriptionPreview.pending_subscription_flow`).

Just like the organization creation, the entire flow is slightly different - instead of creating a payment method separately, the payment method is added inline while doing the upgrade and then attached to the customer. If payment fails, the upgrade will not go through. If payment requires additional action, the user needs to confirm the payment before allowing the upgrade.

For testing the new flow locally, toggle the flag in `flags.ts` on the backend.

Changes include
- No longer rely on the `changeType` from the plans endpoint as this is regularly out-of-sync and displays wrong up/downgrade info due to race conditions
- `readOnly` mode for Stripe elements if anything is loading/submitting
- Reduced prop drilling for some components
- Hide payment method and address selection on downgrade
2025-06-18 15:11:33 +08:00

96 lines
2.6 KiB
TypeScript

import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { handleError, post } from 'data/fetchers'
import type { ResponseError } from 'types'
import { organizationKeys } from 'data/organizations/keys'
import { permissionKeys } from 'data/permissions/keys'
import { castOrganizationResponseToOrganization } from 'data/organizations/organizations-query'
import type { components } from 'api-types'
export type PendingSubscriptionCreateVariables = {
payment_intent_id: string
name: string
kind?: string
size?: string
}
export async function confirmPendingSubscriptionCreate({
payment_intent_id,
name,
kind,
size,
}: PendingSubscriptionCreateVariables) {
const { data, error } = await post('/platform/organizations/confirm-subscription', {
body: {
payment_intent_id,
name,
kind,
size,
},
})
if (error) handleError(error)
return data
}
type PendingSubscriptionCreateData = Awaited<ReturnType<typeof confirmPendingSubscriptionCreate>>
export const useConfirmPendingSubscriptionCreateMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<
PendingSubscriptionCreateData,
ResponseError,
PendingSubscriptionCreateVariables
>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<
PendingSubscriptionCreateData,
ResponseError,
PendingSubscriptionCreateVariables
>((vars) => confirmPendingSubscriptionCreate(vars), {
async onSuccess(data, variables, context) {
// [Joshen] We're manually updating the query client here as the org's subscription is
// created async, and the invalidation will happen too quick where the GET organizations
// endpoint will error out with a 500 since the subscription isn't created yet.
queryClient.setQueriesData(
{
queryKey: organizationKeys.list(),
exact: true,
},
(prev: any) => {
if (!prev) return prev
return [
...prev,
castOrganizationResponseToOrganization(
data as components['schemas']['OrganizationResponse']
),
]
}
)
await queryClient.invalidateQueries(permissionKeys.list())
// todo replace plan in org
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(data.message, {
dismissible: true,
duration: 10_000,
})
} else {
onError(data, variables, context)
}
},
...options,
})
}