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:
erio
2026-04-07 11:27:00 +08:00
parent 610408a4d0
commit 6c5a2452f2
11 changed files with 73 additions and 73 deletions
@@ -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).
+3 -3
View File
@@ -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
+11 -11
View File
@@ -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: []
+9 -9
View File
@@ -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 {
+2 -2
View File
@@ -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')))
+9 -9
View File
@@ -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)