2025-12-21 09:57:56 +01:00
2025-12-21 09:57:56 +01:00
2025-12-21 09:57:56 +01:00
2025-12-20 19:00:17 +01:00
2025-12-20 19:00:17 +01:00
2025-12-20 19:00:17 +01:00
2025-12-20 19:00:17 +01:00
2025-12-20 19:00:17 +01:00
2025-12-21 09:57:56 +01:00
2025-12-20 19:00:17 +01:00

NOWPayments IPN Listener

A TypeScript Express application for receiving and processing Instant Payment Notifications (IPN) from NOWPayments.

Features

  • Secure IPN signature validation using HMAC SHA512
  • TypeScript for type safety
  • Handles all NOWPayments payment statuses
  • MySQL/MariaDB database integration
  • Automatic payment processing: moves finished/confirmed payments from pending_orders to sales
  • 10-minute reservation mechanism to prevent overselling
  • Expiration checking and automatic cleanup of expired orders
  • Final inventory validation before creating sales
  • Transaction-safe database operations
  • Idempotent IPN processing (handles duplicate callbacks)
  • Error handling and logging

Setup

1. Install Dependencies

npm install

2. Configure Environment Variables

Copy .env.example to .env and fill in your NOWPayments IPN Secret Key:

cp .env.example .env

Edit .env and configure:

NOWPayments IPN Secret Key:

  • Go to NOWPayments Dashboard → Store Settings → IPN Secret Key
  • Generate a new key if you don't have one
  • Add it to the .env file

Database Configuration:

  • Set your MySQL/MariaDB connection details:
    • DB_HOST - Database host (default: localhost)
    • DB_PORT - Database port (default: 3306)
    • DB_USER - Database username
    • DB_PASSWORD - Database password
    • DB_NAME - Database name (default: cbd420)

3. Database Migration

The application requires the expires_at column in the pending_orders table for the 10-minute reservation mechanism. Run the migration:

mysql -u your_user -p your_database < migrations/add_expires_at_to_pending_orders.sql

Or manually add the column:

ALTER TABLE `pending_orders`
  ADD COLUMN `expires_at` datetime NOT NULL DEFAULT (DATE_ADD(NOW(), INTERVAL 10 MINUTE))
  AFTER `created_at`;

ALTER TABLE `pending_orders`
  ADD INDEX `idx_expires_at` (`expires_at`);

4. Build the Project

npm run build

5. Run the Server

Development mode (with auto-reload):

npm run dev

Production mode:

npm start

The server will start on port 3000 (or the port specified in your .env file).

Endpoints

POST /ipn

Receives IPN notifications from NOWPayments. This endpoint:

  • Validates the request signature
  • Processes payment status updates
  • Returns 200 OK to acknowledge receipt

GET /health

Health check endpoint to verify the server is running.

IPN Callback URL Setup

When creating a payment via the NOWPayments API, include the ipn_callback_url parameter:

{
  "price_amount": 100,
  "price_currency": "usd",
  "pay_currency": "btc",
  "ipn_callback_url": "https://yourdomain.com/ipn",
  // ... other parameters
}

Payment Statuses

The listener handles the following payment statuses:

  • waiting - Payment is waiting
  • confirming - Payment is being confirmed on blockchain
  • confirmed - Payment confirmed on blockchain
  • sending - Payment is being sent
  • partially_paid - Payment partially received
  • finished - Payment completed successfully
  • failed - Payment failed
  • refunded - Payment refunded
  • expired - Payment expired

Database Integration

The application implements a 10-minute reservation mechanism to prevent race conditions when multiple buyers attempt to purchase the last available units simultaneously.

Payment Processing Flow

When a payment status is finished or confirmed, the system:

  1. Find Pending Order - Looks up the pending order by payment_id or invoice_id
  2. Check Expiration - Verifies the pending order hasn't expired (10-minute window)
  3. Validate Payment Status - Processes based on status:
    • finished or confirmed → Proceed to create sale
    • failed or expired → Delete pending order
    • waiting, confirming → Acknowledge and wait
  4. Final Inventory Check - Validates inventory is still available before creating sale
  5. Create Sale Record - Inserts into sales table and deletes from pending_orders

All operations are performed within a database transaction to ensure data consistency and prevent race conditions.

Database Schema

The application expects the following tables (as defined in cbd420(1).sql + migration):

  • pending_orders - Stores pending payment orders with 10-minute reservations

    • payment_id (unique) - NOWPayments payment/invoice ID
    • order_id (unique) - Internal order ID (format: SALE-{timestamp}-{drop_id}-{buyer_id})
    • drop_id - Reference to drops table
    • buyer_id - Reference to buyers table
    • size - Order size (in grams)
    • price_amount - Payment amount
    • price_currency - Payment currency (default: 'chf')
    • created_at - Order creation timestamp
    • expires_at - Expiration timestamp (10 minutes from creation) - REQUIRED
  • sales - Stores completed sales

    • drop_id - Reference to drops table
    • buyer_id - Reference to buyers table
    • size - Sale size (in grams)
    • payment_id - NOWPayments payment ID (matches pending_orders.payment_id)
    • created_at - Sale creation timestamp
  • drops - Product drop information

    • id - Drop ID
    • size - Available size
    • unit - Unit of measurement ('g' or 'kg')

Inventory Calculation

Available inventory is calculated as:

Available = drop.size - (SUM(sales.size) + SUM(pending_orders.size WHERE expires_at > NOW()))

The system automatically handles unit conversion (kg to grams) when calculating inventory.

Key Features

  • Expiration Handling: Pending orders expire after 10 minutes and are automatically excluded from inventory calculations
  • Inventory Validation: Final inventory check ensures no overselling occurs between payment initiation and completion
  • Idempotency: IPN callbacks can be processed multiple times safely (checks for existing sales)
  • Transaction Safety: All database operations use transactions to ensure atomicity

IPN Callback Processing

The IPN handler follows this flow for each callback:

  1. Find Pending Order - Searches by payment_id or invoice_id (tries both)
  2. Check Expiration - Verifies expires_at > NOW() - expired orders are rejected
  3. Payment Status Processing:
    • finished or confirmed → Creates sale (after inventory check)
    • failed or expired → Deletes pending order
    • Other statuses → Acknowledged, waiting for final status
  4. Final Inventory Check - Re-validates inventory before creating sale (within transaction)
  5. Create Sale - Atomically creates sale and deletes pending order

Important Notes

  • IPN callbacks may be sent multiple times - the system handles this with idempotency checks
  • Expired pending orders are automatically excluded from inventory calculations
  • Always returns HTTP 200 to NOWPayments (even on errors) to prevent retries
  • Inventory is checked within a database transaction to prevent race conditions

Security

  • All IPN requests are validated using HMAC SHA512 signature verification
  • Invalid signatures are rejected with 400 Bad Request
  • The IPN secret key should never be committed to version control
  • Database transactions ensure data consistency and prevent race conditions

Testing

You can test the IPN endpoint using tools like:

  • Postman
  • curl
  • NOWPayments test payments

Example curl command (with test signature):

curl -X POST http://localhost:3000/ipn \
  -H "Content-Type: application/json" \
  -H "x-nowpayments-sig: your_signature_here" \
  -d '{"payment_id":"test123","payment_status":"waiting","price_amount":100,"price_currency":"usd"}'

Documentation

For detailed implementation guide and database schema, refer to the IPN Callback Integration Guide provided with this application.

License

ISC

Description
No description provided
Readme 129 KiB
Languages
TypeScript 100%