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
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
/**
|
|
* Payment Store
|
|
* Manages payment configuration, current order state, and subscription plans
|
|
*/
|
|
|
|
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import { paymentAPI } from '@/api/payment'
|
|
import type { PaymentConfig, PaymentOrder, SubscriptionPlan, CreateOrderRequest } from '@/types/payment'
|
|
|
|
export const usePaymentStore = defineStore('payment', () => {
|
|
// ==================== State ====================
|
|
|
|
/** Payment configuration from backend */
|
|
const config = ref<PaymentConfig | null>(null)
|
|
/** Currently active order (for payment flow) */
|
|
const currentOrder = ref<PaymentOrder | null>(null)
|
|
/** Available subscription plans */
|
|
const plans = ref<SubscriptionPlan[]>([])
|
|
|
|
const configLoading = ref(false)
|
|
const configLoaded = ref(false)
|
|
|
|
// ==================== Actions ====================
|
|
|
|
/** Fetch payment configuration */
|
|
async function fetchConfig(force = false): Promise<PaymentConfig | null> {
|
|
if (configLoaded.value && !force) return config.value
|
|
if (configLoading.value) return config.value
|
|
|
|
configLoading.value = true
|
|
try {
|
|
const response = await paymentAPI.getConfig()
|
|
config.value = response.data
|
|
configLoaded.value = true
|
|
return config.value
|
|
} catch (error: unknown) {
|
|
console.error('[payment] Failed to fetch config:', error)
|
|
return null
|
|
} finally {
|
|
configLoading.value = false
|
|
}
|
|
}
|
|
|
|
/** Fetch available subscription plans */
|
|
async function fetchPlans(): Promise<SubscriptionPlan[]> {
|
|
try {
|
|
const response = await paymentAPI.getPlans()
|
|
// Backend returns features as newline-separated string; parse to array
|
|
plans.value = (response.data || []).map((p: Omit<SubscriptionPlan, 'features'> & { features: string | string[] }) => ({
|
|
...p,
|
|
features: typeof p.features === 'string'
|
|
? p.features.split('\n').map((f: string) => f.trim()).filter(Boolean)
|
|
: (p.features || []),
|
|
}))
|
|
return plans.value
|
|
} catch (error: unknown) {
|
|
console.error('[payment] Failed to fetch plans:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/** Create a new order and set it as current */
|
|
async function createOrder(params: CreateOrderRequest) {
|
|
const response = await paymentAPI.createOrder(params)
|
|
return response.data
|
|
}
|
|
|
|
/** Poll order status by ID (read-only, no upstream check) */
|
|
async function pollOrderStatus(orderId: number): Promise<PaymentOrder | null> {
|
|
try {
|
|
const response = await paymentAPI.getOrder(orderId)
|
|
const order = response.data
|
|
if (currentOrder.value?.id === orderId) {
|
|
currentOrder.value = order
|
|
}
|
|
return order
|
|
} catch (error: unknown) {
|
|
console.error('[payment] Failed to poll order status:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Clear current order state */
|
|
function clearCurrentOrder() {
|
|
currentOrder.value = null
|
|
}
|
|
|
|
return {
|
|
config,
|
|
currentOrder,
|
|
plans,
|
|
configLoading,
|
|
configLoaded,
|
|
fetchConfig,
|
|
fetchPlans,
|
|
createOrder,
|
|
pollOrderStatus,
|
|
clearCurrentOrder
|
|
}
|
|
})
|