@@ -22,6 +22,10 @@ type BillingPreferenceRequest struct {
|
||||
BillingPreference string `json:"billing_preference"`
|
||||
}
|
||||
|
||||
type SubscriptionBalancePayRequest struct {
|
||||
PlanId int `json:"plan_id"`
|
||||
}
|
||||
|
||||
// ---- User APIs ----
|
||||
|
||||
func GetSubscriptionPlans(c *gin.Context) {
|
||||
@@ -92,6 +96,25 @@ func UpdateSubscriptionPreference(c *gin.Context) {
|
||||
common.ApiSuccess(c, gin.H{"billing_preference": pref})
|
||||
}
|
||||
|
||||
func SubscriptionRequestBalancePay(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
var req SubscriptionBalancePayRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
|
||||
common.ApiErrorMsg(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := model.PurchaseSubscriptionWithBalance(userId, req.PlanId); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
common.ApiSuccess(c, nil)
|
||||
}
|
||||
|
||||
// ---- Admin APIs ----
|
||||
|
||||
func AdminListSubscriptionPlans(c *gin.Context) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/pkg/cachex"
|
||||
"github.com/samber/hot"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -665,6 +666,106 @@ func AdminBindSubscription(userId int, planId int, sourceNote string) (string, e
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func calcSubscriptionBalanceQuota(priceAmount float64) (int, error) {
|
||||
if priceAmount <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if common.QuotaPerUnit <= 0 {
|
||||
return 0, errors.New("额度单位配置错误")
|
||||
}
|
||||
quota := decimal.NewFromFloat(priceAmount).
|
||||
Mul(decimal.NewFromFloat(common.QuotaPerUnit)).
|
||||
Ceil().
|
||||
IntPart()
|
||||
return int(quota), nil
|
||||
}
|
||||
|
||||
// PurchaseSubscriptionWithBalance creates a subscription by deducting the user's wallet quota.
|
||||
func PurchaseSubscriptionWithBalance(userId int, planId int) error {
|
||||
if userId <= 0 || planId <= 0 {
|
||||
return errors.New("invalid userId or planId")
|
||||
}
|
||||
|
||||
var logPlanTitle string
|
||||
var logMoney float64
|
||||
var chargedQuota int
|
||||
var upgradeGroup string
|
||||
err := DB.Transaction(func(tx *gorm.DB) error {
|
||||
plan, err := getSubscriptionPlanByIdTx(tx, planId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !plan.Enabled {
|
||||
return errors.New("套餐未启用")
|
||||
}
|
||||
if plan.PriceAmount < 0 {
|
||||
return errors.New("套餐价格不能为负数")
|
||||
}
|
||||
|
||||
requiredQuota, err := calcSubscriptionBalanceQuota(plan.PriceAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var user User
|
||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").Where("id = ?", userId).First(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if requiredQuota > 0 && user.Quota < requiredQuota {
|
||||
return errors.New("余额不足")
|
||||
}
|
||||
if requiredQuota > 0 {
|
||||
if err := tx.Model(&User{}).Where("id = ?", userId).
|
||||
Update("quota", gorm.Expr("quota - ?", requiredQuota)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := CreateUserSubscriptionFromPlanTx(tx, userId, plan, PaymentMethodBalance); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := common.GetTimestamp()
|
||||
tradeNo := fmt.Sprintf("SUBBALUSR%dNO%s%d", userId, common.GetRandomString(6), time.Now().UnixNano())
|
||||
order := &SubscriptionOrder{
|
||||
UserId: userId,
|
||||
PlanId: plan.Id,
|
||||
Money: plan.PriceAmount,
|
||||
TradeNo: tradeNo,
|
||||
PaymentMethod: PaymentMethodBalance,
|
||||
PaymentProvider: PaymentProviderBalance,
|
||||
Status: common.TopUpStatusSuccess,
|
||||
CreateTime: now,
|
||||
CompleteTime: now,
|
||||
ProviderPayload: fmt.Sprintf("charged_quota=%d", requiredQuota),
|
||||
}
|
||||
if err := tx.Create(order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logPlanTitle = plan.Title
|
||||
logMoney = plan.PriceAmount
|
||||
chargedQuota = requiredQuota
|
||||
upgradeGroup = strings.TrimSpace(plan.UpgradeGroup)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chargedQuota > 0 {
|
||||
if err := cacheDecrUserQuota(userId, int64(chargedQuota)); err != nil {
|
||||
common.SysLog("failed to decrease user quota cache after subscription balance purchase: " + err.Error())
|
||||
}
|
||||
}
|
||||
if upgradeGroup != "" {
|
||||
_ = UpdateUserGroupCache(userId, upgradeGroup)
|
||||
}
|
||||
msg := fmt.Sprintf("使用余额购买订阅成功,套餐: %s,支付金额: %.2f,扣除额度: %d", logPlanTitle, logMoney, chargedQuota)
|
||||
RecordLog(userId, LogTypeTopup, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllActiveUserSubscriptions returns all active subscriptions for a user.
|
||||
func GetAllActiveUserSubscriptions(userId int) ([]SubscriptionSummary, error) {
|
||||
if userId <= 0 {
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
PaymentMethodCreem = "creem"
|
||||
PaymentMethodWaffo = "waffo"
|
||||
PaymentMethodWaffoPancake = "waffo_pancake"
|
||||
PaymentMethodBalance = "balance"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,6 +38,7 @@ const (
|
||||
PaymentProviderCreem = "creem"
|
||||
PaymentProviderWaffo = "waffo"
|
||||
PaymentProviderWaffoPancake = "waffo_pancake"
|
||||
PaymentProviderBalance = "balance"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -153,6 +153,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
subscriptionRoute.GET("/plans", controller.GetSubscriptionPlans)
|
||||
subscriptionRoute.GET("/self", controller.GetSubscriptionSelf)
|
||||
subscriptionRoute.PUT("/self/preference", controller.UpdateSubscriptionPreference)
|
||||
subscriptionRoute.POST("/balance/pay", middleware.CriticalRateLimit(), controller.SubscriptionRequestBalancePay)
|
||||
subscriptionRoute.POST("/epay/pay", middleware.CriticalRateLimit(), controller.SubscriptionRequestEpay)
|
||||
subscriptionRoute.POST("/stripe/pay", middleware.CriticalRateLimit(), controller.SubscriptionRequestStripePay)
|
||||
subscriptionRoute.POST("/creem/pay", middleware.CriticalRateLimit(), controller.SubscriptionRequestCreemPay)
|
||||
|
||||
@@ -129,6 +129,13 @@ export async function paySubscriptionWaffoPancake(
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function paySubscriptionBalance(
|
||||
data: SubscriptionPayRequest
|
||||
): Promise<SubscriptionPayResponse> {
|
||||
const res = await api.post('/api/subscription/balance/pay', data)
|
||||
return res.data
|
||||
}
|
||||
|
||||
// Mints a Pancake OnetimeProduct (see controller for the OnetimeProduct vs
|
||||
// SubscriptionProduct rationale) using persisted creds + StoreID.
|
||||
export async function createWaffoPancakeSubscriptionProduct(data: {
|
||||
|
||||
+62
-9
@@ -20,7 +20,9 @@ import { useState, useEffect } from 'react'
|
||||
import { Crown, CalendarClock, Package } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { DEFAULT_CURRENCY_CONFIG } from '@/stores/system-config-store'
|
||||
import { formatQuota } from '@/lib/format'
|
||||
import { useSystemConfig } from '@/hooks/use-system-config'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
paySubscriptionCreem,
|
||||
paySubscriptionEpay,
|
||||
paySubscriptionWaffoPancake,
|
||||
paySubscriptionBalance,
|
||||
} from '../../api'
|
||||
import { formatDuration, formatResetPeriod } from '../../lib'
|
||||
import type { PlanRecord } from '../../types'
|
||||
@@ -64,10 +67,13 @@ interface Props {
|
||||
epayMethods?: PaymentMethod[]
|
||||
purchaseLimit?: number
|
||||
purchaseCount?: number
|
||||
userQuota?: number
|
||||
onPurchaseSuccess?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
export function SubscriptionPurchaseDialog(props: Props) {
|
||||
const { t } = useTranslation()
|
||||
const { currency } = useSystemConfig()
|
||||
const [paying, setPaying] = useState(false)
|
||||
const [selectedEpayMethod, setSelectedEpayMethod] = useState('')
|
||||
|
||||
@@ -96,6 +102,16 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
t('Select payment method')
|
||||
const totalAmount = Number(plan.total_amount || 0)
|
||||
const price = Number(plan.price_amount || 0).toFixed(2)
|
||||
const quotaPerUnit =
|
||||
currency?.quotaPerUnit && currency.quotaPerUnit > 0
|
||||
? currency.quotaPerUnit
|
||||
: DEFAULT_CURRENCY_CONFIG.quotaPerUnit
|
||||
const balanceCost = Math.max(
|
||||
0,
|
||||
Math.ceil(Number(plan.price_amount || 0) * quotaPerUnit)
|
||||
)
|
||||
const userQuota = Math.max(0, Number(props.userQuota || 0))
|
||||
const insufficientBalance = userQuota < balanceCost
|
||||
const limitReached =
|
||||
(props.purchaseLimit || 0) > 0 &&
|
||||
(props.purchaseCount || 0) >= (props.purchaseLimit || 0)
|
||||
@@ -215,6 +231,28 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const handlePayBalance = async () => {
|
||||
setPaying(true)
|
||||
try {
|
||||
const res = await paySubscriptionBalance({ plan_id: plan.id })
|
||||
if (res.success) {
|
||||
toast.success(t('Subscription purchased successfully'))
|
||||
void props.onPurchaseSuccess?.()
|
||||
props.onOpenChange(false)
|
||||
} else {
|
||||
toast.error(
|
||||
res.message && res.message !== 'success'
|
||||
? res.message
|
||||
: t('Payment request failed')
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
toast.error(t('Payment request failed'))
|
||||
} finally {
|
||||
setPaying(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<DialogContent className='max-sm:w-[calc(100vw-1.5rem)] sm:max-w-md'>
|
||||
@@ -285,7 +323,30 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasAnyPayment ? (
|
||||
<div className='flex flex-col gap-2 rounded-md border p-3'>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Required')}</span>
|
||||
<span>{formatQuota(balanceCost)}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Available')}</span>
|
||||
<span>{formatQuota(userQuota)}</span>
|
||||
</div>
|
||||
{insufficientBalance && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>{t('Insufficient balance')}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handlePayBalance}
|
||||
disabled={paying || limitReached || insufficientBalance}
|
||||
>
|
||||
{t('Pay with Balance')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{hasAnyPayment && (
|
||||
<div className='space-y-3'>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Select payment method')}
|
||||
@@ -361,14 +422,6 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
'Online payment is not enabled. Please contact the administrator.'
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -62,6 +62,8 @@ import type { PaymentMethod, TopupInfo } from '../types'
|
||||
interface SubscriptionPlansCardProps {
|
||||
topupInfo: TopupInfo | null
|
||||
onAvailabilityChange?: (available: boolean) => void
|
||||
userQuota?: number
|
||||
onPurchaseSuccess?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
function getEpayMethods(payMethods: PaymentMethod[] = []): PaymentMethod[] {
|
||||
@@ -91,6 +93,8 @@ function getBillingPreferenceLabel(
|
||||
export function SubscriptionPlansCard({
|
||||
topupInfo,
|
||||
onAvailabilityChange,
|
||||
userQuota,
|
||||
onPurchaseSuccess,
|
||||
}: SubscriptionPlansCardProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -633,6 +637,8 @@ export function SubscriptionPlansCard({
|
||||
enableWaffoPancake={enableWaffoPancake}
|
||||
enableOnlineTopUp={enableOnlineTopUp}
|
||||
epayMethods={epayMethods}
|
||||
userQuota={userQuota}
|
||||
onPurchaseSuccess={onPurchaseSuccess}
|
||||
purchaseLimit={
|
||||
selectedPlan?.plan?.max_purchase_per_user
|
||||
? Number(selectedPlan.plan.max_purchase_per_user)
|
||||
|
||||
+2
@@ -309,6 +309,8 @@ export function Wallet(props: WalletProps) {
|
||||
<SubscriptionPlansCard
|
||||
topupInfo={topupInfo}
|
||||
onAvailabilityChange={handleSubscriptionAvailabilityChange}
|
||||
userQuota={user?.quota}
|
||||
onPurchaseSuccess={fetchUser}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Vendored
+3
@@ -2035,6 +2035,7 @@
|
||||
"Inspect requests, errors, and billing details": "Inspect requests, errors, and billing details",
|
||||
"Inspect user prompts": "Inspect user prompts",
|
||||
"Instance": "Instance",
|
||||
"Insufficient balance": "Insufficient balance",
|
||||
"Integrations": "Integrations",
|
||||
"Inter-group overrides": "Inter-group overrides",
|
||||
"Inter-group ratio overrides": "Inter-group ratio overrides",
|
||||
@@ -2852,6 +2853,7 @@
|
||||
"Path:": "Path:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Pay-as-you-go with real-time usage monitoring",
|
||||
"Pay with Balance": "Pay with Balance",
|
||||
"Payment": "Payment",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.",
|
||||
"Payment Channel": "Payment Channel",
|
||||
@@ -3763,6 +3765,7 @@
|
||||
"Subscription First": "Subscription First",
|
||||
"Subscription Management": "Subscription Management",
|
||||
"Subscription Only": "Subscription Only",
|
||||
"Subscription purchased successfully": "Subscription purchased successfully",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Subscription Plans": "Subscription Plans",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).",
|
||||
|
||||
Vendored
+3
@@ -2035,6 +2035,7 @@
|
||||
"Inspect requests, errors, and billing details": "Inspecter les requêtes, les erreurs et les détails de facturation",
|
||||
"Inspect user prompts": "Inspecter les invites utilisateur",
|
||||
"Instance": "Instance",
|
||||
"Insufficient balance": "Solde insuffisant",
|
||||
"Integrations": "Intégrations",
|
||||
"Inter-group overrides": "Dérogations inter-groupes",
|
||||
"Inter-group ratio overrides": "Dérogations de ratio inter-groupes",
|
||||
@@ -2852,6 +2853,7 @@
|
||||
"Path:": "Chemin :",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Paiement à l'usage avec suivi de la consommation en temps réel",
|
||||
"Pay with Balance": "Payer avec le solde",
|
||||
"Payment": "Paiement",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Mode agrégateur de paiement — embarquez avec votre propre société enregistrée (entité offshore). Conçu pour les entreprises.",
|
||||
"Payment Channel": "Canal de paiement",
|
||||
@@ -3763,6 +3765,7 @@
|
||||
"Subscription First": "Abonnement en priorité",
|
||||
"Subscription Management": "Gestion des abonnements",
|
||||
"Subscription Only": "Abonnement uniquement",
|
||||
"Subscription purchased successfully": "Abonnement acheté avec succès",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "La création et la modification des forfaits d’abonnement sont verrouillées jusqu’à ce que l’administrateur confirme les conditions de conformité dans les paramètres de la passerelle de paiement.",
|
||||
"Subscription Plans": "Plans d'abonnement",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Les forfaits d’abonnement n’utilisent PAS le produit associé : chaque forfait dispose de son propre produit Pancake dédié, défini dans l’administration des abonnements (ou créé automatiquement via le bouton « + Créer »).",
|
||||
|
||||
Vendored
+3
@@ -2035,6 +2035,7 @@
|
||||
"Inspect requests, errors, and billing details": "リクエスト、エラー、請求詳細を確認",
|
||||
"Inspect user prompts": "ユーザープロンプトの検査",
|
||||
"Instance": "インスタンス",
|
||||
"Insufficient balance": "残高が不足しています",
|
||||
"Integrations": "統合",
|
||||
"Inter-group overrides": "グループ間上書き",
|
||||
"Inter-group ratio overrides": "グループ間比率上書き",
|
||||
@@ -2852,6 +2853,7 @@
|
||||
"Path:": "パス:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "リアルタイム使用量監視付き従量課金制",
|
||||
"Pay with Balance": "残高で支払う",
|
||||
"Payment": "支払い",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "決済アグリゲーターモード — 自社の登録済み法人(オフショア法人)でオンボーディングします。エンタープライズ向けです。",
|
||||
"Payment Channel": "決済チャネル",
|
||||
@@ -3763,6 +3765,7 @@
|
||||
"Subscription First": "サブスクリプション優先",
|
||||
"Subscription Management": "サブスクリプション管理",
|
||||
"Subscription Only": "サブスクリプションのみ",
|
||||
"Subscription purchased successfully": "サブスクリプションを購入しました",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "管理者が支払いゲートウェイ設定でコンプライアンス条件を確認するまで、サブスクリプションプランの作成と変更はロックされます。",
|
||||
"Subscription Plans": "サブスクリプションプラン",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "サブスクリプションプランは紐付け済み商品を使用しません。各プランには専用の Pancake 商品があり、サブスクリプション管理画面で設定します(または「+ 作成」ボタンで自動作成します)。",
|
||||
|
||||
Vendored
+3
@@ -2035,6 +2035,7 @@
|
||||
"Inspect requests, errors, and billing details": "Проверяйте запросы, ошибки и детали оплаты",
|
||||
"Inspect user prompts": "Просмотр запросов пользователя",
|
||||
"Instance": "Экземпляр",
|
||||
"Insufficient balance": "Недостаточно средств",
|
||||
"Integrations": "Интеграции",
|
||||
"Inter-group overrides": "Переопределения между группами",
|
||||
"Inter-group ratio overrides": "Переопределения соотношений между группами",
|
||||
@@ -2852,6 +2853,7 @@
|
||||
"Path:": "Путь:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Оплата по мере использования с мониторингом в реальном времени",
|
||||
"Pay with Balance": "Оплатить балансом",
|
||||
"Payment": "Платеж",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Режим платежного агрегатора — подключение через вашу зарегистрированную компанию (офшорное юрлицо). Создано для Enterprise.",
|
||||
"Payment Channel": "Платёжный канал",
|
||||
@@ -3763,6 +3765,7 @@
|
||||
"Subscription First": "Подписка в приоритете",
|
||||
"Subscription Management": "Управление подписками",
|
||||
"Subscription Only": "Только подписка",
|
||||
"Subscription purchased successfully": "Подписка успешно приобретена",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Создание и изменение планов подписки заблокированы, пока администратор не подтвердит условия соответствия в настройках платежного шлюза.",
|
||||
"Subscription Plans": "Планы подписки",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Планы подписки НЕ используют привязанный продукт — у каждого плана есть собственный продукт Pancake, задаваемый в администрировании подписок (или автоматически создаваемый кнопкой «+ Создать»).",
|
||||
|
||||
Vendored
+3
@@ -2035,6 +2035,7 @@
|
||||
"Inspect requests, errors, and billing details": "Kiểm tra yêu cầu, lỗi và chi tiết thanh toán",
|
||||
"Inspect user prompts": "Kiểm tra lời nhắc của người dùng",
|
||||
"Instance": "Phiên bản",
|
||||
"Insufficient balance": "Số dư không đủ",
|
||||
"Integrations": "Tích hợp",
|
||||
"Inter-group overrides": "Ghi đè liên nhóm",
|
||||
"Inter-group ratio overrides": "Tỷ lệ liên nhóm ghi đè",
|
||||
@@ -2852,6 +2853,7 @@
|
||||
"Path:": "Đường dẫn:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Thanh toán theo mức sử dụng với theo dõi mức sử dụng theo thời gian thực",
|
||||
"Pay with Balance": "Thanh toán bằng số dư",
|
||||
"Payment": "Thanh toán",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Chế độ tổng hợp thanh toán — đăng ký bằng công ty đã đăng ký của bạn (pháp nhân offshore). Dành cho doanh nghiệp.",
|
||||
"Payment Channel": "Kênh thanh toán",
|
||||
@@ -3763,6 +3765,7 @@
|
||||
"Subscription First": "Ưu tiên đăng ký",
|
||||
"Subscription Management": "Quản lý đăng ký",
|
||||
"Subscription Only": "Chỉ đăng ký",
|
||||
"Subscription purchased successfully": "Đã mua gói đăng ký thành công",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Việc tạo và thay đổi gói đăng ký bị khóa cho đến khi quản trị viên xác nhận điều khoản tuân thủ trong cài đặt Cổng thanh toán.",
|
||||
"Subscription Plans": "Gói đăng ký",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Gói đăng ký KHÔNG dùng Sản phẩm đã liên kết — mỗi gói có một sản phẩm Pancake riêng, được đặt trong quản trị Đăng ký (hoặc tự động tạo bằng nút \"+ Create\" tại đó).",
|
||||
|
||||
Vendored
+3
@@ -2035,6 +2035,7 @@
|
||||
"Inspect requests, errors, and billing details": "查看请求、错误和计费详情",
|
||||
"Inspect user prompts": "检查用户提示",
|
||||
"Instance": "实例",
|
||||
"Insufficient balance": "余额不足",
|
||||
"Integrations": "集成",
|
||||
"Inter-group overrides": "分组间覆盖",
|
||||
"Inter-group ratio overrides": "分组间比例覆盖",
|
||||
@@ -2852,6 +2853,7 @@
|
||||
"Path:": "路径:",
|
||||
"Pay": "支付",
|
||||
"Pay-as-you-go with real-time usage monitoring": "按量付费,实时监控使用情况",
|
||||
"Pay with Balance": "使用余额支付",
|
||||
"Payment": "支付",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "支付聚合模式:使用你自己的注册公司(离岸实体)入驻。面向企业场景构建。",
|
||||
"Payment Channel": "支付渠道",
|
||||
@@ -3763,6 +3765,7 @@
|
||||
"Subscription First": "优先订阅",
|
||||
"Subscription Management": "订阅管理",
|
||||
"Subscription Only": "仅用订阅",
|
||||
"Subscription purchased successfully": "订阅购买成功",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "管理员在支付网关设置中确认合规条款之前,订阅套餐的创建和修改会被锁定。",
|
||||
"Subscription Plans": "订阅套餐",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "订阅套餐不会使用已绑定的产品。每个套餐都有独立的 Pancake 产品,可在订阅管理中设置,或通过其中的“+ 创建”按钮自动生成。",
|
||||
|
||||
Reference in New Issue
Block a user