Files
kra-oa/internal/biz/payment/payment_log.go
T
2026-09-24 17:44:17 +08:00

89 lines
2.7 KiB
Go

package payment
import (
"context"
"errors"
"log/slog"
"net"
"time"
)
// PaymentLogger isolates payment audit logging from the payment workflow and
// lets an application replace slog with its own structured logger.
type PaymentLogger interface {
Info(context.Context, string, ...any)
Error(context.Context, string, ...any)
}
type slogPaymentLogger struct {
logger *slog.Logger
}
func NewPaymentLogger(logger *slog.Logger) PaymentLogger {
if logger == nil {
logger = slog.Default()
}
return &slogPaymentLogger{logger: logger.With("mod", "payment")}
}
func (l *slogPaymentLogger) Info(ctx context.Context, message string, args ...any) {
l.logger.InfoContext(ctx, message, args...)
}
func (l *slogPaymentLogger) Error(ctx context.Context, message string, args ...any) {
for i := 0; i+1 < len(args); i += 2 {
if key, ok := args[i].(string); ok && (key == "error" || key == "err") {
args[i] = "error_code"
if err, ok := args[i+1].(error); ok {
args[i+1] = paymentErrorCode(err)
} else {
args[i+1] = "redacted"
}
}
}
l.logger.ErrorContext(ctx, message, args...)
}
func paymentErrorCode(err error) string {
switch {
case errors.Is(err, ErrPaymentOrderNotFound):
return "order_not_found"
case errors.Is(err, ErrPaymentProviderNotFound):
return "provider_not_configured"
case errors.Is(err, ErrPaymentOrderConflict):
return "order_conflict"
case errors.Is(err, ErrPaymentProviderConflict):
return "provider_conflict"
case errors.Is(err, ErrPaymentOrderBusy):
return "order_busy"
case errors.Is(err, ErrPaymentOrderState):
return "invalid_order_state"
case errors.Is(err, ErrPaymentOperationRejected):
return "provider_rejected"
case errors.Is(err, context.Canceled):
return "cancelled"
case errors.Is(err, context.DeadlineExceeded):
return "timeout"
default:
var networkError net.Error
if errors.As(err, &networkError) && networkError.Timeout() {
return "timeout"
}
return "operation_failed"
}
}
func (uc *PaymentUsecase) logPaymentOperation(ctx context.Context, operation string, started time.Time, provider, tradeNo string, result *PaymentResult, err error) {
attrs := []any{"provider", provider, "trade_no", tradeNo, "duration_ms", time.Since(started).Milliseconds()}
if result != nil {
attrs = append(attrs, "provider_trade_no", result.ProviderTradeNo, "provider_status", result.Status,
"order_status", result.OrderStatus, "fulfillment_status", result.FulfillmentStatus,
"refund_status", result.RefundStatus, "duplicate", result.Duplicate)
}
if err != nil {
uc.logger.Error(ctx, "payment."+operation+".failed", append(attrs, "error_code", paymentErrorCode(err))...)
return
}
uc.logger.Info(ctx, "payment."+operation+".completed", attrs...)
}