fixed code duplication #459

Merged
tech merged 1 commits from mntx-452 into main 2026-02-10 13:40:17 +00:00
Showing only changes of commit 2711d601b0 - Show all commits

View File

@@ -29,6 +29,77 @@ type cardPayoutProcessor struct {
producer msg.Producer producer msg.Producer
} }
func mergePayoutStateWithExisting(state, existing *model.CardPayout) {
if state == nil || existing == nil {
return
}
state.ID = existing.ID // preserve ID for upsert
if !existing.CreatedAt.IsZero() {
state.CreatedAt = existing.CreatedAt
}
if state.OperationRef == "" {
state.OperationRef = existing.OperationRef
}
if state.IdempotencyKey == "" {
state.IdempotencyKey = existing.IdempotencyKey
}
if state.IntentRef == "" {
state.IntentRef = existing.IntentRef
}
}
func (p *cardPayoutProcessor) findAndMergePayoutState(ctx context.Context, state *model.CardPayout) (*model.CardPayout, error) {
if p == nil || state == nil {
return nil, nil
}
existing, err := p.store.Payouts().FindByPaymentID(ctx, state.PaymentRef)
if err != nil {
return nil, err
}
mergePayoutStateWithExisting(state, existing)
return existing, nil
}
func (p *cardPayoutProcessor) resolveProjectID(requestProjectID int64, logFieldKey, logFieldValue string) (int64, error) {
projectID := requestProjectID
if projectID == 0 {
projectID = p.config.ProjectID
}
if projectID == 0 {
p.logger.Warn("Monetix project_id is not configured", zap.String(logFieldKey, logFieldValue))
return 0, merrors.Internal("monetix project_id is not configured")
}
return projectID, nil
}
func applyCardPayoutSendResult(state *model.CardPayout, result *monetix.CardPayoutSendResult) {
if state == nil || result == nil {
return
}
state.ProviderPaymentID = strings.TrimSpace(result.ProviderRequestID)
if result.Accepted {
state.Status = model.PayoutStatusWaiting
return
}
state.Status = model.PayoutStatusFailed
state.ProviderCode = strings.TrimSpace(result.ErrorCode)
state.ProviderMessage = strings.TrimSpace(result.ErrorMessage)
}
func payoutStateLogFields(state *model.CardPayout) []zap.Field {
if state == nil {
return nil
}
return []zap.Field{
zap.String("payment_ref", state.PaymentRef),
zap.String("customer_id", state.CustomerID),
zap.String("operation_ref", state.OperationRef),
zap.String("idempotency_key", state.IdempotencyKey),
zap.String("intent_ref", state.IntentRef),
}
}
func newCardPayoutProcessor( func newCardPayoutProcessor(
logger mlogger.Logger, logger mlogger.Logger,
cfg monetix.Config, cfg monetix.Config,
@@ -77,13 +148,9 @@ func (p *cardPayoutProcessor) Submit(ctx context.Context, req *mntxv1.CardPayout
return nil, err return nil, err
} }
projectID := req.GetProjectId() projectID, err := p.resolveProjectID(req.GetProjectId(), "payout_id", req.GetPayoutId())
if projectID == 0 { if err != nil {
projectID = p.config.ProjectID return nil, err
}
if projectID == 0 {
p.logger.Warn("Monetix project_id is not configured", zap.String("payout_id", req.GetPayoutId()))
return nil, merrors.Internal("monetix project_id is not configured")
} }
now := p.clock.Now() now := p.clock.Now()
@@ -106,18 +173,7 @@ func (p *cardPayoutProcessor) Submit(ctx context.Context, req *mntxv1.CardPayout
} }
// Keep CreatedAt/refs if record already exists. // Keep CreatedAt/refs if record already exists.
if existing, err := p.store.Payouts().FindByPaymentID(ctx, state.PaymentRef); err == nil && existing != nil { _, _ = p.findAndMergePayoutState(ctx, state)
state.ID = existing.ID // preserve ID for upsert
if !existing.CreatedAt.IsZero() {
state.CreatedAt = existing.CreatedAt
}
if state.OperationRef == "" {
state.OperationRef = existing.OperationRef
}
if state.IdempotencyKey == "" {
state.IdempotencyKey = existing.IdempotencyKey
}
}
client := monetix.NewClient(p.config, p.httpClient, p.logger) client := monetix.NewClient(p.config, p.httpClient, p.logger)
apiReq := buildCardPayoutRequest(projectID, req) apiReq := buildCardPayoutRequest(projectID, req)
@@ -129,39 +185,18 @@ func (p *cardPayoutProcessor) Submit(ctx context.Context, req *mntxv1.CardPayout
state.UpdatedAt = p.clock.Now() state.UpdatedAt = p.clock.Now()
if e := p.updatePayoutStatus(ctx, state); e != nil { if e := p.updatePayoutStatus(ctx, state); e != nil {
p.logger.Warn("Failed to update payout status", fields := append([]zap.Field{zap.Error(e)}, payoutStateLogFields(state)...)
zap.Error(e), p.logger.Warn("Failed to update payout status", fields...)
zap.String("payment_ref", state.PaymentRef),
zap.String("customer_id", state.CustomerID),
zap.String("operation_ref", state.OperationRef),
zap.String("idempotency_key", state.IdempotencyKey),
zap.String("operation_ref", state.OperationRef),
zap.String("intent_ref", state.IntentRef),
)
} }
p.logger.Warn("Monetix payout submission failed", fields := append([]zap.Field{zap.Error(err)}, payoutStateLogFields(state)...)
zap.Error(err), p.logger.Warn("Monetix payout submission failed", fields...)
zap.String("payment_ref", state.PaymentRef),
zap.String("customer_id", state.CustomerID),
zap.String("operation_ref", state.OperationRef),
zap.String("idempotency_key", state.IdempotencyKey),
zap.String("operation_ref", state.OperationRef),
zap.String("intent_ref", state.IntentRef),
)
return nil, err return nil, err
} }
// Provider request id is the provider-side payment id in your model. // Provider request id is the provider-side payment id in your model.
state.ProviderPaymentID = strings.TrimSpace(result.ProviderRequestID) applyCardPayoutSendResult(state, result)
if result.Accepted {
state.Status = model.PayoutStatusWaiting
} else {
state.Status = model.PayoutStatusFailed
state.ProviderCode = strings.TrimSpace(result.ErrorCode)
state.ProviderMessage = strings.TrimSpace(result.ErrorMessage)
}
state.UpdatedAt = p.clock.Now() state.UpdatedAt = p.clock.Now()
if err := p.updatePayoutStatus(ctx, state); err != nil { if err := p.updatePayoutStatus(ctx, state); err != nil {
@@ -223,13 +258,9 @@ func (p *cardPayoutProcessor) SubmitToken(ctx context.Context, req *mntxv1.CardT
return nil, err return nil, err
} }
projectID := req.GetProjectId() projectID, err := p.resolveProjectID(req.GetProjectId(), "payout_id", req.GetPayoutId())
if projectID == 0 { if err != nil {
projectID = p.config.ProjectID return nil, err
}
if projectID == 0 {
p.logger.Warn("Monetix project_id is not configured", zap.String("payout_id", req.GetPayoutId()))
return nil, merrors.Internal("monetix project_id is not configured")
} }
now := p.clock.Now() now := p.clock.Now()
@@ -246,17 +277,7 @@ func (p *cardPayoutProcessor) SubmitToken(ctx context.Context, req *mntxv1.CardT
UpdatedAt: now, UpdatedAt: now,
} }
if existing, err := p.store.Payouts().FindByPaymentID(ctx, state.PaymentRef); err == nil && existing != nil { _, _ = p.findAndMergePayoutState(ctx, state)
if !existing.CreatedAt.IsZero() {
state.CreatedAt = existing.CreatedAt
}
if state.OperationRef == "" {
state.OperationRef = existing.OperationRef
}
if state.IdempotencyKey == "" {
state.IdempotencyKey = existing.IdempotencyKey
}
}
client := monetix.NewClient(p.config, p.httpClient, p.logger) client := monetix.NewClient(p.config, p.httpClient, p.logger)
apiReq := buildCardTokenPayoutRequest(projectID, req) apiReq := buildCardTokenPayoutRequest(projectID, req)
@@ -278,14 +299,7 @@ func (p *cardPayoutProcessor) SubmitToken(ctx context.Context, req *mntxv1.CardT
return nil, err return nil, err
} }
state.ProviderPaymentID = strings.TrimSpace(result.ProviderRequestID) applyCardPayoutSendResult(state, result)
if result.Accepted {
state.Status = model.PayoutStatusWaiting
} else {
state.Status = model.PayoutStatusFailed
state.ProviderCode = strings.TrimSpace(result.ErrorCode)
state.ProviderMessage = strings.TrimSpace(result.ErrorMessage)
}
state.UpdatedAt = p.clock.Now() state.UpdatedAt = p.clock.Now()
if err := p.updatePayoutStatus(ctx, state); err != nil { if err := p.updatePayoutStatus(ctx, state); err != nil {
@@ -331,13 +345,9 @@ func (p *cardPayoutProcessor) Tokenize(ctx context.Context, req *mntxv1.CardToke
return nil, err return nil, err
} }
projectID := req.GetProjectId() projectID, err := p.resolveProjectID(req.GetProjectId(), "request_id", req.GetRequestId())
if projectID == 0 { if err != nil {
projectID = p.config.ProjectID return nil, err
}
if projectID == 0 {
p.logger.Warn("Monetix project_id is not configured", zap.String("request_id", req.GetRequestId()))
return nil, merrors.Internal("monetix project_id is not configured")
} }
req = sanitizeCardTokenizeRequest(req) req = sanitizeCardTokenizeRequest(req)
@@ -450,22 +460,15 @@ func (p *cardPayoutProcessor) ProcessCallback(ctx context.Context, payload []byt
state := CardPayoutStateFromProto(p.clock, pbState) state := CardPayoutStateFromProto(p.clock, pbState)
// Preserve CreatedAt + internal keys from existing record if present. // Preserve CreatedAt + internal keys from existing record if present.
if existing, err := p.store.Payouts().FindByPaymentID(ctx, state.PaymentRef); err != nil { existing, err := p.findAndMergePayoutState(ctx, state)
if err != nil {
p.logger.Warn("Failed to fetch payout state while processing callback", p.logger.Warn("Failed to fetch payout state while processing callback",
zap.Error(err), zap.Error(err),
zap.String("payment_ref", state.PaymentRef), zap.String("payment_ref", state.PaymentRef),
) )
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} else if existing != nil { }
if !existing.CreatedAt.IsZero() { if existing != nil {
state.CreatedAt = existing.CreatedAt
}
if state.OperationRef == "" {
state.OperationRef = existing.OperationRef
}
if state.IdempotencyKey == "" {
state.IdempotencyKey = existing.IdempotencyKey
}
// keep failure reason if you want, or override depending on callback semantics // keep failure reason if you want, or override depending on callback semantics
if state.FailureReason == "" { if state.FailureReason == "" {
state.FailureReason = existing.FailureReason state.FailureReason = existing.FailureReason