mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 13:00:14 +09:00
update subscription migration feature
This commit is contained in:
parent
a0392c041a
commit
324cc1b8f1
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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 месяц",
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user