diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index ac0465828d..9abb1df557 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -5144,6 +5144,7 @@ export default { errors: { tooManyPending: 'Too many pending orders (max {max}). Please complete or cancel existing orders first.', cancelRateLimited: 'Too many cancellations. Please try again later.', + PENDING_ORDERS: 'This provider has pending orders. Please wait for them to complete before making changes.', }, stripePay: 'Pay Now', stripeSuccessProcessing: 'Payment successful, processing your order...', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 3dd42bec65..30569be74b 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -5341,6 +5341,7 @@ export default { errors: { tooManyPending: '待支付订单过多(最多 {max} 个),请先完成或取消现有订单', cancelRateLimited: '取消订单过于频繁,请稍后再试', + PENDING_ORDERS: '该服务商有未完成的订单,请等待订单完成后再操作', }, stripePay: '立即支付', stripeSuccessProcessing: '支付成功,正在处理订单...', diff --git a/frontend/src/utils/apiError.ts b/frontend/src/utils/apiError.ts index ad92a79b40..e1fe0c30cf 100644 --- a/frontend/src/utils/apiError.ts +++ b/frontend/src/utils/apiError.ts @@ -10,6 +10,8 @@ interface ApiErrorLike { code?: number | string message?: string error?: string + reason?: string + metadata?: Record response?: { data?: { detail?: string @@ -19,21 +21,36 @@ interface ApiErrorLike { } } +/** + * Extract the error code from an API error object. + */ +export function extractApiErrorCode(err: unknown): string | undefined { + if (!err || typeof err !== 'object') return undefined + const e = err as ApiErrorLike + const code = e.code ?? e.reason ?? e.response?.data?.code + return code != null ? String(code) : undefined +} + /** * Extract a displayable error message from an API error. * - * Handles: - * - Plain objects from API client interceptor: { status, code, message } - * - Axios errors with response.data - * - Standard Error instances - * - Unknown types (stringified) - * * @param err - The caught error (unknown type) * @param fallback - Fallback message if none can be extracted (use t('common.error') or similar) + * @param i18nMap - Optional map of error codes to i18n translated strings */ -export function extractApiErrorMessage(err: unknown, fallback = 'Unknown error'): string { +export function extractApiErrorMessage( + err: unknown, + fallback = 'Unknown error', + i18nMap?: Record, +): string { if (!err) return fallback + // Try i18n mapping by error code first + if (i18nMap) { + const code = extractApiErrorCode(err) + if (code && i18nMap[code]) return i18nMap[code] + } + // Plain object from API client interceptor (most common case) if (typeof err === 'object' && err !== null) { const e = err as ApiErrorLike diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index be2fd9a958..54a41e1522 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -2888,6 +2888,10 @@ const cancelRateLimitModeOptions = computed(() => [ { value: 'fixed', label: t('admin.settings.payment.cancelRateLimitWindowModeFixed') }, ]) +const paymentErrorMap = computed(() => ({ + PENDING_ORDERS: t('payment.errors.PENDING_ORDERS'), +})) + async function loadProviders() { providersLoading.value = true try { const res = await adminAPI.payment.getProviders(); providers.value = res.data || [] } @@ -2920,7 +2924,7 @@ async function handleSaveProvider(payload: any) { // Also save the overall settings so payment config changes are persisted together await saveSettings() } catch (err: unknown) { - appStore.showError(extractApiErrorMessage(err, t('common.error'))) + appStore.showError(extractApiErrorMessage(err, t('common.error'), paymentErrorMap.value)) } finally { providerSaving.value = false } @@ -2932,7 +2936,7 @@ async function handleToggleField(provider: ProviderInstance, field: 'enabled' | await adminAPI.payment.updateProvider(provider.id, { [field]: newValue }) if (field === 'enabled') provider.enabled = newValue else provider.refund_enabled = newValue - } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) } + } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'), paymentErrorMap.value)) } } async function handleToggleType(provider: ProviderInstance, type: string) { @@ -2946,7 +2950,7 @@ async function handleToggleType(provider: ProviderInstance, type: string) { try { await adminAPI.payment.updateProvider(provider.id, { supported_types: updated } as any) provider.supported_types = updated - } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) } + } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'), paymentErrorMap.value)) } } function confirmDeleteProvider(provider: ProviderInstance) { @@ -2961,7 +2965,7 @@ async function handleDeleteProvider() { appStore.showSuccess(t('common.deleted')) showDeleteProviderDialog.value = false loadProviders() - } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) } + } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'), paymentErrorMap.value)) } } onMounted(() => {