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, andvoidvia NMI's Direct Post API (transact.php). Sandbox mode targetshttps://sandbox.nmi.com/api/transact.php; production targetshttps://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,nmiis 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 MD5hashparameter 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 whenenable_3dsis set on the gateway config. - GraphQL admin mutations:
nmiAdminCaptureTransaction,nmiAdminVoidTransaction,nmiAdminRefundTransaction, andnmiAdminTestGatewayConnection. - 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¶
- Once installed (or dev-linked in development), the extension is discovered at startup via its
vectis.extensionsentry point. Enable it for a channel under Settings > Extensions. - In the admin panel, open Settings > Payments and click Add Method; choose the NMI gateway. See Configuring Payment Gateways.
- 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 theNMI_SECURITY_KEYenvironment variable. - 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.
- Leave Sandbox on while testing — it routes API calls to
sandbox.nmi.cominstead ofsecure.networkmerchants.com. TheNMI_SANDBOXenvironment variable (defaulttrue) is the fallback when the config doesn't set it. - Click Test Connection. This runs the
nmiAdminTestGatewayConnectionmutation, which validates the credentials with a zero-amountvalidatetransaction. - 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 Settingpayment.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 Settingpayment.nmi.webhook_signature_key, with theNMI_SECURITY_KEYenv 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_KEYcasually: it is not webhook-only. It doubles as the charge-path credential fallback — a deployment whose payment-method config lackssecurity_keystill 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=1marks the tender settled;response=2(declined) andresponse=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_firstmode 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_saveandsave_firstsave without an opt-in; guests never save. - Refund idempotency: NMI's refund API has no deduplication parameter, so the
idempotency_keyis accepted but unused. The TemporalRefundExecutionWorkflowpersists per-tender progress before each gateway call as the guard against double refunds. - No transaction-details endpoint:
transact.phphas no single get-transaction call; the storedgateway_responsefrom 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.