🚸 (billing) Improve feedback when subscription is "past_due"
This commit is contained in:
@@ -61,20 +61,24 @@ export const getSubscription = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
const subscriptions = await stripe.subscriptions.list({
|
const subscriptions = await stripe.subscriptions.list({
|
||||||
customer: workspace.stripeId,
|
customer: workspace.stripeId,
|
||||||
limit: 1,
|
|
||||||
status: 'active',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const subscription = subscriptions?.data.shift()
|
const currentSubscription = subscriptions.data
|
||||||
|
.filter((sub) => ['past_due', 'active'].includes(sub.status))
|
||||||
|
.sort((a, b) => a.created - b.created)
|
||||||
|
.shift()
|
||||||
|
|
||||||
if (!subscription)
|
if (!currentSubscription)
|
||||||
return {
|
return {
|
||||||
subscription: null,
|
subscription: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscription: {
|
subscription: {
|
||||||
isYearly: subscription.items.data.some((item) => {
|
status: subscriptionSchema.shape.status.parse(
|
||||||
|
currentSubscription.status
|
||||||
|
),
|
||||||
|
isYearly: currentSubscription.items.data.some((item) => {
|
||||||
return (
|
return (
|
||||||
priceIds.STARTER.chats.yearly === item.price.id ||
|
priceIds.STARTER.chats.yearly === item.price.id ||
|
||||||
priceIds.STARTER.storage.yearly === item.price.id ||
|
priceIds.STARTER.storage.yearly === item.price.id ||
|
||||||
@@ -82,9 +86,9 @@ export const getSubscription = authenticatedProcedure
|
|||||||
priceIds.PRO.storage.yearly === item.price.id
|
priceIds.PRO.storage.yearly === item.price.id
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
currency: subscription.currency as 'usd' | 'eur',
|
currency: currentSubscription.currency as 'usd' | 'eur',
|
||||||
cancelDate: subscription.cancel_at
|
cancelDate: currentSubscription.cancel_at
|
||||||
? new Date(subscription.cancel_at * 1000)
|
? new Date(currentSubscription.cancel_at * 1000)
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
import { trpc } from '@/lib/trpc'
|
import { trpc } from '@/lib/trpc'
|
||||||
import { useScopedI18n } from '@/locales'
|
import { useScopedI18n } from '@/locales'
|
||||||
import { Button, Link } from '@chakra-ui/react'
|
import { Button, ButtonProps, Link } from '@chakra-ui/react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceId: string
|
workspaceId: string
|
||||||
}
|
} & Pick<ButtonProps, 'colorScheme'>
|
||||||
|
|
||||||
export const BillingPortalButton = ({ workspaceId }: Props) => {
|
export const BillingPortalButton = ({ workspaceId, colorScheme }: Props) => {
|
||||||
const scopedT = useScopedI18n('billing')
|
const scopedT = useScopedI18n('billing')
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const { data } = trpc.billing.getBillingPortalUrl.useQuery(
|
const { data } = trpc.billing.getBillingPortalUrl.useQuery(
|
||||||
@@ -23,7 +23,12 @@ export const BillingPortalButton = ({ workspaceId }: Props) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Button as={Link} href={data?.billingPortalUrl} isLoading={!data}>
|
<Button
|
||||||
|
as={Link}
|
||||||
|
href={data?.billingPortalUrl}
|
||||||
|
isLoading={!data}
|
||||||
|
colorScheme={colorScheme}
|
||||||
|
>
|
||||||
{scopedT('billingPortalButton.label')}
|
{scopedT('billingPortalButton.label')}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -100,7 +100,11 @@ export const ChangePlanForm = ({ workspace }: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.subscription?.cancelDate) return null
|
if (
|
||||||
|
data?.subscription?.cancelDate ||
|
||||||
|
data?.subscription?.status === 'past_due'
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Text, HStack, Stack, Heading } from '@chakra-ui/react'
|
import {
|
||||||
|
Text,
|
||||||
|
HStack,
|
||||||
|
Stack,
|
||||||
|
Heading,
|
||||||
|
Alert,
|
||||||
|
AlertIcon,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
import { Plan } from '@typebot.io/prisma'
|
import { Plan } from '@typebot.io/prisma'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { PlanTag } from './PlanTag'
|
import { PlanTag } from './PlanTag'
|
||||||
@@ -35,8 +42,21 @@ export const CurrentSubscriptionSummary = ({ workspace }: Props) => {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
{data?.subscription?.status === 'past_due' && (
|
||||||
|
<Alert fontSize="sm" status="error">
|
||||||
|
<AlertIcon />
|
||||||
|
{scopedT('pastDueAlert')}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
{isSubscribed && <BillingPortalButton workspaceId={workspace.id} />}
|
{isSubscribed && (
|
||||||
|
<BillingPortalButton
|
||||||
|
workspaceId={workspace.id}
|
||||||
|
colorScheme={
|
||||||
|
data?.subscription?.status === 'past_due' ? 'blue' : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ export default {
|
|||||||
'billing.currentSubscription.heading': 'Abonnement',
|
'billing.currentSubscription.heading': 'Abonnement',
|
||||||
'billing.currentSubscription.subheading': 'Aktuelles Workspace-Abonnement:',
|
'billing.currentSubscription.subheading': 'Aktuelles Workspace-Abonnement:',
|
||||||
'billing.currentSubscription.cancelDate': 'Wird storniert am',
|
'billing.currentSubscription.cancelDate': 'Wird storniert am',
|
||||||
|
'billing.currentSubscription.pastDueAlert':
|
||||||
|
'Die letzte Zahlung ist fehlgeschlagen. Gehen Sie zum Abrechnungsportal, um fortzufahren und eine Kündigung Ihres Abonnements zu vermeiden.',
|
||||||
'billing.invoices.heading': 'Rechnungen',
|
'billing.invoices.heading': 'Rechnungen',
|
||||||
'billing.invoices.empty': 'Keine Rechnungen für diesen Workspace gefunden.',
|
'billing.invoices.empty': 'Keine Rechnungen für diesen Workspace gefunden.',
|
||||||
'billing.invoices.paidAt': 'Bezahlt am',
|
'billing.invoices.paidAt': 'Bezahlt am',
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ export default {
|
|||||||
'billing.currentSubscription.heading': 'Subscription',
|
'billing.currentSubscription.heading': 'Subscription',
|
||||||
'billing.currentSubscription.subheading': 'Current workspace subscription:',
|
'billing.currentSubscription.subheading': 'Current workspace subscription:',
|
||||||
'billing.currentSubscription.cancelDate': 'Will be cancelled on',
|
'billing.currentSubscription.cancelDate': 'Will be cancelled on',
|
||||||
|
'billing.currentSubscription.pastDueAlert':
|
||||||
|
'The latest payment failed. Head over to the billing portal to proceed and avoid having your subscription canceled.',
|
||||||
'billing.invoices.heading': 'Invoices',
|
'billing.invoices.heading': 'Invoices',
|
||||||
'billing.invoices.empty': 'No invoices found for this workspace.',
|
'billing.invoices.empty': 'No invoices found for this workspace.',
|
||||||
'billing.invoices.paidAt': 'Paid at',
|
'billing.invoices.paidAt': 'Paid at',
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ export default {
|
|||||||
'billing.currentSubscription.heading': 'Abonnement',
|
'billing.currentSubscription.heading': 'Abonnement',
|
||||||
'billing.currentSubscription.subheading': 'Abonnement actuel du workspace :',
|
'billing.currentSubscription.subheading': 'Abonnement actuel du workspace :',
|
||||||
'billing.currentSubscription.cancelDate': 'Sera annulé le',
|
'billing.currentSubscription.cancelDate': 'Sera annulé le',
|
||||||
|
'billing.currentSubscription.pastDueAlert':
|
||||||
|
"Le dernier paiement a échoué. Rendez-vous sur le portail de facturation pour effectuer la procédure et éviter l'annulation de votre abonnement.",
|
||||||
'billing.invoices.heading': 'Factures',
|
'billing.invoices.heading': 'Factures',
|
||||||
'billing.invoices.empty': 'Aucune facture trouvée pour ce workspace.',
|
'billing.invoices.empty': 'Aucune facture trouvée pour ce workspace.',
|
||||||
'billing.invoices.paidAt': 'Payé le',
|
'billing.invoices.paidAt': 'Payé le',
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ export default {
|
|||||||
'billing.currentSubscription.subheading':
|
'billing.currentSubscription.subheading':
|
||||||
'Assinatura atual do espaço de trabalho:',
|
'Assinatura atual do espaço de trabalho:',
|
||||||
'billing.currentSubscription.cancelDate': 'Será cancelado em',
|
'billing.currentSubscription.cancelDate': 'Será cancelado em',
|
||||||
|
'billing.currentSubscription.pastDueAlert':
|
||||||
|
'O último pagamento falhou. Acesse o portal de faturamento para prosseguir e evitar o cancelamento da sua assinatura.',
|
||||||
'billing.invoices.heading': 'Faturas',
|
'billing.invoices.heading': 'Faturas',
|
||||||
'billing.invoices.empty':
|
'billing.invoices.empty':
|
||||||
'Nenhuma fatura encontrada para este espaço de trabalho.',
|
'Nenhuma fatura encontrada para este espaço de trabalho.',
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ export default {
|
|||||||
'billing.currentSubscription.subheading':
|
'billing.currentSubscription.subheading':
|
||||||
'Subscrição actual do espaço de trabalho:',
|
'Subscrição actual do espaço de trabalho:',
|
||||||
'billing.currentSubscription.cancelDate': 'Será cancelado em',
|
'billing.currentSubscription.cancelDate': 'Será cancelado em',
|
||||||
|
'billing.currentSubscription.pastDueAlert':
|
||||||
|
'O último pagamento falhou. Acesse o portal de faturamento para continuar e evitar o cancelamento da sua assinatura.',
|
||||||
'billing.invoices.heading': 'Facturas',
|
'billing.invoices.heading': 'Facturas',
|
||||||
'billing.invoices.empty':
|
'billing.invoices.empty':
|
||||||
'Nenhuma factura encontrada para este espaço de trabalho.',
|
'Nenhuma factura encontrada para este espaço de trabalho.',
|
||||||
|
|||||||
@@ -30689,11 +30689,19 @@
|
|||||||
"cancelDate": {
|
"cancelDate": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"active",
|
||||||
|
"past_due"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"isYearly",
|
"isYearly",
|
||||||
"currency"
|
"currency",
|
||||||
|
"status"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
@@ -32760,7 +32768,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/typebots/{typebotId}/blocks/{blockId}/openai/models": {
|
"/openai/models": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "openAI-listModels",
|
"operationId": "openAI-listModels",
|
||||||
"summary": "List OpenAI models",
|
"summary": "List OpenAI models",
|
||||||
@@ -32773,22 +32781,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
|
||||||
"name": "typebotId",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "blockId",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "credentialsId",
|
"name": "credentialsId",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@@ -32804,6 +32796,23 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "baseUrl",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "https://api.openai.com/v1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiVersion",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const subscriptionSchema = z.object({
|
|||||||
isYearly: z.boolean(),
|
isYearly: z.boolean(),
|
||||||
currency: z.enum(['eur', 'usd']),
|
currency: z.enum(['eur', 'usd']),
|
||||||
cancelDate: z.date().optional(),
|
cancelDate: z.date().optional(),
|
||||||
|
status: z.enum(['active', 'past_due']),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Subscription = z.infer<typeof subscriptionSchema>
|
export type Subscription = z.infer<typeof subscriptionSchema>
|
||||||
|
|||||||
Reference in New Issue
Block a user