Skip to main content
Routing rules are evaluated top-to-bottom. The first matching rule wins unless that rule is part of a weighted group.

Match Fields

Each routing rule can match on multiple criteria:
FieldTypeDescription
currencystring | string[]ISO currency code (e.g., “USD”, “EUR”)
countrystring | string[]ISO country code (e.g., “US”, “BR”)
paymentMethodstring | string[]Payment method type (e.g., “card”, “pix”, “boleto”)
amountMinnumberMinimum transaction amount in cents
amountMaxnumberMaximum transaction amount in cents
metadataRecord<string, string>Exact key/value pairs from request metadata
defaultbooleanFallback rule (required - at least one)
VaultClient validates that routing configuration contains at least one default fallback rule. Initialization will fail without one.

Rule Evaluation

The Router evaluates rules sequentially:
src/router/router.ts:44
decide(context: RoutingContext): RoutingDecision | null {
  // Check for provider override first
  if (context.providerOverride) {
    // ...
  }

  // Iterate through rules top-to-bottom
  for (let index = 0; index < this.rules.length; index += 1) {
    const rule = this.rules[index];
    
    // Skip excluded providers
    if (context.exclude?.includes(rule.provider)) {
      continue;
    }

    // Check if rule matches context
    if (!ruleMatchesContext(rule, context)) {
      continue;
    }

    // Validate provider capabilities
    if (!this.providerSupportsContext(rule.provider, context)) {
      continue;
    }

    // Handle weighted selection if applicable
    if (rule.weight !== undefined) {
      // ...
    }

    return {
      provider: rule.provider,
      reason: this.buildRuleReason(rule, index),
      rule,
    };
  }

  return null;
}

Weighted Selection

If a matching rule has a weight property, the router builds a contiguous weighted group starting at that rule and selects one provider proportionally.
const vault = new VaultClient({
  providers: {
    stripe: { adapter: StripeAdapter, config: { /*...*/ } },
    dlocal: { adapter: DLocalAdapter, config: { /*...*/ } },
  },
  routing: {
    rules: [
      {
        provider: 'stripe',
        weight: 70,
        match: {
          currency: 'USD',
          paymentMethod: 'card',
        },
      },
      {
        provider: 'dlocal',
        weight: 30,
        match: {
          currency: 'USD',
          paymentMethod: 'card',
        },
      },
      {
        provider: 'stripe',
        match: { default: true },
      },
    ],
  },
});
Weighted groups are contiguous. The router stops collecting candidates when it encounters a rule with a different match criteria or no weight.

Per-Request Controls

You can override routing behavior for individual requests:

Force Specific Provider

Force Provider
const result = await vault.charge({
  amount: 2500,
  currency: 'USD',
  paymentMethod: { type: 'card', token: 'pm_card_visa' },
  routing: {
    provider: 'stripe', // Force Stripe
  },
});

Exclude Providers

Exclude Providers
const result = await vault.charge({
  amount: 2500,
  currency: 'USD',
  paymentMethod: { type: 'card', token: 'pm_card_visa' },
  routing: {
    exclude: ['dlocal'], // Don't use dLocal
  },
});
If a forced provider is also in the exclusion list, the SDK throws VaultRoutingError with code ROUTING_PROVIDER_EXCLUDED.

Provider Capability Validation

The router automatically validates that the selected provider supports the request:
src/router/router.ts:109
private providerSupportsContext(
  provider: string,
  context: RoutingContext,
): boolean {
  const meta = this.adapterMetadata[provider];
  if (!meta) {
    return true;
  }

  // Check payment method support
  if (
    context.paymentMethod &&
    meta.supportedMethods.length > 0 &&
    !meta.supportedMethods.includes(context.paymentMethod)
  ) {
    this.logger?.warn(
      `Provider "${provider}" does not support payment method "${context.paymentMethod}". Skipping.`
    );
    return false;
  }

  // Check currency support
  if (
    context.currency &&
    meta.supportedCurrencies.length > 0 &&
    !meta.supportedCurrencies.includes(context.currency)
  ) {
    this.logger?.warn(
      `Provider "${provider}" does not support currency "${context.currency}". Skipping.`
    );
    return false;
  }

  // Check country support
  if (
    context.country &&
    meta.supportedCountries.length > 0 &&
    !meta.supportedCountries.includes(context.country)
  ) {
    this.logger?.warn(
      `Provider "${provider}" does not support country "${context.country}". Skipping.`
    );
    return false;
  }

  return true;
}
Enable logging to see when providers are skipped due to capability mismatches:
const vault = new VaultClient({
  // ...
  logging: {
    level: 'warn',
    logger: customLogger,
  },
});

Complete Example

Multi-Provider Routing
import {
  DLocalAdapter,
  StripeAdapter,
  VaultClient,
  type ChargeRequest,
} from '@vaultsaas/core';

function mustEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing env var: ${name}`);
  return value;
}

const vault = new VaultClient({
  providers: {
    dlocal: {
      adapter: DLocalAdapter,
      config: {
        xLogin: mustEnv('DLOCAL_X_LOGIN'),
        xTransKey: mustEnv('DLOCAL_X_TRANS_KEY'),
        secretKey: mustEnv('DLOCAL_SECRET_KEY'),
      },
      priority: 1,
    },
    stripe: {
      adapter: StripeAdapter,
      config: {
        apiKey: mustEnv('STRIPE_API_KEY'),
      },
      priority: 2,
    },
  },
  routing: {
    rules: [
      {
        provider: 'dlocal',
        match: {
          country: 'BR',
          paymentMethod: ['pix', 'boleto'],
        },
      },
      {
        provider: 'dlocal',
        weight: 30,
        match: {
          currency: 'USD',
          paymentMethod: 'card',
        },
      },
      {
        provider: 'stripe',
        weight: 70,
        match: {
          currency: 'USD',
          paymentMethod: 'card',
        },
      },
      {
        provider: 'stripe',
        match: { default: true },
      },
    ],
  },
});

const request: ChargeRequest = {
  amount: 4200,
  currency: 'USD',
  paymentMethod: {
    type: 'card',
    token: 'pm_card_visa',
  },
  customer: {
    email: '[email protected]',
    address: {
      line1: '100 Market St',
      city: 'San Francisco',
      postalCode: '94105',
      country: 'US',
    },
  },
  metadata: {
    merchantSegment: 'enterprise',
  },
  routing: {
    exclude: ['dlocal'],
  },
};

const result = await vault.charge(request);
console.log(result.provider, result.routing.reason);
// "stripe" "rule matched at index 2 using currency, paymentMethod"

Routing Decision Details

Every payment result includes routing information:
interface PaymentResult {
  // ...
  routing: {
    source: 'local' | 'platform';
    reason: string;
  };
}
  • source: Whether the decision came from local rules or platform API
  • reason: Human-readable explanation of why this provider was selected

Next Steps

Architecture

Understand the routing component

Error Handling

Handle routing errors

Build docs developers (and LLMs) love