zsty.us

Before / After · Case Study

BMH payment rails — Wix payments to a two-rail Medusa module with hard caps

  • Hemp / CBD
  • Local Pro Services

Big Moose Hemp ran customer payments through WIX-PAY-PRO + Square. Hemp/CBD is a high-risk MID category and the federal hemp ban (P.L. 119-37, effective Nov 12 2026) accelerates the need to move off a stack that doesn't expose payment-policy controls. The rebuild ships a two-rail payment provider abstraction inside the BMH Medusa 2.x backend — Clover (card, capped at $250) via Global Payments Solutions ISO for the high-risk MID, and Aeropay (ACH) for any purchase over the cap and for the 50% deposit on bulk clone orders. Hard caps are enforced at three levels — admin, API, and storefront — so no path can bypass them.

Belt and suspenders — the card cap is enforced in three places so neither the admin nor the API can bypass it.

Receipts — measured

Numbers that moved.

1
Payment rails under one Medusa module
was 1
0
Cap-enforcement layers (module + API + storefront)
was 0
0 $
Hard card cap
was 0 $
0
Webhook reconciliation surfaces
was 0
  • 2 payment rails (Clover card + Aeropay ACH) under one Medusa module
  • Hard $250 card cap enforced at admin + API + storefront
  • 13-method contract surface across the two providers
  • Feature-flagged rail toggle (FEATURE_CLOVER_CARD_RAIL)

The story

Problem · Insight · Build · Outcome.

  1. 01 · Problem

    High-risk MID on a vendor stack that doesn't expose payment policy.

    Hemp/CBD is a high-risk merchant category. The legacy WIX-PAY-PRO + Square stack didn't expose payment-policy controls, didn't have an ACH fallback for over-cap purchases, and didn't allow swapping rails without rebuilding the storefront. With the federal hemp ban accelerating, the platform needed payment infrastructure that could survive a rail change without going dark.

  2. 02 · Insight

    Move the policy into the platform, split the rails by amount.

    Card processing is fine for the long tail of small orders — under $250. Above that, ACH is cheaper, doesn't burn the card MID's risk budget, and matches how bulk clone deposits actually flow. The fix is a two-rail Medusa module where the cap is platform policy, not vendor policy — enforced in three places so no path can bypass it.

  3. 03 · Build

    Two payment provider modules + a policy endpoint, all under the Medusa 2.x contract.

    `backend/src/modules/payment-clover/index.ts` (Clover Ecomm SDK via GPS ISO) and `backend/src/modules/payment-aeropay/index.ts` (Aeropay bank-linked ACH). Each implements the Medusa 2.x payment provider interface — initiate, authorize, capture, refund, cancel, retrieve, webhook. The Clover module enforces the card cap in `authorize()`; the storefront enforces it again in `/api/checkout`; the admin surfaces it via `/admin/settings/payment-policy`. Sandbox dev account JM3X09S2DHK06 (B-IT) is checked in for Clover dev.

  4. 04 · Outcome

    A payment surface that survives a rail change without going dark.

    Card-rail vendor changes are a feature flag flip. ACH stays put. Webhook reconciliation lands in one view. The same module pattern is reusable across BMH, Buck Mountain, and any other brand that needs the high-risk MID + ACH-fallback split — which is most of the cannabis/hemp portfolio.

  5. 05 · Pending

    Open work tracked at CODEX_HANDOFF.md #17.

    Both modules ship the contract surface with TODO(#17) on the actual provider integration. Clover sandbox account is wired; production credentials gated on the GPS ISO onboarding completing. Aeropay sandbox URL switching is wired; bank-linked pull testing pending account approval. Until then the storefront falls back to manual ACH instructions for over-cap purchases.

Architecture

One chain replaced by another. Receipts above.

Old stack
  • Wix payments
  • Square (single MID)
  • No ACH fallback
  • No cap enforcement
  • No admin policy
New stack
  • Medusa 2.x modular payments
  • payment-clover (card ≤ $250)
  • payment-aeropay (ACH > $250)
  • /admin/settings/payment-policy
  • HMAC webhooks (settlement + reversal)
  • Three-level cap enforcement
  • Feature flag toggle

What changed

Grouped by what kind of system shipped.

Each claim ships with concrete evidence — env vars, table names, cadence chips. No marketing fluff.

Agent backbone

Two-rail abstraction — card under the cap, ACH over

`payment-clover/index.ts` and `payment-aeropay/index.ts` both implement the Medusa 2.x payment provider contract. The card rail (Clover Ecomm SDK via Global Payments Solutions ISO) handles tokenize → auth → capture → refund → void → status sync → webhook decode. The ACH rail (Aeropay) handles the bank-linked pull (1-2 business days) → settlement → reversal — used for any card-over-cap purchase and for the 50% deposit on the 500+ clone Standard-tier orders.

Before
Single payment surface inside Wix — no rail switching, no cap enforcement, no admin policy.
After
Two independent rails under one Medusa module, swappable per order amount, with a single admin policy endpoint.
  • Clover: 7-method contract (initiate, authorize, capture, refund, cancel, retrieve, webhook)
  • Aeropay: 6-method contract (initiate, authorize, capture-n/a, refund, retrieve, webhook)
  • Both modules expose sandbox vs production URLs via environment switch
  • Sandbox merchant id JM3X09S2DHK06 (B-IT) for Clover dev
Agent backbone

Hard cap at three levels — belt and suspenders

The $250 card cap (NEXT_PUBLIC_CARD_MAX_CENTS, default 25000) is enforced in the Clover module's `authorize()` (rejects any over-cap call), in the `/api/checkout` storefront API, AND in the `/admin/settings/payment-policy` surface. No path — including admin-authed manual orders — can route a card charge above the cap. Above-cap orders automatically route to the Aeropay ACH rail.

  • Module-level: payment-clover rejects authorize() with amount > cardMaxCents
  • Storefront: /api/checkout enforces the same cap before calling the provider
  • Admin: /admin/settings/payment-policy returns the cap + ACH-required-above flag
  • Feature flag FEATURE_CLOVER_CARD_RAIL gates the card rail entirely
Agent backbone

Reconciliation by webhook — settlement + reversal land in the same surface

Both rails publish settlement + reversal events. Clover's `getWebhookActionAndData()` verifies the HMAC + decodes the event into a typed action; Aeropay's webhook handles settlement + reversal asynchronously. Both feed back into the Medusa order-state machine so the operator sees one reconciliation view, not two.

Retention

Designed to swap rails as the federal hemp ban hits

The architecture is rail-agnostic on purpose. As P.L. 119-37 (Nov 12 2026) re-classifies most of the current catalog, the storefront swaps from `payment-clover` (high-risk hemp MID) → `payment-stripe` (compliant wellness MID) by changing one provider registration in `medusa-config.ts`. ACH stays on Aeropay throughout.

While the owner sleeps.

Autonomous surfaces

The agent backbone keeps the brand earning after hours. The owner approves; the system runs.

  • Routes payments by amount, not by operator choice

    on every checkout

    Storefront and admin both consult the payment-policy endpoint; over-cap purchases auto-route to ACH without operator intervention.

  • Reconciles webhook events into the Medusa order-state machine

    real-time on each event

    Settlement + reversal events from both rails land in the same operator surface — no separate dashboards.

  • Gate-flips the card rail without a redeploy

    instant on env flip

    FEATURE_CLOVER_CARD_RAIL=false takes the card rail offline immediately; storefront automatically presents ACH-only checkout.

← All rebuilds

BMH payment rails — Wix payments to a two-rail Medusa module with hard caps — zsty.us