Skip to content

Commit

Permalink
Add max reward limit
Browse files Browse the repository at this point in the history
  • Loading branch information
MauAraujo committed May 1, 2024
1 parent 85f4806 commit eea2a25
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 57 deletions.
16 changes: 4 additions & 12 deletions api/server/handlers/billing/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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")
}
Expand Down
10 changes: 6 additions & 4 deletions api/server/handlers/project/referrals.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 16 additions & 11 deletions dashboard/src/lib/billing/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ const TrialValidator = z.object({
});

export type Plan = z.infer<typeof PlanValidator>;
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<typeof UsageMetricValidator>;
export const UsageMetricValidator = z.object({
Expand Down Expand Up @@ -52,7 +54,10 @@ export const CreditGrantsValidator = z.object({
export const ClientSecretResponse = z.string();

export type ReferralDetails = z.infer<typeof ReferralDetailsValidator>;
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();
47 changes: 24 additions & 23 deletions dashboard/src/main/home/project-settings/ReferralsPage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Text size={16}>Referrals</Text>
<Spacer y={1} />
<Text color="helper">Refer people to Porter to earn credits.</Text>
<Spacer y={1} />
{referralDetails !== null && (
<>
<Text size={16}>Referrals</Text>
<Spacer y={1} />
<Text color="helper">
Refer people to Porter to earn credits.
</Text>
<Spacer y={1} />
{referralDetails !== null && (
<>
<Text>
Your referral link is {" "}
</Text>
<Link to={baseUrl + "/register?referral=" + referralDetails.code}>{baseUrl + "/register?referral=" + referralDetails.code}</Link>
<Spacer y={1} />
<Text>You have referred {referralDetails.referral_count} users</Text>
</>

)}
<Spacer y={1} />
<Text>Your referral link is </Text>
<Link to={baseUrl + "/register?referral=" + referralDetails.code}>
{baseUrl + "/register?referral=" + referralDetails.code}
</Link>
<Spacer y={1} />
<Text>
You have referred {referralDetails.referral_count}/
{referralDetails.max_allowed_referrals} users
</Text>
</>
)
)}
<Spacer y={1} />
</>
);
}

export default ReferralsPage;
29 changes: 22 additions & 7 deletions internal/billing/metronome.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import (
)

const (
metronomeBaseUrl = "https://api.metronome.com/v1/"
defaultCollectionMethod = "charge_automatically"
defaultMaxRetries = 10
porterStandardTrialDays = 15
metronomeBaseUrl = "https://api.metronome.com/v1/"
defaultCollectionMethod = "charge_automatically"
defaultMaxRetries = 10
porterStandardTrialDays = 15
defaultRewardAmountCents = 1000
defaultPaidAmountCents = 0
maxReferralRewards = 10
)

// MetronomeClient is the client used to call the Metronome API
Expand All @@ -28,6 +31,15 @@ type MetronomeClient struct {
billableMetrics []types.BillableMetric
PorterCloudPlanID uuid.UUID
PorterStandardPlanID uuid.UUID

// DefaultRewardAmountCents is the default amount in USD cents rewarded to users
// who successfully refer a new user
DefaultRewardAmountCents float64
// DefaultPaidAmountCents is the amount paid by the user to get the credits
// grant, if set to 0 it means they are free
DefaultPaidAmountCents float64
// MaxReferralRewards is the maximum number of referral rewards a user can receive
MaxReferralRewards int64
}

// NewMetronomeClient returns a new Metronome client
Expand All @@ -43,9 +55,12 @@ func NewMetronomeClient(metronomeApiKey string, porterCloudPlanID string, porter
}

return MetronomeClient{
ApiKey: metronomeApiKey,
PorterCloudPlanID: porterCloudPlanUUID,
PorterStandardPlanID: porterStandardPlanUUID,
ApiKey: metronomeApiKey,
PorterCloudPlanID: porterCloudPlanUUID,
PorterStandardPlanID: porterStandardPlanUUID,
DefaultRewardAmountCents: defaultRewardAmountCents,
DefaultPaidAmountCents: defaultPaidAmountCents,
MaxReferralRewards: maxReferralRewards,
}, nil
}

Expand Down

0 comments on commit eea2a25

Please sign in to comment.