PayinferenceDocs
payinference.com

Testing and local sandbox

There is no hosted sandbox environment today. The supported ways to test are test-mode API keys against your deployment, and the local instance with seeded demo data, which behaves exactly like production because it is the same code.

Test mode#

API keys carry a mode. pi_test_… keys mark every decision they create with "mode": "test", keeping test traffic distinguishable in the decision log and analytics. Use test keys everywhere except production.

The local sandbox#

Run the full platform locally (see Getting started or Local Setup):

bash
docker compose up -d
pnpm db:generate && pnpm db:migrate && pnpm db:seed
pnpm dev

The seed gives you a complete working environment:

Item Value
API http://localhost:4000
Dashboard http://localhost:3000 (demo@payinference.dev / demo-password-123)
Merchant m_123
API key pi_demo_key_123 (all scopes)
Data A published policy, provider health snapshots, 24 h of decision history

All providers in the local environment are mocks driven by synthetic probes. No real PSP credentials exist anywhere in the repository, and no real payment can occur.

Testing decision handling#

Exercise every instruction path in your handler. Instructions are driven by policy rules, retry context, and provider health, so drive them deliberately:

  • block and step_up: these come from matched policy rules, so publish a test policy with low thresholds, for example { "when": { "amount_gte": 1 }, "then": { "type": "step_up" } }, or a rule on risk_score_gte combined with a transaction.risk_score in your request. Sending risk_signals alone raises the advisory risk object in the response but does not change the instruction.
  • hold: publish a rule with { "then": { "type": "hold" } }; hold outranks step_up when both match.
  • retry: publish a retry_on_timeout rule and include previous_attempt with outcome: "timeout" in the request.
  • failover: include previous_attempt with outcome: "timeout" naming a provider whose cached health state is degraded or worse.
  • block via retry suppression: publish a no_retry rule listing a reason code, then send previous_attempt.provider_reason_code matching it.
  • use_default_route: make every offered provider ineligible (for example a require_min_health rule with a score of 100) while the policy defines a default_provider.

Verify each handled decision lands in the decision log with the outcome you reported: GET /v1/decisions/:decisionId.

Simulating policies#

Test a policy against sample transactions without publishing or affecting live decisions:

bash
curl -s http://localhost:4000/v1/policies/simulate \
  -H "content-type: application/json" \
  -H "x-api-key: pi_demo_key_123" \
  -d '{
    "merchant_id": "m_123",
    "transaction": { "amount": 75000, "currency": "EUR", "country": "DE", "payment_method": "card" },
    "available_providers": ["stripe", "adyen"]
  }'

The response shows the resulting action, matched rules, excluded providers, and per-provider evaluations, using the same engine and cached health as the live path.

Unit testing your integration#

Inject a fake fetch into the Node SDK to test your handler without any network:

ts
import { PayInferenceClient } from '@payinference/sdk-node';

const client = new PayInferenceClient({
  apiKey: 'pi_test_x',
  fetch: async () =>
    new Response(
      JSON.stringify({
        decision_id: 'dec_test',
        merchant_id: 'm_123',
        mode: 'test',
        action: 'route',
        route: { primary_provider: 'stripe', fallback_provider: null },
        provider_health: {},
        policy: { policy_id: null, policy_version: null, matched_rules: [] },
        model: { model_version: 'test', route_scores: {} },
        reason_codes: [],
        decision_latency_ms: 1,
        ttl_ms: 3000,
      }),
      { status: 200 },
    ),
});

Test your outage path too: point baseUrl at an unroutable address and assert that your fallback executes.

Latency verification#

pnpm benchmark preloads the cache, fires 1,000 decision requests at the running API, verifies health was served from cache, and prints average, p50, p95, p99, and max latency. Local targets: average under 25 ms, p95 under 50 ms, zero errors. Run it against your own deployment to validate the budget from your network position.