SRS: UC_9.8 — Flow 30: MID Capacity Warning
| Field | Value |
|---|---|
| BA in Charge | linh.nguyen34 |
| Date Created | 2026-09-03 |
| Version | v1.2 |
| Last Updated | 2026-11-05 — client answer 2026-11-03: refunds decrement current_mtd_volume (floored at 0); warning_80_fired re-armed inside Check MID Capacity when volume drops below 80% |
| Document References | Zapier Integration V7.pdf (§Flow 30 lines 2461–2469) · RFQ_ Stack Trading Prop Tech V7.pdf (§Check MID Capacity lines 1455–1459; §Process Payment Webhook lines 1339–1373; §Merchant_Routing_Config lines 847–855; §Alerting lines 3273) |
UC Index
| UC_ID | Use Case Name | Business Description |
|---|---|---|
| UC_9.8 | MID Capacity Warning | Real-time: after each successful payment, Middleware checks if the used MID has hit 80% of its monthly volume limit. If yes and not yet alerted this month, fires a webhook → Slack alert to #ops-treasury. |
<a id="UC_9.8"></a>
UC_9.8 — Flow 30: MID Capacity Warning
1. Overview
| Field | Content |
|---|---|
| ID | UC_9.8 |
| Use Case | MID Capacity Warning |
| Description | Early-warning system for MID (Merchant Identification Number) monthly processing capacity. After every successful payment, Middleware evaluates whether the MID used has consumed ≥ 80% of its monthly_limit. If the threshold is crossed and warning_80_fired = FALSE, Middleware sets the flag and fires a webhook that sends a Slack alert to the Operations channel. Prevents revenue pipeline freeze caused by MID over-limit rejection. |
| Flow | Flow 30 (MID Capacity Warning) |
| Table | Merchant_Routing_Config (PostgreSQL table) |
| 3rd Party | NMI (payment gateway — source of successful payment events) · Slack (alert delivery) |
2. Trigger
Type: Internal — fired programmatically by Middleware Hub immediately after Process Payment Webhook successfully updates current_mtd_volume in the database.
Trigger condition: Every successful payment from any gateway (NMI, Triple-A, Nomupay, EBANX, Dusupay, Skrill) and every refund results in a call to POST /merchant/check-capacity for the MID used.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity lines 1455–1459; §Process Payment Webhook lines 1339–1346)
3. Basic Flow
Step 1 — Volume Update (upstream — Process Payment Webhook)
After a successful payment, Process Payment Webhook updates the MID's running total in Merchant_Routing_Config:
current_mtd_volume = current_mtd_volume + transaction_amountOn a refund, the MID's running total is decremented and never drops below 0 (e.g. a prior month's transaction refunded right after the 1st-of-month reset):
current_mtd_volume = GREATEST(0, current_mtd_volume - refund_amount)current_mtd_volume is only an internal month-to-date counter used for load balancing — it is not the merchant bank balance. The refund itself is processed by NMI normally even when current_mtd_volume = 0.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Process Payment Webhook line 1345; QnA_BACKGROUND_MECHANISMS_CRON.md [BG-024] — client answer 2026-11-03)
Step 2 — Capacity Check (POST /merchant/check-capacity)
Immediately after the DB update, Middleware calls POST /merchant/check-capacity internally.
Input payload:
{
"merchant_name": "<MID identifier>",
"current_mtd_volume": <updated value>
}Evaluation logic:
ratio = current_mtd_volume / monthly_limit
IF ratio >= 0.80 AND warning_80_fired = FALSE:
→ SET warning_80_fired = TRUE
→ FIRE webhook
ELSE IF ratio < 0.80 AND warning_80_fired = TRUE:
→ SET warning_80_fired = FALSE (no Slack alert)
ELSE:
→ No action. Return HTTP 200.The warning_80_fired flag makes the 80% alert fire only once per crossing: while it is set, further transactions on the same MID do not re-trigger the flow even if ratio climbs above 90% or 100%. If volume drops back below 80% (a refund, or an increased monthly_limit), the flag is cleared and the next crossing fires a new alert. No separate database trigger or watcher is built — this all happens inside this check.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity lines 1456–1459; QnA_BACKGROUND_MECHANISMS_CRON.md [BG-025] — client answer 2026-11-03)
Response: HTTP 200 OK in all cases (fire or no-fire).
Step 3 — Slack Alert
Send a Slack message to the Operations channel (#ops-treasury or configured Ops channel).
Slack message content:
⚠️ [WARN] MID VOLUME CAPACITY ALERT
MID [merchant_name] has reached [ratio]% of its monthly processing limit.
Current MTD Volume: $[current_mtd_volume]
Monthly Limit: $[monthly_limit]
Action Required: Contact processor to request limit increase, or adjust
Weighted Round-Robin weights in Merchant_Routing_Config to offload traffic.(Source: Zapier Integration V7.pdf §Flow 30 lines 2466–2469)
4. Data Model
Merchant_Routing_Config table (PostgreSQL)
| Field | Type | Description |
|---|---|---|
merchant_name | VARCHAR (PK) | Unique MID identifier (e.g.NMI_US_Credit_01) |
nmi_routing_id | VARCHAR | Routing code configured in NMI gateway |
monthly_limit | DECIMAL | Maximum transaction volume per calendar month |
current_mtd_volume | DECIMAL | Running month-to-date total of successful transactions |
is_active | BOOLEAN | Whether this MID is currently in rotation |
warning_80_fired | BOOLEAN (default: FALSE) | Idempotency flag — prevents duplicate alerts within the same month |
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Merchant_Routing_Config lines 847–855; §Alerting line 3273; client confirmed 2026-09-18)
5. API Endpoints
| Endpoint | Method | Auth | Caller | Purpose |
|---|---|---|---|---|
POST /merchant/check-capacity | POST | Internal — no public auth | Middleware (called after Process Payment Webhook) | Evaluate 80% threshold; fire webhook if triggered |
Input:
| Field | Type | Required | Description |
|---|---|---|---|
merchant_name | VARCHAR | ✅ | Identifies which MID to evaluate |
current_mtd_volume | DECIMAL | ✅ | Post-update MTD volume for this MID |
Output: HTTP 200 OK (no body required — side effect only)
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity lines 1455–1459)
6. Exception Flows
Exc-1 — warning_80_fired already TRUE (duplicate suppression):
- No webhook fired. HTTP 200 returned silently.
- This is expected behavior — the flag is reset by the 1st-of-month cron, or by the next capacity check that finds volume below 80% (see §7).
Exc-2 — MID monthly_limit is NULL or zero:
Middleware MUST apply a guard clause: if monthly_limit IS NULL OR monthly_limit = 0, skip the 80% capacity calculation for that MID, log a server-side warning code NULL_OR_ZERO_MONTHLY_LIMIT_WARNING, and return HTTP 200 (no 500 exception). Additionally, Middleware MUST dispatch an active Ops alert to the #ops-treasury Slack channel with the message:
⚠️ Configuration Error: MID [merchant_name] has an active status but a zero or null monthly limit.Rationale: a NULL or zero limit on an active MID is a critical configuration error — it must be routed to the Ops team for immediate database correction. This is an internal configuration alert and MUST NOT be routed to Freshdesk.
(Source: client confirmed 2026-09-18)
Exc-3 — Webhook delivery fails:
When Middleware fires the capacity alert webhook and receives a non-200 response or timeout:
Phase 1 — Retry: Middleware executes 3 automatic retries with exponential backoff.
Phase 2 — Fallback (all 3 retries exhausted): Middleware pushes a fallback alert directly to #dev_ops via a native Slack API webhook call.
(Source: client confirmed 2026-09-18)
7. Business Rules
| Rule ID | Rule | Source |
|---|---|---|
| BR_9.8.1 | POST /merchant/check-capacity MUST be called synchronously after every successful payment and every refund that updates current_mtd_volume. It is an internal trigger — not exposed to external clients. | RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity lines 1455–1459 |
| BR_9.8.2 | Capacity threshold is exactly 80% (ratio >= 0.80). The alert fires once per crossing — while warning_80_fired = TRUE, further transactions do NOT re-trigger even if ratio reaches 90% or 100%. If ratio drops below 80%, the flag is reset to FALSE and the next crossing alerts again. | RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity lines 1456–1458; RFQ_ Stack Trading Prop Tech V7.pdf §Alerting line 3273; QnA [BG-025] client answer 2026-11-03 |
| BR_9.8.3 | warning_80_fired MUST be set to TRUE atomically with the webhook dispatch. If the DB update and webhook fire are not atomic, a retry could send duplicate Slack alerts. Implementation note: set the flag before firing the webhook; if webhook fails, the flag remains set (prefer missed alert over duplicate alert). | RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity lines 1456–1459; client confirmed 2026-09-03 |
| BR_9.8.4 | Monthly reset: current_mtd_volume reset to 0.00 AND warning_80_fired reset to FALSE for all MIDs at 00:00 UTC on the 1st of each month by the monthly cron job. | RFQ_ Stack Trading Prop Tech V7.pdf §Merchant_Routing_Config line 222; client confirmed 2026-09-03 |
| BR_9.8.5 | Only MIDs with is_active = TRUE participate in the Weighted Round-Robin routing. Inactive MIDs are not evaluated for capacity. | RFQ_ Stack Trading Prop Tech V7.pdf §Merchant_Routing_Config lines 847–855 |
| BR_9.8.6 | Flag re-arm — no separate trigger: No database trigger or watcher on monthly_limit changes. Re-arming happens inside Check MID Capacity: if current_mtd_volume < 0.80 × monthly_limit AND warning_80_fired = TRUE → set warning_80_fired = FALSE. Covers both an increased monthly_limit and a refund that drops volume below 80%. | QnA_BACKGROUND_MECHANISMS_CRON.md [BG-025] — client answer 2026-11-03 |
| BR_9.8.7 | Null/Zero limit guard — active Slack alert required: If monthly_limit IS NULL OR monthly_limit = 0 for an is_active = TRUE MID, Middleware MUST (a) skip 80% capacity calculation, (b) log NULL_OR_ZERO_MONTHLY_LIMIT_WARNING server-side, (c) return HTTP 200 (no 500), AND (d) dispatch a Slack alert to #ops-treasury: "⚠️ Configuration Error: MID [merchant_name] has an active status but a zero or null monthly limit." MUST NOT be routed to Freshdesk. | Client confirmed 2026-09-18 |
| BR_9.8.8 | Webhook delivery failure — retry + native Slack fallback: If the webhook call fails (non-200 or timeout), Middleware MUST execute 3 automatic retries with exponential backoff. If all 3 retries fail, Middleware MUST push the capacity warning directly to #dev_ops via a native Slack API webhook call. | Client confirmed 2026-09-18 |
| BR_9.8.9 | Refund decrement floor: A refund decrements current_mtd_volume with GREATEST(0, current_mtd_volume - refund_amount) — the value never goes below 0. current_mtd_volume is an internal load-balancing counter, not a bank balance; refunds are processed by NMI regardless of its value. | QnA_BACKGROUND_MECHANISMS_CRON.md [BG-024] — client answer 2026-11-03 |
8. Acceptance Criteria
| ID | Scenario | Expected Result |
|---|---|---|
| AC-9.8-01 | MID at 79.9% → successful payment pushes it to 80.1% →warning_80_fired = FALSE | Webhook fires;warning_80_fired set to TRUE; Slack alert received in #ops-treasury |
| AC-9.8-02 | MID already at 82% (warning_80_fired = TRUE) → new payment processed | No webhook fired; no Slack alert;warning_80_fired remains TRUE; HTTP 200 returned |
| AC-9.8-03 | MID at 79% → payment pushes to 79.9% | No webhook fired;warning_80_fired remains FALSE; HTTP 200 returned |
| AC-9.8-04 | End of month cron fires at 00:00 UTC on the 1st | current_mtd_volume = 0.00 and warning_80_fired = FALSE for all MIDs |
| AC-9.8-05 | MID with is_active = FALSE receives a payment update | Capacity check skipped; no alert fired |
| AC-9.8-06 | monthly_limit = 0 or NULL for an active MID | HTTP 200 returned; no 500 thrown; NULL_OR_ZERO_MONTHLY_LIMIT_WARNING logged; Slack alert sent to #ops-treasury with message "Configuration Error: MID [merchant_name] has an active status but a zero or null monthly limit." |
| AC-9.8-07 | Ops upgrades monthly_limit for a MID (warning_80_fired = TRUE); next payment/refund runs the capacity check with volume < 80% of the new limit | warning_80_fired reset to FALSE at that check; no Slack alert |
| AC-9.8-08 | Webhook fails on first attempt → retries → all 3 retries exhausted | 3 retries executed with exponential backoff; fallback Slack alert pushed directly to #dev_ops via native Slack API |
| AC-9.8-09 | warning_80_fired = FALSE, monthly_limit is NULL, MID is is_active = TRUE | Guard clause fires: capacity check skipped; NULL_OR_ZERO_MONTHLY_LIMIT_WARNING logged; #ops-treasury Slack alert sent; no division-by-zero error |
| AC-9.8-10 | 1st-of-month reset done (current_mtd_volume = 0); a prior month's $500 transaction is refunded | current_mtd_volume = 0 (not negative); NMI refund processed normally |
| AC-9.8-11 | MID at 85% (warning_80_fired = TRUE); refund drops it to 75% | warning_80_fired = FALSE; no Slack alert |
| AC-9.8-12 | After AC-9.8-11, a payment pushes the MID back to 81% | Webhook fires (second alert this month); warning_80_fired = TRUE |
9. Changelog
| Date | Version | Summary | Before | After | Source |
|---|---|---|---|---|---|
| 2026-11-05 | v1.1 → v1.2 | §2 Trigger (refunds) · §3 Step 1 (refund decrement) · Step 2 (re-arm branch) · §6 Exc-1, Exc-3 · §7 BR_9.8.1/2/6 updated, BR_9.8.9 added · §8 AC-9.8-07 updated, AC-9.8-10/11/12 added · §3 Step 3 (Zapier Receive Webhook) removed, Slack Alert renumbered Step 4 → Step 3 · Zapier wording removed (alert = webhook) | Refunds not covered; flag only reset by monthly cron or on BPS limit upgrade (BR_9.8.6, 2026-09-18) | Refund: GREATEST(0, volume - refund); flag re-armed inside Check MID Capacity when volume < 80%; no separate trigger | QnA_BACKGROUND_MECHANISMS_CRON.md [BG-024] [BG-025] — client answer 2026-11-03 |
| 2026-09-18 | v1.0 → v1.1 | §4 Data Model Q1 closed · §6 Exc-2/Exc-3 resolved · §7 BR_9.8.6/7/8 added · §8 AC-9.8-06 expanded + AC-9.8-07/08/09 added | Q1: warning_80_fired field unconfirmed. Exc-2: guard logged warning only, no active alert. Exc-3: [OPEN] — no retry or fallback specified. | Q1 closed: warning_80_fired BOOLEAN NOT NULL DEFAULT FALSE confirmed for Merchant_Routing_Config. Exc-2: null/zero limit now triggers #ops-treasury Slack alert (not Freshdesk) in addition to server-side warning + HTTP 200. Exc-3: 3 retries with exponential backoff; on exhaustion, native Slack API fallback to #dev_ops (bypasses Zapier). BR_9.8.6: manual reset on limit upgrade. BR_9.8.7: null/zero guard + active Slack alert spec. BR_9.8.8: retry + fallback spec. | Client confirmed 2026-09-18 |
| 2026-09-03 | v1.0 | Initial document creation | — | — | Zapier Integration V7.pdf §Flow 30; RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity |