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.
$3
-
๐ก๏ธ 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
``
bash
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
$3
`
javascript
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);
`
$3
Create a .env
file in your project root:
`
env
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
$3
`
javascript
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
}
});
`
$3
The SDK automatically detects your deployment environment and adjusts behavior accordingly:
`
javascript
// 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
$3
#### new TestluyPaymentSDK(options)
Creates a new SDK instance with the specified configuration.
Parameters:
- options.clientId
(string, required): Your TestLuy application client ID
- options.secretKey
(string, required): Your TestLuy application secret key
- options.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
$3
#### async init()
Validates credentials and initializes the SDK. Recommended to call before other operations.
Returns: Promise
- true if credentials are valid
Example:
`javascript
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 callback
- backUrl (string, optional): URL for user cancellation/back navigation
Returns: Promise<{paymentUrl: string, transactionId: string}>
Example:
`javascript
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 from initiatePayment
Returns: Promise - Payment status object
Example:
`javascript
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 - Verified payment information
Example:
`javascript
// 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
$3
`javascript
// 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' });
}
}
`
$3
`javascript
// 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;
`
$3
`javascript
// 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
$3
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 |
$3
`javascript
// 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
$3
The SDK provides specific error classes for different scenarios:
`javascript
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});
}
}
`
$3
`javascript
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
$3
`javascript
// 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
}
});
`
$3
`javascript
// 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
$3
`bash
Run all tests
npm test
Run specific test suites
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
`
$3
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
$3
`javascript
// 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
$3
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 |
$3
`javascript
// 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
$3
`javascript
// โ
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'
});
`
$3
`javascript
// 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' });
}
});
`
$3
`javascript
// 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
$3
#### 1. "Invalid credentials" Error
`javascript
// 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
`javascript
// 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
`javascript
// 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 bypass
`
#### 4. Build Issues in Next.js/Vite
`javascript
// 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'
}
}
`
$3
`javascript
// 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.
$3
`bash
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 : cseng1@paragoniu.edu.kh
---
Made with โค๏ธ by the TestLuy Team