# Risk Management

Transaction limits, WatchDog volume rules and decline rerouting.

---

## Overview

AuxCORE's risk controls are rule-based. They are configured per merchant and per processor account (gateway), and enforced on every sale/auth:

- 🛡️ **AVS / CVV filters** - Decline on address or security-code results ([AVS](./avs.md), [CVV](./cvv.md))
- 💳 **Gateway limits** - A per-transaction cap plus card-brand toggles on each gateway
- 📊 **WatchDog volume rules** - Daily/monthly volume and count limits that block or reroute to another gateway
- 🔁 **Decline rerouting** - OTP-verified retry of a declined sale on an alternative gateway

> **Note:** There is no risk-score, velocity-rule, country-blocking or device-fingerprinting API, and no `fraud.alert` webhook event. Endpoints such as `/merchants/:merchantId/risk-settings` and `/analytics/fraud` do not exist.

All endpoints below are admin API routes: `Authorization: Bearer <JWT>` (or an API key in `x-api-key`).

---

## Gateway Limits

Each merchant gateway carries its own limits. Set them on the gateway record:

```http
PUT /api/v1/merchants/:merchantId/gateways/:id
```

```bash
curl -X PUT https://uat.auxcore.net/api/v1/merchants/3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90/gateways/9b2c4d6e-1a3f-4b5c-8d7e-0f1a2b3c4d5e \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "perTransactionLimit": 2500.00,
    "dailyLimit": 10000.00,
    "monthlyLimit": 150000.00,
    "amexEnabled": false
  }'
```

| Field | Type | Description |
|-------|------|-------------|
| `perTransactionLimit` | number | Max amount of a single sale on this gateway |
| `dailyLimit` / `weeklyLimit` / `monthlyLimit` | number | Stored on the gateway, but not enforced on the payment path — use WatchDog rules for volume caps |
| `maxTicketAmount` | number | Stored on the gateway, but not enforced on the payment path — use `perTransactionLimit` |
| `visaEnabled` / `mastercardEnabled` / `amexEnabled` / `discoverEnabled` | boolean | Card brands accepted (default `true`) |

A sale above `perTransactionLimit`, or with a disabled card brand, is refused before it reaches the processor with `LIMIT_EXCEEDED`. Brand detection uses the card number the router receives; if the brand cannot be determined it is not blocked.

---

## WatchDog Volume Rules

WatchDog rules cap a merchant's daily/monthly volume and either **block** the sale or **reroute** it to another gateway.

### Get WatchDog Rules

```http
GET /api/v1/merchants/:merchantId/watchdog-rules
```

**Response:**
```json
{
  "success": true,
  "data": {
    "perTransactionLimit": 0,
    "dailyVolumeLimit": 5000,
    "dailyCountLimit": 0,
    "monthlyVolumeLimit": 100000,
    "monthlyCountLimit": 0,
    "alertThreshold": 80,
    "alertEmail": "ops@example.com",
    "rerouteOnLimit": true,
    "rerouteToGatewayId": "9b2c4d6e-1a3f-4b5c-8d7e-0f1a2b3c4d5e",
    "isActive": true
  }
}
```

Unset values come back as `0` (no limit), `alertThreshold` defaults to `80`, `rerouteOnLimit` and `isActive` default to `false`, and `alertEmail` falls back to the merchant contact email only until rules are first saved (a save without `alertEmail` stores an empty string).

### Update WatchDog Rules

```http
POST /api/v1/merchants/:merchantId/watchdog-rules
```

```bash
curl -X POST https://uat.auxcore.net/api/v1/merchants/3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90/watchdog-rules \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "perTransactionLimit": 2000,
    "dailyVolumeLimit": 5000,
    "monthlyVolumeLimit": 100000,
    "dailyCountLimit": 200,
    "rerouteOnLimit": true,
    "rerouteToGatewayId": "9b2c4d6e-1a3f-4b5c-8d7e-0f1a2b3c4d5e",
    "isActive": true
  }'
```

| Field | Type | Description |
|-------|------|-------------|
| `perTransactionLimit` | number | Max single sale (`0` = none) |
| `dailyVolumeLimit` / `monthlyVolumeLimit` | number | Volume caps (`0` = none) |
| `dailyCountLimit` / `monthlyCountLimit` | integer | Transaction-count caps (`0` = none) |
| `alertThreshold` | number | Alert percentage, default `80` |
| `alertEmail` | string | Alert recipient |
| `rerouteOnLimit` | boolean | `true` = reroute to `rerouteToGatewayId` when a daily/monthly limit is reached; `false` = block |
| `rerouteToGatewayId` | string | Gateway to reroute to |
| `isActive` | boolean | Enable the rules |

The whole object is replaced on every save (omitted fields reset to `0` / `false` / empty). **Response:** `{ "success": true, "data": { …saved rules } }`. Returns `404` `NOT_FOUND` if the merchant is not in your tenant. The rules are then synced to the WatchDog service in the background; a sync failure is logged and does not fail the request.

### Enforcement

When a sale breaches a rule and rerouting is off (or the per-transaction limit is breached), the transaction is rejected with HTTP `422`:

```json
{
  "success": false,
  "error": {
    "code": "WATCHDOG_LIMIT_EXCEEDED",
    "message": "Daily transaction limit of $5000.00 would be exceeded"
  }
}
```

Merchant transaction-limit rules return the same shape with code `LIMIT_EXCEEDED`. A reroute is never applied across payment purposes: a gateway reserved for donations cannot receive an ordinary sale, and vice versa.

---

## Decline Rerouting

WatchDog can offer to retry a declined card sale on an alternative gateway after the payer confirms with an OTP. These endpoints are used by the hosted payment flows; they run on the WatchDog service and take a merchant JWT (the merchant comes from the token; `check-rerouting` and `sessions` also accept `merchantId` in the body). `/api/v1/watchdog/*` is gated by the tenant's `watchdog` feature — without it the gateway returns `403` `SERVICE_UNAVAILABLE`.

### Check Eligibility

```http
POST /api/v1/watchdog/check-rerouting
```

```json
{
  "declineReason": "Insufficient funds",
  "declineCode": "51",
  "gatewayType": "NMI"
}
```

**Response:** `data` = `{ shouldReroute, reason, message, reroutingGateways?: [{ id, type, priority }], primaryGateway?: { id, type } }`.

### Create Rerouting Session

```http
POST /api/v1/watchdog/sessions
```

Body: `transactionData` (required, includes `transactionId`, `amount` and billing details), `declineInfo` (required), `cardToken` (optional). Returns `201` with the session.

### Verify OTP

```http
POST /api/v1/watchdog/sessions/:id/verify-otp
```

Body: `{ "otpCode": "123456" }`. Wrong code → `400` `OTP_VERIFICATION_FAILED`.

### Process Rerouting

```http
POST /api/v1/watchdog/sessions/:id/process-rerouting
```

Retries the sale on the next gateway. **Response:** `data` = `{ success, message, transactionId?, gatewayType? }`; failure → `400` `REROUTING_FAILED`.

### Convert to Gift Card

```http
POST /api/v1/watchdog/convert-to-gift-card
```

Body: `{ "sessionId": "…", "transactionData": { … } }`. Available when the merchant enabled gift-card conversion; failure → `400` `CONVERSION_FAILED`.

> **Note:** The public endpoints `POST /api/v1/public/gateway-state`, `/watchdog-transaction-count` and `/gateway-selection` are not currently functional and should not be used.

---

## Best Practices

### ✅ DO:
- **Set gateway limits** that match your processor's approved volumes
- **Use WatchDog reroute** to keep processing when a primary MID hits its cap
- **Combine with AVS/CVV** - Multi-layered protection
- **Monitor declines** in the transaction list

### ❌ DON'T:
- **Don't set limits below normal ticket sizes** - legitimate sales get blocked
- **Don't reroute to a gateway that is not ACTIVE**

---

## Next Steps

- [Configure AVS filters →](./avs.md)
- [Configure CVV filters →](./cvv.md)
- [Gateway routing →](../advanced/gateway-routing.md)

---

**Need help?** Contact support@auxcore.net
