diff --git a/backend/internal/service/payment_config_providers.go b/backend/internal/service/payment_config_providers.go index 0f7cb99a..3c406b45 100644 --- a/backend/internal/service/payment_config_providers.go +++ b/backend/internal/service/payment_config_providers.go @@ -231,10 +231,18 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in } } if req.AllowUserRefund != nil { - // Only allow enabling when refund_enabled is true + // Only allow enabling when refund_enabled is (or will be) true if *req.AllowUserRefund { - inst, err := s.entClient.PaymentProviderInstance.Get(ctx, id) - if err == nil && inst.RefundEnabled { + refundEnabled := false + if req.RefundEnabled != nil { + refundEnabled = *req.RefundEnabled + } else { + inst, err := s.entClient.PaymentProviderInstance.Get(ctx, id) + if err == nil { + refundEnabled = inst.RefundEnabled + } + } + if refundEnabled { u.SetAllowUserRefund(true) } } else { diff --git a/backend/internal/service/payment_refund.go b/backend/internal/service/payment_refund.go index 75d75b2f..99468433 100644 --- a/backend/internal/service/payment_refund.go +++ b/backend/internal/service/payment_refund.go @@ -2,6 +2,7 @@ package service import ( "context" + "errors" "fmt" "log/slog" "math" @@ -93,15 +94,16 @@ func (s *PaymentService) PrepareRefund(ctx context.Context, oid int64, amt float // Check provider instance allows admin refund inst, instErr := s.getOrderProviderInstance(ctx, o) if instErr != nil { - slog.Warn("refund: provider instance not found", "orderID", oid, "error", instErr) + slog.Warn("refund: provider instance lookup failed", "orderID", oid, "error", instErr) + return nil, nil, infraerrors.InternalServer("PROVIDER_LOOKUP_FAILED", "failed to look up payment provider for this order") } - if inst != nil && !inst.RefundEnabled { - return nil, nil, infraerrors.Forbidden("REFUND_DISABLED", "refund is not enabled for this provider") - } - if inst == nil && instErr == nil { + if inst == nil { // Legacy order without provider_instance_id — block refund return nil, nil, infraerrors.Forbidden("REFUND_DISABLED", "refund is not available for this order") } + if !inst.RefundEnabled { + return nil, nil, infraerrors.Forbidden("REFUND_DISABLED", "refund is not enabled for this provider") + } if math.IsNaN(amt) || math.IsInf(amt, 0) { return nil, nil, infraerrors.BadRequest("INVALID_AMOUNT", "invalid refund amount") } @@ -183,10 +185,17 @@ func (s *PaymentService) ExecuteRefund(ctx context.Context, p *RefundPlan) (*Ref if !s.hasAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED") { _, err := s.subscriptionSvc.ExtendSubscription(ctx, p.SubscriptionID, -p.SubDaysToDeduct) if err != nil { - slog.Info("subscription deduction would expire, revoking", "orderID", p.OrderID, "subID", p.SubscriptionID, "days", p.SubDaysToDeduct) - if revokeErr := s.subscriptionSvc.RevokeSubscription(ctx, p.SubscriptionID); revokeErr != nil { + if errors.Is(err, ErrAdjustWouldExpire) { + // Deduction would expire the subscription — revoke it entirely + slog.Info("subscription deduction would expire, revoking", "orderID", p.OrderID, "subID", p.SubscriptionID, "days", p.SubDaysToDeduct) + if revokeErr := s.subscriptionSvc.RevokeSubscription(ctx, p.SubscriptionID); revokeErr != nil { + s.restoreStatus(ctx, p) + return nil, fmt.Errorf("revoke subscription: %w", revokeErr) + } + } else { + // Other errors (DB failure, not found) — abort refund s.restoreStatus(ctx, p) - return nil, fmt.Errorf("revoke subscription: %w", revokeErr) + return nil, fmt.Errorf("deduct subscription days: %w", err) } } } else {