Compare commits
10 Commits
961380dd88
...
e2a13d009f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2a13d009f | ||
|
|
a5c32b574c | ||
|
|
4685a5e42a | ||
|
|
aa93c50341 | ||
|
|
184f28fe5d | ||
|
|
bf1f368a24 | ||
|
|
91259e98c6 | ||
|
|
05cdee8236 | ||
|
|
ba36f96f4c | ||
|
|
52d4735712 |
85
README.md
85
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Cashu Redeem API 🪙⚡
|
# Cashu Redeem API 🪙⚡
|
||||||
|
|
||||||
A production-grade API for redeeming Cashu tokens (ecash) to Lightning addresses using the cashu-ts library and LNURLp protocol.
|
API for redeeming Cashu tokens (ecash) to Lightning addresses using the cashu-ts library and LNURLp protocol.
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ A production-grade API for redeeming Cashu tokens (ecash) to Lightning addresses
|
|||||||
- **Redeem to Lightning addresses** - Convert ecash to Lightning payments via LNURLp
|
- **Redeem to Lightning addresses** - Convert ecash to Lightning payments via LNURLp
|
||||||
- **Security features** - Domain restrictions, rate limiting, input validation
|
- **Security features** - Domain restrictions, rate limiting, input validation
|
||||||
- **Robust error handling** - Comprehensive error messages
|
- **Robust error handling** - Comprehensive error messages
|
||||||
- **In-memory caching** - Fast mint and wallet instances with connection pooling
|
|
||||||
- **Interactive API Documentation** - Complete Swagger/OpenAPI documentation at `/docs`
|
- **Interactive API Documentation** - Complete Swagger/OpenAPI documentation at `/docs`
|
||||||
|
|
||||||
|
|
||||||
@@ -18,14 +17,6 @@ A production-grade API for redeeming Cashu tokens (ecash) to Lightning addresses
|
|||||||
|
|
||||||
Example: `https://cashu-redeem.azzamo.net/docs/`
|
Example: `https://cashu-redeem.azzamo.net/docs/`
|
||||||
|
|
||||||
The documentation includes:
|
|
||||||
- Complete endpoint specifications
|
|
||||||
- Request/response schemas
|
|
||||||
- Try-it-out functionality
|
|
||||||
- Example requests and responses
|
|
||||||
- Authentication requirements
|
|
||||||
- Error code documentation
|
|
||||||
|
|
||||||
## 📡 API Endpoints
|
## 📡 API Endpoints
|
||||||
|
|
||||||
### 1. `POST /api/decode`
|
### 1. `POST /api/decode`
|
||||||
@@ -76,7 +67,6 @@ Redeem a Cashu token to a Lightning address. Lightning address is optional - if
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"redeemId": "8e99101e-d034-4d2e-9ccf-dfda24d26762",
|
|
||||||
"paid": true,
|
"paid": true,
|
||||||
"amount": 21000,
|
"amount": 21000,
|
||||||
"invoiceAmount": 20580,
|
"invoiceAmount": 20580,
|
||||||
@@ -94,7 +84,6 @@ Redeem a Cashu token to a Lightning address. Lightning address is optional - if
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"redeemId": "8e99101e-d034-4d2e-9ccf-dfda24d26762",
|
|
||||||
"paid": true,
|
"paid": true,
|
||||||
"amount": 21000,
|
"amount": 21000,
|
||||||
"invoiceAmount": 20580,
|
"invoiceAmount": 20580,
|
||||||
@@ -178,6 +167,9 @@ Edit `.env` file:
|
|||||||
PORT=3000
|
PORT=3000
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
|
# API Domain/IP Configuration (for Swagger docs and CORS)
|
||||||
|
API_DOMAIN=localhost:3000
|
||||||
|
|
||||||
# Security Configuration
|
# Security Configuration
|
||||||
ALLOW_REDEEM_DOMAINS=ln.tips,getalby.com,wallet.mutinywallet.com
|
ALLOW_REDEEM_DOMAINS=ln.tips,getalby.com,wallet.mutinywallet.com
|
||||||
API_SECRET=your-secret-key-here
|
API_SECRET=your-secret-key-here
|
||||||
@@ -201,7 +193,6 @@ npm run dev
|
|||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
The API will be available at `http://localhost:3000`
|
|
||||||
|
|
||||||
## 🔧 Configuration
|
## 🔧 Configuration
|
||||||
|
|
||||||
@@ -234,25 +225,6 @@ DEFAULT_LIGHTNING_ADDRESS=admin@your-domain.com
|
|||||||
|
|
||||||
This allows users to redeem tokens without specifying a Lightning address - the tokens will automatically be sent to your configured default address. If no default is set, Lightning address becomes required for all redemption requests.
|
This allows users to redeem tokens without specifying a Lightning address - the tokens will automatically be sent to your configured default address. If no default is set, Lightning address becomes required for all redemption requests.
|
||||||
|
|
||||||
## 🏗 Architecture
|
|
||||||
|
|
||||||
### Services
|
|
||||||
|
|
||||||
#### `services/cashu.js`
|
|
||||||
- Manages Cashu token parsing and validation
|
|
||||||
- Handles mint connections and wallet instances
|
|
||||||
- Performs token melting operations
|
|
||||||
- Caches mint/wallet connections for performance
|
|
||||||
|
|
||||||
#### `services/lightning.js`
|
|
||||||
- Validates Lightning address formats
|
|
||||||
- Resolves LNURLp endpoints
|
|
||||||
- Generates Lightning invoices
|
|
||||||
- Handles domain restrictions
|
|
||||||
|
|
||||||
#### `services/redemption.js`
|
|
||||||
- Manages redemption status tracking
|
|
||||||
- Handles duplicate token detection
|
|
||||||
|
|
||||||
### Data Flow
|
### Data Flow
|
||||||
|
|
||||||
@@ -280,16 +252,6 @@ This allows users to redeem tokens without specifying a Lightning address - the
|
|||||||
| `paid` | Successfully paid and completed |
|
| `paid` | Successfully paid and completed |
|
||||||
| `failed` | Redemption failed (see error details) |
|
| `failed` | Redemption failed (see error details) |
|
||||||
|
|
||||||
## 📊 Monitoring
|
|
||||||
|
|
||||||
### Health Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/api/health
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logs
|
|
||||||
The server logs all requests and errors to console. In production, consider using a proper logging solution like Winston.
|
|
||||||
|
|
||||||
## 🧪 Testing
|
## 🧪 Testing
|
||||||
|
|
||||||
@@ -301,45 +263,6 @@ The easiest way to test the API is using the interactive Swagger documentation a
|
|||||||
- Fill in the request parameters
|
- Fill in the request parameters
|
||||||
- Execute the request directly from the browser
|
- Execute the request directly from the browser
|
||||||
|
|
||||||
### Example cURL commands
|
|
||||||
|
|
||||||
**Decode a token:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/decode \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"token":"your-cashu-token-here"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Redeem a token to specific address:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/redeem \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"token": "your-cashu-token-here",
|
|
||||||
"lightningAddress": "user@ln.tips"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Redeem a token to default address:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/redeem \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"token": "your-cashu-token-here"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Production Deployment
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
1. **Use a process manager** (PM2, systemd)
|
|
||||||
2. **Set up reverse proxy** (nginx, Apache)
|
|
||||||
3. **Enable HTTPS** with SSL certificates
|
|
||||||
4. **Use Redis** for persistent storage instead of in-memory
|
|
||||||
5. **Set up monitoring** (Prometheus, Grafana)
|
|
||||||
6. **Configure logging** (Winston, structured logs)
|
|
||||||
7. **Set resource limits** and health checks
|
|
||||||
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
PORT=3000
|
PORT=3000
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
|
# API Domain/IP Configuration (for Swagger docs and CORS)
|
||||||
|
API_DOMAIN=localhost:3000
|
||||||
|
|
||||||
# Security Configuration
|
# Security Configuration
|
||||||
ALLOW_REDEEM_DOMAINS=*
|
ALLOW_REDEEM_DOMAINS=*
|
||||||
API_SECRET=your-secret-key-here
|
|
||||||
|
|
||||||
# Default Lightning Address (used when no address is provided in redeem requests)
|
# Default Lightning Address (used when no address is provided in redeem requests)
|
||||||
DEFAULT_LIGHTNING_ADDRESS=admin@your-domain.com
|
DEFAULT_LIGHTNING_ADDRESS=admin@your-domain.com
|
||||||
|
|||||||
31
package-lock.json
generated
31
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cashu-redeem-api",
|
"name": "cashu-redeem-api",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cashu-redeem-api",
|
"name": "cashu-redeem-api",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cashu/cashu-ts": "^1.1.0",
|
"@cashu/cashu-ts": "^1.1.0",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"express-rate-limit": "^8.0.0",
|
||||||
"swagger-jsdoc": "^6.2.8",
|
"swagger-jsdoc": "^6.2.8",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
@@ -26,6 +27,10 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0",
|
"node": ">=18.0.0",
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/Michilis"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||||
@@ -1464,6 +1469,23 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-rate-limit": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-FXEAp2ccTeN1ZSO+sPHRHWB0/CrTP5asFBjUaNeD9A0v3iPmgFbLu24vqPjiM9utszI58VGlMokjXQ0W9Dbmjw==",
|
||||||
|
"dependencies": {
|
||||||
|
"ip": "2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/express-rate-limit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": ">= 4.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -1961,6 +1983,11 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ip": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "cashu-redeem-api",
|
"name": "cashu-redeem-api",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "Redeem ecash (Cashu tokens) to Lightning Address using cashu-ts library and LNURLp",
|
"description": "A production-grade API for redeeming Cashu tokens (ecash) to Lightning addresses using the cashu-ts library and LNURLp protocol",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "nodemon server.js",
|
"dev": "nodemon server.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix"
|
"lint:fix": "eslint . --fix",
|
||||||
|
"docs": "echo \"API documentation available at http://localhost:3000/docs\""
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cashu",
|
"cashu",
|
||||||
@@ -19,10 +20,18 @@
|
|||||||
"lnurl",
|
"lnurl",
|
||||||
"lnurlp",
|
"lnurlp",
|
||||||
"mint",
|
"mint",
|
||||||
"satoshi"
|
"satoshi",
|
||||||
|
"bolt11",
|
||||||
|
"lightning-network",
|
||||||
|
"payment",
|
||||||
|
"redemption"
|
||||||
],
|
],
|
||||||
"author": "",
|
"author": "Michilis",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/Michilis"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cashu/cashu-ts": "^1.1.0",
|
"@cashu/cashu-ts": "^1.1.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
@@ -44,10 +53,10 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/yourusername/cashu-redeem-api.git"
|
"url": "git+https://github.com/Michilis/cashu-redeem-api.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/yourusername/cashu-redeem-api/issues"
|
"url": "https://github.com/Michilis/cashu-redeem-api/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/yourusername/cashu-redeem-api#readme"
|
"homepage": "https://github.com/Michilis/cashu-redeem-api#readme"
|
||||||
}
|
}
|
||||||
|
|||||||
95
server.js
95
server.js
@@ -10,23 +10,56 @@ const redemptionService = require('./services/redemption');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Get API domain for CORS configuration
|
||||||
|
const apiDomain = process.env.API_DOMAIN || 'localhost:3000';
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
const protocol = isProduction ? 'https' : 'http';
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(express.json({ limit: '10mb' }));
|
app.use(express.json({ limit: '10mb' }));
|
||||||
|
|
||||||
|
// Enhanced CORS configuration for Swagger UI
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: process.env.ALLOWED_ORIGINS
|
origin: process.env.ALLOWED_ORIGINS
|
||||||
? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
|
? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
|
||||||
: ['http://localhost:3000'],
|
: [`${protocol}://${apiDomain}`],
|
||||||
methods: ['GET', 'POST'],
|
methods: ['GET', 'POST', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization']
|
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With'],
|
||||||
|
credentials: true,
|
||||||
|
optionsSuccessStatus: 200
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Additional middleware for Swagger UI preflight requests
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept, Origin, X-Requested-With');
|
||||||
|
res.status(200).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debug endpoint to test CORS
|
||||||
|
app.get('/api/cors-test', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'CORS test successful',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
origin: req.headers.origin,
|
||||||
|
host: req.headers.host
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Swagger Documentation
|
// Swagger Documentation
|
||||||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs, {
|
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs, {
|
||||||
customCss: '.swagger-ui .topbar { display: none }',
|
customCss: '.swagger-ui .topbar { display: none }',
|
||||||
customSiteTitle: 'Cashu Redeem API Documentation',
|
customSiteTitle: 'Cashu Redeem API Documentation',
|
||||||
swaggerOptions: {
|
swaggerOptions: {
|
||||||
filter: true,
|
filter: true,
|
||||||
showRequestHeaders: true
|
showRequestHeaders: true,
|
||||||
|
tryItOutEnabled: true
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -79,52 +112,6 @@ function asyncHandler(fn) {
|
|||||||
|
|
||||||
// API Routes
|
// API Routes
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /:
|
|
||||||
* get:
|
|
||||||
* summary: API Information
|
|
||||||
* description: Get basic information about the Cashu Redeem API
|
|
||||||
* tags: [General]
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: API information
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* name:
|
|
||||||
* type: string
|
|
||||||
* example: "Cashu Redeem API"
|
|
||||||
* version:
|
|
||||||
* type: string
|
|
||||||
* example: "1.0.0"
|
|
||||||
* description:
|
|
||||||
* type: string
|
|
||||||
* example: "A production-grade API for redeeming Cashu tokens (ecash) to Lightning addresses"
|
|
||||||
* documentation:
|
|
||||||
* type: string
|
|
||||||
* example: "/docs"
|
|
||||||
* endpoints:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* decode:
|
|
||||||
* type: string
|
|
||||||
* example: "POST /api/decode"
|
|
||||||
* redeem:
|
|
||||||
* type: string
|
|
||||||
* example: "POST /api/redeem"
|
|
||||||
* validate:
|
|
||||||
* type: string
|
|
||||||
* example: "POST /api/validate-address"
|
|
||||||
* health:
|
|
||||||
* type: string
|
|
||||||
* example: "GET /api/health"
|
|
||||||
* github:
|
|
||||||
* type: string
|
|
||||||
* example: "https://github.com/yourusername/cashu-redeem-api"
|
|
||||||
*/
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
name: 'Cashu Redeem API',
|
name: 'Cashu Redeem API',
|
||||||
@@ -305,9 +292,6 @@ app.post('/api/decode', asyncHandler(async (req, res) => {
|
|||||||
* error:
|
* error:
|
||||||
* type: string
|
* type: string
|
||||||
* example: "This token has already been spent and cannot be redeemed again"
|
* example: "This token has already been spent and cannot be redeemed again"
|
||||||
* redeemId:
|
|
||||||
* type: string
|
|
||||||
* format: uuid
|
|
||||||
* errorType:
|
* errorType:
|
||||||
* type: string
|
* type: string
|
||||||
* example: "token_already_spent"
|
* example: "token_already_spent"
|
||||||
@@ -324,9 +308,6 @@ app.post('/api/decode', asyncHandler(async (req, res) => {
|
|||||||
* error:
|
* error:
|
||||||
* type: string
|
* type: string
|
||||||
* example: "Token amount is insufficient to cover the minimum fee"
|
* example: "Token amount is insufficient to cover the minimum fee"
|
||||||
* redeemId:
|
|
||||||
* type: string
|
|
||||||
* format: uuid
|
|
||||||
* errorType:
|
* errorType:
|
||||||
* type: string
|
* type: string
|
||||||
* example: "insufficient_funds"
|
* example: "insufficient_funds"
|
||||||
@@ -355,7 +336,6 @@ app.post('/api/redeem', asyncHandler(async (req, res) => {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
const response = {
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
redeemId: result.redeemId,
|
|
||||||
paid: result.paid,
|
paid: result.paid,
|
||||||
amount: result.amount,
|
amount: result.amount,
|
||||||
invoiceAmount: result.invoiceAmount,
|
invoiceAmount: result.invoiceAmount,
|
||||||
@@ -400,7 +380,6 @@ app.post('/api/redeem', asyncHandler(async (req, res) => {
|
|||||||
res.status(statusCode).json({
|
res.status(statusCode).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: result.error,
|
error: result.error,
|
||||||
redeemId: result.redeemId,
|
|
||||||
errorType: statusCode === 409 ? 'token_already_spent' :
|
errorType: statusCode === 409 ? 'token_already_spent' :
|
||||||
statusCode === 422 ? 'insufficient_funds' : 'validation_error'
|
statusCode === 422 ? 'insufficient_funds' : 'validation_error'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
require('dotenv').config();
|
||||||
const swaggerJsdoc = require('swagger-jsdoc');
|
const swaggerJsdoc = require('swagger-jsdoc');
|
||||||
|
|
||||||
|
// Get the API domain from environment variable, default to localhost:3000
|
||||||
|
const apiDomain = process.env.API_DOMAIN || 'localhost:3000';
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
const protocol = isProduction ? 'https' : 'http';
|
||||||
|
|
||||||
|
// For production behind Nginx, we need to ensure the URL doesn't include the internal port
|
||||||
|
const serverUrl = isProduction
|
||||||
|
? `${protocol}://${apiDomain}`
|
||||||
|
: `${protocol}://${apiDomain}`;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
definition: {
|
definition: {
|
||||||
openapi: '3.0.0',
|
openapi: '3.0.0',
|
||||||
@@ -18,12 +29,8 @@ const options = {
|
|||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: 'http://localhost:3000',
|
url: serverUrl,
|
||||||
description: 'Development server'
|
description: isProduction ? 'Production server' : 'Development server'
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'https://api.example.com',
|
|
||||||
description: 'Production server'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
@@ -138,12 +145,6 @@ const options = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
example: true
|
example: true
|
||||||
},
|
},
|
||||||
redeemId: {
|
|
||||||
type: 'string',
|
|
||||||
format: 'uuid',
|
|
||||||
description: 'Unique redemption ID for tracking',
|
|
||||||
example: '8e99101e-d034-4d2e-9ccf-dfda24d26762'
|
|
||||||
},
|
|
||||||
paid: {
|
paid: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether the payment was successful',
|
description: 'Whether the payment was successful',
|
||||||
@@ -329,10 +330,6 @@ const options = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
tags: [
|
tags: [
|
||||||
{
|
|
||||||
name: 'General',
|
|
||||||
description: 'General API information and utilities'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Token Operations',
|
name: 'Token Operations',
|
||||||
description: 'Operations for decoding and redeeming Cashu tokens'
|
description: 'Operations for decoding and redeeming Cashu tokens'
|
||||||
|
|||||||
Reference in New Issue
Block a user