feat(admin-v2): add billing management page

- Plan cards with feature comparison and pricing
- Usage progress bars with quota visualization
- Alipay/WeChat Pay method selection modal
- Payment status polling with auto-refresh on success
- Navigation + route registration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-02 00:48:35 +08:00
parent b1e3a27043
commit c8dc654fd4
4 changed files with 457 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
import request, { withSignal } from './request'
// === Types ===
export interface BillingPlan {
id: string
name: string
display_name: string
description: string | null
price_cents: number
currency: string
interval: string
features: Record<string, unknown>
limits: Record<string, unknown>
is_default: boolean
sort_order: number
status: string
created_at: string
updated_at: string
}
export interface Subscription {
id: string
account_id: string
plan_id: string
status: string
current_period_start: string
current_period_end: string
trial_end: string | null
canceled_at: string | null
cancel_at_period_end: boolean
created_at: string
updated_at: string
}
export interface UsageQuota {
id: string
account_id: string
period_start: string
period_end: string
input_tokens: number
output_tokens: number
relay_requests: number
hand_executions: number
pipeline_runs: number
max_input_tokens: number | null
max_output_tokens: number | null
max_relay_requests: number | null
max_hand_executions: number | null
max_pipeline_runs: number | null
created_at: string
updated_at: string
}
export interface SubscriptionInfo {
plan: BillingPlan
subscription: Subscription | null
usage: UsageQuota
}
export interface PaymentResult {
payment_id: string
trade_no: string
pay_url: string
amount_cents: number
}
export interface PaymentStatus {
id: string
method: string
amount_cents: number
currency: string
status: string
}
// === Service ===
export const billingService = {
listPlans: (signal?: AbortSignal) =>
request.get<BillingPlan[]>('/billing/plans', withSignal({}, signal))
.then((r) => r.data),
getPlan: (id: string, signal?: AbortSignal) =>
request.get<BillingPlan>(`/billing/plans/${id}`, withSignal({}, signal))
.then((r) => r.data),
getSubscription: (signal?: AbortSignal) =>
request.get<SubscriptionInfo>('/billing/subscription', withSignal({}, signal))
.then((r) => r.data),
getUsage: (signal?: AbortSignal) =>
request.get<UsageQuota>('/billing/usage', withSignal({}, signal))
.then((r) => r.data),
createPayment: (data: { plan_id: string; payment_method: 'alipay' | 'wechat' }) =>
request.post<PaymentResult>('/billing/payments', data).then((r) => r.data),
getPaymentStatus: (id: string, signal?: AbortSignal) =>
request.get<PaymentStatus>(`/billing/payments/${id}`, withSignal({}, signal))
.then((r) => r.data),
}