Skip to content

Commit

Permalink
feat: add past due to payment banner
Browse files Browse the repository at this point in the history
  • Loading branch information
jsfez committed Nov 23, 2023
1 parent 833cdc1 commit 379390c
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @param {import('knex').Knex} knex
*/
export const up = async (knex) => {
await knex.schema.alterTable("purchases", async (table) => {
await knex.schema.alterTable("purchases", (table) => {
table.string("status");
});

Expand All @@ -15,7 +15,7 @@ export const up = async (knex) => {
END`),
});

await knex.schema.alterTable("purchases", async (table) => {
await knex.schema.alterTable("purchases", (table) => {
table.string("status").notNullable().alter();
});
};
Expand All @@ -24,7 +24,7 @@ export const up = async (knex) => {
* @param {import('knex').Knex} knex
*/
export const down = async (knex) => {
await knex.schema.alterTable("purchases", async (table) => {
await knex.schema.alterTable("purchases", (table) => {
table.dropColumn("status");
});
};
10 changes: 1 addition & 9 deletions apps/backend/db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ CREATE TABLE public.files (
key character varying(255) NOT NULL,
width integer,
height integer,
type text,
type text NOT NULL,
CONSTRAINT files_type_check CHECK ((type = ANY (ARRAY['screenshot'::text, 'screenshotDiff'::text, 'playwrightTrace'::text])))
);

Expand Down Expand Up @@ -1609,14 +1609,6 @@ ALTER TABLE ONLY public.files
ADD CONSTRAINT files_pkey PRIMARY KEY (id);


--
-- Name: files files_type_not_null_constraint; Type: CHECK CONSTRAINT; Schema: public; Owner: postgres
--

ALTER TABLE public.files
ADD CONSTRAINT files_type_not_null_constraint CHECK ((type IS NOT NULL)) NOT VALID;


--
-- Name: github_accounts github_accounts_githubid_unique; Type: CONSTRAINT; Schema: public; Owner: postgres
--
Expand Down
3 changes: 1 addition & 2 deletions apps/backend/src/synchronize/github/updatePurchase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export const updatePurchase = async (
Purchase.query(trx)
.patch({
endDate: effectiveDate,
status:
effectiveDate > new Date().toISOString() ? "cancelled" : "active",
status: new Date(effectiveDate) > new Date() ? "cancelled" : "active",
})
.findById(activePurchase.id),
Purchase.query(trx).insert({
Expand Down
133 changes: 61 additions & 72 deletions apps/frontend/src/containers/PaymentBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ import { Button } from "@/ui/Button";
import { Container } from "@/ui/Container";
import { StripePortalLink } from "@/ui/StripeLink";

const now = new Date();
const FREE_PLAN_EXPIRATION_DATE = new Date("2023-06-01");

export const PaymentBannerFragment = graphql(`
fragment PaymentBanner_Account on Account {
id
Expand Down Expand Up @@ -93,14 +90,14 @@ const BannerCta = ({
}
};

const getBannerProps = ({
const getTeamBannerProps = ({
purchaseStatus,
trialDaysRemaining,
hasGithubPurchase,
missingPaymentMethod,
pendingCancelAt,
}: {
purchaseStatus: PurchaseStatus | null;
purchaseStatus: PurchaseStatus;
trialDaysRemaining: number | null;
hasGithubPurchase: boolean;
missingPaymentMethod: boolean;
Expand All @@ -111,66 +108,63 @@ const getBannerProps = ({
bannerColor?: BannerProps["color"];
action: SubmitAction;
} => {
if (
(purchaseStatus === PurchaseStatus.Active ||
purchaseStatus === PurchaseStatus.Trialing ||
purchaseStatus === PurchaseStatus.Unpaid) &&
missingPaymentMethod
) {
const trialMessage =
trialDaysRemaining !== null
? `Your trial ends in ${trialDaysRemaining} days. `
: "";

return {
message: `${trialMessage}Add a payment method to retain access to team features.`,
buttonLabel: "Add payment method",
bannerColor:
!trialDaysRemaining || trialDaysRemaining < 5 ? "warning" : "neutral",
action: "stripePortalSession",
};
}

if (
purchaseStatus === PurchaseStatus.Trialing ||
purchaseStatus === PurchaseStatus.Active
) {
const subscriptionType =
purchaseStatus === PurchaseStatus.Trialing ? "trial" : "subscription";
const action = "stripePortalSession";

if (!pendingCancelAt) {
return { action, message: "" };
switch (purchaseStatus) {
case PurchaseStatus.PastDue:
return {
bannerColor: "warning",
message:
"Your subscription is past due. Please update your payment info.",
buttonLabel: "Manage subscription",
action: hasGithubPurchase ? "settings" : "stripeCheckoutSession",
};

case PurchaseStatus.Canceled:
case PurchaseStatus.Missing:
return {
bannerColor: "danger",
message: "Upgrade to Pro plan to use team features.",
...(hasGithubPurchase
? { action: "settings", buttonLabel: "Manage subscription" }
: { action: "stripeCheckoutSession", buttonLabel: "Upgrade" }),
};

case PurchaseStatus.Active:
case PurchaseStatus.Trialing: {
if (missingPaymentMethod) {
const remainingDayMessage = `Your trial ends in ${trialDaysRemaining} days. `;
return {
message: `${
trialDaysRemaining ? remainingDayMessage : ""
}Add a payment method to retain access to team features.`,
buttonLabel: "Add payment method",
bannerColor:
!trialDaysRemaining || trialDaysRemaining < 5
? "warning"
: "neutral",
action: "stripePortalSession",
};
}

if (pendingCancelAt) {
const formatDate = (date: string) => moment(date).format("LL");
const subscriptionTypeLabel =
purchaseStatus === "trialing" ? "trial" : "subscription";
return {
action: "stripePortalSession",
buttonLabel: `Reactivate ${subscriptionTypeLabel}`,
message: `Your ${subscriptionTypeLabel} has been canceled. You can still use team features until the trial ends on ${formatDate(
pendingCancelAt,
)}.`,
};
}

// Trial is active
return { action: "stripePortalSession", message: "" };
}

return {
action,
buttonLabel: `Reactivate ${subscriptionType}`,
message: `Your ${subscriptionType} has been canceled. You can still use team features until the trial ends on ${moment(
pendingCancelAt,
).format("LL")}.`,
};
}

const action = hasGithubPurchase ? "settings" : "stripeCheckoutSession";
const buttonLabel = hasGithubPurchase ? "Manage subscription" : "Upgrade";

if (now < FREE_PLAN_EXPIRATION_DATE) {
return {
action,
buttonLabel,
bannerColor: "neutral",
message:
"Starting June 1st, 2023, a Pro plan will be required to use team features.",
};
default:
return { action: "stripePortalSession", message: "" };
}

return {
action,
buttonLabel,
bannerColor: "danger",
message: "Upgrade to Pro plan to use team features.",
};
};

export type PaymentBannerProps = {
Expand All @@ -181,10 +175,6 @@ export const PaymentBanner = memo((props: PaymentBannerProps) => {
const account = useFragment(PaymentBannerFragment, props.account);
const { data: { me } = {} } = useQuery(PaymentBannerQuery);

if (!me || account.__typename === "User") {
return null;
}

const {
purchase,
permissions,
Expand All @@ -193,15 +183,14 @@ export const PaymentBanner = memo((props: PaymentBannerProps) => {
pendingCancelAt,
} = account;

const missingPaymentMethod = Boolean(
purchase && !purchase.paymentMethodFilled,
);
// no banner for user account
if (!me || !purchaseStatus) return null;

const { message, buttonLabel, bannerColor, action } = getBannerProps({
purchaseStatus: purchaseStatus ?? null,
const { message, buttonLabel, bannerColor, action } = getTeamBannerProps({
purchaseStatus,
trialDaysRemaining: purchase?.trialDaysRemaining ?? null,
hasGithubPurchase: Boolean(purchase && purchase.source === "github"),
missingPaymentMethod,
missingPaymentMethod: Boolean(purchase && !purchase.paymentMethodFilled),
pendingCancelAt: pendingCancelAt,
});
const userIsOwner = permissions.includes(Permission.Write);
Expand Down

0 comments on commit 379390c

Please sign in to comment.