StackTrading Docs

SRS: UC_9.8 — Flow 30: MID Capacity Warning

FieldValue
BA in Chargelinh.nguyen34
Date Created2026-09-03
Versionv1.2
Last Updated2026-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 ReferencesZapier 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_IDUse Case NameBusiness Description
UC_9.8MID Capacity WarningReal-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

FieldContent
IDUC_9.8
Use CaseMID Capacity Warning
DescriptionEarly-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.
FlowFlow 30 (MID Capacity Warning)
TableMerchant_Routing_Config (PostgreSQL table)
3rd PartyNMI (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_amount

On 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)

FieldTypeDescription
merchant_nameVARCHAR (PK)Unique MID identifier (e.g.NMI_US_Credit_01)
nmi_routing_idVARCHARRouting code configured in NMI gateway
monthly_limitDECIMALMaximum transaction volume per calendar month
current_mtd_volumeDECIMALRunning month-to-date total of successful transactions
is_activeBOOLEANWhether this MID is currently in rotation
warning_80_firedBOOLEAN (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

EndpointMethodAuthCallerPurpose
POST /merchant/check-capacityPOSTInternal — no public authMiddleware (called after Process Payment Webhook)Evaluate 80% threshold; fire webhook if triggered

Input:

FieldTypeRequiredDescription
merchant_nameVARCHAR✅Identifies which MID to evaluate
current_mtd_volumeDECIMAL✅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 IDRuleSource
BR_9.8.1POST /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.2Capacity 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.3warning_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.4Monthly 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.5Only 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.6Flag 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.7Null/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.8Webhook 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.9Refund 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

IDScenarioExpected Result
AC-9.8-01MID at 79.9% → successful payment pushes it to 80.1% →warning_80_fired = FALSEWebhook fires;warning_80_fired set to TRUE; Slack alert received in #ops-treasury
AC-9.8-02MID already at 82% (warning_80_fired = TRUE) → new payment processedNo webhook fired; no Slack alert;warning_80_fired remains TRUE; HTTP 200 returned
AC-9.8-03MID at 79% → payment pushes to 79.9%No webhook fired;warning_80_fired remains FALSE; HTTP 200 returned
AC-9.8-04End of month cron fires at 00:00 UTC on the 1stcurrent_mtd_volume = 0.00 and warning_80_fired = FALSE for all MIDs
AC-9.8-05MID with is_active = FALSE receives a payment updateCapacity check skipped; no alert fired
AC-9.8-06monthly_limit = 0 or NULL for an active MIDHTTP 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-07Ops upgrades monthly_limit for a MID (warning_80_fired = TRUE); next payment/refund runs the capacity check with volume < 80% of the new limitwarning_80_fired reset to FALSE at that check; no Slack alert
AC-9.8-08Webhook fails on first attempt → retries → all 3 retries exhausted3 retries executed with exponential backoff; fallback Slack alert pushed directly to #dev_ops via native Slack API
AC-9.8-09warning_80_fired = FALSE, monthly_limit is NULL, MID is is_active = TRUEGuard 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-101st-of-month reset done (current_mtd_volume = 0); a prior month's $500 transaction is refundedcurrent_mtd_volume = 0 (not negative); NMI refund processed normally
AC-9.8-11MID at 85% (warning_80_fired = TRUE); refund drops it to 75%warning_80_fired = FALSE; no Slack alert
AC-9.8-12After AC-9.8-11, a payment pushes the MID back to 81%Webhook fires (second alert this month); warning_80_fired = TRUE

9. Changelog

DateVersionSummaryBeforeAfterSource
2026-11-05v1.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 triggerQnA_BACKGROUND_MECHANISMS_CRON.md [BG-024] [BG-025] — client answer 2026-11-03
2026-09-18v1.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 addedQ1: 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-03v1.0Initial document creation——Zapier Integration V7.pdf §Flow 30; RFQ_ Stack Trading Prop Tech V7.pdf §Check MID Capacity

On this page