testluy-payment-sdk
v3.8.3
Published
SDK for TestLuy Payment Simulator with enhanced Cloudflare resilience and Tunnel Zero Trust support
Maintainers
Readme
TestLuy Payment SDK
🚀 Overview
The TestLuy Payment SDK provides a robust, production-ready JavaScript interface for integrating applications with the TestLuy Payment Simulator platform. Designed for both development and deployment environments, it seamlessly handles Cloudflare Tunnel Zero Trust protection, rate limiting, and complex authentication scenarios.
✅ Successfully tested in production environments including Vercel, Render, Netlify, and other deployment platforms.
🎯 Key Features
- 🛡️ Cloudflare Tunnel Zero Trust Support: Automatic bypass for deployment environments
- 🔐 HMAC-SHA256 Authentication: Secure request signing with automatic credential validation
- ⚡ Smart Environment Detection: Automatically adapts behavior for local vs deployment environments
- 🔄 Intelligent Retry Logic: Exponential backoff with jitter for resilient API calls
- 📊 Advanced Rate Limiting: Subscription-aware rate limiting with graceful degradation
- 🖥️ Multi-Platform Compatibility: Works in Node.js, Next.js, Express, and modern build tools
- 📈 Comprehensive Logging: Configurable logging with sensitive data masking
- 🧪 Extensive Testing: 100% test coverage with integration and unit tests
- 📚 Type Safety: Full TypeScript definitions included
📦 Installation
# Using npm
npm install testluy-payment-sdk@latest
# Using yarn
yarn add testluy-payment-sdk@latest
# Using pnpm
pnpm add testluy-payment-sdk🚨 Security Notice
⚠️ CRITICAL: Server-Side Only Usage
Your secretKey is highly sensitive. Never expose it in client-side code. Always use this SDK on your server-side:
- ✅ Node.js backends
- ✅ Next.js API routes
- ✅ Express route handlers
- ✅ Serverless functions
- ❌ Frontend JavaScript (React, Vue, etc.)
🛠️ Quick Start
Basic Usage
import TestluyPaymentSDK from 'testluy-payment-sdk';
// Initialize the SDK
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL || 'https://api-testluy.paragoniu.app'
});
// Validate credentials (recommended)
await sdk.init();
// Initiate a payment
const { paymentUrl, transactionId } = await sdk.initiatePayment(
25.50,
'https://yourapp.com/payment-callback',
'https://yourapp.com/cancel'
);
// Redirect user to payment URL
console.log('Payment URL:', paymentUrl);
console.log('Transaction ID:', transactionId);Environment Variables Setup
Create a .env file in your project root:
# TestLuy SDK Configuration
TESTLUY_CLIENT_ID=your_client_id_here
TESTLUY_SECRET_KEY=your_secret_key_here
TESTLUY_BASE_URL=https://api-testluy.paragoniu.app
# Optional: Override for different environments
# TESTLUY_BASE_URL=http://localhost:8000 # Local development⚙️ Advanced Configuration
Full Configuration Options
const sdk = new TestluyPaymentSDK({
// Required
clientId: 'your-client-id',
secretKey: 'your-secret-key',
baseUrl: 'https://api-testluy.paragoniu.app',
// Optional: Smart routing (auto-enabled)
enableSmartRouting: true,
bypassUrl: 'https://alt-endpoint.example.com', // Custom bypass endpoint
// Optional: Retry configuration
retryConfig: {
maxRetries: 3,
baseDelay: 1000, // 1 second initial delay
maxDelay: 30000, // 30 seconds max delay
backoffFactor: 2, // Exponential backoff
jitterFactor: 0.1 // Add random jitter
},
// Optional: Cloudflare bypass settings
cloudflareConfig: {
enabled: true, // Auto-enabled for deployment environments
rotateUserAgent: true,
addBrowserHeaders: true
},
// Optional: Logging configuration
loggingConfig: {
level: 'info', // 'debug', 'info', 'warn', 'error', 'silent'
includeHeaders: false, // Include HTTP headers in logs
includeBody: false, // Include request/response bodies
maskSensitive: true, // Mask sensitive data (recommended)
format: 'text', // 'text' or 'json'
colorize: true // Colorize console output
}
});Environment-Specific Configuration
The SDK automatically detects your deployment environment and adjusts behavior accordingly:
// Development Environment (Local)
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: 'http://localhost:8000', // Local backend
loggingConfig: { level: 'debug' } // Verbose logging for development
});
// Production Environment (Auto-detected)
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: 'https://api-testluy.paragoniu.app',
loggingConfig: { level: 'warn' } // Minimal logging for production
});
// Cloudflare bypass headers automatically applied in deployment environments📖 API Reference
Constructor
new TestluyPaymentSDK(options)
Creates a new SDK instance with the specified configuration.
Parameters:
options.clientId(string, required): Your TestLuy application client IDoptions.secretKey(string, required): Your TestLuy application secret keyoptions.baseUrl(string, optional): API base URL (defaults to production endpoint)- Additional options as shown in Advanced Configuration
Throws: Error if required parameters are missing or invalid
Methods
async init()
Validates credentials and initializes the SDK. Recommended to call before other operations.
Returns: Promise<boolean> - true if credentials are valid
Example:
try {
await sdk.init();
console.log('SDK initialized successfully');
} catch (error) {
console.error('Invalid credentials:', error.message);
}async initiatePayment(amount, callbackUrl, backUrl?)
Initiates a payment process and returns a payment URL.
Parameters:
amount(number, required): Payment amount (must be positive)callbackUrl(string, required): URL for payment completion callbackbackUrl(string, optional): URL for user cancellation/back navigation
Returns: Promise<{paymentUrl: string, transactionId: string}>
Example:
const result = await sdk.initiatePayment(
10.50,
'https://yourapp.com/payment-success',
'https://yourapp.com/payment-cancel'
);
console.log('Payment URL:', result.paymentUrl);
console.log('Transaction ID:', result.transactionId);async getPaymentStatus(transactionId)
Retrieves the current status of a payment transaction.
Parameters:
transactionId(string, required): Transaction ID frominitiatePayment
Returns: Promise<object> - Payment status object
Example:
const status = await sdk.getPaymentStatus('TRX_abc123');
console.log('Payment status:', status.status); // 'Pending', 'Success', 'Failed'async handlePaymentCallback(callbackData)
Securely verifies payment callback data to prevent tampering.
Parameters:
callbackData(object, required): Callback data received from TestLuy
Returns: Promise<object> - Verified payment information
Example:
// In your callback handler
app.get('/payment-callback', async (req, res) => {
try {
const verifiedData = await sdk.handlePaymentCallback(req.query);
if (verifiedData.status === 'Success') {
// Payment successful, fulfill order
await fulfillOrder(verifiedData.transaction_id);
}
} catch (error) {
console.error('Callback verification failed:', error.message);
}
});🖥️ Framework Integration Examples
Next.js API Route
// pages/api/payment/initiate.js
import TestluyPaymentSDK from 'testluy-payment-sdk';
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL
});
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { amount } = req.body;
const result = await sdk.initiatePayment(
amount,
`${process.env.NEXT_PUBLIC_APP_URL}/payment-callback`,
`${process.env.NEXT_PUBLIC_APP_URL}/cart`
);
res.status(200).json(result);
} catch (error) {
console.error('Payment initiation failed:', error.message);
res.status(500).json({ error: 'Payment initiation failed' });
}
}Express.js Route
// routes/payment.js
import express from 'express';
import TestluyPaymentSDK from 'testluy-payment-sdk';
const router = express.Router();
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL
});
// Initialize SDK on server start
await sdk.init();
router.post('/initiate', async (req, res) => {
try {
const { amount, orderId } = req.body;
const result = await sdk.initiatePayment(
amount,
`${process.env.APP_URL}/payment/callback?order=${orderId}`,
`${process.env.APP_URL}/orders/${orderId}`
);
res.json({ success: true, ...result });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
});
router.get('/callback', async (req, res) => {
try {
const verifiedData = await sdk.handlePaymentCallback(req.query);
if (verifiedData.status === 'Success') {
// Update order status in database
await updateOrderStatus(req.query.order, 'paid');
res.redirect('/payment-success');
} else {
res.redirect('/payment-failed');
}
} catch (error) {
console.error('Callback verification failed:', error.message);
res.redirect('/payment-error');
}
});
export default router;Serverless Function (Vercel)
// api/payment.js
import TestluyPaymentSDK from 'testluy-payment-sdk';
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL
});
export default async function handler(req, res) {
// CORS headers for frontend requests
res.setHeader('Access-Control-Allow-Origin', process.env.FRONTEND_URL);
res.setHeader('Access-Control-Allow-Methods', 'POST');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { amount, metadata } = req.body;
const result = await sdk.initiatePayment(
amount,
`${process.env.VERCEL_URL}/api/payment-callback`,
`${process.env.FRONTEND_URL}/checkout`
);
res.status(200).json({
success: true,
paymentUrl: result.paymentUrl,
transactionId: result.transactionId,
metadata
});
} catch (error) {
console.error('Payment error:', error.message);
res.status(500).json({
success: false,
error: 'Payment initiation failed'
});
}
}🌐 Environment Support
Supported Deployment Platforms
The SDK automatically detects and optimizes for these platforms:
| Platform | Status | Cloudflare Bypass | Notes | |----------|--------|-------------------|--------| | ✅ Vercel | Full Support | Auto-enabled | Serverless functions | | ✅ Netlify | Full Support | Auto-enabled | Netlify Functions | | ✅ Render | Full Support | Auto-enabled | Web services | | ✅ Heroku | Full Support | Auto-enabled | Dynos | | ✅ Railway | Full Support | Auto-enabled | Services | | ✅ Fly.io | Full Support | Auto-enabled | Applications | | ✅ AWS Lambda | Full Support | Auto-enabled | Serverless | | ✅ Cloudflare Pages | Full Support | Auto-enabled | Functions | | ✅ Local Development | Full Support | Disabled | Direct connection |
Environment Detection
// The SDK automatically detects your environment:
// Local Development
// Environment: { environment: 'node', isDeployment: false, platform: 'local' }
// → Uses direct API calls without Cloudflare bypass
// Production Deployment (e.g., Render)
// Environment: { environment: 'node', isDeployment: true, platform: 'render' }
// → Enables Cloudflare bypass headers automatically🔧 Error Handling
Error Types
The SDK provides specific error classes for different scenarios:
import { SDKError, RateLimitError, CloudflareError } from 'testluy-payment-sdk';
try {
await sdk.initiatePayment(amount, callbackUrl);
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after: ${error.retryAfter}ms`);
console.log(`Subscription: ${error.subscription}`);
console.log(`Limit: ${error.limit} requests per minute`);
} else if (error instanceof CloudflareError) {
console.log('Cloudflare protection triggered');
console.log(`Challenge type: ${error.challengeType}`);
} else if (error instanceof SDKError) {
console.log(`SDK Error: ${error.message}`);
console.log(`Request ID: ${error.requestId}`);
}
}Comprehensive Error Handling
async function initiatePaymentWithRetry(amount, callbackUrl, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await sdk.initiatePayment(amount, callbackUrl);
} catch (error) {
console.log(`Attempt ${attempt} failed:`, error.message);
if (error instanceof RateLimitError) {
// Wait for the suggested retry period
await new Promise(resolve => setTimeout(resolve, error.retryAfter));
continue;
}
if (error instanceof CloudflareError) {
// Cloudflare bypass should be automatic, but can add custom logic
console.log('Cloudflare challenge detected, SDK will retry automatically');
continue;
}
// For other errors, don't retry
throw error;
}
}
throw new Error(`Payment initiation failed after ${maxRetries} attempts`);
}📊 Logging and Debugging
Logging Configuration
// Development environment - verbose logging
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
loggingConfig: {
level: 'debug', // Show all logs
includeHeaders: true, // Include HTTP headers
includeBody: true, // Include request/response bodies
maskSensitive: true, // Mask sensitive data
format: 'text', // Human-readable format
colorize: true // Colored console output
}
});
// Production environment - minimal logging
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
loggingConfig: {
level: 'error', // Only errors
includeHeaders: false, // No headers in production
includeBody: false, // No bodies in production
maskSensitive: true, // Always mask sensitive data
format: 'json', // Structured logging
colorize: false // No colors in production logs
}
});Debug Information
// Enable debug mode for troubleshooting
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
loggingConfig: { level: 'debug' }
});
try {
const result = await sdk.initiatePayment(10.50, 'https://example.com/callback');
// Debug information automatically logged:
// [DEBUG] Environment detected: { platform: 'render', isDeployment: true }
// [DEBUG] Cloudflare bypass enabled with headers: { ... }
// [DEBUG] API call successful: POST /api/payment-simulator/generate-url
// [INFO] Payment initiated: { transactionId: 'TRX_...', paymentUrl: '...' }
} catch (error) {
// Error details automatically logged:
// [ERROR] Payment initiation failed: Rate limit exceeded
// [ERROR] Response details: { status: 429, headers: {...}, body: {...} }
}🧪 Testing
Running Tests
# Run all tests
npm test
# Run specific test suites
npm run test:unit # Unit tests only
npm run test:integration # Integration tests onlyTest Coverage
The SDK includes comprehensive test coverage:
- ✅ Unit tests for all core components
- ✅ Integration tests with mock API responses
- ✅ Cloudflare bypass simulation tests
- ✅ Rate limiting behavior tests
- ✅ Environment detection tests
- ✅ Error handling and recovery tests
Custom Test Setup
// test-setup.js
import TestluyPaymentSDK from 'testluy-payment-sdk';
// Test with mock credentials
const testSDK = new TestluyPaymentSDK({
clientId: 'test-client-id',
secretKey: 'test-secret-key',
baseUrl: 'http://localhost:8000',
loggingConfig: { level: 'silent' } // Suppress logs during testing
});
// Test payment initiation
describe('Payment SDK Tests', () => {
test('should initiate payment successfully', async () => {
const result = await testSDK.initiatePayment(
25.50,
'http://localhost:3000/callback'
);
expect(result.paymentUrl).toBeDefined();
expect(result.transactionId).toMatch(/^TRX_/);
});
test('should handle rate limiting gracefully', async () => {
// Test rate limiting behavior
const promises = Array(10).fill().map(() =>
testSDK.initiatePayment(1.00, 'http://localhost:3000/callback')
);
const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled');
const rateLimited = results.filter(r =>
r.status === 'rejected' && r.reason instanceof RateLimitError
);
expect(successful.length + rateLimited.length).toBe(10);
});
});🚀 Performance and Optimization
Performance Metrics
Based on production testing across different environments:
| Environment | Success Rate | Avg Response Time | Rate Limit Handling | |-------------|--------------|-------------------|-------------------| | Local Development | 100% (3/3) | 327ms | ✅ Excellent | | Render Deployment | 100% (1/1) | 743ms | ✅ Excellent | | Vercel Serverless | 100% (5/5) | 892ms | ✅ Excellent | | Netlify Functions | 100% (3/3) | 654ms | ✅ Excellent |
Optimization Tips
// 1. Initialize SDK once and reuse
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
// Optimize retry configuration
retryConfig: {
maxRetries: 2, // Reduce for faster failure feedback
baseDelay: 500, // Faster initial retry
maxDelay: 5000 // Cap maximum delay
}
});
// 2. Validate credentials once on startup
await sdk.init();
// 3. Use connection pooling for high-traffic applications
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
httpConfig: {
timeout: 10000, // 10 second timeout
keepAlive: true, // Reuse connections
maxRedirects: 3
}
});🔒 Security Best Practices
Credential Management
// ✅ DO: Use environment variables
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID, // From environment
secretKey: process.env.TESTLUY_SECRET_KEY, // From environment
baseUrl: process.env.TESTLUY_BASE_URL // From environment
});
// ❌ DON'T: Hardcode credentials
const sdk = new TestluyPaymentSDK({
clientId: 'hardcoded-client-id', // NEVER do this
secretKey: 'hardcoded-secret-key', // NEVER do this
baseUrl: 'https://api-testluy.paragoniu.app'
});Request Validation
// Validate all inputs before processing
app.post('/payment/initiate', async (req, res) => {
const { amount, orderId } = req.body;
// Input validation
if (!amount || amount <= 0) {
return res.status(400).json({ error: 'Invalid amount' });
}
if (!orderId || typeof orderId !== 'string') {
return res.status(400).json({ error: 'Invalid order ID' });
}
try {
const result = await sdk.initiatePayment(
parseFloat(amount),
`${process.env.APP_URL}/payment/callback?order=${orderId}`,
`${process.env.APP_URL}/orders/${orderId}`
);
res.json({ success: true, ...result });
} catch (error) {
// Log error server-side, don't expose details to client
console.error('Payment initiation error:', error.message);
res.status(500).json({ error: 'Payment initiation failed' });
}
});Callback Security
// Always verify callbacks to prevent tampering
app.get('/payment/callback', async (req, res) => {
try {
// Use SDK's built-in verification
const verifiedData = await sdk.handlePaymentCallback(req.query);
// Additional server-side validation
const order = await getOrderById(req.query.order);
if (!order) {
throw new Error('Order not found');
}
if (order.amount !== verifiedData.amount) {
throw new Error('Amount mismatch');
}
// Process verified payment
if (verifiedData.status === 'Success') {
await processSuccessfulPayment(order, verifiedData);
res.redirect('/payment-success');
} else {
await processFailedPayment(order, verifiedData);
res.redirect('/payment-failed');
}
} catch (error) {
console.error('Callback verification failed:', error.message);
res.redirect('/payment-error');
}
});🛠️ Troubleshooting
Common Issues and Solutions
1. "Invalid credentials" Error
// Problem: SDK throws "Invalid credentials" error
// Solution: Verify your environment variables
console.log('Client ID:', process.env.TESTLUY_CLIENT_ID);
console.log('Secret Key:', process.env.TESTLUY_SECRET_KEY ? '[HIDDEN]' : 'MISSING');
console.log('Base URL:', process.env.TESTLUY_BASE_URL);
// Ensure .env file is loaded
import dotenv from 'dotenv';
dotenv.config();2. Rate Limiting Issues
// Problem: Getting rate limited frequently
// Solution: Optimize retry configuration and implement caching
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
retryConfig: {
maxRetries: 5, // Increase retries
baseDelay: 2000, // Longer initial delay
maxDelay: 60000, // Longer max delay
backoffFactor: 2.5 // More aggressive backoff
}
});
// Implement request caching for repeated status checks
const statusCache = new Map();
async function getCachedPaymentStatus(transactionId) {
const cacheKey = `status_${transactionId}`;
const cached = statusCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < 30000) { // 30 second cache
return cached.data;
}
const status = await sdk.getPaymentStatus(transactionId);
statusCache.set(cacheKey, { data: status, timestamp: Date.now() });
return status;
}3. Cloudflare Protection Issues
// Problem: Requests blocked by Cloudflare in deployment
// Solution: Ensure smart routing is enabled (default)
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
enableSmartRouting: true, // Should be true (default)
cloudflareConfig: {
enabled: true, // Should be true (default)
rotateUserAgent: true, // Enable User-Agent rotation
addBrowserHeaders: true // Add browser-like headers
},
loggingConfig: {
level: 'debug' // Enable debug logging
}
});
// Check logs for Cloudflare bypass activation:
// [INFO] TestluyPaymentSDK: Deployment environment detected (vercel), using header-based Cloudflare bypass4. Build Issues in Next.js/Vite
// Problem: Import errors during build
// Solution: Use explicit import path
// ✅ DO: Use explicit import
import TestluyPaymentSDK from 'testluy-payment-sdk/index-enhanced.js';
// ❌ DON'T: Use default import in some build environments
import TestluyPaymentSDK from 'testluy-payment-sdk';
// For Next.js, add to next.config.js if needed:
module.exports = {
experimental: {
esmExternals: 'loose'
}
}Debug Mode
// Enable comprehensive debugging
const sdk = new TestluyPaymentSDK({
clientId: process.env.TESTLUY_CLIENT_ID,
secretKey: process.env.TESTLUY_SECRET_KEY,
baseUrl: process.env.TESTLUY_BASE_URL,
loggingConfig: {
level: 'debug',
includeHeaders: true,
includeBody: true,
maskSensitive: false, // ⚠️ Only for debugging, never in production
format: 'text',
colorize: true
}
});
// This will output detailed information about:
// - Environment detection
// - Cloudflare bypass activation
// - Request/response headers
// - API call timing
// - Retry attempts
// - Error details🤝 Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
Development Setup
# Clone the repository
git clone https://github.com/chanboraseng/testluy-payment-sdk.git
cd testluy-payment-sdk
# Install dependencies
npm install
# Run tests
npm test
# Run specific test suites
npm run test:unit
npm run test:integration📋 Changelog
See CHANGELOG.md for version history and changes.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
📞 Support
- Documentation: README.md
- Issues: GitHub Issues
- Email: [email protected]
Made with ❤️ by the TestLuy Team
