fix: keep paid limits during Stripe retries (#386)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Plan } from "@prisma/client";
|
||||
import { PLAN_PERKS } from "~/lib/constants/payments";
|
||||
import { isEntitledSubscriptionStatus } from "~/lib/subscription-status";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
import { api } from "~/trpc/react";
|
||||
import Spinner from "@usesend/ui/src/spinner";
|
||||
@@ -17,13 +18,14 @@ export const PlanDetails = () => {
|
||||
|
||||
const planKey = currentTeam.plan as keyof typeof PLAN_PERKS;
|
||||
const perks = PLAN_PERKS[planKey] || [];
|
||||
const isEntitled = isEntitledSubscriptionStatus(
|
||||
subscriptionQuery.data?.status,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="capitalize text-lg">
|
||||
{subscriptionQuery.data?.status === "active"
|
||||
? planKey.toLowerCase()
|
||||
: "free"}
|
||||
{isEntitled ? planKey.toLowerCase() : "free"}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-muted-foreground text-sm">Current plan</div>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const ENTITLED_SUBSCRIPTION_STATUSES = new Set([
|
||||
"active",
|
||||
"trialing",
|
||||
"past_due",
|
||||
]);
|
||||
|
||||
export function isEntitledSubscriptionStatus(
|
||||
status: string | null | undefined,
|
||||
) {
|
||||
return Boolean(status && ENTITLED_SUBSCRIPTION_STATUSES.has(status));
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isEntitledSubscriptionStatus } from "~/lib/subscription-status";
|
||||
|
||||
describe("isEntitledSubscriptionStatus", () => {
|
||||
it("treats retrying subscriptions as entitled", () => {
|
||||
expect(isEntitledSubscriptionStatus("past_due")).toBe(true);
|
||||
});
|
||||
|
||||
it("treats active and trialing subscriptions as entitled", () => {
|
||||
expect(isEntitledSubscriptionStatus("active")).toBe(true);
|
||||
expect(isEntitledSubscriptionStatus("trialing")).toBe(true);
|
||||
});
|
||||
|
||||
it("treats exhausted or incomplete subscriptions as not entitled", () => {
|
||||
expect(isEntitledSubscriptionStatus("unpaid")).toBe(false);
|
||||
expect(isEntitledSubscriptionStatus("canceled")).toBe(false);
|
||||
expect(isEntitledSubscriptionStatus("incomplete")).toBe(false);
|
||||
expect(isEntitledSubscriptionStatus(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import Stripe from "stripe";
|
||||
import { env } from "~/env";
|
||||
import { isEntitledSubscriptionStatus } from "~/lib/subscription-status";
|
||||
import { db } from "../db";
|
||||
import { sendSubscriptionConfirmationEmail } from "../mailer";
|
||||
import { TeamService } from "../service/team-service";
|
||||
@@ -149,6 +150,7 @@ export async function syncStripeData(customerId: string) {
|
||||
.filter((id): id is string => Boolean(id));
|
||||
|
||||
const nextPlan = getPlanFromPriceIds(priceIds);
|
||||
const isEntitled = isEntitledSubscriptionStatus(subscription.status);
|
||||
const isNowPaid = subscription.status === "active" && nextPlan !== "FREE";
|
||||
const shouldSendSubscriptionConfirmation = !wasPaid && isNowPaid;
|
||||
|
||||
@@ -159,10 +161,10 @@ export async function syncStripeData(customerId: string) {
|
||||
priceId: subscription.items.data[0]?.price?.id || "",
|
||||
priceIds: priceIds,
|
||||
currentPeriodEnd: new Date(
|
||||
subscription.items.data[0]?.current_period_end * 1000
|
||||
subscription.items.data[0]?.current_period_end * 1000,
|
||||
),
|
||||
currentPeriodStart: new Date(
|
||||
subscription.items.data[0]?.current_period_start * 1000
|
||||
subscription.items.data[0]?.current_period_start * 1000,
|
||||
),
|
||||
cancelAtPeriodEnd: subscription.cancel_at
|
||||
? new Date(subscription.cancel_at * 1000)
|
||||
@@ -176,10 +178,10 @@ export async function syncStripeData(customerId: string) {
|
||||
priceId: subscription.items.data[0]?.price?.id || "",
|
||||
priceIds: priceIds,
|
||||
currentPeriodEnd: new Date(
|
||||
subscription.items.data[0]?.current_period_end * 1000
|
||||
subscription.items.data[0]?.current_period_end * 1000,
|
||||
),
|
||||
currentPeriodStart: new Date(
|
||||
subscription.items.data[0]?.current_period_start * 1000
|
||||
subscription.items.data[0]?.current_period_start * 1000,
|
||||
),
|
||||
cancelAtPeriodEnd: subscription.cancel_at
|
||||
? new Date(subscription.cancel_at * 1000)
|
||||
@@ -191,7 +193,7 @@ export async function syncStripeData(customerId: string) {
|
||||
|
||||
await TeamService.updateTeam(team.id, {
|
||||
plan: subscription.status === "canceled" ? "FREE" : nextPlan,
|
||||
isActive: subscription.status === "active",
|
||||
isActive: isEntitled,
|
||||
});
|
||||
|
||||
if (shouldSendSubscriptionConfirmation) {
|
||||
@@ -201,12 +203,12 @@ export async function syncStripeData(customerId: string) {
|
||||
teamUsers
|
||||
.map((tu) => tu.user?.email)
|
||||
.filter((email): email is string => Boolean(email))
|
||||
.map((email) => sendSubscriptionConfirmationEmail(email))
|
||||
.map((email) => sendSubscriptionConfirmationEmail(email)),
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
{ err, teamId: team.id },
|
||||
"[Billing]: Failed sending subscription confirmation email"
|
||||
"[Billing]: Failed sending subscription confirmation email",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user