🚸 (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({
|
||||
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 {
|
||||
subscription: null,
|
||||
}
|
||||
|
||||
return {
|
||||
subscription: {
|
||||
isYearly: subscription.items.data.some((item) => {
|
||||
status: subscriptionSchema.shape.status.parse(
|
||||
currentSubscription.status
|
||||
),
|
||||
isYearly: currentSubscription.items.data.some((item) => {
|
||||
return (
|
||||
priceIds.STARTER.chats.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
|
||||
)
|
||||
}),
|
||||
currency: subscription.currency as 'usd' | 'eur',
|
||||
cancelDate: subscription.cancel_at
|
||||
? new Date(subscription.cancel_at * 1000)
|
||||
currency: currentSubscription.currency as 'usd' | 'eur',
|
||||
cancelDate: currentSubscription.cancel_at
|
||||
? new Date(currentSubscription.cancel_at * 1000)
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { Button, Link } from '@chakra-ui/react'
|
||||
import { Button, ButtonProps, Link } from '@chakra-ui/react'
|
||||
|
||||
type Props = {
|
||||
workspaceId: string
|
||||
}
|
||||
} & Pick<ButtonProps, 'colorScheme'>
|
||||
|
||||
export const BillingPortalButton = ({ workspaceId }: Props) => {
|
||||
export const BillingPortalButton = ({ workspaceId, colorScheme }: Props) => {
|
||||
const scopedT = useScopedI18n('billing')
|
||||
const { showToast } = useToast()
|
||||
const { data } = trpc.billing.getBillingPortalUrl.useQuery(
|
||||
@@ -23,7 +23,12 @@ export const BillingPortalButton = ({ workspaceId }: Props) => {
|
||||
}
|
||||
)
|
||||
return (
|
||||
<Button as={Link} href={data?.billingPortalUrl} isLoading={!data}>
|
||||
<Button
|
||||
as={Link}
|
||||
href={data?.billingPortalUrl}
|
||||
isLoading={!data}
|
||||
colorScheme={colorScheme}
|
||||
>
|
||||
{scopedT('billingPortalButton.label')}
|
||||
</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 (
|
||||
<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 React from 'react'
|
||||
import { PlanTag } from './PlanTag'
|
||||
@@ -35,8 +42,21 @@ export const CurrentSubscriptionSummary = ({ workspace }: Props) => {
|
||||
</Text>
|
||||
)}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ export default {
|
||||
'billing.currentSubscription.heading': 'Abonnement',
|
||||
'billing.currentSubscription.subheading': 'Aktuelles Workspace-Abonnement:',
|
||||
'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.empty': 'Keine Rechnungen für diesen Workspace gefunden.',
|
||||
'billing.invoices.paidAt': 'Bezahlt am',
|
||||
|
||||
@@ -116,6 +116,8 @@ export default {
|
||||
'billing.currentSubscription.heading': 'Subscription',
|
||||
'billing.currentSubscription.subheading': 'Current workspace subscription:',
|
||||
'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.empty': 'No invoices found for this workspace.',
|
||||
'billing.invoices.paidAt': 'Paid at',
|
||||
|
||||
@@ -118,6 +118,8 @@ export default {
|
||||
'billing.currentSubscription.heading': 'Abonnement',
|
||||
'billing.currentSubscription.subheading': 'Abonnement actuel du workspace :',
|
||||
'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.empty': 'Aucune facture trouvée pour ce workspace.',
|
||||
'billing.invoices.paidAt': 'Payé le',
|
||||
|
||||
@@ -120,6 +120,8 @@ export default {
|
||||
'billing.currentSubscription.subheading':
|
||||
'Assinatura atual do espaço de trabalho:',
|
||||
'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.empty':
|
||||
'Nenhuma fatura encontrada para este espaço de trabalho.',
|
||||
|
||||
@@ -121,6 +121,8 @@ export default {
|
||||
'billing.currentSubscription.subheading':
|
||||
'Subscrição actual do espaço de trabalho:',
|
||||
'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.empty':
|
||||
'Nenhuma factura encontrada para este espaço de trabalho.',
|
||||
|
||||
@@ -30689,11 +30689,19 @@
|
||||
"cancelDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"active",
|
||||
"past_due"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"isYearly",
|
||||
"currency"
|
||||
"currency",
|
||||
"status"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -32760,7 +32768,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/typebots/{typebotId}/blocks/{blockId}/openai/models": {
|
||||
"/openai/models": {
|
||||
"get": {
|
||||
"operationId": "openAI-listModels",
|
||||
"summary": "List OpenAI models",
|
||||
@@ -32773,22 +32781,6 @@
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "typebotId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "blockId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "credentialsId",
|
||||
"in": "query",
|
||||
@@ -32804,6 +32796,23 @@
|
||||
"schema": {
|
||||
"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": {
|
||||
|
||||
@@ -4,6 +4,7 @@ export const subscriptionSchema = z.object({
|
||||
isYearly: z.boolean(),
|
||||
currency: z.enum(['eur', 'usd']),
|
||||
cancelDate: z.date().optional(),
|
||||
status: z.enum(['active', 'past_due']),
|
||||
})
|
||||
|
||||
export type Subscription = z.infer<typeof subscriptionSchema>
|
||||
|
||||
Reference in New Issue
Block a user