# 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 ```bash npm install ``` ### 2. Configure Environment Variables Copy `.env.example` to `.env` and fill in your NOWPayments IPN Secret Key: ```bash 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: ```bash mysql -u your_user -p your_database < migrations/add_expires_at_to_pending_orders.sql ``` Or manually add the column: ```sql 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 ```bash npm run build ``` ### 5. Run the Server **Development mode (with auto-reload):** ```bash npm run dev ``` **Production mode:** ```bash 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: ```javascript { "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): ```bash 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