Commit 63f539b3 authored by erio's avatar erio
Browse files

fix: merge general improvements from release branch

Backend:
- gateway_handler: pass subject.UserID instead of int64(0) for user-level routing
- setting_handler: add missing BalanceLowNotifyRechargeURL to UpdateSettings response
- openai_gateway_service: use applyAccountStatsCost for account stats pricing integration
- embed_on: add local file override (data/public/) for embedded frontend assets

Frontend:
- useTableSelection: add batchUpdate method for batch operations
- AccountsView: virtual scrolling params, Set-based isSelected, swipe virtualization
- ProxiesView: add batchUpdate to selection and swipe-select
- BulkEditAccountModal: fix submit handler to prevent event object as argument
- SettingsView: move payload construction outside try block
- i18n: add general translation keys (saved, deleted, view, validation, allowUserRefund)
- api/client: reorder error fields for consistency
- stores/payment: clarify pollOrderStatus JSDoc
parent c14d7393
...@@ -1071,6 +1071,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { ...@@ -1071,6 +1071,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
EnableCCHSigning: updatedSettings.EnableCCHSigning, EnableCCHSigning: updatedSettings.EnableCCHSigning,
BalanceLowNotifyEnabled: updatedSettings.BalanceLowNotifyEnabled, BalanceLowNotifyEnabled: updatedSettings.BalanceLowNotifyEnabled,
BalanceLowNotifyThreshold: updatedSettings.BalanceLowNotifyThreshold, BalanceLowNotifyThreshold: updatedSettings.BalanceLowNotifyThreshold,
BalanceLowNotifyRechargeURL: updatedSettings.BalanceLowNotifyRechargeURL,
AccountQuotaNotifyEnabled: updatedSettings.AccountQuotaNotifyEnabled, AccountQuotaNotifyEnabled: updatedSettings.AccountQuotaNotifyEnabled,
AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(updatedSettings.AccountQuotaNotifyEmails), AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(updatedSettings.AccountQuotaNotifyEmails),
PaymentEnabled: updatedPaymentCfg.Enabled, PaymentEnabled: updatedPaymentCfg.Enabled,
......
...@@ -522,7 +522,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) { ...@@ -522,7 +522,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
for { for {
// 选择支持该模型的账号 // 选择支持该模型的账号
selection, err := h.gatewayService.SelectAccountWithLoadAwareness(c.Request.Context(), currentAPIKey.GroupID, sessionKey, reqModel, fs.FailedAccountIDs, parsedReq.MetadataUserID, int64(0)) selection, err := h.gatewayService.SelectAccountWithLoadAwareness(c.Request.Context(), currentAPIKey.GroupID, sessionKey, reqModel, fs.FailedAccountIDs, parsedReq.MetadataUserID, subject.UserID)
if err != nil { if err != nil {
if len(fs.FailedAccountIDs) == 0 { if len(fs.FailedAccountIDs) == 0 {
h.handleStreamingAwareError(c, http.StatusServiceUnavailable, "api_error", "No available accounts: "+err.Error(), streamStarted) h.handleStreamingAwareError(c, http.StatusServiceUnavailable, "api_error", "No available accounts: "+err.Error(), streamStarted)
......
...@@ -4575,14 +4575,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec ...@@ -4575,14 +4575,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
// 计算账号统计定价费用(使用最终上游模型匹配自定义规则) // 计算账号统计定价费用(使用最终上游模型匹配自定义规则)
if apiKey.GroupID != nil { if apiKey.GroupID != nil {
statsModel := result.UpstreamModel applyAccountStatsCost(ctx, usageLog, s.channelService, s.billingService,
if statsModel == "" { account.ID, *apiKey.GroupID, result.UpstreamModel, result.Model,
statsModel = result.Model tokens, cost.TotalCost,
}
usageLog.AccountStatsCost = resolveAccountStatsCost(
ctx, s.channelService, s.billingService,
account.ID, *apiKey.GroupID, statsModel,
tokens, 1, cost.TotalCost,
) )
} }
......
...@@ -10,6 +10,8 @@ import ( ...@@ -10,6 +10,8 @@ import (
"io" "io"
"io/fs" "io/fs"
"net/http" "net/http"
"os"
"path/filepath"
"strings" "strings"
"time" "time"
...@@ -37,6 +39,7 @@ type FrontendServer struct { ...@@ -37,6 +39,7 @@ type FrontendServer struct {
baseHTML []byte baseHTML []byte
cache *HTMLCache cache *HTMLCache
settings PublicSettingsProvider settings PublicSettingsProvider
overrideDir string // local file override directory
} }
// NewFrontendServer creates a new frontend server with settings injection // NewFrontendServer creates a new frontend server with settings injection
...@@ -67,6 +70,7 @@ func NewFrontendServer(settingsProvider PublicSettingsProvider) (*FrontendServer ...@@ -67,6 +70,7 @@ func NewFrontendServer(settingsProvider PublicSettingsProvider) (*FrontendServer
baseHTML: baseHTML, baseHTML: baseHTML,
cache: cache, cache: cache,
settings: settingsProvider, settings: settingsProvider,
overrideDir: filepath.Join("data", "public"),
}, nil }, nil
} }
...@@ -99,6 +103,11 @@ func (s *FrontendServer) Middleware() gin.HandlerFunc { ...@@ -99,6 +103,11 @@ func (s *FrontendServer) Middleware() gin.HandlerFunc {
return return
} }
// Try local override first
if s.tryServeOverride(c, cleanPath) {
return
}
// Serve static files normally // Serve static files normally
s.fileServer.ServeHTTP(c.Writer, c.Request) s.fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort() c.Abort()
...@@ -114,6 +123,22 @@ func (s *FrontendServer) fileExists(path string) bool { ...@@ -114,6 +123,22 @@ func (s *FrontendServer) fileExists(path string) bool {
return true return true
} }
// tryServeOverride checks if a local override file exists and serves it.
// Files in overrideDir take precedence over embedded files.
func (s *FrontendServer) tryServeOverride(c *gin.Context, cleanPath string) bool {
if s.overrideDir == "" {
return false
}
filePath := filepath.Join(s.overrideDir, filepath.Clean("/"+cleanPath))
info, err := os.Stat(filePath)
if err != nil || info.IsDir() {
return false
}
c.File(filePath)
c.Abort()
return true
}
func (s *FrontendServer) serveIndexHTML(c *gin.Context) { func (s *FrontendServer) serveIndexHTML(c *gin.Context) {
// Get nonce from context (generated by SecurityHeaders middleware) // Get nonce from context (generated by SecurityHeaders middleware)
nonce := middleware.GetNonceFromContext(c) nonce := middleware.GetNonceFromContext(c)
...@@ -226,6 +251,7 @@ func ServeEmbeddedFrontend() gin.HandlerFunc { ...@@ -226,6 +251,7 @@ func ServeEmbeddedFrontend() gin.HandlerFunc {
panic("failed to get dist subdirectory: " + err.Error()) panic("failed to get dist subdirectory: " + err.Error())
} }
fileServer := http.FileServer(http.FS(distFS)) fileServer := http.FileServer(http.FS(distFS))
overrideDir := filepath.Join("data", "public")
return func(c *gin.Context) { return func(c *gin.Context) {
path := c.Request.URL.Path path := c.Request.URL.Path
...@@ -242,6 +268,10 @@ func ServeEmbeddedFrontend() gin.HandlerFunc { ...@@ -242,6 +268,10 @@ func ServeEmbeddedFrontend() gin.HandlerFunc {
if file, err := distFS.Open(cleanPath); err == nil { if file, err := distFS.Open(cleanPath); err == nil {
_ = file.Close() _ = file.Close()
// Try local override first
if tryServeOverrideFile(c, overrideDir, cleanPath) {
return
}
fileServer.ServeHTTP(c.Writer, c.Request) fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort() c.Abort()
return return
...@@ -251,6 +281,21 @@ func ServeEmbeddedFrontend() gin.HandlerFunc { ...@@ -251,6 +281,21 @@ func ServeEmbeddedFrontend() gin.HandlerFunc {
} }
} }
// tryServeOverrideFile is a standalone version of tryServeOverride for legacy usage.
func tryServeOverrideFile(c *gin.Context, overrideDir, cleanPath string) bool {
if overrideDir == "" {
return false
}
filePath := filepath.Join(overrideDir, filepath.Clean("/"+cleanPath))
info, err := os.Stat(filePath)
if err != nil || info.IsDir() {
return false
}
c.File(filePath)
c.Abort()
return true
}
func shouldBypassEmbeddedFrontend(path string) bool { func shouldBypassEmbeddedFrontend(path string) bool {
trimmed := strings.TrimSpace(path) trimmed := strings.TrimSpace(path)
return strings.HasPrefix(trimmed, "/api/") || return strings.HasPrefix(trimmed, "/api/") ||
......
...@@ -270,9 +270,9 @@ apiClient.interceptors.response.use( ...@@ -270,9 +270,9 @@ apiClient.interceptors.response.use(
return Promise.reject({ return Promise.reject({
status, status,
code: apiData.code, code: apiData.code,
reason: apiData.reason,
error: apiData.error, error: apiData.error,
message: apiData.message || apiData.detail || error.message, message: apiData.message || apiData.detail || error.message,
reason: apiData.reason,
metadata: apiData.metadata, metadata: apiData.metadata,
}) })
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
width="wide" width="wide"
@close="handleClose" @close="handleClose"
> >
<form id="bulk-edit-account-form" class="space-y-5" @submit.prevent="handleSubmit"> <form id="bulk-edit-account-form" class="space-y-5" @submit.prevent="() => handleSubmit()">
<!-- Info --> <!-- Info -->
<div class="rounded-lg bg-blue-50 p-4 dark:bg-blue-900/20"> <div class="rounded-lg bg-blue-50 p-4 dark:bg-blue-900/20">
<p class="text-sm text-blue-700 dark:text-blue-400"> <p class="text-sm text-blue-700 dark:text-blue-400">
......
...@@ -76,6 +76,12 @@ export function useTableSelection<T>({ rows, getId }: UseTableSelectionOptions<T ...@@ -76,6 +76,12 @@ export function useTableSelection<T>({ rows, getId }: UseTableSelectionOptions<T
replaceSelectedSet(next) replaceSelectedSet(next)
} }
const batchUpdate = (updater: (draft: Set<number>) => void) => {
const draft = new Set(selectedSet.value)
updater(draft)
replaceSelectedSet(draft)
}
const selectVisible = () => { const selectVisible = () => {
toggleVisible(true) toggleVisible(true)
} }
...@@ -93,6 +99,7 @@ export function useTableSelection<T>({ rows, getId }: UseTableSelectionOptions<T ...@@ -93,6 +99,7 @@ export function useTableSelection<T>({ rows, getId }: UseTableSelectionOptions<T
clear, clear,
removeMany, removeMany,
toggleVisible, toggleVisible,
selectVisible selectVisible,
batchUpdate
} }
} }
...@@ -247,6 +247,8 @@ export default { ...@@ -247,6 +247,8 @@ export default {
loading: 'Loading...', loading: 'Loading...',
justNow: 'just now', justNow: 'just now',
save: 'Save', save: 'Save',
saved: 'Saved successfully',
deleted: 'Deleted successfully',
cancel: 'Cancel', cancel: 'Cancel',
delete: 'Delete', delete: 'Delete',
edit: 'Edit', edit: 'Edit',
...@@ -304,6 +306,7 @@ export default { ...@@ -304,6 +306,7 @@ export default {
saving: 'Saving...', saving: 'Saving...',
selectedCount: '({count} selected)', selectedCount: '({count} selected)',
refresh: 'Refresh', refresh: 'Refresh',
view: 'View',
settings: 'Settings', settings: 'Settings',
chooseFile: 'Choose File', chooseFile: 'Choose File',
notAvailable: 'N/A', notAvailable: 'N/A',
...@@ -5487,6 +5490,7 @@ export default { ...@@ -5487,6 +5490,7 @@ export default {
refundSuccess: 'Refund successful', refundSuccess: 'Refund successful',
refundInfo: 'Refund Info', refundInfo: 'Refund Info',
refundEnabled: 'Refund Enabled', refundEnabled: 'Refund Enabled',
allowUserRefund: 'Allow User Refund',
alreadyRefunded: 'Already Refunded', alreadyRefunded: 'Already Refunded',
deductBalance: 'Deduct Balance', deductBalance: 'Deduct Balance',
deductBalanceHint: 'Subtract recharged amount from user balance', deductBalanceHint: 'Subtract recharged amount from user balance',
...@@ -5556,6 +5560,9 @@ export default { ...@@ -5556,6 +5560,9 @@ export default {
tabPlanConfig: 'Plan Configuration', tabPlanConfig: 'Plan Configuration',
tabUserSubs: 'User Subscriptions', tabUserSubs: 'User Subscriptions',
selectGroup: 'Select a group', selectGroup: 'Select a group',
groupRequired: 'Please select a subscription group',
priceRequired: 'Price must be greater than 0',
validityDaysRequired: 'Validity days must be greater than 0',
groupMissing: 'Missing', groupMissing: 'Missing',
groupInfo: 'Group Info', groupInfo: 'Group Info',
platform: 'Platform', platform: 'Platform',
......
...@@ -247,6 +247,8 @@ export default { ...@@ -247,6 +247,8 @@ export default {
loading: '加载中...', loading: '加载中...',
justNow: '刚刚', justNow: '刚刚',
save: '保存', save: '保存',
saved: '保存成功',
deleted: '删除成功',
cancel: '取消', cancel: '取消',
delete: '删除', delete: '删除',
edit: '编辑', edit: '编辑',
...@@ -304,6 +306,7 @@ export default { ...@@ -304,6 +306,7 @@ export default {
saving: '保存中...', saving: '保存中...',
selectedCount: '(已选 {count} 个)', selectedCount: '(已选 {count} 个)',
refresh: '刷新', refresh: '刷新',
view: '查看',
settings: '设置', settings: '设置',
chooseFile: '选择文件', chooseFile: '选择文件',
notAvailable: '不可用', notAvailable: '不可用',
...@@ -5744,6 +5747,9 @@ export default { ...@@ -5744,6 +5747,9 @@ export default {
tabPlanConfig: '套餐配置', tabPlanConfig: '套餐配置',
tabUserSubs: '用户订阅', tabUserSubs: '用户订阅',
selectGroup: '请选择分组', selectGroup: '请选择分组',
groupRequired: '请选择订阅分组',
priceRequired: '价格必须大于 0',
validityDaysRequired: '有效期天数必须大于 0',
groupMissing: '缺失', groupMissing: '缺失',
groupInfo: '分组信息', groupInfo: '分组信息',
platform: '平台', platform: '平台',
......
...@@ -66,7 +66,7 @@ export const usePaymentStore = defineStore('payment', () => { ...@@ -66,7 +66,7 @@ export const usePaymentStore = defineStore('payment', () => {
return response.data return response.data
} }
/** Poll order status by ID */ /** Poll order status by ID (read-only, no upstream check) */
async function pollOrderStatus(orderId: number): Promise<PaymentOrder | null> { async function pollOrderStatus(orderId: number): Promise<PaymentOrder | null> {
try { try {
const response = await paymentAPI.getOrder(orderId) const response = await paymentAPI.getOrder(orderId)
......
...@@ -144,6 +144,7 @@ ...@@ -144,6 +144,7 @@
<AccountBulkActionsBar :selected-ids="selIds" @delete="handleBulkDelete" @reset-status="handleBulkResetStatus" @refresh-token="handleBulkRefreshToken" @edit="showBulkEdit = true" @clear="clearSelection" @select-page="selectPage" @toggle-schedulable="handleBulkToggleSchedulable" /> <AccountBulkActionsBar :selected-ids="selIds" @delete="handleBulkDelete" @reset-status="handleBulkResetStatus" @refresh-token="handleBulkRefreshToken" @edit="showBulkEdit = true" @clear="clearSelection" @select-page="selectPage" @toggle-schedulable="handleBulkToggleSchedulable" />
<div ref="accountTableRef" class="flex min-h-0 flex-1 flex-col overflow-hidden"> <div ref="accountTableRef" class="flex min-h-0 flex-1 flex-col overflow-hidden">
<DataTable <DataTable
ref="dataTableRef"
:columns="cols" :columns="cols"
:data="accounts" :data="accounts"
:loading="loading" :loading="loading"
...@@ -153,6 +154,8 @@ ...@@ -153,6 +154,8 @@
default-sort-key="name" default-sort-key="name"
default-sort-order="asc" default-sort-order="asc"
:sort-storage-key="ACCOUNT_SORT_STORAGE_KEY" :sort-storage-key="ACCOUNT_SORT_STORAGE_KEY"
:estimate-row-height="72"
:overscan="5"
> >
<template #header-select> <template #header-select>
<input <input
...@@ -164,7 +167,7 @@ ...@@ -164,7 +167,7 @@
/> />
</template> </template>
<template #cell-select="{ row }"> <template #cell-select="{ row }">
<input type="checkbox" :checked="selIds.includes(row.id)" @change="toggleSel(row.id)" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" /> <input type="checkbox" :checked="isSelected(row.id)" @change="toggleSel(row.id)" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
</template> </template>
<template #cell-name="{ row, value }"> <template #cell-name="{ row, value }">
<div class="flex flex-col"> <div class="flex flex-col">
...@@ -197,7 +200,9 @@ ...@@ -197,7 +200,9 @@
<AccountCapacityCell :account="row" /> <AccountCapacityCell :account="row" />
</template> </template>
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<div class="flex items-center gap-1.5">
<AccountStatusIndicator :account="row" @show-temp-unsched="handleShowTempUnsched" /> <AccountStatusIndicator :account="row" @show-temp-unsched="handleShowTempUnsched" />
</div>
</template> </template>
<template #cell-schedulable="{ row }"> <template #cell-schedulable="{ row }">
<button @click="handleToggleSchedulable(row)" :disabled="togglingSchedulable === row.id" class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:ring-offset-dark-800" :class="[row.schedulable ? 'bg-primary-500 hover:bg-primary-600' : 'bg-gray-200 hover:bg-gray-300 dark:bg-dark-600 dark:hover:bg-dark-500']" :title="row.schedulable ? t('admin.accounts.schedulableEnabled') : t('admin.accounts.schedulableDisabled')"> <button @click="handleToggleSchedulable(row)" :disabled="togglingSchedulable === row.id" class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:ring-offset-dark-800" :class="[row.schedulable ? 'bg-primary-500 hover:bg-primary-600' : 'bg-gray-200 hover:bg-gray-300 dark:bg-dark-600 dark:hover:bg-dark-500']" :title="row.schedulable ? t('admin.accounts.schedulableEnabled') : t('admin.accounts.schedulableDisabled')">
...@@ -313,7 +318,7 @@ import { useAppStore } from '@/stores/app' ...@@ -313,7 +318,7 @@ import { useAppStore } from '@/stores/app'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { adminAPI } from '@/api/admin' import { adminAPI } from '@/api/admin'
import { useTableLoader } from '@/composables/useTableLoader' import { useTableLoader } from '@/composables/useTableLoader'
import { useSwipeSelect } from '@/composables/useSwipeSelect' import { useSwipeSelect, type SwipeSelectVirtualContext } from '@/composables/useSwipeSelect'
import { useTableSelection } from '@/composables/useTableSelection' import { useTableSelection } from '@/composables/useTableSelection'
import AppLayout from '@/components/layout/AppLayout.vue' import AppLayout from '@/components/layout/AppLayout.vue'
import TablePageLayout from '@/components/layout/TablePageLayout.vue' import TablePageLayout from '@/components/layout/TablePageLayout.vue'
...@@ -351,6 +356,7 @@ const authStore = useAuthStore() ...@@ -351,6 +356,7 @@ const authStore = useAuthStore()
const proxies = ref<AccountProxy[]>([]) const proxies = ref<AccountProxy[]>([])
const groups = ref<AdminGroup[]>([]) const groups = ref<AdminGroup[]>([])
const accountTableRef = ref<HTMLElement | null>(null) const accountTableRef = ref<HTMLElement | null>(null)
const dataTableRef = ref<InstanceType<typeof DataTable> | null>(null)
const selPlatforms = computed<AccountPlatform[]>(() => { const selPlatforms = computed<AccountPlatform[]>(() => {
const platforms = new Set( const platforms = new Set(
accounts.value accounts.value
...@@ -650,17 +656,25 @@ const { ...@@ -650,17 +656,25 @@ const {
clear: clearSelection, clear: clearSelection,
removeMany: removeSelectedAccounts, removeMany: removeSelectedAccounts,
toggleVisible, toggleVisible,
selectVisible: selectPage selectVisible: selectPage,
batchUpdate
} = useTableSelection<Account>({ } = useTableSelection<Account>({
rows: accounts, rows: accounts,
getId: (account) => account.id getId: (account) => account.id
}) })
const swipeVirtualContext: SwipeSelectVirtualContext = {
getVirtualizer: () => dataTableRef.value?.virtualizer ?? null,
getSortedData: () => dataTableRef.value?.sortedData ?? accounts.value,
getRowId: (row: any) => row.id,
}
useSwipeSelect(accountTableRef, { useSwipeSelect(accountTableRef, {
isSelected, isSelected,
select, select,
deselect deselect,
}) batchUpdate
}, swipeVirtualContext)
const resetAutoRefreshCache = () => { const resetAutoRefreshCache = () => {
autoRefreshETag.value = null autoRefreshETag.value = null
......
...@@ -985,7 +985,8 @@ const { ...@@ -985,7 +985,8 @@ const {
deselect, deselect,
clear: clearSelectedProxies, clear: clearSelectedProxies,
removeMany: removeSelectedProxies, removeMany: removeSelectedProxies,
toggleVisible toggleVisible,
batchUpdate
} = useTableSelection<Proxy>({ } = useTableSelection<Proxy>({
rows: proxies, rows: proxies,
getId: (proxy) => proxy.id getId: (proxy) => proxy.id
...@@ -993,7 +994,8 @@ const { ...@@ -993,7 +994,8 @@ const {
useSwipeSelect(proxyTableRef, { useSwipeSelect(proxyTableRef, {
isSelected, isSelected,
select, select,
deselect deselect,
batchUpdate
}) })
const accountsProxy = ref<Proxy | null>(null) const accountsProxy = ref<Proxy | null>(null)
const proxyAccounts = ref<ProxyAccountSummary[]>([]) const proxyAccounts = ref<ProxyAccountSummary[]>([])
......
...@@ -4116,12 +4116,13 @@ async function handleToggleField(provider: ProviderInstance, field: 'enabled' | ...@@ -4116,12 +4116,13 @@ async function handleToggleField(provider: ProviderInstance, field: 'enabled' |
if (field === 'enabled') newValue = !provider.enabled if (field === 'enabled') newValue = !provider.enabled
else if (field === 'refund_enabled') newValue = !provider.refund_enabled else if (field === 'refund_enabled') newValue = !provider.refund_enabled
else newValue = !provider.allow_user_refund else newValue = !provider.allow_user_refund
try {
const payload: Record<string, boolean> = { [field]: newValue } const payload: Record<string, boolean> = { [field]: newValue }
// Cascade: turning off refund_enabled also disables allow_user_refund // Cascade: turning off refund_enabled also turns off allow_user_refund
if (field === 'refund_enabled' && !newValue) { if (field === 'refund_enabled' && !newValue) {
payload.allow_user_refund = false payload.allow_user_refund = false
} }
try {
await adminAPI.payment.updateProvider(provider.id, payload) await adminAPI.payment.updateProvider(provider.id, payload)
if (field === 'enabled') provider.enabled = newValue if (field === 'enabled') provider.enabled = newValue
else if (field === 'refund_enabled') { else if (field === 'refund_enabled') {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment