Cascade Manager

Failover logic across PSPs. Iterates candidates by priority until first success.

Phase 2
Spec
Cascade Manager is a sub-component of the Orchestrator. The Orchestrator builds the candidate list and passes it to the Cascade Manager. The Cascade Manager owns all iteration and failover logic: it knows nothing about routing rules or the PSP database. Its sole responsibility is to try each candidate in turn and stop at the first success or when the list is exhausted.
MAX_ATTEMPTS = 3. Even if there are more than 3 candidates in the list, the cascade stops after 3 attempts. This limit prevents excessive user-facing latency during widespread PSP outages. Configurable via the CASCADE_MAX_ATTEMPTS environment variable.
1.

Cascade Example

PSP #1 and PSP #2 are unavailable. Cascade automatically falls over to PSP #3, which processes the request successfully.

1

PSP #1 (priority 1)

Timeout after 30 s

Failed
2

PSP #2 (priority 2)

Soft rejection: limit

Failed
3

PSP #3 (priority 3)

Invoice created

Success

Total latency = sum(timeout of each unresponsive PSP) + successful PSP time. In the example: ~60 s + ~1 s. This is why a short adapter-level timeout is critical.

2.

Algorithm

1

Receive ordered candidate list from Orchestrator

List is already filtered by brand, geo, direction, method and sorted by priority ASC. Cascade iterates it left to right.

2

Pick first candidate (attempt #1)

Write psp_config_id to the transaction record. Log a cascade_attempt event to transaction_events.

3

Call adapter.initiateDeposit()

Synchronous call with configured timeout (default: 30 s). Cascade wraps the call in try/catch.

4

Success path

Success

Adapter returns UnifiedResponse. State machine transitions to PROCESSING. Cascade returns result immediately -- no further candidates tried.

5

Failure path -- classify error

Failure

Determine if the error is retryable (network, soft rejection, adapter error) or not (hard rejection). Log to transaction_events with full error detail.

6

Hard rejection -- stop immediately

Terminal

Do not try remaining candidates. Transition transaction to FAILED. Return error code CASCADE_HARD_REJECTION.

7

Retryable error -- advance to next candidate

Remove failed PSP from candidate list. If candidates remain and attempt count < MAX_ATTEMPTS (3) -- go back to step 2 with next candidate.

8

All candidates exhausted

Terminal

No more PSPs to try or MAX_ATTEMPTS reached. Transition transaction to FAILED. Return error code CASCADE_ALL_FAILED.

3.

Cascade Rules

On every failure the Cascade Manager answers two questions: will another PSP help? is it safe to try further? If both are "yes" -- continue. If either is "no" -- hard stop.

Continue cascade -- try next PSP

SituationWhy continue
Network error / timeoutInfrastructure failure of this specific PSP -- another may be alive
PSP scheduled maintenanceTemporary unavailability -- next PSP unaffected
Our merchant rate limit at this PSPQuota exhausted on our merchant account -- next PSP has its own independent quota
Method not supported by this PSPPSP-level gap -- another PSP may support the same method
Amount outside this PSP's limitsPSP-specific limit -- next PSP may have different min/max
PSP temporarily does not support currencyTemporary or config issue on this PSP -- next PSP may accept
Unexpected / unparseable PSP responseBug in adapter or non-standard response -- different adapter, different code path
User not registered in this PSPSome PSPs require pre-registration -- next PSP may not have this requirement

Hard stop -- stop cascade

SituationWhy stop
Fraud block (confirmed)Cascading past a fraud block bypasses fraud controls -- legal and chargeback risk. Hard stop always.
KYC requiredUser must complete identity verification before any PSP will accept them -- not a PSP issue
Duplicate transactionTrying next PSP creates another duplicate -- makes the situation worse
User account blocked at platform levelPlatform-level block -- not a PSP problem, cascading will not help
Invalid wallet address (withdrawal)Malformed address -- every PSP will reject for the same reason
Regulatory geo-blockCountry is legally restricted -- no licensed PSP can process it
Amount exceeds limits of ALL active PSPsCan be detected in Router before cascade starts -- if amount > max of every candidate, no point trying

Grey zones -- require explicit operator decision

SituationProblemDecision needed
"Insufficient funds" from PSPCould be PSP liquidity issue (continue) or player actually has no funds (stop)Clarify with PassimPay which error code maps to which cause
"Fraud suspected" (soft signal)PSPs vary: some return soft "suspicious" vs hard "confirmed fraud". Soft may be worth cascading.Agree with PassimPay on exact error codes for soft vs hard fraud signals
User deposit limit exceeded"Limit exceeded" can mean player's personal PSP limit (stop) OR our merchant quota at that PSP (continue)PSP must return distinct error codes for player limit vs merchant limit
Method temporarily disabled by PSPTemporary PSP config (continue to next PSP) vs regulatory suspension (stop for all)Track known regulatory suspensions in psp_configs.is_active -- keep PSP disabled until resolved

Implementation: decision at adapter level

Cascade Manager does not interpret PSP-specific error codes -- that is the adapter's responsibility. Each adapter returns a cascade field alongside the error. Cascade Manager only looks at that field.

type CascadeDecision = 'continue' | 'hard_stop'

interface AdapterError {
  code:     string            // PSP-specific error code
  message:  string
  cascade:  CascadeDecision   // adapter decides, not Cascade Manager
  pspRaw?:  unknown           // raw PSP response for logs
}

// Example: PassimPay adapter maps its error codes
function mapPassimPayError(pspCode: string): CascadeDecision {
  const HARD_STOPS = ['FRAUD_CONFIRMED', 'KYC_REQUIRED', 'DUPLICATE_TX', 'USER_BLOCKED']
  return HARD_STOPS.includes(pspCode) ? 'hard_stop' : 'continue'
}
4.

Failure Types & Response

Failure typeExamplesRetryable?Cascade actionLog
Network / timeoutConnection refused, read timeout, DNS failure
Yes
Next candidate in listwarn
PSP soft rejectionLimit exceeded, method unavailable, currency mismatch
Yes
Next candidate in listwarn
PSP hard rejectionFraud block, KYC required, account suspended
No
Stop cascade, return FAILEDerror
Adapter internal errorUnexpected response format, missing required field in PSP response
Yes
Next candidate in listerror
5.

Events in transaction_events

Every cascade action is written to transaction_events. This provides a full audit trail for debugging and reconciliation.

event_typeWhenpayload
cascade_attemptBefore each adapter call{ psp_name, attempt_number, candidate_count }
cascade_attempt_failedAfter a retryable failure{ psp_name, error_type, error_message, duration_ms }
cascade_hard_stopOn hard rejection{ psp_name, error_code, psp_raw_response }
cascade_successAfter successful adapter call{ psp_name, attempt_number, duration_ms }
cascade_all_failedWhen all candidates exhausted{ attempts: [{ psp_name, error }], total_duration_ms }
6.

Timeouts

30 s

Per-adapter timeout

ADAPTER_TIMEOUT_MS

Max time to wait for a single PSP response. After this, the call is classified as a network timeout and cascade proceeds to the next candidate.

90 s

Total cascade budget

CASCADE_TOTAL_TIMEOUT_MS

Hard limit for the entire cascade (all attempts combined). If exceeded, remaining candidates are skipped and the transaction transitions to TIMED_OUT.

3

Max attempts

CASCADE_MAX_ATTEMPTS

Maximum number of PSP candidates to try per request, regardless of how many are configured. Prevents tail latency from growing unbounded with many PSPs.

7.

Health Score (Phase 6)

Not implemented in Phase 2

In Phase 6 the Cascade Manager will gain access to the health_score field from psp_configs. Instead of pure priority ordering, the candidate list will be re-ranked: priority * (health_score / 100). PSPs with sustained failures automatically sink lower in the cascade without manual ops intervention.

DEPO44 | CASCADE MANAGER SPEC v1 | PHASE 2