fix: structured errors, slog logging, snake_case JSON tags

- Registry uses infraerrors.NotFound instead of fmt.Errorf
- Webhook handler uses slog instead of log.Printf
- PaymentConfigService JSON tags unified to snake_case
- PaymentOrderExpiryService uses slog
- Remove duplicate declarations in registry.go
This commit is contained in:
erio
2026-04-05 22:25:51 +08:00
parent 5cde1bcc59
commit 9af4a53fe9
4 changed files with 40 additions and 35 deletions
@@ -2,7 +2,7 @@ package handler
import (
"io"
"log"
"log/slog"
"net/http"
"github.com/Wei-Shaw/sub2api/internal/payment"
@@ -53,14 +53,14 @@ func (h *PaymentWebhookHandler) StripeWebhook(c *gin.Context) {
func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string) {
body, err := io.ReadAll(io.LimitReader(c.Request.Body, 1<<20)) // 1MB limit
if err != nil {
log.Printf("[Payment Webhook] failed to read body for %s: %v", providerKey, err)
slog.Error("[Payment Webhook] failed to read body", "provider", providerKey, "error", err)
c.String(http.StatusBadRequest, "failed to read body")
return
}
provider, err := h.registry.GetProviderByKey(providerKey)
if err != nil {
log.Printf("[Payment Webhook] provider %s not registered: %v", providerKey, err)
slog.Warn("[Payment Webhook] provider not registered", "provider", providerKey, "error", err)
c.String(http.StatusOK, successResponse(providerKey))
return
}
@@ -72,7 +72,7 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
notification, err := provider.VerifyNotification(c.Request.Context(), string(body), headers)
if err != nil {
log.Printf("[Payment Webhook] %s verify failed: %v", providerKey, err)
slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err)
c.String(http.StatusBadRequest, "verify failed")
return
}
@@ -84,7 +84,7 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
}
if err := h.paymentService.HandlePaymentNotification(c.Request.Context(), notification, providerKey); err != nil {
log.Printf("[Payment Webhook] %s handle notification failed: %v", providerKey, err)
slog.Error("[Payment Webhook] handle notification failed", "provider", providerKey, "error", err)
c.String(http.StatusInternalServerError, "handle failed")
return
}
+11 -6
View File
@@ -1,8 +1,9 @@
package payment
import (
"fmt"
"sync"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// Registry is a thread-safe registry mapping PaymentType to Provider.
@@ -11,6 +12,10 @@ type Registry struct {
providers map[PaymentType]Provider
}
// ErrProviderNotFound is returned when a requested payment provider is not registered.
var ErrProviderNotFound = infraerrors.NotFound("PROVIDER_NOT_FOUND", "payment provider not registered")
// NewRegistry creates a new empty provider registry.
func NewRegistry() *Registry {
return &Registry{
@@ -34,7 +39,7 @@ func (r *Registry) GetProvider(t PaymentType) (Provider, error) {
defer r.mu.RUnlock()
p, ok := r.providers[t]
if !ok {
return nil, fmt.Errorf("no payment provider registered for type: %s", t)
return nil, ErrProviderNotFound
}
return p, nil
}
@@ -51,7 +56,7 @@ func (r *Registry) GetProviderByKey(key string) (Provider, error) {
}
seen[k] = true
}
return nil, fmt.Errorf("no payment provider registered with key: %s", key)
return nil, ErrProviderNotFound
}
// GetProviderKey returns the provider key for the given payment type,
@@ -81,7 +86,7 @@ func (r *Registry) SupportedTypes() []PaymentType {
func (r *Registry) Clear() {
r.mu.Lock()
defer r.mu.Unlock()
for k := range r.providers {
delete(r.providers, k)
}
r.providers = make(map[PaymentType]Provider)
}
// Clear removes all registered providers.
@@ -37,38 +37,38 @@ const (
// PaymentConfig holds the payment system configuration.
type PaymentConfig struct {
Enabled bool `json:"enabled"`
MinAmount float64 `json:"minAmount"`
MaxAmount float64 `json:"maxAmount"`
DailyLimit float64 `json:"dailyLimit"`
OrderTimeoutMin int `json:"orderTimeoutMinutes"`
MaxPendingOrders int `json:"maxPendingOrders"`
EnabledTypes []string `json:"enabledTypes"`
BalanceDisabled bool `json:"balanceDisabled"`
LoadBalanceStrategy string `json:"loadBalanceStrategy"`
ProductNamePrefix string `json:"productNamePrefix"`
ProductNameSuffix string `json:"productNameSuffix"`
MinAmount float64 `json:"min_amount"`
MaxAmount float64 `json:"max_amount"`
DailyLimit float64 `json:"daily_limit"`
OrderTimeoutMin int `json:"order_timeout_minutes"`
MaxPendingOrders int `json:"max_pending_orders"`
EnabledTypes []string `json:"enabled_payment_types"`
BalanceDisabled bool `json:"balance_disabled"`
LoadBalanceStrategy string `json:"load_balance_strategy"`
ProductNamePrefix string `json:"product_name_prefix"`
ProductNameSuffix string `json:"product_name_suffix"`
}
// UpdatePaymentConfigRequest contains fields to update payment configuration.
type UpdatePaymentConfigRequest struct {
Enabled *bool `json:"enabled"`
MinAmount *float64 `json:"minAmount"`
MaxAmount *float64 `json:"maxAmount"`
DailyLimit *float64 `json:"dailyLimit"`
OrderTimeoutMin *int `json:"orderTimeoutMinutes"`
MaxPendingOrders *int `json:"maxPendingOrders"`
EnabledTypes []string `json:"enabledTypes"`
BalanceDisabled *bool `json:"balanceDisabled"`
LoadBalanceStrategy *string `json:"loadBalanceStrategy"`
ProductNamePrefix *string `json:"productNamePrefix"`
ProductNameSuffix *string `json:"productNameSuffix"`
MinAmount *float64 `json:"min_amount"`
MaxAmount *float64 `json:"max_amount"`
DailyLimit *float64 `json:"daily_limit"`
OrderTimeoutMin *int `json:"order_timeout_minutes"`
MaxPendingOrders *int `json:"max_pending_orders"`
EnabledTypes []string `json:"enabled_payment_types"`
BalanceDisabled *bool `json:"balance_disabled"`
LoadBalanceStrategy *string `json:"load_balance_strategy"`
ProductNamePrefix *string `json:"product_name_prefix"`
ProductNameSuffix *string `json:"product_name_suffix"`
}
// MethodLimits holds per-payment-type limits.
type MethodLimits struct {
PaymentType string `json:"paymentType"`
FeeRate float64 `json:"feeRate"`
DailyLimit float64 `json:"dailyLimit"`
DailyLimit float64 `json:"daily_limit"`
SingleMin float64 `json:"singleMin"`
SingleMax float64 `json:"singleMax"`
}
@@ -2,7 +2,7 @@ package service
import (
"context"
"log"
"log/slog"
"sync"
"time"
)
@@ -62,10 +62,10 @@ func (s *PaymentOrderExpiryService) runOnce() {
expired, err := s.paymentSvc.ExpireTimedOutOrders(ctx)
if err != nil {
log.Printf("[PaymentOrderExpiry] failed to expire orders: %v", err)
slog.Error("[PaymentOrderExpiry] failed to expire orders", "error", err)
return
}
if expired > 0 {
log.Printf("[PaymentOrderExpiry] expired %d timed-out orders", expired)
slog.Info("[PaymentOrderExpiry] expired timed-out orders", "count", expired)
}
}