Integrating the Frontend Component

Installation

We offer a variety of npm modules for popular frameworks. The easiest way to get started is to install the module for the frame work being used.

npm i @nmipayments/nmi-pay-react
npm i @nmipayments/nmi-pay

Example Usage

The following demonstrates basic usage of the payment component.

// 1. Import the payment component
import { NmiPayments } from '@nmipayments/nmi-pay-react';
import { useState } from 'react';

// 2. Create your payment form component
function PaymentForm() {
  // 3. Set up state for amount and payment status
  const [amount, setAmount] = useState('10.99');
  const [paymentStatus, setPaymentStatus] = useState('');
  
  // 4. Function to handle form submission
  return (
    <div>
      {/* 5. Add your form input for amount */}
      <div className="form-row">
        <label htmlFor="amount">Amount:</label>
        <input
          id="amount"
          type="text"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
        />
      </div>
      
      {/* 6. Status message display */}
      {paymentStatus && (
        <div className={paymentStatus === true ? 'success' : 'error'}>
          {typeof paymentStatus === 'string' ? paymentStatus : 'Payment successful!'}
        </div>
      )}
      
      {/* 7. Add the NmiPayments component */}
      <NmiPayments 
        tokenizationKey="{user.publicApiKey}"
        onPay={async (token) => {
          // 8. Handle the payment token
          try {
            // Send token to your server
            const response = await fetch('/api/process-payment', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                paymentToken: token,
                amount: parseFloat(amount)
              }),
            });
            
            // Process the response
            const data = await response.json();
            setPaymentStatus(data.success ? true : data.error || 'Payment failed');
            return data.success ? true : data.error || 'Payment failed';
          } catch (err) {
            // Handle errors
            const errorMessage = err instanceof Error ? err.message : 'An error occurred';
            setPaymentStatus(errorMessage);
            return errorMessage;
          }
        }}
      />
    </div>
  );
}
// 1. Add the HTML container
<div id="payment-container"></div>

// 2. JavaScript implementation
import { mountNmiPayments } from '@nmipayments/nmi-pay';

// 3. Initialize the payment component
document.addEventListener('DOMContentLoaded', async function() {
  try {
    // 4. Mount the payment widget
    const widget = mountNmiPayments('#payment-container', {
      tokenizationKey: 'KXypee-Vq3y2f-K6c7Dy-Wq92R5',
      
      // 5. Handle payment submission
      onPay: async (token) => {
        try {
          // 6. Send token to your server
          const response = await fetch('/api/process-payment', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              paymentToken: token,
              amount: 10.99 // Replace with your amount
            }),
          });
          
          // 7. Process the response
          const data = await response.json();
          if (data.success) {
            console.log('Payment successful!');
            return true;
          } else {
            console.error('Payment failed:', data.error);
            return data.error || 'Payment failed';
          }
        } catch (err) {
          // 8. Handle errors
          console.error('Payment error:', err.message);
          return err.message || 'An error occurred';
        }
      }
    });
    
    console.log('Payment widget loaded successfully');
  } catch (error) {
    console.error('Error loading payment widget:', error);
  }
});

Key Functional Properties

Tokenization Key

To get started you will need to populate your tokenization key. For testing, this can be found on the key management screen under 'Merchant keys'. The tokenization key is public and is safe to be included in your frontend application. It identifies your merchant and is tied to a corresponding private API key that is used to process the payment.

When you are ready to go live, this key must be replaced with a key from the production merchant account The product key can be found in the Merchant console.

onPay

If you want the component to handle the submission and loading state of the payment, use onPay. It is an optional function prop that if provided, will display a pay button in the component.

The function will be invoked when the user clicks the pay button in the component. It passes the token representing the card data, and expects a Promise<true | string> as a result.

It will display a spinner while you process the payment token with your server and ultimately the NMI API. If true is returned, it means the payment is successful. If a string is returned, it indicates an error and the component will display it.

  onPay: ((token: string) => Promise<true | string>) | null;

📘

Tip: If you plan on integrating 3DS, using onChange is recommended as it provides more flexibility.


onChange

onChange is fired when any inputs are changed in the component. When the form is completed and the payment is ready to process, complete will be true and you can use the token in the event to process your payment.

If complete is false, the payment fields are not yet completed. Watch out for cases where the fields are completed, but then a field is cleared and complete is no longer true.

  onChange?: (data: PaymentChangeEvent) => void;

  interface PaymentChangeEvent {
    token: string;
    complete: boolean;
  }

onFieldsAvailable

onFieldsAvailable is fired when the payment fields are available and ready to be filled out. The fields take a moment to initialize, so if you want to customize the loading experience, use this prop. We will display a spinner while the fields are initializing.

onFieldsAvailable: () => void;

🚧

Note: One of these function properties (onPay or onChange) is required to obtain the payment token.

Handling Errors

In the event that the payment was submitted to the Payments API, but it failed, the fields will need to be reset before another payment can be attempted. Use the resetFields function on the component to reset the fields. This does not need to be called when using the onPay integration.

function resetFields(): void

Payer Authentication (3DS)

Payer Authentication is a fraud prevention service that facilitates the authentication of transactions before submitting them to the gateway for processing.

Payer Authentication enables the exchange of data between the merchant, card issuer, and the customer to validate the transaction is being performed by the rightful owner.

For the majority of transactions, the authentication occurs behind the scenes without interrupting the customer. Otherwise known as frictionless flows. In rare instances, the issuer may request a challenge. This step-up authentication flow would require the customer to validate themselves through additional information such as a one-time password.

This additional verification data is submitted in the transaction request and protects you from fraudulent transactions, chargebacks, and malicious behavior.

Integration

Integrating Payer Authentication works with the NMI Payments component described above. Simply mount the NmiThreeDSecure component on your page with your tokenization key, and when you have obtained a payment token from the NMI Payments component and your form is ready to be submitted, call startThreeDSecure with your payment token to perform the payer auth.

The onComplete function prop on the NmiThreeDSecure component will give you back 3DS information that you then include in the submission to your server and ultimately the NMI Payments API.

Example

import React, { useRef, useState } from 'react';
import { NmiPayments, NmiPaymentsRef, NmiThreeDSecure, NmiThreeDSecureRef } from '@nmipayments/nmi-pay-react';
import type { PaymentChangeEvent, PaymentInformation } from '@nmipayments/nmi-pay';

const ThreeDSBasic: React.FC = () => {
  const threeDSRef = useRef<NmiThreeDSecureRef>(null);
  
  const [isValid, setIsValid] = useState<boolean>(false);
  const [paymentToken, setPaymentToken] = useState<string>('');

  const handlePay = () => {
    if (!isValid || !paymentToken) return;

    // Start 3D Secure authentication
    const paymentInfo: PaymentInformation = {
      currency: 'USD',
      amount: 10.00,
      firstName: "John", // From your form
      lastName: "Doe", // From your form
      paymentToken: paymentToken
    };

    threeDSRef.current?.startThreeDSecure(paymentInfo);
  };

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', padding: '2rem' }}>
      {/* Payment form */}
      <NmiPayments
        tokenizationKey="q2U4fW-DubpT2-98Zng2-aKeqaG"
        layout="multiLine"
        paymentMethods={['card']}
        onChange={(data) => {
          // Called when payment fields change
          setIsValid(data.complete);
          
          if (data.complete && data.token) {
            setPaymentToken(data.token);
          }
        }}
      />
      
      {/* 3D Secure component */}
      <NmiThreeDSecure
        ref={threeDSRef}
        tokenizationKey="q2U4fW-DubpT2-98Zng2-aKeqaG"
        modal={true}
        onComplete={async (result) => {
          // Called when 3DS authentication succeeds
          console.log('3DS authentication complete:', result);
          
          // Send token and 3DS data to your server
          const response = await fetch('/api/process-payment', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              paymentToken: paymentToken,
              amount: 10.00,
              cardHolderAuth: result.cardHolderAuth,
              cavv: result.cavv,
              directoryServerId: result.directoryServerId,
              eci: result.eci,
              threeDsVersion: result.threeDsVersion,
              xid: result.xid
            }),
          });
          
          const data = await response.json();
        }}
        onFailure={(error) => {
          // Called when 3DS authentication fails
        }}
        onChallenge={() => {
          // Called when 3DS challenge is triggered
        }}
      />
      
      {/* Pay button */}
      <button onClick={handlePay} disabled={!isValid}>
        Pay with 3D Secure
      </button>
    </div>
  );
};

export default ThreeDSBasic;
import { mountNmiPayments, mountNmiThreeDSecure } from '@nmipayments/nmi-pay';

let isValid = false;
let paymentToken = '';
let threeDSInstance = null;

const handlePay = () => {
  if (!isValid || !paymentToken) return;

  // Start 3D Secure authentication
  const paymentInfo = {
    currency: 'USD',
    amount: 10.00,
    firstName: "John", // From your form
    lastName: "Doe", // From your form
    paymentToken: paymentToken
  };

  threeDSInstance?.startThreeDSecure(paymentInfo);
};

// Mount payment form
const paymentInstance = mountNmiPayments(document.getElementById('payment-container'), {
  tokenizationKey: "q2U4fW-DubpT2-98Zng2-aKeqaG",
  layout: "multiLine",
  paymentMethods: ['card'],
  onChange: (data) => {
    // Called when payment fields change
    isValid = data.complete;
    
    if (data.complete && data.token) {
      paymentToken = data.token;
    }
  }
});

// Mount 3D Secure component
threeDSInstance = mountNmiThreeDSecure(document.getElementById('threeds-container'), {
  tokenizationKey: "q2U4fW-DubpT2-98Zng2-aKeqaG",
  modal: true,
  onComplete: async (result) => {
    // Called when 3DS authentication succeeds
    console.log('3DS authentication complete:', result);
    
    // Send token and 3DS data to your server
    const response = await fetch('/api/process-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        paymentToken: paymentToken,
        amount: 10.00,
        cardHolderAuth: result.cardHolderAuth,
        cavv: result.cavv,
        directoryServerId: result.directoryServerId,
        eci: result.eci,
        threeDsVersion: result.threeDsVersion,
        xid: result.xid
      }),
    });
    
    const data = await response.json();
  },
  onFailure: (error) => {
    // Called when 3DS authentication fails
    console.log('3DS authentication failed:', error);
  },
  onChallenge: () => {
    // Called when 3DS challenge is triggered
    console.log('3D Secure challenge in progress...');
  }
});

// Add event listener to pay button
document.getElementById('pay-button').addEventListener('click', handlePay);

Component Setup


interface NmiThreeDSecureProps {
  tokenizationKey: string;
  modal?: boolean;
  onChallenge?: () => void;
  onComplete?: (e: ThreeDSecureCompleteEvent) => void;
  onFailure?: (e: ThreeDSecureFailureEvent) => void;
}

interface ThreeDSecureCompleteEvent {
  cardHolderAuth: string;
  cavv: string;
  directoryServerId: string;
  eci: string;
  threeDsVersion: string;
  xid: string;
}

interface ThreeDSecureFailureEvent {
  code: string;
  message: string;
}

Starting Authentication

function startThreeDSecure(paymentInfo: PaymentInformation): void

Payment Information Interface

The call to startThreeDSecure takes a payment information object:

interface PaymentInformation {
  paymentToken: string;
  currency: string;
  amount: string;
  firstName: string;
  lastName: string;
  email?: string;
  city?: string;
  postalCode?: string;
  country?: string;
  phone?: string;
  address1?: string;
  address2?: string;
  address3?: string;
  state?: string;
  shippingCity?: string;
  shippingAddress1?: string;
  shippingAddress2?: string;
  shippingAddress3?: string;
  shippingCountry?: string;
  shippingFirstName?: string;
  shippingLastName?: string;
  shippingPostalCode?: string;
  shippingState?: string;
  challengeIndicator?: string;
}
🚧

Required fields: paymentToken, currency, amount, firstName, lastName are required at a minimum.

👍

Tip: The more information you supply to this call, the higher the likelihood the user will not be prompted for authentication.

📘

Tip: By default payer auth challenges will be displayed in a modal, if you want to control where the challenge shows up, set modal to false, and it will show up where you mount the NmiThreeDSecure component.


Security Considerations

PCI DSS Compliance

Using NMI's tokenization approach significantly reduces your PCI DSS compliance scope. By ensuring that sensitive card data never touches your server, you can qualify for the simplest level of PCI compliance (SAQ A).

Direct Transmission

Information entered in the payment form is sent directly to NMI so that your application never processes, stores, or has access to your customer's payment card data.

Data Isolation

The payment card information entered in the form is isolated from your environment and does not pass through your infrastructure. This creates a secure conduit between your customers and the payment processor.

Additional Security Measures

  • Always use HTTPS for all payment-related communications
  • Implement proper error handling and validation
  • Consider adding 3D Secure authentication for high-risk transactions
  • Regularly update your dependencies and security patches

What’s Next

Configure the component to look and feel native to your experience.