fixed code duplication #459
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user