mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
refactor(payment): unify all JSON tags to snake_case
Backend request/response structs used camelCase JSON tags while the rest of the codebase uses snake_case (from Ent ORM entities). This caused field name mismatches — notably the admin refund deduct_balance field was silently ignored. Backend: changed all camelCase JSON tags in payment handler/service structs to snake_case (CreateOrderRequest, CreateOrderResponse, CreatePlanRequest, UpdatePlanRequest, CreateProviderInstanceRequest, UpdateProviderInstanceRequest, AdminProcessRefundRequest, RefundResult). Frontend: updated all API call payloads and response field accesses to use snake_case, removed manual camelCase conversion in buildPlanPayload, toggleForSale, provider dialog emit, etc.
This commit is contained in:
@@ -118,7 +118,7 @@ type AdminProcessRefundRequest struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Reason string `json:"reason"`
|
||||
Force bool `json:"force"`
|
||||
DeductBalance bool `json:"deductBalance"`
|
||||
DeductBalance bool `json:"deduct_balance"`
|
||||
}
|
||||
|
||||
// ProcessRefund processes a refund for an order (admin).
|
||||
|
||||
@@ -80,9 +80,9 @@ func (h *PaymentHandler) GetLimits(c *gin.Context) {
|
||||
// CreateOrderRequest is the request body for creating a payment order.
|
||||
type CreateOrderRequest struct {
|
||||
Amount float64 `json:"amount"`
|
||||
PaymentType string `json:"paymentType" binding:"required"`
|
||||
OrderType string `json:"orderType"`
|
||||
PlanID int64 `json:"planId"`
|
||||
PaymentType string `json:"payment_type" binding:"required"`
|
||||
OrderType string `json:"order_type"`
|
||||
PlanID int64 `json:"plan_id"`
|
||||
}
|
||||
|
||||
// CreateOrder creates a new payment order.
|
||||
|
||||
@@ -87,51 +87,51 @@ type MethodLimits struct {
|
||||
}
|
||||
|
||||
type CreateProviderInstanceRequest struct {
|
||||
ProviderKey string `json:"providerKey"`
|
||||
ProviderKey string `json:"provider_key"`
|
||||
Name string `json:"name"`
|
||||
Config map[string]string `json:"config"`
|
||||
SupportedTypes []string `json:"supportedTypes"`
|
||||
SupportedTypes []string `json:"supported_types"`
|
||||
Enabled bool `json:"enabled"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Limits string `json:"limits"`
|
||||
RefundEnabled bool `json:"refundEnabled"`
|
||||
RefundEnabled bool `json:"refund_enabled"`
|
||||
}
|
||||
|
||||
type UpdateProviderInstanceRequest struct {
|
||||
Name *string `json:"name"`
|
||||
Config map[string]string `json:"config"`
|
||||
SupportedTypes []string `json:"supportedTypes"`
|
||||
SupportedTypes []string `json:"supported_types"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
SortOrder *int `json:"sortOrder"`
|
||||
SortOrder *int `json:"sort_order"`
|
||||
Limits *string `json:"limits"`
|
||||
RefundEnabled *bool `json:"refundEnabled"`
|
||||
RefundEnabled *bool `json:"refund_enabled"`
|
||||
}
|
||||
type CreatePlanRequest struct {
|
||||
GroupID int64 `json:"groupId"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
OriginalPrice *float64 `json:"originalPrice"`
|
||||
ValidityDays int `json:"validityDays"`
|
||||
ValidityUnit string `json:"validityUnit"`
|
||||
OriginalPrice *float64 `json:"original_price"`
|
||||
ValidityDays int `json:"validity_days"`
|
||||
ValidityUnit string `json:"validity_unit"`
|
||||
Features string `json:"features"`
|
||||
ProductName string `json:"productName"`
|
||||
ForSale bool `json:"forSale"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
ProductName string `json:"product_name"`
|
||||
ForSale bool `json:"for_sale"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
type UpdatePlanRequest struct {
|
||||
GroupID *int64 `json:"groupId"`
|
||||
GroupID *int64 `json:"group_id"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Price *float64 `json:"price"`
|
||||
OriginalPrice *float64 `json:"originalPrice"`
|
||||
ValidityDays *int `json:"validityDays"`
|
||||
ValidityUnit *string `json:"validityUnit"`
|
||||
OriginalPrice *float64 `json:"original_price"`
|
||||
ValidityDays *int `json:"validity_days"`
|
||||
ValidityUnit *string `json:"validity_unit"`
|
||||
Features *string `json:"features"`
|
||||
ProductName *string `json:"productName"`
|
||||
ForSale *bool `json:"forSale"`
|
||||
SortOrder *int `json:"sortOrder"`
|
||||
ProductName *string `json:"product_name"`
|
||||
ForSale *bool `json:"for_sale"`
|
||||
SortOrder *int `json:"sort_order"`
|
||||
}
|
||||
|
||||
// PaymentConfigService manages payment configuration and CRUD for
|
||||
|
||||
@@ -73,16 +73,16 @@ type CreateOrderRequest struct {
|
||||
}
|
||||
|
||||
type CreateOrderResponse struct {
|
||||
OrderID int64 `json:"orderId"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
PayAmount float64 `json:"payAmount"`
|
||||
FeeRate float64 `json:"feeRate"`
|
||||
PayAmount float64 `json:"pay_amount"`
|
||||
FeeRate float64 `json:"fee_rate"`
|
||||
Status string `json:"status"`
|
||||
PaymentType string `json:"paymentType"`
|
||||
PayURL string `json:"payUrl,omitempty"`
|
||||
QRCode string `json:"qrCode,omitempty"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
PayURL string `json:"pay_url,omitempty"`
|
||||
QRCode string `json:"qr_code,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
type OrderListParams struct {
|
||||
@@ -109,9 +109,9 @@ type RefundPlan struct {
|
||||
type RefundResult struct {
|
||||
Success bool `json:"success"`
|
||||
Warning string `json:"warning,omitempty"`
|
||||
RequireForce bool `json:"requireForce,omitempty"`
|
||||
BalanceDeducted float64 `json:"balanceDeducted,omitempty"`
|
||||
SubDaysDeducted int `json:"subscriptionDaysDeducted,omitempty"`
|
||||
RequireForce bool `json:"require_force,omitempty"`
|
||||
BalanceDeducted float64 `json:"balance_deducted,omitempty"`
|
||||
SubDaysDeducted int `json:"subscription_days_deducted,omitempty"`
|
||||
}
|
||||
|
||||
type DashboardStats struct {
|
||||
|
||||
@@ -226,11 +226,11 @@ const props = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
save: [payload: {
|
||||
providerKey: string
|
||||
provider_key: string
|
||||
name: string
|
||||
supportedTypes: string[]
|
||||
supported_types: string[]
|
||||
enabled: boolean
|
||||
refundEnabled: boolean
|
||||
refund_enabled: boolean
|
||||
config: Record<string, string>
|
||||
limits: string
|
||||
}]
|
||||
@@ -388,11 +388,11 @@ function handleSave() {
|
||||
}
|
||||
|
||||
emit('save', {
|
||||
providerKey: form.provider_key,
|
||||
provider_key: form.provider_key,
|
||||
name: form.name,
|
||||
supportedTypes: form.supported_types,
|
||||
supported_types: form.supported_types,
|
||||
enabled: form.enabled,
|
||||
refundEnabled: form.refund_enabled,
|
||||
refund_enabled: form.refund_enabled,
|
||||
config: filteredConfig,
|
||||
limits: serializeLimits(),
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ const emit = defineEmits<{
|
||||
create: []
|
||||
edit: [provider: ProviderInstance]
|
||||
delete: [provider: ProviderInstance]
|
||||
toggleField: [provider: ProviderInstance, field: 'enabled' | 'refundEnabled']
|
||||
toggleField: [provider: ProviderInstance, field: 'enabled' | 'refund_enabled']
|
||||
toggleType: [provider: ProviderInstance, type: string]
|
||||
}>()
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<!-- Right: toggles + actions -->
|
||||
<div class="flex items-center gap-4">
|
||||
<ToggleSwitch :label="t('common.enabled')" :checked="provider.enabled" @toggle="emit('toggleField', 'enabled')" />
|
||||
<ToggleSwitch :label="t('admin.settings.payment.refundEnabled')" :checked="provider.refund_enabled" @toggle="emit('toggleField', 'refundEnabled')" />
|
||||
<ToggleSwitch :label="t('admin.settings.payment.refundEnabled')" :checked="provider.refund_enabled" @toggle="emit('toggleField', 'refund_enabled')" />
|
||||
<div class="flex items-center gap-1 border-l border-gray-200 pl-3 dark:border-dark-600">
|
||||
<button @click="emit('edit')" class="btn-icon text-blue-500 hover:text-blue-700" :title="t('common.edit')">
|
||||
<Icon name="edit" size="sm" />
|
||||
@@ -81,7 +81,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleField: [field: 'enabled' | 'refundEnabled']
|
||||
toggleField: [field: 'enabled' | 'refund_enabled']
|
||||
toggleType: [type: string]
|
||||
edit: []
|
||||
delete: []
|
||||
|
||||
@@ -114,18 +114,18 @@ export interface ProviderInstance {
|
||||
|
||||
export interface CreateOrderRequest {
|
||||
amount: number
|
||||
paymentType: string
|
||||
orderType: string
|
||||
planId?: number
|
||||
payment_type: string
|
||||
order_type: string
|
||||
plan_id?: number
|
||||
}
|
||||
|
||||
export interface CreateOrderResult {
|
||||
orderId: number
|
||||
payUrl?: string
|
||||
qrCode?: string
|
||||
clientSecret?: string
|
||||
payAmount: number
|
||||
expiresAt: string
|
||||
order_id: number
|
||||
pay_url?: string
|
||||
qr_code?: string
|
||||
client_secret?: string
|
||||
pay_amount: number
|
||||
expires_at: string
|
||||
}
|
||||
|
||||
export interface DashboardStats {
|
||||
|
||||
@@ -2887,7 +2887,7 @@ async function handleSaveProvider(payload: any) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleField(provider: ProviderInstance, field: 'enabled' | 'refundEnabled') {
|
||||
async function handleToggleField(provider: ProviderInstance, field: 'enabled' | 'refund_enabled') {
|
||||
const newValue = field === 'enabled' ? !provider.enabled : !provider.refund_enabled
|
||||
try {
|
||||
await adminAPI.payment.updateProvider(provider.id, { [field]: newValue })
|
||||
@@ -2905,7 +2905,7 @@ async function handleToggleType(provider: ProviderInstance, type: string) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await adminAPI.payment.updateProvider(provider.id, { supportedTypes: updated } as any)
|
||||
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'))) }
|
||||
}
|
||||
|
||||
@@ -236,19 +236,19 @@ function openPlanEdit(plan: SubscriptionPlan | null) {
|
||||
showPlanDialog.value = true
|
||||
}
|
||||
|
||||
/** Build request payload with camelCase keys matching backend JSON tags */
|
||||
/** Build request payload with snake_case keys matching backend JSON tags */
|
||||
function buildPlanPayload() {
|
||||
const features = planFeaturesText.value.split('\n').map(f => f.trim()).filter(Boolean).join('\n')
|
||||
return {
|
||||
name: planForm.name,
|
||||
groupId: planForm.group_id,
|
||||
group_id: planForm.group_id,
|
||||
description: planForm.description,
|
||||
price: planForm.price,
|
||||
originalPrice: planForm.original_price || 0,
|
||||
validityDays: planForm.validity_days,
|
||||
validityUnit: planForm.validity_unit,
|
||||
forSale: planForm.for_sale,
|
||||
sortOrder: planForm.sort_order,
|
||||
original_price: planForm.original_price || 0,
|
||||
validity_days: planForm.validity_days,
|
||||
validity_unit: planForm.validity_unit,
|
||||
for_sale: planForm.for_sale,
|
||||
sort_order: planForm.sort_order,
|
||||
features,
|
||||
}
|
||||
}
|
||||
@@ -267,7 +267,7 @@ async function handleSavePlan() {
|
||||
/** Quick toggle for_sale from the list */
|
||||
async function toggleForSale(plan: SubscriptionPlan) {
|
||||
try {
|
||||
await adminPaymentAPI.updatePlan(plan.id, { forSale: !plan.for_sale })
|
||||
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')))
|
||||
|
||||
@@ -226,16 +226,16 @@ async function createOrder(orderAmount: number, orderType: string, planId?: numb
|
||||
try {
|
||||
const result = await paymentStore.createOrder({
|
||||
amount: orderAmount,
|
||||
paymentType: selectedMethod.value,
|
||||
orderType: orderType,
|
||||
planId: planId,
|
||||
payment_type: selectedMethod.value,
|
||||
order_type: orderType,
|
||||
plan_id: planId,
|
||||
})
|
||||
if (result.clientSecret) {
|
||||
router.push({ path: '/payment/stripe', query: { order_id: String(result.orderId), client_secret: result.clientSecret } })
|
||||
} else if (result.qrCode) {
|
||||
router.push({ path: '/payment/qrcode', query: { order_id: String(result.orderId), qr: result.qrCode || '', pay_url: result.payUrl || '', expires_at: result.expiresAt || '' } })
|
||||
} else if (result.payUrl) {
|
||||
window.location.href = result.payUrl
|
||||
if (result.client_secret) {
|
||||
router.push({ path: '/payment/stripe', query: { order_id: String(result.order_id), client_secret: result.client_secret } })
|
||||
} else if (result.qr_code) {
|
||||
router.push({ path: '/payment/qrcode', query: { order_id: String(result.order_id), qr: result.qr_code || '', pay_url: result.pay_url || '', expires_at: result.expires_at || '' } })
|
||||
} else if (result.pay_url) {
|
||||
window.location.href = result.pay_url
|
||||
} else {
|
||||
errorMessage.value = t('payment.result.failed')
|
||||
appStore.showError(errorMessage.value)
|
||||
|
||||
Reference in New Issue
Block a user