Files
sase.tr/packages/shared/src/constants/plans.ts
Semih Yesilyurt 3926e276fd
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
feat(referrals): rework reward engine + email-verified landing
Reward engine:
- Recurring milestones (every 3 → +7d, every 5 → +14d) instead of one-time
  tiers capped at 5; idempotent + transactional grants serialised per
  referrer so concurrent qualifications can't double-count.
- Rewards now gated on the referred user's email verification
  (afterEmailVerification hook); already-verified referees (OAuth) qualify
  at apply time.
- Reward days banked as users.referral_credit_days when the referrer has no
  live subscription, consumed on next trial start / activation (no more
  silently lost rewards).
- Accurate cumulative rewardDays in stats; getMyReferrals returns referee
  name/masked email/status.

Hardening / cleanup:
- onConflictDoNothing makes apply idempotent (no unhandled unique violation).
- Anti-fraud: normalizeEmail blocks self-referral via gmail dot/+tag aliases.
- Collision-safe referral code generation at signup.
- Single apply path (welcome onboarding modal); removed duplicate calls in
  register + subscription pages. Input validation on the apply code.

Email verification UX:
- Verification link now lands on a dedicated /email-verified confirmation
  page instead of the deep-linked VIN/search page.

Schema: referrals.status + qualified_at, users.referral_credit_days (0009).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 13:28:32 +03:00

64 lines
1.7 KiB
TypeScript

export const PLANS = {
SINGLE: {
name: "1 Marka",
brandCount: 1,
priceMonthly: 200_00,
priceYearly: 2000_00,
},
DOUBLE: {
name: "2 Marka",
brandCount: 2,
priceMonthly: 350_00,
priceYearly: 3500_00,
},
TRIPLE: {
name: "3 Marka",
brandCount: 3,
priceMonthly: 500_00,
priceYearly: 5000_00,
},
FULL: {
name: "Full Paket",
brandCount: 0,
priceMonthly: 999_00,
priceYearly: 9990_00,
},
} as const;
/** Sentinel value representing unlimited brand access for display purposes. */
export const FULL_PLAN_BRAND_LIMIT = 999;
/**
* Referrer reward milestones. Each time the referrer's *qualified* referral
* count reaches a multiple of `every`, they earn `days` of subscription time.
* Milestones are independent and recurring — they stack on a shared multiple
* (e.g. count 15 fires both the every-3 and every-5 milestone). A referral
* qualifies only once the referred user verifies their email.
*/
export const REFERRAL_REWARDS = {
MILESTONES: [
{ every: 3, days: 7 },
{ every: 5, days: 14 },
],
} as const;
/** Reward days earned exactly when the qualified count reaches `count`. */
export function referralRewardForCount(count: number): number {
if (count <= 0) return 0;
return REFERRAL_REWARDS.MILESTONES.reduce(
(sum, m) => (count % m.every === 0 ? sum + m.days : sum),
0,
);
}
/** Cumulative reward days earned across all referrals up to `count`. */
export function referralTotalRewardDays(count: number): number {
if (count <= 0) return 0;
return REFERRAL_REWARDS.MILESTONES.reduce(
(sum, m) => sum + Math.floor(count / m.every) * m.days,
0,
);
}
export const CURRENCY = "TRY" as const;