Version: 1.0
A production-ready Go microservice for integrating with the NMI (Network Merchants, Inc.) payment gateway. This service provides comprehensive payment processing capabilities, including tokenization, recurring payments, refunds, voids, and detailed transaction logging and monitoring.
- Features
- Installation
- Configuration
- API Reference
- Migrating from Sandbox to Production
- Docker Deployment
- Monitoring and Logging
- Troubleshooting
- License
- Tokenization: Securely tokenize credit card details for future use.
- Recurring Payments: Set up recurring billing based on pre-defined plans.
- Refunds: Full or partial refunds for transactions.
- Voids: Cancel a transaction before settlement.
- Plan Management: Add, update, and list subscription plans.
- Validates credit card details using the Luhn algorithm.
- Ensures proper CVV, expiration date, and amount formatting.
- Supports idempotency keys to prevent duplicate transactions.
- Logs all transactions in structured format.
- Exposes Prometheus metrics for real-time monitoring.
- Supports logging to files (CSV and JSON).
- Go 1.20 or higher.
- Docker (for containerized deployment).
- An NMI API Key (sandbox or production).
-
Clone the repository:
git clone https://github.com/goldenhippo58/nmi-payment.git cd nmi-payment
-
Install dependencies:
go mod tidy
-
Build the application:
go build -o payment-service ./cmd/main.go
-
Run the service locally:
MODE=serve API_URL=https://secure.nmi.com/api/transact.php NMI_API_KEY=your_api_key ./payment-service
Set the following environment variables:
NMI_API_KEY=your_nmi_api_key
LOG_FILE=transactions.log
CSV_FILE=transactions.csv
API_URL=https://secure.networkmerchants.com/api/transact.php # Sandbox
# API_URL=https://secure.nmi.com/api/transact.php # Production
DEBUG_MODE=true
Endpoint: GET /health
Checks if the service is running.
Response:
{
"status": "OK",
"timestamp": "2025-01-15T18:25:43Z"
}
Endpoint: POST /plans/add
Adds a new subscription plan.
Request Example:
{
"event_id": "35f56738-4755-4cd9-9068-ea9e83d60d2e",
"event_type": "recurring.plan.add",
"event_body": {
"merchant": {
"id": "944025",
"name": "Test merchant account"
},
"features": {
"is_test_mode": true
},
"plan": {
"id": "TestPlanId1",
"name": "Test Plan",
"amount": "10.00",
"day_frequency": "",
"payments": "Until canceled",
"month_frequency": "1",
"day_of_month": "15"
}
}
}
Response Example:
{
"plan": {
"id": "TestPlanId1",
"name": "Test Plan",
"amount": "10.00",
"day_frequency": "",
"payments": "Until canceled",
"month_frequency": "1",
"day_of_month": "15"
},
"message": "Plan added successfully"
}
Endpoint: GET /plans/list
Retrieves all subscription plans.
Response Example:
{
"TestPlanId1": {
"id": "TestPlanId1",
"name": "Test Plan",
"amount": "10.00",
"day_frequency": "",
"payments": "Until canceled",
"month_frequency": "1",
"day_of_month": "15"
}
}
Endpoint: POST /payments/tokenize
Tokenizes a credit card for future transactions.
Request Example:
{
"credit_card": "4111111111111111",
"exp_date": "1225",
"cvv": "123",
"amount": "1.00",
"type": "sale",
"billing": {
"first_name": "John",
"last_name": "Doe",
"address1": "123 Test St",
"city": "TestCity",
"state": "TX",
"zip": "12345",
"country": "US",
"email": "[email protected]",
"phone": "1234567890"
}
}
Response Example:
{
"customer_vault_id": "5508470413134828416",
"token": "5508470413134828416",
"masked_card": "************1111",
"card_type": "VISA",
"expiry_date": "1225",
"success": true,
"message": "SUCCESS"
}
Endpoint: POST /payments/sale
Processes a sale transaction.
Request Example:
{
"customer_vault_id": "5508470413134828416",
"amount": "10.00",
"type": "sale",
"billing": {
"first_name": "John",
"last_name": "Doe",
"address1": "123 Test St",
"city": "TestCity",
"state": "TX",
"zip": "12345",
"country": "US",
"email": "[email protected]",
"phone": "1234567890"
}
}
Response Example:
{
"transaction_id": "10317389463",
"status": "success",
"response": "SUCCESS"
}
Endpoint: POST /payments/recurring/create
Sets up a recurring payment based on a plan.
Request Example:
{
"customer_vault_id": "5508470413134828416",
"plan_id": "TestPlanId1",
"billing": {
"first_name": "John",
"last_name": "Doe",
"address1": "123 Test St",
"city": "TestCity",
"state": "TX",
"zip": "12345",
"country": "US",
"email": "[email protected]",
"phone": "1234567890"
}
}
Response Example:
{
"subscription_id": "10317410976",
"status": "success",
"plan_id": "TestPlanId1",
"customer_vault_id": "5508470413134828416"
}
Endpoint: POST /payments/refund
Refunds a transaction (full or partial).
Request Example:
{
"transaction_id": "10317389463",
"amount": "5.00"
}
Response Example:
{
"status": "success",
"response": "SUCCESS",
"transaction_id": "10317415000"
}
Endpoint: POST /payments/void
Voids a transaction before settlement.
Request Example:
{
"transaction_id": "10317389463"
}
Response Example:
{
"status": "success",
"response": "SUCCESS",
"transaction_id": "10317389463"
}
Endpoint: POST /terminal/init
Initializes a payment terminal for processing transactions.
Request Example:
{
"terminal_id": "TERM123",
"location": "Store-01",
"merchant_id": "MER12345",
"type": "terminal_setup"
}
Reponse Example:
{
"status": "success",
"terminal_id": "TERM123",
"message": "Terminal initialized successfully"
}
Endpoint: POST /terminal/payment
Process a payment through an initialized terminal.
Request Example:
{
"terminal_id": "TERM123",
"amount": "25.99",
"type": "cc:sale",
"order_id": "ORD-123456",
"ccnumber": "4111111111111111",
"ccexp": "1225",
"cvv": "123"
}
Response Example:
{
"status": "success",
"transaction_id": "10317389463",
"auth_code": "ABC123",
"response_text": "Transaction Approved",
"amount": "25.99"
}
Endpoint GET /terminal/status/{terminal_id}
Retrieves the current status of a terminal
Response Example:
{
"status": "active",
"terminal_id": "TERM123",
"location": "Store-01",
"last_transaction": "2025-01-15T18:25:43Z"
}
Endpoint POST /terminal/cancel/{terminal_id}
Cancels as in-progress terminal transaction.
Reponse Example:
{
"status": "success",
"message": "Transaction cancelled successfully",
"terminal_id": "TERM123"
}
Set the following environment variables for production:
API_URL=https://secure.nmi.com/api/transact.php
NMI_API_KEY=your_production_api_key
DEBUG_MODE=false
TLS settings should enforce modern security standards:
// TLS configuration example
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
Prometheus configuration example for monitoring:
scrape_configs:
- job_name: 'nmi-payment-service'
scrape_interval: 15s
static_configs:
- targets: ['nmi-payment:8080']
-
Build the Docker image:
docker build -t nmi-payment-service .
-
Run the Docker container:
docker run -p 8080:8080 -e MODE=serve -e NMI_API_KEY=your_api_key nmi-payment-service
version: '3.7'
services:
nmi-payment:
build: .
ports:
- "8080:8080"
environment:
- MODE=serve
- NMI_API_KEY=your_api_key
Available at http://localhost:8080/metrics
Key Metrics:
http_requests_total
: Total HTTP requests.http_request_duration_seconds
: Request duration histograms.nmi_transactions_total
: Total processed transactions.
transactions.log
: Logs all transactions.transactions.csv
: Logs transaction records in CSV format.
-
Docker Desktop Not Running:
error during connect: Get "http://%2F%2F.%2Fpipe%2FdockerDesktopLinuxEngine/v1.47/images/...": open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.
Solution: Start Docker Desktop before running docker commands.
-
Environment Variables Not Set:
time="2025-01-08T14:38:04-05:00" level=warning msg="The \"DEBUG_MODE\" variable is not set. Defaulting to a blank string."
Solution: Ensure all environment variables are properly set in the
.env
file. -
Build Issues: If you encounter build issues, try cleaning Docker cache:
docker-compose down docker system prune -f docker-compose up --build -d
-
Authentication Error (300):
{ "raw_response": "response=3&responsetext=Authentication Failed&response_code=300" }
Solution: Verify API key and environment settings.
-
Duplicate Transaction:
NMI Error duplicate_transaction: duplicate transaction detected
Solution: Use a unique
idempotency_key
for each transaction. -
Invalid Card:
NMI Error invalid_card: invalid credit card number length
Solution: Verify card number format and validation.
This project is licensed under the MIT License.
Zachary Kleckner | [email protected]