Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
poolsar42 committed Jul 26, 2023
1 parent 8d1504b commit 0097c06
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 144 deletions.
76 changes: 30 additions & 46 deletions apps/console/app/routes/__layout/billing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ import {
createOrUpdateSubscription,
getCurrentAndUpcomingInvoices,
process3DSecureCard,
setPurchaseToastNotification,
UnpaidInvoiceNotification,
type StripeInvoice,
} from '~/utils/stripe'
import { IoWarningOutline } from 'react-icons/io5'
import { type ToastNotification } from '~/types'
import { setPurchaseToastNotification } from '~/utils'

type LoaderData = {
STRIPE_PUBLISHABLE_KEY: string
Expand Down Expand Up @@ -195,26 +196,11 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(

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

for (const invoice of invoices) {
// We are not creating and/or updating subscriptions
// until we resolve our unpaid invoices
if (invoice?.status) {
if (['open', 'uncollectible'].includes(invoice.status)) {
flashSession.flash(
'toast_notification',
JSON.stringify({
type: ToastType.Error,
message: 'Payment failed - check your card details',
})
)
return new Response(null, {
headers: {
'Set-Cookie': await commitFlashSession(flashSession, context.env),
},
})
}
}
}
await UnpaidInvoiceNotification({
invoices,
flashSession,
env: context.env,
})

const fd = await request.formData()
const { customerID, quantity, txType } = JSON.parse(
Expand Down Expand Up @@ -289,8 +275,8 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
)
}

return new Response(
JSON.stringify({
return json(
{
status: (sub.latest_invoice as unknown as StripeInvoice)?.payment_intent
?.status,
client_secret: (sub.latest_invoice as unknown as StripeInvoice)
Expand All @@ -299,7 +285,7 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
.payment_intent?.payment_method,
quantity,
subId: sub.id,
}),
},
{
headers: {
'Set-Cookie': await commitFlashSession(flashSession, context.env),
Expand Down Expand Up @@ -981,18 +967,15 @@ export default () => {

useEffect(() => {
if (actionData) {
const { status, client_secret, payment_method, subId } =
JSON.parse(actionData)
;(async () => {
await process3DSecureCard({
STRIPE_PUBLISHABLE_KEY,
status,
subId,
client_secret,
payment_method,
submit,
})
})()
const { status, client_secret, payment_method, subId } = actionData
process3DSecureCard({
STRIPE_PUBLISHABLE_KEY,
status,
subId,
client_secret,
payment_method,
submit,
})
}
}, [actionData])

Expand Down Expand Up @@ -1296,14 +1279,15 @@ export default () => {
{hydrated && (
<div className="flex flex-row items-center space-x-3">
<Text size="sm" className="gray-500">
{new Date(invoice.timestamp).toLocaleString(
'default',
{
day: '2-digit',
month: 'short',
year: 'numeric',
}
)}
{hydrated &&
new Date(invoice.timestamp).toLocaleString(
'default',
{
day: '2-digit',
month: 'short',
year: 'numeric',
}
)}
</Text>

{(invoice.status === 'open' ||
Expand Down Expand Up @@ -1360,7 +1344,7 @@ export default () => {
</a>
<button
type="button"
onClick={async () => {
onClick={() => {
submit(
{
invoice_id: invoice.id,
Expand All @@ -1375,7 +1359,7 @@ export default () => {
}}
>
<Text size="xs" className="text-red-500">
Cancel Invoice
Cancel Payment
</Text>
</button>
</div>
Expand Down
16 changes: 11 additions & 5 deletions apps/console/app/routes/__layout/billing/webhook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import createCoreClient from '@proofzero/platform-clients/core'
import { type AccountURN } from '@proofzero/urns/account'

import { getAuthzHeaderConditionallyFromToken } from '@proofzero/utils'
import {
reconcileAppSubscriptions,
updateSubscriptionMetadata,
} from '~/services/billing/stripe'
import { InternalServerError } from '@proofzero/errors'
import { reconcileAppSubscriptions } from '~/services/billing/stripe'
import { InternalServerError, RollupError } from '@proofzero/errors'
import { type AddressURN } from '@proofzero/urns/address'

type StripeInvoicePayload = {
Expand Down Expand Up @@ -80,6 +77,15 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
return null
}

const entitlements = await coreClient.account.getEntitlements.query({
accountURN: subMeta.accountURN,
})
if (entitlements?.subscriptionID !== id) {
throw new RollupError({
message: `Subscription ID ${id} does not match entitlements subscription ID ${entitlements?.subscriptionID}`,
})
}

await reconcileAppSubscriptions(
{
subscriptionID: id,
Expand Down
57 changes: 22 additions & 35 deletions apps/console/app/routes/apps/$clientId/billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ import { type Env } from 'bindings'
import {
type StripeInvoice,
getCurrentAndUpcomingInvoices,
setPurchaseToastNotification,
createOrUpdateSubscription,
process3DSecureCard,
UnpaidInvoiceNotification,
} from '~/utils/stripe'
import { setPurchaseToastNotification } from '~/utils'

export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper(
async ({ request, context }) => {
Expand Down Expand Up @@ -195,16 +196,14 @@ const processPurchaseOp = async (
: 1
: 1

sub = (
await createOrUpdateSubscription({
customerID,
SECRET_STRIPE_PRO_PLAN_ID: env.SECRET_STRIPE_PRO_PLAN_ID,
SECRET_STRIPE_API_KEY: env.SECRET_STRIPE_API_KEY,
quantity,
subscriptionID: entitlements.subscriptionID,
accountURN,
})
).sub
sub = await createOrUpdateSubscription({
customerID,
SECRET_STRIPE_PRO_PLAN_ID: env.SECRET_STRIPE_PRO_PLAN_ID,
SECRET_STRIPE_API_KEY: env.SECRET_STRIPE_API_KEY,
quantity,
subscriptionID: entitlements.subscriptionID,
accountURN,
})

setPurchaseToastNotification({
sub,
Expand Down Expand Up @@ -261,26 +260,11 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(

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

for (const invoice of invoices) {
// We are not creating and/or updating subscriptions
// until we resolve our unpaid invoices
if (invoice?.status) {
if (['open', 'uncollectible'].includes(invoice.status)) {
flashSession.flash(
'toast_notification',
JSON.stringify({
type: ToastType.Error,
message: 'Payment failed - check your card details',
})
)
return new Response(null, {
headers: {
'Set-Cookie': await commitFlashSession(flashSession, context.env),
},
})
}
}
}
await UnpaidInvoiceNotification({
invoices,
flashSession,
env: context.env,
})

const fd = await request.formData()
const op = fd.get('op') as 'update' | 'purchase'
Expand Down Expand Up @@ -311,15 +295,15 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
traceHeader
)

return new Response(
JSON.stringify({
return json(
{
status: (sub?.latest_invoice as unknown as StripeInvoice)
?.payment_intent?.status,
client_secret: (sub?.latest_invoice as unknown as StripeInvoice)
.payment_intent?.client_secret,
payment_method: (sub?.latest_invoice as unknown as StripeInvoice)
.payment_intent?.payment_method,
}),
},
{
headers: {
'Set-Cookie': await commitFlashSession(flashSession, context.env),
Expand Down Expand Up @@ -747,16 +731,19 @@ export default () => {
hasUnpaidInvoices: boolean
}>()

const navigate = useNavigate()

useEffect(() => {
if (actionData) {
const { status, client_secret, payment_method } = JSON.parse(actionData)
const { status, client_secret, payment_method } = actionData
;(async () => {
await process3DSecureCard({
STRIPE_PUBLISHABLE_KEY,
status,
client_secret,
payment_method,
})
navigate('.', { replace: true })
})()
}
}, [actionData])
Expand Down
3 changes: 2 additions & 1 deletion apps/console/app/services/billing/stripe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InternalServerError } from '@proofzero/errors'
import { type CoreClientType } from '@proofzero/platform-clients/core'
import { ReconcileAppsSubscriptionsOutput } from '@proofzero/platform/starbase/src/jsonrpc/methods/reconcileAppSubscriptions'
import { ServicePlanType } from '@proofzero/types/account'
import { AccountURN } from '@proofzero/urns/account'
Expand Down Expand Up @@ -264,7 +265,7 @@ export const reconcileAppSubscriptions = async (
}: {
subscriptionID: string
accountURN: AccountURN
coreClient: any
coreClient: CoreClientType
billingURL: string
settingsURL: string
},
Expand Down
68 changes: 57 additions & 11 deletions apps/console/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
* @file app/utils.ts
*/

import { useMatches } from "@remix-run/react";
import { useMemo } from "react";
import { ToastType } from '@proofzero/design-system/src/atoms/toast'
import { useMatches } from '@remix-run/react'
import { useMemo } from 'react'
import { type StripeInvoice } from './utils/stripe'
import type Stripe from 'stripe'

const DEFAULT_REDIRECT = "/";
const DEFAULT_REDIRECT = '/'

// safeRedirect
// -----------------------------------------------------------------------------
Expand All @@ -21,15 +24,15 @@ export function safeRedirect(
to: FormDataEntryValue | string | null | undefined,
defaultRedirect: string = DEFAULT_REDIRECT
) {
if (!to || typeof to !== "string") {
return defaultRedirect;
if (!to || typeof to !== 'string') {
return defaultRedirect
}

if (!to.startsWith("/") || to.startsWith("//")) {
return defaultRedirect;
if (!to.startsWith('/') || to.startsWith('//')) {
return defaultRedirect
}

return to;
return to
}

// useMatchesData
Expand All @@ -44,10 +47,53 @@ export function safeRedirect(
export function useMatchesData(
id: string
): Record<string, unknown> | undefined {
const matchingRoutes = useMatches();
const matchingRoutes = useMatches()
const route = useMemo(
() => matchingRoutes.find((route) => route.id === id),
[matchingRoutes, id]
);
return route?.data;
)
return route?.data
}

export const setPurchaseToastNotification = ({
sub,
flashSession,
}: {
sub: Stripe.Subscription
flashSession: any
}) => {
// https://stripe.com/docs/billing/subscriptions/overview#subscription-statuses
if (
(sub.status === 'active' || sub.status === 'trialing') &&
sub.latest_invoice?.status === 'paid'
) {
flashSession.flash(
'toast_notification',
JSON.stringify({
type: ToastType.Success,
message: 'Entitlement(s) successfully bought',
})
)
} else {
if (
(sub.latest_invoice as unknown as StripeInvoice)?.payment_intent
?.status === 'requires_action'
) {
flashSession.flash(
'toast_notification',
JSON.stringify({
type: ToastType.Warning,
message: 'Payment requires additional action',
})
)
} else {
flashSession.flash(
'toast_notification',
JSON.stringify({
type: ToastType.Error,
message: 'Payment failed - check your card details',
})
)
}
}
}
Loading

0 comments on commit 0097c06

Please sign in to comment.