89 lines
2.7 KiB
Go
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...)
|
|
}
|