Skip to content

NMI Gateway

NMI payment gateway with Customer Vault, Collect.js, and 3D Secure. The extension registers a full-lifecycle payment strategy (authorize, capture, sale, void, refund) against NMI's Direct Post API, saves cards on file through the NMI Customer Vault, tokenizes card data in the browser with Collect.js so raw card numbers never reach the server, and receives NMI settlement postbacks. Choose it when your merchant account is with NMI and you want saved cards and optional 3D Secure data pass-through on the same gateway.

At a glance

Extension id nmi
Category payment
Version 0.0.1
Provides Payment gateway strategy, saved-card (Customer Vault) strategy, settlement webhook, GraphQL admin mutations
External account NMI merchant account — Security Key required (plus a public Collect.js Tokenization Key for the storefront card form)

What It Provides

  • Payment gateway strategy registered under the name nmi (PaymentProcessStrategy): authorize (auth), capture, charge (sale), refund, and void via NMI's Direct Post API (transact.php). Sandbox mode targets https://sandbox.nmi.com/api/transact.php; production targets https://secure.networkmerchants.com/api/transact.php.
  • Saved-card strategy registered under the same name (SavedPaymentMethodStrategy): Customer Vault add / delete / charge. The unified core GraphQL fields — addPaymentMethod, removePaymentMethod, setDefaultPaymentMethod, listPaymentMethods, adminChargeSavedCard — dispatch to it by gateway name.
  • Token claim: the strategy claims any payload carrying a Collect.js payment_token, so checkout can infer the NMI gateway from the tokenization shape without an explicitly named payment method. On the saved-card add path, nmi is asked first when both token shapes are present. See Gateway Inference via Token Claims.
  • Settlement webhook at POST /api/webhooks/payment/nmi: NMI Direct Post transaction postbacks, verified against an MD5 hash parameter and dispatched into the core settlement pipeline.
  • 3D Secure pass-through: when the storefront supplies 3DS authentication results (cavv, xid, eci, directory_server_id, three_ds_version, cardholder_auth), the strategy forwards them on auth and sale requests. The storefront runs NMI 3DS at checkout when enable_3ds is set on the gateway config.
  • GraphQL admin mutations: nmiAdminCaptureTransaction, nmiAdminVoidTransaction, nmiAdminRefundTransaction, and nmiAdminTestGatewayConnection.
  • Deprecated GraphQL saved-card aliases (nmiSavedPaymentMethods, nmiSavedPaymentMethod, nmiAddPaymentMethod, nmiDeletePaymentMethod, nmiSetDefaultPaymentMethod, nmiAdminChargeSavedCard): thin delegates to the unified core saved-card surface, kept for one release and slated for removal.

Setup

  1. Once installed (or dev-linked in development), the extension is discovered at startup via its vectis.extensions entry point. Enable it for a channel under Settings > Extensions.
  2. In the admin panel, open Settings > Payments and click Add Method; choose the NMI gateway. See Configuring Payment Gateways.
  3. Enter the Security Key (secret — masked in the admin). It is used for every outbound Direct Post API call. If the payment-method config has no security_key, the extension falls back to the NMI_SECURITY_KEY environment variable.
  4. Enter the Tokenization Key (public). The storefront's Collect.js card form and the 3D Secure component read it from the payment-method config to tokenize card data in the browser.
  5. Leave Sandbox on while testing — it routes API calls to sandbox.nmi.com instead of secure.networkmerchants.com. The NMI_SANDBOX environment variable (default true) is the fallback when the config doesn't set it.
  6. Click Test Connection. This runs the nmiAdminTestGatewayConnection mutation, which validates the credentials with a zero-amount validate transaction.
  7. For settlement postbacks, configure NMI's per-merchant postback URL to point at https://<your-api-host>/api/webhooks/payment/nmi. The signing material is the same per-merchant security key the strategy uses for API calls; store it in the Fernet-encrypted Setting payment.nmi.webhook_signature_key (see Operational Notes).

Configuration Reference

The manifest config_schema (values live on the payment-method row created in Settings > Payments):

Key Type Required Secret Description
security_key string yes yes NMI security key sent as security_key on every Direct Post API call. Falls back to the NMI_SECURITY_KEY env var when unset.
tokenization_key string no no Public Collect.js tokenization key; read by the storefront card form and 3DS component.
sandbox boolean no no Default true. Selects the sandbox vs. production Direct Post endpoint. Falls back to the NMI_SANDBOX env var.
checkout_save_mode string no no Default order_and_save; one of off, order_and_save, charge_and_save, save_first. Labeled "Save card at checkout" in the admin.
use_collectjs boolean no no Default true. Exposed as a toggle on the admin Payments page.
enable_3ds boolean no no Default false. When on, the storefront runs NMI 3D Secure at checkout and forwards the authentication results with the charge.
challenge_indicator string no no Default "01"; one of 01, 03, 04. Passed to the storefront 3DS flow.
allowed_card_brands array no no Default ["visa", "mastercard", "amex", "discover"]. Enforced by core twice: against the client's brand hint before charging, and against the gateway-reported brand after (with a best-effort void on mismatch).
supported_currencies array no no Default ["USD"]. Enforced by core process_payment before any money moves; an empty list passes every currency.

Permissions

  • payment.nmi.manage

Manifest permissions are upserted at boot but carry no role assignments — assign them to roles manually. Independently of the manifest permission, the extension's admin mutations enforce core permissions at call time: order.update for capture/void/refund and settings.edit for the connection test.

Operational Notes

  • Webhook signature: postbacks are verified against hash = MD5(orderid + amount + transactionid + response + responsetext + authcode + security_key) with a constant-time compare. The secret is read Settings-first from the Fernet-encrypted Setting payment.nmi.webhook_signature_key, with the NMI_SECURITY_KEY env var as a deprecated last-resort fallback that is backfilled into the Setting once at api boot. A missing secret returns HTTP 503 (so NMI retries while you fix the config); a bad signature returns 401.
  • Do not unset NMI_SECURITY_KEY casually: it is not webhook-only. It doubles as the charge-path credential fallback — a deployment whose payment-method config lacks security_key still needs the env var for live charging and vaulting even after the webhook secret has been backfilled into Settings.
  • Postback dedup: NMI postbacks carry no unique delivery id, so the dedup event id is the composite transactionid:r<response> — a later state transition for the same transaction (e.g. settled-then-chargeback) is processed, while identical retries collapse to one effect. response=1 marks the tender settled; response=2 (declined) and response=3 (error) mark it bounced; anything else is ignored.
  • Atomic charge-and-save: when checkout requests a card save and pays with a single-use Collect.js token, the sale itself carries customer_vault=add_customer — NMI creates the vault record only if the transaction is approved, so a declined charge never saves a card. The vault-add response carries no display card metadata, so the brand and expiry shown for the saved card come from the client-side tokenizer's card detection (the last four prefer the gateway's masked card number when the response includes one). save_first mode instead vaults first and charges the stored profile.
  • Save-mode opt-in: with order_and_save, the card is saved only when a signed-in account customer ticks the save checkbox; charge_and_save and save_first save without an opt-in; guests never save.
  • Refund idempotency: NMI's refund API has no deduplication parameter, so the idempotency_key is accepted but unused. The Temporal RefundExecutionWorkflow persists per-tender progress before each gateway call as the guard against double refunds.
  • No transaction-details endpoint: transact.php has no single get-transaction call; the stored gateway_response from the original transaction is the source of truth for transaction details.
  • Timeouts and concurrency: every gateway call uses a 30-second HTTP timeout, and split-tender authorizations run at the core default cap of 4 concurrent calls per gateway (not overridden by this extension).

For the payment lifecycle these strategies plug into, see Payments; for the strategy interfaces and gateway walkthrough, see Building Extensions. The Authorize.Net counterpart is documented at Authorize.Net.