Skip to content

fix: return error from AssignPaymentOrder when no provider is matched#694

Merged
onahprosper merged 3 commits intostablefrom
fix/assign-payment-order-empty-queue
Feb 18, 2026
Merged

fix: return error from AssignPaymentOrder when no provider is matched#694
onahprosper merged 3 commits intostablefrom
fix/assign-payment-order-empty-queue

Conversation

@sundayonah
Copy link
Collaborator

@sundayonah sundayonah commented Feb 18, 2026

[[## Summary

  • AssignPaymentOrder was silently swallowing redis: nil errors when both the current and previous bucket queues had no matching providers, returning nil (success) instead of an error.
  • This caused RetryStaleUserOperations in stale_ops.go to treat "no providers available" as a successful assignment, hitting continue and skipping both the fallback provider path and the refund path.
  • Orders would loop through stale_ops every 60s with no DB update, until updated_at aged past the 15-minute refund query window, permanently orphaning them.

What changed

In AssignPaymentOrder (services/priority_queue.go), removed the redis: nil error swallowing so the function correctly returns an error when no provider is matched. The error is wrapped with context ("no provider matched for order") for debuggability.

Impact on callers

Caller Behavior Impact
stale_ops.go (refund flow) if err == nil { continue } Fixed — now falls through to fallback/refund
order_requests.go (reassignment) Logs error, continues No change — already handles errors gracefully
common/order.go (blockchain indexing) _ = assignPaymentOrder(...) No change — return value is discarded
priority_queue.go (recursive matchRate) Propagates error up Correct — outer call still tries _prev queue
Tests assert.Error(t, err, "...expected to fail...") Already expects this error

Test plan

  • Verify orders with no available providers now reach fallback assignment
  • Verify orders where both queue and fallback fail proceed to refund
  • Verify normal assignment flow (providers available) is unaffected
  • Verify existing TestAssignPaymentOrder and fallback tests pass](fix: return error from AssignPaymentOrder when no provider is matched)](fix: return error from AssignPaymentOrder when no provider is matched)

Summary by CodeRabbit

  • Bug Fixes

    • Improved error messages when order assignment fails due to no matching provider
  • Improvements

    • Added fallback provider mechanism to enable order assignment when exact match is unavailable
    • Adjusted rate calculation methodology

onahprosper and others added 2 commits February 18, 2026 16:26
AssignPaymentOrder was silently swallowing redis: nil errors when both
the current and previous bucket queues had no matching providers. This
caused stale_ops to treat "no providers available" as a successful
assignment, skipping both fallback and refund paths. Orders would then
loop without DB updates until updated_at aged past the 15-minute refund
window, permanently orphaning them.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds TestAssignPaymentOrderReturnsErrorWhenQueueEmpty which ensures
that AssignPaymentOrder returns a "no provider matched" error when both
current and previous bucket queues have no providers, rather than
silently returning nil.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

These changes simplify error handling in priority queue assignment by removing special-case error checks, add test coverage for empty queue scenarios, streamline rate extraction to use only sellRate, and implement a fallback provider validation mechanism when primary matching fails.

Changes

Cohort / File(s) Summary
Error Handling & Queue Management
services/priority_queue.go, services/priority_queue_test.go
Simplified error handling in matchRate to return wrapped error for any failure instead of selectively propagating certain errors. Added test case validating behavior when Redis queues are empty, asserting proper error message for unmatched orders.
Rate Extraction Logic
tasks/rates.go
Removed buyRate extraction and average calculation logic; now returns decimal value derived solely from sellRate.
Provider Validation Fallback
utils/utils.go
Added config package import and implemented fallback mechanism in validateBucketRate: attempts validation with configured FallbackProviderID before logging no-suitable-provider warning.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • 5ran6
  • onahprosper

Poem

🐰 With errors streamlined and queues set free,
Rates simplified—sellRate, that's the key!
Fallback providers stand ready and true,
When matching fails, there's always a clue!
Tests now ensure when buckets run bare,

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: fixing AssignPaymentOrder to return an error when no provider is matched, which is the core fix addressing the silent error-swallowing bug.
Description check ✅ Passed The description is comprehensive and well-structured, covering the bug (silent error swallowing), root cause (orphaned orders), changes made, impact on all callers, and a detailed test plan—exceeding template requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/assign-payment-order-empty-queue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
utils/utils.go (1)

1033-1039: Log the fallback validation failure for observability.

When fallbackErr != nil, it's silently swallowed. If the fallback provider is misconfigured or the DB lookup fails, there's no signal in the logs.

♻️ Suggested addition
 		fallbackResult, fallbackErr := validateProviderRate(ctx, token, currency, amount, fallbackID, networkIdentifier)
 		if fallbackErr == nil {
 			return fallbackResult, nil
 		}
+		logger.WithFields(logger.Fields{
+			"FallbackProviderID": fallbackID,
+			"Error":              fmt.Sprintf("%v", fallbackErr),
+			"Token":              token.Symbol,
+			"Currency":           currency.Code,
+		}).Warnf("ValidateRate.FallbackValidationFailed: fallback provider could not be used")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/utils.go` around lines 1033 - 1039, When no exact match is found and a
fallback provider ID is present (config.OrderConfig().FallbackProviderID), the
call to validateProviderRate may return a non-nil fallbackErr that is currently
ignored; update the branch where validateProviderRate is called (around the
foundExactMatch logic and the fallbackResult/fallbackErr handling) to log the
fallbackErr with context (include fallbackID, token, currency, amount,
networkIdentifier) using the existing logger so failures in validateProviderRate
are observable before falling through or returning the fallbackResult when
successful.
tasks/rates.go (1)

68-71: Remove the commented-out buyRate extraction instead of leaving it as dead code.

Leaving commented-out code signals the change is tentative and adds noise. If the revert is no longer on the table, delete these lines.

♻️ Proposed cleanup
-	// buyRate, ok := rateData["buyRate"].(float64)
-	// if !ok {
-	// 	return decimal.Zero, fmt.Errorf("ComputeMarketRate: Invalid buyRate format")
-	// }
-
 	sellRate, ok := rateData["sellRate"].(float64)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tasks/rates.go` around lines 68 - 71, Delete the dead commented-out buyRate
extraction lines (the three commented lines referencing buyRate and its ok
check) from the ComputeMarketRate implementation so the codebase has no leftover
commented code; ensure there are no remaining references to buyRate in that
function and run unit tests or linters to confirm nothing else depends on the
removed comments.
services/priority_queue_test.go (1)

701-702: Ignore errors from Del silently — at minimum assign to _ to signal intent.

-		db.RedisClient.Del(ctx, redisKey)
-		db.RedisClient.Del(ctx, redisKey+"_prev")
+		_, _ = db.RedisClient.Del(ctx, redisKey).Result()
+		_, _ = db.RedisClient.Del(ctx, redisKey+"_prev").Result()

If Del fails (e.g., miniredis panic or context cancellation), the subsequent AssignPaymentOrder call would find a non-empty queue and return nil, causing the assertion at line 717 to fail with an unintuitive message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/priority_queue_test.go` around lines 701 - 702, The test currently
calls db.RedisClient.Del(ctx, redisKey) and db.RedisClient.Del(ctx,
redisKey+"_prev") without handling return values; change these calls to
explicitly ignore errors by assigning the results to the blank identifier (e.g.,
_, _ = db.RedisClient.Del(ctx, redisKey) and _, _ = db.RedisClient.Del(ctx,
redisKey+"_prev")) so any Del failure (miniredis/context) won’t cause the test
to proceed with leftover keys and break the subsequent AssignPaymentOrder call;
keep the same ctx and redisKey identifiers when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@services/priority_queue_test.go`:
- Around line 701-702: The test currently calls db.RedisClient.Del(ctx,
redisKey) and db.RedisClient.Del(ctx, redisKey+"_prev") without handling return
values; change these calls to explicitly ignore errors by assigning the results
to the blank identifier (e.g., _, _ = db.RedisClient.Del(ctx, redisKey) and _, _
= db.RedisClient.Del(ctx, redisKey+"_prev")) so any Del failure
(miniredis/context) won’t cause the test to proceed with leftover keys and break
the subsequent AssignPaymentOrder call; keep the same ctx and redisKey
identifiers when making the change.

In `@tasks/rates.go`:
- Around line 68-71: Delete the dead commented-out buyRate extraction lines (the
three commented lines referencing buyRate and its ok check) from the
ComputeMarketRate implementation so the codebase has no leftover commented code;
ensure there are no remaining references to buyRate in that function and run
unit tests or linters to confirm nothing else depends on the removed comments.

In `@utils/utils.go`:
- Around line 1033-1039: When no exact match is found and a fallback provider ID
is present (config.OrderConfig().FallbackProviderID), the call to
validateProviderRate may return a non-nil fallbackErr that is currently ignored;
update the branch where validateProviderRate is called (around the
foundExactMatch logic and the fallbackResult/fallbackErr handling) to log the
fallbackErr with context (include fallbackID, token, currency, amount,
networkIdentifier) using the existing logger so failures in validateProviderRate
are observable before falling through or returning the fallbackResult when
successful.

fetchExternalRate was changed to use only sellRate (buyRate commented
out), but the tests still expected the old average-of-buy-and-sell
behavior. Update SuccessfulResponse to expect sellRate and
MalformedRateData to test an invalid sellRate instead of buyRate.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sundayonah sundayonah changed the base branch from main to stable February 18, 2026 17:07
@onahprosper onahprosper merged commit e070221 into stable Feb 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments