ownpay-nodejs

Official Node.js SDK for the OwnPay Payment Gateway API. A zero-dependency, TypeScript-first SDK for seamless payment integration.
Features
- 🚀 Zero Dependencies - Uses only Node.js native APIs (fetch, crypto)
- 📘 TypeScript First - Full type safety with comprehensive type definitions
- 🔄 ESM + CJS - Supports both ES Modules and CommonJS
- 🌲 Tree-Shakeable - Import only what you need
- 🔒 Secure - Built-in webhook signature verification with HMAC-SHA256
- ⚡ High Performance - Native fetch API with automatic retries
- 🛡️ Error Handling - Comprehensive error hierarchy for easy debugging
- 📝 Idempotency - Built-in support for safe request retries
Requirements
- Node.js 18.0.0 or higher
- An OwnPay API key (starts with
op_)
Installation
npm install ownpay-nodejs
yarn add ownpay-nodejs
pnpm add ownpay-nodejs
Quick Start
Initialize the Client
import OwnPay from 'ownpay-nodejs';
const client = new OwnPay('op_your_api_key_here');
const client = new OwnPay({
apiKey: 'op_your_api_key_here',
timeout: 30000,
maxRetries: 2,
baseUrl: 'https://api.ownpay.com',
});
Create a Payment
const payment = await client.payments.create({
amount: '100.00',
currency: 'BDT',
customer_email: 'customer@example.com',
customer_name: 'John Doe',
callback_url: 'https://your-app.com/webhook',
redirect_url: 'https://your-app.com/success',
cancel_url: 'https://your-app.com/cancel',
reference: 'ORDER-12345',
metadata: {
order_id: '12345',
product: 'Widget',
},
});
console.log('Payment ID:', payment.payment_id);
console.log('Checkout URL:', payment.checkout_url);
console.log('Status:', payment.status);
Check Payment Status
const paymentDetails = await client.payments.get('payment-uuid-here');
console.log('Status:', paymentDetails.status);
console.log('Amount:', paymentDetails.amount);
console.log('Currency:', paymentDetails.currency);
if (paymentDetails.status === 'completed') {
console.log('Payment completed!');
}
API Reference
Payments
client.payments.create(params, options?)
Creates a new payment intent.
Parameters:
amount | string | number | ✅ | Payment amount (positive number) |
currency | string | ✅ | 3-letter ISO currency code (e.g., 'BDT', 'USD') |
callback_url | string | ❌ | Webhook notification URL |
redirect_url | string | ❌ | Success redirect URL |
cancel_url | string | ❌ | Cancel redirect URL |
customer_email | string | ❌ | Customer email address |
customer_name | string | ❌ | Customer name (max 150 chars) |
customer_phone | string | ❌ | Customer phone (max 30 chars) |
reference | string | ❌ | Merchant reference ID |
gateway | string | ❌ | Preferred payment gateway |
metadata | Record<string, unknown> | ❌ | Custom key-value metadata |
Returns: Promise<PaymentIntent>
interface PaymentIntent {
payment_id: string;
token: string;
checkout_url: string;
status: string;
}
client.payments.get(paymentId, options?)
Retrieves payment details by ID.
Parameters:
paymentId | string | ✅ | Payment UUID |
Returns: Promise<Payment>
Transactions
client.transactions.list(params?, options?)
Lists transactions with optional filtering and pagination.
Parameters:
page | number | ❌ | Page number (default: 1) |
per_page | number | ❌ | Items per page (default: 25, max: 100) |
status | string | ❌ | Filter by status |
gateway | string | ❌ | Filter by gateway |
from | string | ❌ | Start date (ISO format) |
to | string | ❌ | End date (ISO format) |
Returns: Promise<PaginatedList<Transaction>>
const result = await client.transactions.list({
page: 1,
per_page: 50,
status: 'completed',
from: '2024-01-01',
to: '2024-01-31',
});
console.log('Transactions:', result.items);
console.log('Total:', result.meta.total);
console.log('Pages:', result.meta.total_pages);
client.transactions.get(trxId, options?)
Retrieves a transaction by ID (OwnPay reference or gateway ID).
Parameters:
trxId | string | ✅ | Transaction ID (e.g., 'OP-ABC123') |
Returns: Promise<Transaction>
Refunds
client.refunds.create(params, options?)
Creates a refund for a transaction.
Parameters:
transaction_id | string | number | ✅* | Transaction ID |
trx_id | string | number | ✅* | OwnPay transaction reference |
amount | string | number | ❌ | Refund amount (partial refund) |
reason | string | ❌ | Refund reason |
*Either transaction_id or trx_id is required
Returns: Promise<Refund>
const refund = await client.refunds.create({
transaction_id: 'OP-ABC123',
reason: 'Customer request',
});
const refund = await client.refunds.create({
transaction_id: 'OP-ABC123',
amount: '50.00',
reason: 'Partial return',
});
client.refunds.list(params?, options?)
Lists refunds with optional filtering.
Returns: Promise<PaginatedList<Refund>>
client.refunds.get(trxId, options?)
Retrieves refund details by transaction reference.
Returns: Promise<Refund>
Customers
client.customers.create(params, options?)
Creates a new customer.
Parameters:
name | string | ✅ | Customer name (max 150 chars) |
email | string | ❌ | Email address |
phone | string | ❌ | Phone number |
Returns: Promise<{ id: number; uuid: string }>
const customer = await client.customers.create({
name: 'Jane Smith',
email: 'jane@example.com',
phone: '+8801712345678',
});
console.log('Customer ID:', customer.id);
console.log('Customer UUID:', customer.uuid);
client.customers.list(params?, options?)
Lists customers with pagination.
Returns: Promise<PaginatedList<Customer>>
client.customers.get(identifier, options?)
Retrieves a customer by email or phone.
Parameters:
identifier | string | ✅ | Email or phone number |
Returns: Promise<Customer>
const customer = await client.customers.get('jane@example.com');
const customer = await client.customers.get('+8801712345678');
API Keys
Note: API key operations require an API key with both write and admin scopes, and must include the X-Super-Admin-Email header.
client.apiKeys.list(options?)
Lists all API keys for the merchant.
Returns: Promise<ApiKey[]>
const keys = await client.apiKeys.list({
headers: { 'X-Super-Admin-Email': 'admin@example.com' },
});
client.apiKeys.generate(params?, options?)
Generates a new API key.
Parameters:
name | string | ❌ | Key name/label |
scopes | string[] | ❌ | Permission scopes: ['read', 'write', 'admin'] |
Returns: Promise<GeneratedApiKey>
const result = await client.apiKeys.generate(
{
name: 'Production Key',
scopes: ['read', 'write'],
},
{
headers: { 'X-Super-Admin-Email': 'admin@example.com' },
}
);
console.log('New API Key:', result.key);
console.log('Warning:', result.warning);
client.apiKeys.revoke(keyId, options?)
Revokes an API key.
Parameters:
keyId | number | ✅ | API key ID to revoke |
Webhooks
client.webhooks.test(options?)
Sends a test webhook to the configured endpoint.
Returns: Promise<WebhookTestResult>
const result = await client.webhooks.test();
console.log('Status Code:', result.status_code);
console.log('Response Time:', result.response_time_ms, 'ms');
client.webhooks.deliveries(options?)
Lists recent webhook deliveries.
Returns: Promise<WebhookDelivery[]>
Health
client.health.check(options?)
Performs a system health check.
Returns: Promise<HealthStatus>
const health = await client.health.check();
console.log('Status:', health.status);
console.log('Version:', health.version);
console.log('Database:', health.db);
console.log('Gateways:', health.gateways);
console.log('Customers:', health.customers);
Webhook Handling
Verify Webhook Signatures
Always verify webhook signatures to ensure payloads are from OwnPay.
import { verifyWebhookSignature } from 'ownpay-nodejs/webhooks';
app.post('/webhook', (req, res) => {
try {
const signature = req.headers['x-ownpay-signature'];
const timestamp = req.headers['x-ownpay-timestamp'];
const payload = req.body;
verifyWebhookSignature({
payload,
signature,
secret: process.env.OWNPAY_WEBHOOK_SECRET,
timestamp,
tolerance: 300,
});
const event = JSON.parse(payload);
handleWebhookEvent(event);
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook verification failed:', error.message);
res.status(400).json({ error: 'Invalid signature' });
}
});
Webhook Event Structure
interface WebhookPayload {
event: string;
transaction_id: string;
gateway_trx_id: string;
amount: string;
currency: string;
fee: string;
gateway: string;
gateway_type: string;
status: string;
customer: {
name: string;
email: string;
phone: string;
};
metadata: Record<string, unknown>;
timestamp: string;
}
Handle Different Event Types
function handleWebhookEvent(event: WebhookPayload) {
switch (event.event) {
case 'payment.completed':
console.log(`Payment ${event.transaction_id} completed`);
break;
case 'payment.failed':
console.log(`Payment ${event.transaction_id} failed`);
break;
case 'refund.completed':
console.log(`Refund for ${event.transaction_id} completed`);
break;
default:
console.log(`Unhandled event: ${event.event}`);
}
}
Error Handling
The SDK provides a comprehensive error hierarchy for precise error handling.
import OwnPay, {
AuthenticationError,
PermissionError,
NotFoundError,
ValidationError,
RateLimitError,
IdempotencyError,
ApiError,
NetworkError,
AbortError,
} from 'ownpay-nodejs';
try {
const payment = await client.payments.create({
amount: '100.00',
currency: 'BDT',
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed:', error.message);
} else if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
console.error('Field errors:', error.getFieldErrors());
} else if (error instanceof NotFoundError) {
console.error('Not found:', error.message);
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
} else if (error instanceof IdempotencyError) {
console.error('Idempotency conflict:', error.message);
} else if (error instanceof ApiError) {
console.error('API error:', error.message, 'Status:', error.status);
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
} else if (error instanceof AbortError) {
console.error('Request timed out');
}
}
Error Properties
All errors have these common properties:
message | string | Human-readable error message |
type | string | Error type identifier |
status | number | HTTP status code (if applicable) |
requestId | string | Request ID for debugging |
rawResponse | unknown | Raw API response |
Validation Errors
ValidationError provides additional methods:
catch (error) {
if (error instanceof ValidationError) {
const fieldErrors = error.getFieldErrors();
const amountError = error.getFirstFieldError('amount');
}
}
Request Options
All resource methods accept an optional RequestOptions parameter:
const payment = await client.payments.create(
{ amount: '100.00', currency: 'BDT' },
{
idempotencyKey: 'unique-key-123',
headers: {
'X-Custom-Header': 'value',
},
timeout: 60000,
signal: AbortSignal.timeout(10000),
}
);
Idempotency
Use idempotency keys for POST requests to safely retry without creating duplicates:
import { v4 as uuidv4 } from 'node:crypto';
const idempotencyKey = crypto.randomUUID();
const payment = await client.payments.create(
{ amount: '100.00', currency: 'BDT' },
{ idempotencyKey }
);
Request Cancellation
Use AbortSignal to cancel long-running requests:
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
const payment = await client.payments.create(
{ amount: '100.00', currency: 'BDT' },
{ signal: controller.signal }
);
} catch (error) {
if (error instanceof AbortError) {
console.log('Request was cancelled');
}
}
Advanced Configuration
Custom API URL
For self-hosted or white-label deployments:
const client = new OwnPay({
apiKey: 'op_your_key',
baseUrl: 'https://your-domain.com',
});
Environment Variables
Store your API key securely using environment variables:
const client = new OwnPay(process.env.OWNPAY_API_KEY!);
Add custom headers to all requests:
const client = new OwnPay({
apiKey: 'op_your_key',
headers: {
'X-App-Version': '1.0.0',
'X-Client-Id': 'your-app',
},
});
TypeScript Support
The SDK is built with TypeScript and provides comprehensive type definitions.
import OwnPay from 'ownpay-nodejs';
import type {
Payment,
Transaction,
Refund,
Customer,
WebhookPayload,
CreatePaymentParams,
ListTransactionsParams,
} from 'ownpay-nodejs';
const client = new OwnPay('op_key');
const payment = await client.payments.create({
amount: '100.00',
currency: 'BDT',
});
CommonJS Support
The SDK supports CommonJS for backward compatibility:
const OwnPay = require('ownpay-nodejs');
const client = new OwnPay('op_your_key');
Tree-Shaking
Import only what you need for smaller bundle sizes:
import OwnPay from 'ownpay-nodejs';
import { verifyWebhookSignature } from 'ownpay-nodejs/webhooks';
import type { Payment, Transaction } from 'ownpay-nodejs';
import { ValidationError, AuthenticationError } from 'ownpay-nodejs';
Retry Behavior
The SDK automatically retries failed requests for transient errors:
| 429 | ✅ | Respects Retry-After header |
| 500 | ✅ | Exponential backoff |
| 502 | ✅ | Exponential backoff |
| 503 | ✅ | Exponential backoff |
| 504 | ✅ | Exponential backoff |
| 401 | ❌ | Immediate failure |
| 403 | ❌ | Immediate failure |
| 404 | ❌ | Immediate failure |
| 422 | ❌ | Immediate failure |
Configure Retries
const client = new OwnPay({
apiKey: 'op_your_key',
maxRetries: 3,
timeout: 60000,
});
Examples
Complete Payment Flow
import OwnPay from 'ownpay-nodejs';
const client = new OwnPay(process.env.OWNPAY_API_KEY!);
async function createOrder(amount: string, customerEmail: string) {
try {
const customer = await client.customers.create({
name: 'Customer',
email: customerEmail,
});
const payment = await client.payments.create({
amount,
currency: 'BDT',
customer_email: customerEmail,
customer_name: 'Customer',
callback_url: 'https://your-app.com/webhook',
redirect_url: 'https://your-app.com/success',
cancel_url: 'https://your-app.com/cancel',
reference: `ORDER-${Date.now()}`,
metadata: {
customer_id: customer.uuid,
},
});
return {
payment_id: payment.payment_id,
checkout_url: payment.checkout_url,
};
} catch (error) {
console.error('Order creation failed:', error);
throw error;
}
}
async function checkPaymentStatus(paymentId: string) {
const payment = await client.payments.get(paymentId);
switch (payment.status) {
case 'completed':
return { success: true, message: 'Payment completed' };
case 'pending':
return { success: false, message: 'Payment pending' };
case 'failed':
return { success: false, message: 'Payment failed' };
default:
return { success: false, message: `Status: ${payment.status}` };
}
}
Express.js Webhook Handler
import express from 'express';
import OwnPay, { verifyWebhookSignature } from 'ownpay-nodejs';
const app = express();
const client = new OwnPay(process.env.OWNPAY_API_KEY!);
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const signature = req.headers['x-ownpay-signature'] as string;
const timestamp = req.headers['x-ownpay-timestamp'] as string;
verifyWebhookSignature({
payload: req.body,
signature,
secret: process.env.OWNPAY_WEBHOOK_SECRET!,
timestamp,
});
const event = JSON.parse(req.body.toString());
switch (event.event) {
case 'payment.completed':
console.log(`Payment completed: ${event.transaction_id}`);
break;
case 'payment.failed':
console.log(`Payment failed: ${event.transaction_id}`);
break;
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(400).json({ error: 'Invalid webhook' });
}
});
app.listen(3000);
Transaction Reporting
async function getTransactionReport(startDate: string, endDate: string) {
const result = await client.transactions.list({
from: startDate,
to: endDate,
per_page: 100,
});
const totalAmount = result.items.reduce(
(sum, txn) => sum + parseFloat(txn.amount),
0
);
const totalFees = result.items.reduce(
(sum, txn) => sum + parseFloat(txn.fee),
0
);
return {
transactions: result.items,
total: result.meta.total,
totalAmount,
totalFees,
netAmount: totalAmount - totalFees,
};
}
Troubleshooting
Common Issues
"Invalid API key format"
Ensure your API key starts with op_ and is at least 12 characters long.
const client = new OwnPay('sk_live_...');
const client = new OwnPay('op_abcdef123456...');
"Authentication failed"
- Check that your API key is active and not revoked
- Ensure you're using the correct key for your environment (test/live)
"Insufficient scope"
Your API key doesn't have the required permissions:
read scope: GET requests
write scope: POST/PUT/PATCH/DELETE requests
admin scope: API key management operations
"Webhook verification failed"
- Ensure you're using the raw request body (not parsed JSON)
- Check that the webhook secret matches your configuration
- Verify the timestamp is within the tolerance window
Debug Mode
Enable debug logging by checking error details:
try {
await client.payments.create({ amount: '100.00', currency: 'BDT' });
} catch (error) {
console.error('Error:', error.toJSON());
}
API Reference Summary
client.payments | create(), get() |
client.transactions | list(), get() |
client.refunds | create(), list(), get() |
client.customers | create(), list(), get() |
client.apiKeys | list(), generate(), revoke() |
client.webhooks | test(), deliveries() |
client.health | check() |
Support
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
Changelog
See CHANGELOG.md for a list of changes.