Files
cbd420_ipn/README.md
2025-12-21 09:57:56 +01:00

7.7 KiB

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