Merge branch 'main' into rebuild/auth-identity-foundation

This commit is contained in:
IanShaw027
2026-04-22 00:35:34 +08:00
67 changed files with 1550 additions and 613 deletions

View File

@@ -4710,7 +4710,7 @@ import ProxySelector from "@/components/common/ProxySelector.vue";
import ImageUpload from "@/components/common/ImageUpload.vue";
import BackupSettings from "@/views/admin/BackupView.vue";
import { useClipboard } from "@/composables/useClipboard";
import { extractApiErrorMessage } from "@/utils/apiError";
import { extractApiErrorMessage, extractI18nErrorMessage } from "@/utils/apiError";
import { useAppStore } from "@/stores";
import { useAdminSettingsStore } from "@/stores/adminSettings";
import { normalizeVisibleMethod } from "@/components/payment/paymentFlow";
@@ -6431,11 +6431,6 @@ const cancelRateLimitModeOptions = computed(() => [
},
]);
const paymentErrorMap = computed(() => ({
PENDING_ORDERS: t("payment.errors.PENDING_ORDERS"),
PAYMENT_PROVIDER_CONFLICT: t("payment.errors.PAYMENT_PROVIDER_CONFLICT"),
}));
type ProviderEnablementCandidate = Pick<
ProviderInstance,
"id" | "provider_key" | "supported_types" | "enabled" | "name"
@@ -6531,7 +6526,7 @@ async function loadProviders() {
const res = await adminAPI.payment.getProviders();
providers.value = res.data || [];
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t("common.error")));
appStore.showError(extractI18nErrorMessage(err, t, "payment.errors", t("common.error")));
} finally {
providersLoading.value = false;
}
@@ -6580,9 +6575,7 @@ async function handleSaveProvider(payload: Partial<ProviderInstance>) {
// Auto-save settings so provider changes take effect immediately
await saveSettings();
} catch (err: unknown) {
appStore.showError(
extractApiErrorMessage(err, t("common.error"), paymentErrorMap.value),
);
appStore.showError(extractI18nErrorMessage(err, t, "payment.errors", t("common.error")));
} finally {
providerSaving.value = false;
}
@@ -6620,9 +6613,7 @@ async function handleToggleField(
await adminAPI.payment.updateProvider(provider.id, payload);
await loadProviders();
} catch (err: unknown) {
appStore.showError(
extractApiErrorMessage(err, t("common.error"), paymentErrorMap.value),
);
appStore.showError(extractI18nErrorMessage(err, t, "payment.errors", t("common.error")));
}
}
@@ -6647,9 +6638,7 @@ async function handleToggleType(provider: ProviderInstance, type: string) {
} as any);
await loadProviders();
} catch (err: unknown) {
appStore.showError(
extractApiErrorMessage(err, t("common.error"), paymentErrorMap.value),
);
appStore.showError(extractI18nErrorMessage(err, t, "payment.errors", t("common.error")));
}
}
@@ -6671,7 +6660,7 @@ async function handleReorderProviders(
);
await loadProviders();
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t("common.error")));
appStore.showError(extractI18nErrorMessage(err, t, "payment.errors", t("common.error")));
loadProviders();
}
}
@@ -6684,9 +6673,7 @@ async function handleDeleteProvider() {
showDeleteProviderDialog.value = false;
loadProviders();
} catch (err: unknown) {
appStore.showError(
extractApiErrorMessage(err, t("common.error"), paymentErrorMap.value),
);
appStore.showError(extractI18nErrorMessage(err, t, "payment.errors", t("common.error")));
}
}

View File

@@ -116,7 +116,7 @@ import { ref, reactive, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminPaymentAPI } from '@/api/admin/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import { formatOrderDateTime } from '@/components/payment/orderUtils'
import type { PaymentOrder } from '@/types/payment'
import AppLayout from '@/components/layout/AppLayout.vue'
@@ -167,7 +167,7 @@ async function loadOrders() {
orders.value = res.data.items || []
orderPagination.total = res.data.total || 0
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
} finally { ordersLoading.value = false }
}
@@ -214,12 +214,12 @@ async function showOrderDetail(order: PaymentOrder) {
async function handleCancelOrder(order: PaymentOrder) {
try { await adminPaymentAPI.cancelOrder(order.id); appStore.showSuccess(t('payment.admin.orderCancelled')); loadOrders() }
catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) }
catch (err: unknown) { appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error'))) }
}
async function handleRetryOrder(order: PaymentOrder) {
try { await adminPaymentAPI.retryRecharge(order.id); appStore.showSuccess(t('payment.admin.retrySuccess')); loadOrders() }
catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) }
catch (err: unknown) { appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error'))) }
}
function openRefundDialog(order: PaymentOrder) { selectedOrder.value = order; showRefundDialog.value = true }
@@ -230,7 +230,7 @@ async function handleRefund(data: { amount: number; reason: string; deduct_balan
try {
await adminPaymentAPI.refundOrder(selectedOrder.value.id, { amount: data.amount, reason: data.reason, deduct_balance: data.deduct_balance, force: data.force })
appStore.showSuccess(t('payment.admin.refundSuccess')); showRefundDialog.value = false; loadOrders()
} catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) }
} catch (err: unknown) { appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error'))) }
finally { refundSubmitting.value = false }
}

View File

@@ -72,7 +72,7 @@ import { ref, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminPaymentAPI } from '@/api/admin/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import type { DashboardStats } from '@/types/payment'
import AppLayout from '@/components/layout/AppLayout.vue'
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
@@ -110,7 +110,7 @@ async function loadDashboard() {
const res = await adminPaymentAPI.getDashboard(days.value)
stats.value = res.data
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
} finally {
loading.value = false
}

View File

@@ -78,7 +78,7 @@ import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminPaymentAPI } from '@/api/admin/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import adminAPI from '@/api/admin'
import type { SubscriptionPlan } from '@/types/payment'
import type { AdminGroup } from '@/types'
@@ -150,7 +150,7 @@ async function loadPlans() {
: (p.features || []),
}))
}
catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) }
catch (err: unknown) { appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error'))) }
finally { plansLoading.value = false }
}
@@ -166,7 +166,7 @@ async function toggleForSale(plan: SubscriptionPlan) {
await adminPaymentAPI.updatePlan(plan.id, { for_sale: !plan.for_sale })
plan.for_sale = !plan.for_sale
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
}
}
@@ -174,7 +174,7 @@ function confirmDeletePlan(plan: SubscriptionPlan) { deletingPlanId.value = plan
async function handleDeletePlan() {
if (!deletingPlanId.value) return
try { await adminPaymentAPI.deletePlan(deletingPlanId.value); appStore.showSuccess(t('common.deleted')); showDeletePlanDialog.value = false; loadPlans() }
catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) }
catch (err: unknown) { appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error'))) }
}
// ==================== Lifecycle ====================

View File

@@ -39,7 +39,7 @@ import { useRoute, useRouter } from 'vue-router'
import AppLayout from '@/components/layout/AppLayout.vue'
import { usePaymentStore } from '@/stores/payment'
import { paymentAPI } from '@/api/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import { useAppStore } from '@/stores'
import QRCode from 'qrcode'
import alipayIcon from '@/assets/icons/alipay.svg'
@@ -167,7 +167,7 @@ async function handleCancel() {
cleanup()
router.push('/purchase')
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
} finally {
cancelling.value = false
}

View File

@@ -252,13 +252,13 @@ import { usePaymentStore } from '@/stores/payment'
import { useSubscriptionStore } from '@/stores/subscriptions'
import { useAppStore } from '@/stores'
import { paymentAPI } from '@/api/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractApiErrorMessage, extractI18nErrorMessage } from '@/utils/apiError'
import { isMobileDevice } from '@/utils/device'
import type { SubscriptionPlan, CheckoutInfoResponse, CreateOrderResult, OrderType } from '@/types/payment'
import AppLayout from '@/components/layout/AppLayout.vue'
import AmountInput from '@/components/payment/AmountInput.vue'
import PaymentMethodSelector from '@/components/payment/PaymentMethodSelector.vue'
import { METHOD_ORDER, POPUP_WINDOW_FEATURES, STRIPE_POPUP_WINDOW_FEATURES } from '@/components/payment/providerConfig'
import { METHOD_ORDER, getPaymentPopupFeatures } from '@/components/payment/providerConfig'
import {
PAYMENT_RECOVERY_STORAGE_KEY,
buildCreateOrderPayload,
@@ -630,8 +630,8 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
payload.is_mobile = isMobileDevice()
const result = await paymentStore.createOrder(payload) as CreateOrderResult & { resume_token?: string }
const openWindow = (url: string, features = POPUP_WINDOW_FEATURES) => {
const win = window.open(url, 'paymentPopup', features)
const openWindow = (url: string) => {
const win = window.open(url, 'paymentPopup', getPaymentPopupFeatures())
if (!win || win.closed) {
window.location.href = url
}
@@ -672,7 +672,7 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
persistRecoverySnapshot(decision.recovery)
if (decision.kind === 'stripe_popup') {
openWindow(decision.paymentState.payUrl, STRIPE_POPUP_WINDOW_FEATURES)
openWindow(decision.paymentState.payUrl)
return
}
if (decision.kind === 'stripe_route') {
@@ -710,8 +710,8 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
err,
normalizeVisibleMethod(options.paymentType || selectedMethod.value) || selectedMethod.value,
)
if (!errorMessage.value) {
errorMessage.value = extractApiErrorMessage(err, t('payment.result.failed'))
if (!handled) {
errorMessage.value = extractI18nErrorMessage(err, t, 'payment.errors', extractApiErrorMessage(err, t('payment.result.failed')))
errorHintMessage.value = ''
}
if (handled) {
@@ -825,7 +825,7 @@ onMounted(async () => {
}
}
}
} catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) }
} catch (err: unknown) { appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error'))) }
finally { loading.value = false }
// Fetch active subscriptions (uses cache, non-blocking)
subscriptionStore.fetchActiveSubscriptions().catch(() => {})

View File

@@ -99,7 +99,7 @@ import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { usePaymentStore } from '@/stores/payment'
import { paymentAPI } from '@/api/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import { isMobileDevice } from '@/utils/device'
import type { PaymentOrder } from '@/types/payment'
import type { Stripe, StripeElements } from '@stripe/stripe-js'
@@ -167,7 +167,7 @@ onMounted(async () => {
mountPaymentElement(stripe, clientSecret)
}
} catch (err: unknown) {
initError.value = extractApiErrorMessage(err, t('payment.stripeLoadFailed'))
initError.value = extractI18nErrorMessage(err, t, 'payment.errors', t('payment.stripeLoadFailed'))
} finally {
loading.value = false
}
@@ -248,7 +248,7 @@ async function handleGenericPay() {
scheduleClose()
}
} catch (err: unknown) {
stripeError.value = extractApiErrorMessage(err, t('payment.result.failed'))
stripeError.value = extractI18nErrorMessage(err, t, 'payment.errors', t('payment.result.failed'))
} finally {
stripeSubmitting.value = false
}

View File

@@ -56,7 +56,7 @@
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import { isMobileDevice } from '@/utils/device'
interface StripeWithWechatPay {
@@ -143,7 +143,7 @@ async function initStripe(clientSecret: string, publishableKey: string) {
}
}
} catch (err: unknown) {
error.value = extractApiErrorMessage(err, t('payment.stripeLoadFailed'))
error.value = extractI18nErrorMessage(err, t, 'payment.errors', t('payment.stripeLoadFailed'))
}
}

View File

@@ -86,7 +86,7 @@ import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores'
import { paymentAPI } from '@/api/payment'
import { extractApiErrorMessage } from '@/utils/apiError'
import { extractI18nErrorMessage } from '@/utils/apiError'
import type { PaymentOrder } from '@/types/payment'
import AppLayout from '@/components/layout/AppLayout.vue'
import Pagination from '@/components/common/Pagination.vue'
@@ -128,7 +128,7 @@ async function fetchOrders() {
orders.value = res.data.items || []
pagination.total = res.data.total || 0
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
} finally {
loading.value = false
}
@@ -148,7 +148,7 @@ async function confirmCancel() {
cancelTargetId.value = null
await fetchOrders()
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
} finally {
actionLoading.value = false
}
@@ -166,7 +166,7 @@ async function confirmRefund() {
refundReason.value = ''
await fetchOrders()
} catch (err: unknown) {
appStore.showError(extractApiErrorMessage(err, t('common.error')))
appStore.showError(extractI18nErrorMessage(err, t, 'payment.errors', t('common.error')))
} finally {
actionLoading.value = false
}