diff --git a/app/src/components/home/subscription/BuyDialog.tsx b/app/src/components/home/subscription/BuyDialog.tsx index 168230d..ef1d181 100644 --- a/app/src/components/home/subscription/BuyDialog.tsx +++ b/app/src/components/home/subscription/BuyDialog.tsx @@ -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 ? ( @@ -139,7 +149,7 @@ export function Upgrade({ base, level }: UpgradeProps) {

- {t("sub.price", { price: countPrize(subscriptionPrize[base], month).toFixed(2) })} + {t("sub.price", { price: countPrize(base, month).toFixed(2) })}

@@ -165,7 +175,10 @@ export function Upgrade({ base, level }: UpgradeProps) { ) : ( - @@ -173,8 +186,16 @@ export function Upgrade({ base, level }: UpgradeProps) { {t("sub.migrate-plan")} -
- {t('sub.migrate-plan-desc')} +
+ {t("sub.migrate-plan-desc")} + + {isUpgrade && ( +

+ {t("sub.upgrade-price", { + price: countUpgradePrize(level, base, expired).toFixed(2), + })} +

+ )}
diff --git a/app/src/dialogs/SubscriptionDialog.tsx b/app/src/dialogs/SubscriptionDialog.tsx index ab7f558..3aefd68 100644 --- a/app/src/dialogs/SubscriptionDialog.tsx +++ b/app/src/dialogs/SubscriptionDialog.tsx @@ -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(); diff --git a/app/src/i18n.ts b/app/src/i18n.ts index 7a58c09..9b007df 100644 --- a/app/src/i18n.ts +++ b/app/src/i18n.ts @@ -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 месяц", diff --git a/auth/subscription.go b/auth/subscription.go index bf7c987..52e63fc 100644 --- a/auth/subscription.go +++ b/auth/subscription.go @@ -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 diff --git a/connection/database.go b/connection/database.go index 0999259..863377c 100644 --- a/connection/database.go +++ b/connection/database.go @@ -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) {