Skip to content

Commit

Permalink
fix(console): Service plan entitlement assignment during 3DS (#2556)
Browse files Browse the repository at this point in the history
  • Loading branch information
poolsar42 authored Jul 28, 2023
1 parent 277d4b6 commit 0f5c5cd
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 6 deletions.
12 changes: 11 additions & 1 deletion apps/console/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export type LoaderData = {
PASSPORT_URL: string
displayName: string
hasUnpaidInvoices: boolean
unpaidInvoiceURL: string
ENV: {
POSTHOG_API_KEY: string
POSTHOG_PROXY_HOST: string
Expand Down Expand Up @@ -157,16 +158,22 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper(
context.env.SECRET_STRIPE_API_KEY
)

let unpaidInvoiceURL = '/billing/portal'

const hasUnpaidInvoices = invoices.some((invoice) => {
if (invoice.status)
return ['uncollectible', 'open'].includes(invoice.status)
if (['uncollectible', 'open'].includes(invoice.status)) {
unpaidInvoiceURL = invoice.url as string
return true
}
return false
})

return json<LoaderData>({
apps: reshapedApps,
avatarUrl,
hasUnpaidInvoices,
unpaidInvoiceURL,
PASSPORT_URL,
ENV: {
POSTHOG_API_KEY,
Expand Down Expand Up @@ -207,6 +214,7 @@ export default function App() {
displayName,
accountURN,
hasUnpaidInvoices,
unpaidInvoiceURL,
} = loaderData

useEffect(() => {
Expand Down Expand Up @@ -274,6 +282,7 @@ export default function App() {
displayName,
accountURN,
hasUnpaidInvoices,
unpaidInvoiceURL,
}}
/>
</PostHogProvider>
Expand All @@ -286,6 +295,7 @@ export default function App() {
displayName,
accountURN,
hasUnpaidInvoices,
unpaidInvoiceURL,
}}
/>
)}
Expand Down
12 changes: 9 additions & 3 deletions apps/console/app/routes/__layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import { ToastWithLink } from '@proofzero/design-system/src/atoms/toast/ToastWit

export default function DashboardIndexPage() {
const context = useOutletContext<OutletContextData>()
const { apps, avatarUrl, displayName, PASSPORT_URL, hasUnpaidInvoices } =
context
const {
apps,
avatarUrl,
displayName,
PASSPORT_URL,
hasUnpaidInvoices,
unpaidInvoiceURL,
} = context

return (
<Popover className="min-h-[100dvh] relative">
Expand All @@ -36,7 +42,7 @@ export default function DashboardIndexPage() {
{hasUnpaidInvoices && (
<ToastWithLink
message="We couldn't process payment for your account"
linkHref={`/billing/portal`}
linkHref={unpaidInvoiceURL}
linkText="Update payment information"
type={'urgent'}
/>
Expand Down
35 changes: 35 additions & 0 deletions apps/console/app/routes/__layout/billing/update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
import { reconcileAppSubscriptions } from '~/services/billing/stripe'
import { type AccountURN } from '@proofzero/urns/account'
import { ToastType } from '@proofzero/design-system/src/atoms/toast'
import Stripe from 'stripe'
import { type ServicePlanType } from '@proofzero/types/account'

/**
* WARNING: Here be dragons, and not the cute, cuddly kind! This code runs twice in certain scenarios because when the user
Expand All @@ -37,14 +39,20 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(

const subId = fd.get('subId') as string
const redirectUrl = fd.get('redirectUrl') as string
const updatePlanParams = fd.get('updatePlanParams') as string

const coreClient = createCoreClient(context.env.Core, {
...getAuthzHeaderConditionallyFromToken(jwt),
...traceHeader,
})

// if this method was called from "$clientId/billing" page, update the plan
// and assign the new plan to the app

const flashSession = await getFlashSession(request, context.env)

try {
// First we reconcile the subscriptions
await reconcileAppSubscriptions(
{
subscriptionID: subId,
Expand All @@ -55,6 +63,33 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
},
context.env
)

// Then based on reconciled result we update the plan
// We call this only if we update the plan for the app
if (updatePlanParams.length) {
const { clientId, plan } = JSON.parse(updatePlanParams) as {
clientId: string
plan: ServicePlanType
paymentIntentId: string
}

const entitlements = await coreClient.account.getEntitlements.query({
accountURN,
})

const numberOfEntitlements = entitlements.plans[plan]?.entitlements
const apps = await coreClient.starbase.listApps.query()
const allotedApps = apps.filter((a) => a.appPlan === plan).length

if (numberOfEntitlements && numberOfEntitlements > allotedApps) {
await coreClient.starbase.setAppPlan.mutate({
accountURN,
clientId,
plan,
})
}
}

flashSession.flash(
'toast_notification',
JSON.stringify({
Expand Down
3 changes: 2 additions & 1 deletion apps/console/app/routes/apps/$clientId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export default function AppDetailIndexPage() {
displayName,
accountURN,
hasUnpaidInvoices,
unpaidInvoiceURL,
} = useOutletContext<OutletContextData>()
const {
appDetails,
Expand Down Expand Up @@ -203,7 +204,7 @@ export default function AppDetailIndexPage() {
{hasUnpaidInvoices && (
<ToastWithLink
message="We couldn't process payment for your account"
linkHref={`/billing/portal`}
linkHref={unpaidInvoiceURL}
linkText="Update payment information"
type={'urgent'}
/>
Expand Down
12 changes: 11 additions & 1 deletion apps/console/app/routes/apps/$clientId/billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,13 @@ const processPurchaseOp = async (
sub,
flashSession,
})
if (sub.status === 'active' || sub.status === 'trialing') {

const invoiceStatus = (sub.latest_invoice as Stripe.Invoice)?.status

if (
(sub.status === 'active' || sub.status === 'trialing') &&
invoiceStatus === 'paid'
) {
await coreClient.account.updateEntitlements.mutate({
accountURN: accountURN,
subscriptionID: sub.id,
Expand Down Expand Up @@ -753,6 +759,10 @@ export default () => {
status,
client_secret,
payment_method,
updatePlanParams: {
clientId: appDetails.clientId,
plan: ServicePlanType.PRO,
},
redirectUrl: `/apps/${appDetails.clientId}/billing`,
})
}
Expand Down
9 changes: 9 additions & 0 deletions apps/console/app/utils/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { type Session, type SessionData } from '@remix-run/cloudflare'
import { commitFlashSession } from '~/utilities/session.server'
import { type Env } from 'bindings'
import Stripe from 'stripe'
import { type ServicePlanType } from '@proofzero/types/account'

export type StripeInvoice = {
id: string
Expand Down Expand Up @@ -134,6 +135,7 @@ export const process3DSecureCard = async ({
submit,
subId,
redirectUrl,
updatePlanParams,
}: {
STRIPE_PUBLISHABLE_KEY: string
status: string
Expand All @@ -142,6 +144,10 @@ export const process3DSecureCard = async ({
submit: SubmitFunction
subId: string
redirectUrl?: string
updatePlanParams?: {
clientId?: string
plan: ServicePlanType
}
}) => {
const stripeClient = await loadStripe(STRIPE_PUBLISHABLE_KEY)
if (status === 'requires_action') {
Expand All @@ -160,6 +166,9 @@ export const process3DSecureCard = async ({
{
subId,
redirectUrl: redirectUrl ? redirectUrl : '/billing',
updatePlanParams: updatePlanParams
? JSON.stringify(updatePlanParams)
: '',
},
{
method: 'post',
Expand Down

0 comments on commit 0f5c5cd

Please sign in to comment.