feat: add per-provider allow_user_refund control and align wildcard matching

allow_user_refund:
- Add allow_user_refund field to PaymentProviderInstance ent schema
- Migration 103: ALTER TABLE payment_provider_instances ADD COLUMN
- Cascade logic: disabling refund_enabled auto-disables allow_user_refund
- User refund validation: check provider instance allows user refund
- Admin refund validation: check provider instance allows admin refund
- Subscription refund: deduct days on refund, rollback on failure
- New endpoint: GET /payment/orders/refund-eligible-providers
- Frontend: ToggleSwitch in ProviderCard/Dialog, cascade in SettingsView

Wildcard matching:
- Change findPricingForModel from "longest prefix wins" to "config order
  priority (first match wins)", aligning with channel service behavior
This commit is contained in:
erio
2026-04-14 16:26:46 +08:00
parent e8ee400a3f
commit f1297a3694
28 changed files with 405 additions and 98 deletions

View File

@@ -4111,12 +4111,25 @@ async function handleSaveProvider(payload: Partial<ProviderInstance>) {
}
}
async function handleToggleField(provider: ProviderInstance, field: 'enabled' | 'refund_enabled') {
const newValue = field === 'enabled' ? !provider.enabled : !provider.refund_enabled
async function handleToggleField(provider: ProviderInstance, field: 'enabled' | 'refund_enabled' | 'allow_user_refund') {
let newValue: boolean
if (field === 'enabled') newValue = !provider.enabled
else if (field === 'refund_enabled') newValue = !provider.refund_enabled
else newValue = !provider.allow_user_refund
try {
await adminAPI.payment.updateProvider(provider.id, { [field]: newValue })
const payload: Record<string, boolean> = { [field]: newValue }
// Cascade: turning off refund_enabled also disables allow_user_refund
if (field === 'refund_enabled' && !newValue) {
payload.allow_user_refund = false
}
await adminAPI.payment.updateProvider(provider.id, payload)
if (field === 'enabled') provider.enabled = newValue
else provider.refund_enabled = newValue
else if (field === 'refund_enabled') {
provider.refund_enabled = newValue
if (!newValue) provider.allow_user_refund = false
} else {
provider.allow_user_refund = newValue
}
} catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'), paymentErrorMap.value)) }
}