Initial OSS scaffold from Songs2VID (pre-strip)
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Billing / credit constants for Songs2VID hybrid pricing.
|
||||
* Plan enum in DB: FREE | PREMIUM (Pro = €5/mo "starter_5eur").
|
||||
*/
|
||||
|
||||
export const CREDIT_CURRENCY = "eur";
|
||||
|
||||
/** Free monthly allocation */
|
||||
export const FREE_MONTHLY_CREDITS = 10;
|
||||
|
||||
/** Pro monthly allocation */
|
||||
export const PRO_MONTHLY_CREDITS = 50;
|
||||
|
||||
/** Free-tier top-up: buyer picks 1–15 credits */
|
||||
export const FREE_TOP_UP_MIN = 1;
|
||||
export const FREE_TOP_UP_MAX = 15;
|
||||
|
||||
/** €0.25 per extra credit */
|
||||
export const CREDIT_PRICE_CENTS = 25;
|
||||
|
||||
/** @deprecated use FREE_TOP_UP_MAX */
|
||||
export const FREE_TOP_UP_CREDITS = FREE_TOP_UP_MAX;
|
||||
|
||||
/** @deprecated use creditPurchaseTotalCents(FREE_TOP_UP_MAX) */
|
||||
export const FREE_TOP_UP_PRICE_CENTS = FREE_TOP_UP_MAX * CREDIT_PRICE_CENTS;
|
||||
|
||||
/** Pro subscription €5 / month */
|
||||
export const PRO_PRICE_CENTS = 500;
|
||||
|
||||
/** Soft cap on never-expiring extra credits (Pro / legacy storage bound) */
|
||||
export const EXTRA_CREDITS_MAX = 1000;
|
||||
|
||||
/**
|
||||
* Hard cap on total accumulated credits (monthly remaining + extras).
|
||||
* On renewal, unused + new monthly is trimmed to this threshold.
|
||||
*/
|
||||
export const MAX_CREDIT_CAP = 30;
|
||||
|
||||
/**
|
||||
* Free plan: extras balance cannot exceed 15.
|
||||
* After monthly (10) + extras (≤15) are used, upgrade to Pro is required.
|
||||
*/
|
||||
export const FREE_EXTRA_CREDITS_MAX = FREE_TOP_UP_MAX;
|
||||
|
||||
/** Stripe Tax / Managed Payments SaaS personal use */
|
||||
export const STRIPE_PRODUCT_TAX_CODE = "txcd_10103000";
|
||||
|
||||
export const CREDIT_PURCHASE_MIN = FREE_TOP_UP_MIN;
|
||||
export const CREDIT_PURCHASE_MAX = FREE_TOP_UP_MAX;
|
||||
export const CREDIT_BALANCE_MAX = EXTRA_CREDITS_MAX;
|
||||
|
||||
/** Shown in quota UI errors so UpgradeProLink can attach a CTA */
|
||||
export const PRO_UPGRADE_REQUIRED_SUFFIX = " Upload up to 50 videos with Pro!";
|
||||
|
||||
export function monthlyCreditsForPlan(plan: "FREE" | "PREMIUM"): number {
|
||||
return plan === "PREMIUM" ? PRO_MONTHLY_CREDITS : FREE_MONTHLY_CREDITS;
|
||||
}
|
||||
|
||||
export function freeExtraCreditsCap(): number {
|
||||
return FREE_EXTRA_CREDITS_MAX;
|
||||
}
|
||||
|
||||
export function formatEuroFromCents(cents: number): string {
|
||||
const amount = cents / 100;
|
||||
const formatted = amount % 1 === 0 ? String(amount) : amount.toFixed(2);
|
||||
return `€${formatted}`;
|
||||
}
|
||||
|
||||
export function creditPurchaseTotalCents(credits: number): number {
|
||||
return credits * CREDIT_PRICE_CENTS;
|
||||
}
|
||||
|
||||
export function formatCreditPrice(credits = 1): string {
|
||||
return formatEuroFromCents(creditPurchaseTotalCents(credits));
|
||||
}
|
||||
|
||||
export function validateFreeTopUpAmount(
|
||||
credits: number,
|
||||
currentExtraBalance: number,
|
||||
monthlyRemaining = 0,
|
||||
): { ok: true; credits: number; amountCents: number } | { ok: false; error: string } {
|
||||
if (!Number.isInteger(credits)) {
|
||||
return { ok: false, error: "Credit amount must be a whole number." };
|
||||
}
|
||||
if (credits < FREE_TOP_UP_MIN || credits > FREE_TOP_UP_MAX) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `You can buy between ${FREE_TOP_UP_MIN} and ${FREE_TOP_UP_MAX} credits per top-up.`,
|
||||
};
|
||||
}
|
||||
if (currentExtraBalance >= FREE_EXTRA_CREDITS_MAX) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `Free plan extras are capped at ${FREE_EXTRA_CREDITS_MAX}. Upgrade to Pro for 50 videos every month.${PRO_UPGRADE_REQUIRED_SUFFIX}`,
|
||||
};
|
||||
}
|
||||
const freeExtraRoom = FREE_EXTRA_CREDITS_MAX - currentExtraBalance;
|
||||
const totalRoom = Math.max(0, MAX_CREDIT_CAP - monthlyRemaining - currentExtraBalance);
|
||||
const room = Math.min(freeExtraRoom, totalRoom);
|
||||
if (room <= 0) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `Your total credit balance cannot exceed ${MAX_CREDIT_CAP}. Upgrade to Pro or use existing credits first.${PRO_UPGRADE_REQUIRED_SUFFIX}`,
|
||||
};
|
||||
}
|
||||
if (credits > room) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `You can buy up to ${room} more credits under the ${MAX_CREDIT_CAP}-credit account cap.`,
|
||||
};
|
||||
}
|
||||
return { ok: true, credits, amountCents: creditPurchaseTotalCents(credits) };
|
||||
}
|
||||
Reference in New Issue
Block a user