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

240 lines
7.7 KiB
Markdown

# 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