update subscription migration feature

This commit is contained in:
Zhang Minghan 2023-11-27 11:37:06 +08:00
parent a0392c041a
commit 324cc1b8f1
5 changed files with 79 additions and 46 deletions

View File

@ -1,7 +1,7 @@
import React, {useMemo} from "react";
import React, { useMemo } from "react";
import { buySubscription } from "@/api/addition.ts";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { useToast } from "@/components/ui/use-toast.ts";
import {
Dialog,
@ -21,12 +21,12 @@ import {
import { Badge } from "@/components/ui/badge.tsx";
import { DialogClose } from "@radix-ui/react-dialog";
import { Button } from "@/components/ui/button.tsx";
import { refreshSubscription } from "@/store/subscription.ts";
import { expiredSelector, refreshSubscription } from "@/store/subscription.ts";
import { Plus } from "lucide-react";
import {subscriptionPrize} from "@/conf.ts";
import { subscriptionPrize } from "@/conf.ts";
function countPrize(base: number, month: number): number {
const prize = base * month;
const prize = subscriptionPrize[base] * month;
if (month >= 36) {
return prize * 0.7;
} else if (month >= 12) {
@ -38,6 +38,15 @@ function countPrize(base: number, month: number): number {
return prize;
}
function countUpgradePrize(
level: number,
target: number,
days: number,
): number {
const bias = subscriptionPrize[target] - subscriptionPrize[level];
return (bias / 30) * days;
}
type UpgradeProps = {
base: number;
level: number;
@ -91,6 +100,7 @@ async function callMigrateAction(
export function Upgrade({ base, level }: UpgradeProps) {
const { t } = useTranslation();
const expired = useSelector(expiredSelector);
const [open, setOpen] = React.useState(false);
const [month, setMonth] = React.useState(1);
const dispatch = useDispatch();
@ -99,11 +109,11 @@ export function Upgrade({ base, level }: UpgradeProps) {
const isCurrent = useMemo(() => level === base, [level, base]);
const isUpgrade = useMemo(() => level < base, [level, base]);
return (level === 0 || level === base) ? (
return level === 0 || level === base ? (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className={`action`} variant={`default`}>
{isCurrent ? t("sub.renew") : t("sub.upgrade")}
{isCurrent ? t("sub.renew") : t("sub.subscribe")}
</Button>
</DialogTrigger>
<DialogContent className={`flex-dialog`}>
@ -139,7 +149,7 @@ export function Upgrade({ base, level }: UpgradeProps) {
</SelectContent>
</Select>
<p className={`price`}>
{t("sub.price", { price: countPrize(subscriptionPrize[base], month).toFixed(2) })}
{t("sub.price", { price: countPrize(base, month).toFixed(2) })}
</p>
</div>
<DialogFooter className={`translate-y-1.5`}>
@ -165,7 +175,10 @@ export function Upgrade({ base, level }: UpgradeProps) {
) : (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className={`action`} variant={isUpgrade ? `default` : `outline`}>
<Button
className={`action`}
variant={isUpgrade ? `default` : `outline`}
>
{isUpgrade ? t("sub.upgrade") : t("sub.downgrade")}
</Button>
</DialogTrigger>
@ -173,8 +186,16 @@ export function Upgrade({ base, level }: UpgradeProps) {
<DialogHeader>
<DialogTitle>{t("sub.migrate-plan")}</DialogTitle>
</DialogHeader>
<div className={`pt-2`}>
{t('sub.migrate-plan-desc')}
<div className={`upgrade-wrapper`}>
{t("sub.migrate-plan-desc")}
{isUpgrade && (
<p className={`price`}>
{t("sub.upgrade-price", {
price: countUpgradePrize(level, base, expired).toFixed(2),
})}
</p>
)}
</div>
<DialogFooter className={`translate-y-1.5`}>
<DialogClose asChild>

View File

@ -37,7 +37,7 @@ import { selectAuthenticated } from "@/store/auth.ts";
import SubscriptionUsage from "@/components/home/subscription/SubscriptionUsage.tsx";
import Tips from "@/components/Tips.tsx";
import { subscriptionPrize } from "@/conf.ts";
import {Upgrade} from "@/components/home/subscription/BuyDialog.tsx";
import { Upgrade } from "@/components/home/subscription/BuyDialog.tsx";
function SubscriptionDialog() {
const { t } = useTranslation();

View File

@ -162,14 +162,17 @@ const resources = {
"enterprise-deploy": "支持私有化部署",
"contact-sale": "联系销售",
current: "当前计划",
subscribe: "订阅",
upgrade: "升级",
downgrade: "降级",
renew: "续费",
"cannot-select": "无法选择",
"select-time": "选择订阅时间",
"migrate-plan": "变更订阅计划",
"migrate-plan-desc": "变更订阅后,您的订阅时间将会根据剩余天数价格计算,重新计算订阅时间。(如降级会时间翻倍,升级会时间减半)",
"migrate-plan-desc":
"变更订阅后,您的订阅时间将会根据剩余天数价格计算,重新计算订阅时间。(如降级会时间翻倍,升级会补齐差价)",
price: "价格 {{price}} 元",
"upgrade-price": "升级费用 {{price}} 元 (仅供参考)",
expired: "订阅剩余天数",
time: {
1: "1个月",
@ -517,13 +520,17 @@ const resources = {
"enterprise-deploy": "Support Private Cloud Deployment",
"contact-sale": "Contact Sales",
current: "Current Plan",
subscribe: "Subscribe",
upgrade: "Upgrade",
downgrade: "Downgrade",
renew: "Renew",
"cannot-select": "Cannot Select",
"select-time": "Select Subscription Time",
"migrate-plan": "Migrate Subscription Plan",
"migrate-plan-desc":
"After changing the subscription, your subscription time will be calculated based on the remaining days price, and the subscription time will be recalculated. (For example, downgrading will double the time, and upgrading will make up the difference)",
price: "Price {{price}} CNY",
"upgrade-price": "Upgrade Fee {{price}} CNY (for reference only)",
expired: "Subscription Remaining Days",
time: {
1: "1 Month",
@ -536,7 +543,8 @@ const resources = {
"success-prompt":
"You have successfully subscribed to {{month}} months subscription.",
"migrate-success": "Migrate success",
"migrate-success-prompt": "You have successfully migrated subscription.",
"migrate-success-prompt":
"You have successfully migrated subscription.",
failed: "Subscribe failed",
"failed-prompt":
"Failed to subscribe, please make sure you have enough balance, you will soon jump to deeptrain wallet to pay balance.",
@ -883,13 +891,17 @@ const resources = {
"enterprise-deploy": "Поддержка частной облачной инфраструктуры",
"contact-sale": "Связаться с отделом продаж",
current: "Текущая подписка",
subscribe: "Подписаться",
upgrade: "Обновить",
downgrade: "Понизить",
renew: "Продлить",
"cannot-select": "Невозможно выбрать",
"select-time": "Выберите время подписки",
"migrate-plan": "Перенести подписку",
"migrate-plan-desc":
"После изменения подписки ваше время подписки будет рассчитываться на основе цены оставшихся дней, и время подписки будет пересчитано. (Например, понижение удваивает время, а повышение компенсирует разницу)",
price: "Цена {{price}} CNY",
"upgrade-price": "Плата за обновление {{price}} CNY (для справки)",
expired: "Осталось дней подписки",
time: {
1: "1 месяц",

View File

@ -82,17 +82,35 @@ func (u *User) AddSubscription(db *sql.DB, month int, level int) bool {
return err == nil
}
func (u *User) MigratePlan(db *sql.DB, level int) error {
current := u.GetSubscriptionLevel(db)
if current == 0 || current == level {
func (u *User) DowngradePlan(db *sql.DB, target int) error {
expired, current := u.GetSubscription(db)
if current == 0 || current == target {
return fmt.Errorf("invalid plan level")
}
_, err := db.Exec(`CALL migrate_plan(?, ?)`, u.GetID(db), level)
now := time.Now()
weight := GetLevel(current).Price / GetLevel(target).Price
stamp := float32(expired.Unix()-now.Unix()) * weight
// ceil expired time
expiredAt := now.Add(time.Duration(stamp)*time.Second).AddDate(0, 0, -1)
date := utils.ConvertSqlTime(expiredAt)
_, err := db.Exec("UPDATE subscription SET level = ?, expired_at = ? WHERE user_id = ?", target, date, u.GetID(db))
return err
}
func (u *User) CountUpgradePrice(db *sql.DB, target int) float32 {
expired := u.GetSubscriptionExpiredAt(db)
weight := GetLevel(target).Price - u.GetPlan(db).Price
if weight < 0 {
return 0
}
days := expired.Sub(time.Now()).Hours() / 24
return float32(days) * weight / 30
}
func (u *User) SetSubscriptionLevel(db *sql.DB, level int) bool {
_, err := db.Exec("UPDATE subscription SET level = ? WHERE user_id = ?", level, u.GetID(db))
return err == nil
@ -120,19 +138,25 @@ func BuySubscription(db *sql.DB, cache *redis.Client, user *User, level int, mon
if before == 0 || before == level {
// buy new subscription or renew subscription
money := CountSubscriptionPrize(level, month)
fmt.Println(money)
if user.Pay(cache, money) {
user.AddSubscription(db, month, level)
return true
}
} else {
// upgrade or downgrade subscription
err := user.MigratePlan(db, level)
} else if before > level {
// downgrade subscription
err := user.DowngradePlan(db, level)
if err != nil {
fmt.Println(err)
}
return err == nil
} else {
// upgrade subscription
money := user.CountUpgradePrice(db, level)
if user.Pay(cache, money) {
user.SetSubscriptionLevel(db, level)
return true
}
}
return false

View File

@ -152,30 +152,6 @@ func CreateSubscriptionTable(db *sql.DB) {
if err != nil {
fmt.Println(err)
}
_, err = db.Exec(`
CREATE PROCEDURE migrate_plan(IN id INT, IN target_level INT)
BEGIN
DECLARE current_level INT;
DECLARE expired DATETIME;
SELECT level, expired_at INTO current_level, expired FROM subscription WHERE user_id = id;
SET @current = UNIX_TIMESTAMP();
SET @stamp = UNIX_TIMESTAMP(expired) - @current;
SET @offset = target_level - current_level;
IF @offset > 0 THEN
SET @stamp = @stamp / 2 * @offset;
ELSEIF @offset < 0 THEN
SET @stamp = @stamp * 2 * (-@offset);
END IF;
UPDATE subscription SET level = target_level, expired_at = FROM_UNIXTIME(@current + @stamp) WHERE user_id = id;
END;
`)
if err != nil {
fmt.Println(err)
}
}
func CreateApiKeyTable(db *sql.DB) {