From 85f48060837f7fc5121f276db6a0f5c6cbe4da54 Mon Sep 17 00:00:00 2001 From: Mauricio Araujo Date: Tue, 30 Apr 2024 21:58:40 -0400 Subject: [PATCH 1/2] Add max reward limit and fix expired plan bug --- api/server/handlers/billing/create.go | 11 +++++++++++ .../main/home/project-settings/BillingPage.tsx | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api/server/handlers/billing/create.go b/api/server/handlers/billing/create.go index d44276a3c3..bc4e1d0f1d 100644 --- a/api/server/handlers/billing/create.go +++ b/api/server/handlers/billing/create.go @@ -24,6 +24,8 @@ const ( // defaultPaidAmountCents is the amount paid by the user to get the credits // grant, if set to 0 it means they are free defaultPaidAmountCents = 0 + // maxReferralRewards is the maximum number of referral rewards a user can receive + maxReferralRewards = 10 ) // CreateBillingHandler is a handler for creating payment methods @@ -140,6 +142,15 @@ func (c *CreateBillingHandler) grantRewardIfReferral(ctx context.Context, referr return nil } + referralCount, err := c.Repo().Referral().CountReferralsByProjectID(referral.ProjectID, models.ReferralStatusCompleted) + if err != nil { + return telemetry.Error(ctx, span, err, "failed to get referral count by referrer id") + } + + if referralCount >= maxReferralRewards { + return nil + } + referrerProject, err := c.Repo().Project().ReadProject(referral.ProjectID) if err != nil { return telemetry.Error(ctx, span, err, "failed to find referrer project") diff --git a/dashboard/src/main/home/project-settings/BillingPage.tsx b/dashboard/src/main/home/project-settings/BillingPage.tsx index 666090a1f2..dece428ff0 100644 --- a/dashboard/src/main/home/project-settings/BillingPage.tsx +++ b/dashboard/src/main/home/project-settings/BillingPage.tsx @@ -50,6 +50,19 @@ function BillingPage(): JSX.Element { const { usage } = useCustomerUsage("day", true); + const trialEnding = (starting_on: string, ending_before: string,): string => { + if (ending_before === undefined) { + return ""; + } + + const diff = dayjs(ending_before).diff(dayjs()); + if (diff <= 0) { + return `Started on ${readableDate(starting_on)}` + } + + return `Free trial ends ${dayjs().to(dayjs(ending_before))}` + } + const processedData = useMemo(() => { const before = usage; const resultMap = new Map(); @@ -235,8 +248,7 @@ function BillingPage(): JSX.Element { {plan.trial_info !== undefined && plan.trial_info.ending_before !== "" ? ( - Free trial ends{" "} - {dayjs().to(dayjs(plan.trial_info.ending_before))} + {trialEnding(plan.starting_on, plan.trial_info.ending_before)} ) : ( Started on {readableDate(plan.starting_on)} From eea2a25aadd52dd3a2d5c264176c2b063da4c4dc Mon Sep 17 00:00:00 2001 From: Mauricio Araujo Date: Wed, 1 May 2024 09:50:18 -0400 Subject: [PATCH 2/2] Add max reward limit --- api/server/handlers/billing/create.go | 16 ++----- api/server/handlers/project/referrals.go | 10 ++-- dashboard/src/lib/billing/types.tsx | 27 ++++++----- .../home/project-settings/ReferralsPage.tsx | 47 ++++++++++--------- internal/billing/metronome.go | 29 +++++++++--- 5 files changed, 72 insertions(+), 57 deletions(-) diff --git a/api/server/handlers/billing/create.go b/api/server/handlers/billing/create.go index bc4e1d0f1d..881d8856d8 100644 --- a/api/server/handlers/billing/create.go +++ b/api/server/handlers/billing/create.go @@ -17,17 +17,6 @@ import ( "github.com/porter-dev/porter/internal/telemetry" ) -const ( - // defaultRewardAmountCents is the default amount in USD cents rewarded to users - // who successfully refer a new user - defaultRewardAmountCents = 1000 - // defaultPaidAmountCents is the amount paid by the user to get the credits - // grant, if set to 0 it means they are free - defaultPaidAmountCents = 0 - // maxReferralRewards is the maximum number of referral rewards a user can receive - maxReferralRewards = 10 -) - // CreateBillingHandler is a handler for creating payment methods type CreateBillingHandler struct { handlers.PorterHandlerWriter @@ -147,6 +136,7 @@ func (c *CreateBillingHandler) grantRewardIfReferral(ctx context.Context, referr return telemetry.Error(ctx, span, err, "failed to get referral count by referrer id") } + maxReferralRewards := c.Config().BillingManager.MetronomeClient.MaxReferralRewards if referralCount >= maxReferralRewards { return nil } @@ -161,7 +151,9 @@ func (c *CreateBillingHandler) grantRewardIfReferral(ctx context.Context, referr // practice will mean the credits will most likely run out before expiring expiresAt := time.Now().AddDate(5, 0, 0).Format(time.RFC3339) reason := "Referral reward" - err := c.Config().BillingManager.MetronomeClient.CreateCreditsGrant(ctx, referrerProject.UsageID, reason, defaultRewardAmountCents, defaultPaidAmountCents, expiresAt) + rewardAmount := c.Config().BillingManager.MetronomeClient.DefaultRewardAmountCents + paidAmount := c.Config().BillingManager.MetronomeClient.DefaultPaidAmountCents + err := c.Config().BillingManager.MetronomeClient.CreateCreditsGrant(ctx, referrerProject.UsageID, reason, rewardAmount, paidAmount, expiresAt) if err != nil { return telemetry.Error(ctx, span, err, "failed to grand credits reward") } diff --git a/api/server/handlers/project/referrals.go b/api/server/handlers/project/referrals.go index c8d3030f72..80669a2f95 100644 --- a/api/server/handlers/project/referrals.go +++ b/api/server/handlers/project/referrals.go @@ -68,11 +68,13 @@ func (c *GetProjectReferralDetailsHandler) ServeHTTP(w http.ResponseWriter, r *h } referralCodeResponse := struct { - Code string `json:"code"` - ReferralCount int64 `json:"referral_count"` + Code string `json:"code"` + ReferralCount int64 `json:"referral_count"` + MaxAllowedRewards int64 `json:"max_allowed_referrals"` }{ - Code: proj.ReferralCode, - ReferralCount: referralCount, + Code: proj.ReferralCode, + ReferralCount: referralCount, + MaxAllowedRewards: c.Config().BillingManager.MetronomeClient.MaxReferralRewards, } c.WriteResult(w, r, referralCodeResponse) diff --git a/dashboard/src/lib/billing/types.tsx b/dashboard/src/lib/billing/types.tsx index 9a64261975..5228fc835a 100644 --- a/dashboard/src/lib/billing/types.tsx +++ b/dashboard/src/lib/billing/types.tsx @@ -17,13 +17,15 @@ const TrialValidator = z.object({ }); export type Plan = z.infer; -export const PlanValidator = z.object({ - id: z.string(), - plan_name: z.string(), - plan_description: z.string(), - starting_on: z.string(), - trial_info: TrialValidator, -}).nullable(); +export const PlanValidator = z + .object({ + id: z.string(), + plan_name: z.string(), + plan_description: z.string(), + starting_on: z.string(), + trial_info: TrialValidator, + }) + .nullable(); export type UsageMetric = z.infer; export const UsageMetricValidator = z.object({ @@ -52,7 +54,10 @@ export const CreditGrantsValidator = z.object({ export const ClientSecretResponse = z.string(); export type ReferralDetails = z.infer; -export const ReferralDetailsValidator = z.object({ - code: z.string(), - referral_count: z.number(), -}).nullable(); +export const ReferralDetailsValidator = z + .object({ + code: z.string(), + referral_count: z.number(), + max_allowed_referrals: z.number(), + }) + .nullable(); diff --git a/dashboard/src/main/home/project-settings/ReferralsPage.tsx b/dashboard/src/main/home/project-settings/ReferralsPage.tsx index 226ca0b238..2f2a19cef9 100644 --- a/dashboard/src/main/home/project-settings/ReferralsPage.tsx +++ b/dashboard/src/main/home/project-settings/ReferralsPage.tsx @@ -1,35 +1,36 @@ import React from "react"; + +import Link from "components/porter/Link"; import Spacer from "components/porter/Spacer"; import Text from "components/porter/Text"; import { useReferralDetails } from "lib/hooks/useStripe"; -import Link from "components/porter/Link"; function ReferralsPage(): JSX.Element { - const { referralDetails } = useReferralDetails(); - const baseUrl = window.location.origin; + const { referralDetails } = useReferralDetails(); + const baseUrl = window.location.origin; - return ( + return ( + <> + Referrals + + Refer people to Porter to earn credits. + + {referralDetails !== null && ( <> - Referrals - - - Refer people to Porter to earn credits. - - - {referralDetails !== null && ( - <> - - Your referral link is {" "} - -