Files
sub2api/frontend/src/api/payment.ts
erio f1297a3694 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
2026-04-14 16:26:46 +08:00

85 lines
2.5 KiB
TypeScript

/**
* User Payment API endpoints
* Handles payment operations for regular users
*/
import { apiClient } from './client'
import type {
PaymentConfig,
SubscriptionPlan,
PaymentChannel,
MethodLimitsResponse,
CheckoutInfoResponse,
CreateOrderRequest,
CreateOrderResult,
PaymentOrder
} from '@/types/payment'
import type { BasePaginationResponse } from '@/types'
export const paymentAPI = {
/** Get payment configuration (enabled types, limits, etc.) */
getConfig() {
return apiClient.get<PaymentConfig>('/payment/config')
},
/** Get available subscription plans */
getPlans() {
return apiClient.get<SubscriptionPlan[]>('/payment/plans')
},
/** Get available payment channels */
getChannels() {
return apiClient.get<PaymentChannel[]>('/payment/channels')
},
/** Get all checkout page data in a single call */
getCheckoutInfo() {
return apiClient.get<CheckoutInfoResponse>('/payment/checkout-info')
},
/** Get payment method limits and fee rates */
getLimits() {
return apiClient.get<MethodLimitsResponse>('/payment/limits')
},
/** Create a new payment order */
createOrder(data: CreateOrderRequest) {
return apiClient.post<CreateOrderResult>('/payment/orders', data)
},
/** Get current user's orders */
getMyOrders(params?: { page?: number; page_size?: number; status?: string }) {
return apiClient.get<BasePaginationResponse<PaymentOrder>>('/payment/orders/my', { params })
},
/** Get a specific order by ID */
getOrder(id: number) {
return apiClient.get<PaymentOrder>(`/payment/orders/${id}`)
},
/** Cancel a pending order */
cancelOrder(id: number) {
return apiClient.post(`/payment/orders/${id}/cancel`)
},
/** Verify order payment status with upstream provider */
verifyOrder(outTradeNo: string) {
return apiClient.post<PaymentOrder>('/payment/orders/verify', { out_trade_no: outTradeNo })
},
/** Verify order payment status without auth (public endpoint for result page) */
verifyOrderPublic(outTradeNo: string) {
return apiClient.post<PaymentOrder>('/payment/public/orders/verify', { out_trade_no: outTradeNo })
},
/** Request a refund for a completed order */
requestRefund(id: number, data: { reason: string }) {
return apiClient.post(`/payment/orders/${id}/refund-request`, data)
},
/** Get provider instance IDs that allow user refund */
getRefundEligibleProviders() {
return apiClient.get<{ provider_instance_ids: string[] }>('/payment/orders/refund-eligible-providers')
}
}