Node.js Library (omise-node)
The omise-node library provides a modern Node.js interface to the Omise API with TypeScript definitions, Promise/async-await support, and excellent Express and NestJS integration.
Installationโ
Using npmโ
npm install omise
Using Yarnโ
yarn add omise
Using pnpmโ
pnpm add omise
Requirementsโ
- Node.js 12 or higher (Node.js 16+ recommended as Node.js 12 reached EOL in April 2022)
- npm, yarn, or pnpm for package management
- TypeScript 4.0+ (optional, for TypeScript projects)
Quick Startโ
Basic Configurationโ
const omise = require('omise')({
secretKey: 'skey_test_123456789',
publicKey: 'pkey_test_123456789',
omiseVersion: '2019-05-29'
});
TypeScript Configurationโ
The omise-node library includes built-in TypeScript definitions - no separate @types/omise package is needed.
import Omise from 'omise';
const omise = Omise({
secretKey: process.env.OMISE_SECRET_KEY!,
publicKey: process.env.OMISE_PUBLIC_KEY!,
omiseVersion: '2019-05-29'
});
// Type-safe API calls
import type { Charges, Tokens } from 'omise';
// Charges have full type inference
const charge: Charges.ICharge = await omise.charges.create({
amount: 100000,
currency: 'thb',
card: 'tokn_test_123'
});
// Token responses are fully typed
const token: Tokens.IToken = await omise.tokens.create({
card: {
number: '4242424242424242',
expiration_month: 12,
expiration_year: 2027,
security_code: '123',
name: 'JOHN DOE'
}
});
TypeScript Features:
- โ Built-in type definitions (no @types package needed)
- โ Full IntelliSense support in VS Code
- โ Type-safe request/response objects
- โ Enum types for payment methods, currencies, statuses
- โ Strict null checking support
- โ Generic type parameters for advanced usage
Type Definitions Location: The official TypeScript definitions are maintained in the omise-node repository.
Express Configurationโ
// config/omise.js
require('dotenv').config();
module.exports = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY,
publicKey: process.env.OMISE_PUBLIC_KEY,
omiseVersion: '2019-05-29'
});
Environment Variablesโ
Create a .env file:
# Development/Test
OMISE_SECRET_KEY=skey_test_123456789
OMISE_PUBLIC_KEY=pkey_test_123456789
# Production
# OMISE_SECRET_KEY=skey_live_123456789
# OMISE_PUBLIC_KEY=pkey_live_123456789
Common Operationsโ
Creating a Chargeโ
With Promisesโ
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
omise.charges.create({
amount: 100000, // 1,000.00 THB
currency: 'THB',
card: 'tokn_test_123',
description: 'Order #1234',
metadata: {
order_id: '1234',
customer_name: 'John Doe'
}
})
.then(charge => {
if (charge.paid) {
console.log(`Charge successful: ${charge.id}`);
} else {
console.log(`Charge failed: ${charge.failure_message}`);
}
})
.catch(err => {
console.error('Error:', err.message);
});
With Async/Awaitโ
async function createCharge(token, amount) {
try {
const charge = await omise.charges.create({
amount,
currency: 'THB',
card: token,
description: 'Order #1234',
metadata: {
order_id: '1234',
customer_name: 'John Doe'
}
});
if (charge.paid) {
console.log(`Charge successful: ${charge.id}`);
return charge;
} else {
throw new Error(charge.failure_message);
}
} catch (error) {
console.error('Charge failed:', error);
throw error;
}
}
With TypeScriptโ
interface ChargeMetadata {
order_id: string;
customer_name: string;
}
interface CreateChargeParams {
amount: number;
currency: string;
card: string;
metadata: ChargeMetadata;
}
async function createCharge(params: CreateChargeParams): Promise<Omise.Charges.ICharge> {
const charge = await omise.charges.create({
amount: params.amount,
currency: params.currency,
card: params.card,
metadata: params.metadata
});
return charge;
}
With 3D Secureโ
async function createSecureCharge(token, amount, returnUri) {
const charge = await omise.charges.create({
amount,
currency: 'THB',
card: token,
return_uri: returnUri
});
if (charge.authorized) {
if (charge.authorize_uri) {
// Redirect customer to authorize_uri for 3D Secure
return { redirect: charge.authorize_uri };
} else {
// Charge completed without 3D Secure
return { success: true, charge };
}
}
throw new Error(charge.failure_message);
}
Retrieving a Chargeโ
// Promise-based
omise.charges.retrieve('chrg_test_123')
.then(charge => {
console.log(`Amount: ${charge.amount}`);
console.log(`Currency: ${charge.currency}`);
console.log(`Status: ${charge.status}`);
});
// Async/await
async function getCharge(chargeId) {
const charge = await omise.charges.retrieve(chargeId);
return charge;
}
Listing Chargesโ
async function listCharges(options = {}) {
const charges = await omise.charges.list({
limit: 20,
offset: 0,
order: 'reverse_chronological',
...options
});
charges.data.forEach(charge => {
console.log(`${charge.id}: ${charge.amount} ${charge.currency}`);
});
return charges;
}
// List charges with date filter
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const charges = await omise.charges.list({
from: weekAgo.toISOString().split('T')[0],
to: new Date().toISOString().split('T')[0]
});
Creating a Customerโ
async function createCustomer(email, description, metadata = {}) {
const customer = await omise.customers.create({
email,
description,
metadata
});
console.log(`Customer created: ${customer.id}`);
return customer;
}
// Usage
const customer = await createCustomer(
'customer@example.com',
'John Doe',
{ user_id: '12345', account_type: 'premium' }
);
Saving a Card to a Customerโ
// Update customer with card token
async function addCardToCustomer(customerId, token) {
const customer = await omise.customers.update(customerId, {
card: token
});
console.log(`Card saved: ${customer.default_card}`);
return customer;
}
// Create customer with card
async function createCustomerWithCard(email, token) {
const customer = await omise.customers.create({
email,
card: token
});
return customer;
}
Listing Customer Cardsโ
async function listCustomerCards(customerId) {
const customer = await omise.customers.retrieve(customerId);
customer.cards.data.forEach(card => {
console.log(`${card.brand} ending in ${card.last_digits}`);
console.log(`Expires: ${card.expiration_month}/${card.expiration_year}`);
});
return customer.cards.data;
}
Creating a Refundโ
// Full refund
async function refundCharge(chargeId) {
const refund = await omise.charges.refund(chargeId);
return refund;
}
// Partial refund
async function partialRefund(chargeId, amount, metadata = {}) {
const refund = await omise.charges.refund(chargeId, {
amount,
metadata
});
console.log(`Refund ${refund.id}: ${refund.amount} ${refund.currency}`);
return refund;
}
// Usage
await partialRefund('chrg_test_123', 25000, {
reason: 'customer_request',
ticket_id: 'TICKET-123'
});
Creating a Transferโ
async function createTransfer(amount, recipientId, metadata = {}) {
const transfer = await omise.transfers.create({
amount,
recipient: recipientId,
metadata
});
console.log(`Transfer ${transfer.id}: ${transfer.amount}`);
return transfer;
}
Alternative Payment Methodsโ
Creating a Sourceโ
// Prompt Pay QR
async function createPromptPaySource(amount) {
const source = await omise.sources.create({
type: 'promptpay',
amount,
currency: 'THB'
});
console.log(`QR Code URL: ${source.scannable_code.image.download_uri}`);
// Create charge with source
const charge = await omise.charges.create({
amount,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/payment/callback'
});
return { source, charge };
}
Internet Bankingโ
async function createInternetBankingCharge(amount, bankCode = 'scb') {
const source = await omise.sources.create({
type: `internet_banking_${bankCode}`,
amount,
currency: 'THB'
});
const charge = await omise.charges.create({
amount,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/payment/callback'
});
// Return authorize_uri for redirect
return charge.authorize_uri;
}
Installmentsโ
async function createInstallmentCharge(amount, bank, term) {
const source = await omise.sources.create({
type: `installment_${bank}`,
amount,
currency: 'THB',
installment_term: term
});
const charge = await omise.charges.create({
amount,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/payment/callback'
});
return charge;
}
// Usage
const charge = await createInstallmentCharge(100000, 'kbank', 6);
Error Handlingโ
const { OmiseError } = require('omise');
async function createChargeWithErrorHandling(params) {
try {
const charge = await omise.charges.create(params);
return charge;
} catch (error) {
if (error instanceof OmiseError) {
console.error('Omise Error:', error.message);
console.error('Status Code:', error.statusCode);
console.error('Error Code:', error.code);
// Handle specific errors
switch (error.code) {
case 'authentication_failure':
throw new Error('Invalid API key');
case 'invalid_card':
throw new Error('Card was declined');
case 'insufficient_fund':
throw new Error('Insufficient funds');
default:
throw error;
}
}
// Network or other errors
throw error;
}
}
TypeScript Error Handlingโ
import Omise, { OmiseError } from 'omise';
async function handleChargeError(error: unknown): Promise<never> {
if (error instanceof OmiseError) {
const errorMessages: Record<string, string> = {
insufficient_fund: 'Insufficient funds on card',
stolen_or_lost_card: 'Card reported as stolen or lost',
invalid_security_code: 'Invalid CVV code',
payment_cancelled: 'Payment was cancelled'
};
const message = errorMessages[error.code] || error.message;
throw new Error(message);
}
throw error;
}
Express Integrationโ
Controller Exampleโ
// controllers/paymentController.js
const omise = require('../config/omise');
const Order = require('../models/Order');
const Payment = require('../models/Payment');
exports.createPayment = async (req, res) => {
try {
const { orderId } = req.params;
const { omiseToken } = req.body;
const order = await Order.findById(orderId);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
const charge = await omise.charges.create({
amount: Math.round(order.total * 100),
currency: 'THB',
card: omiseToken,
description: `Order #${order.id}`,
metadata: {
order_id: order.id,
customer_email: order.email
},
return_uri: `${req.protocol}://${req.get('host')}/payment/callback`
});
// Save payment record
const payment = await Payment.create({
orderId: order.id,
chargeId: charge.id,
amount: order.total,
status: charge.status,
paid: charge.paid
});
if (charge.paid) {
order.paymentStatus = 'paid';
await order.save();
return res.json({ success: true, charge });
} else if (charge.authorize_uri) {
return res.json({ redirect: charge.authorize_uri });
} else {
return res.status(400).json({ error: charge.failure_message });
}
} catch (error) {
console.error('Payment error:', error);
res.status(500).json({ error: 'Payment failed' });
}
};
exports.paymentCallback = async (req, res) => {
try {
const { id: chargeId } = req.query;
const charge = await omise.charges.retrieve(chargeId);
const payment = await Payment.findOne({ chargeId: charge.id });
if (!payment) {
return res.status(404).send('Payment not found');
}
payment.status = charge.status;
payment.paid = charge.paid;
await payment.save();
if (charge.paid) {
const order = await Order.findById(payment.orderId);
order.paymentStatus = 'paid';
await order.save();
res.redirect(`/orders/${order.id}?success=true`);
} else {
res.redirect(`/payment/${payment.orderId}?error=${charge.failure_message}`);
}
} catch (error) {
console.error('Callback error:', error);
res.status(500).send('Payment verification failed');
}
};
Routesโ
// routes/payment.js
const express = require('express');
const router = express.Router();
const paymentController = require('../controllers/paymentController');
router.post('/payment/:orderId', paymentController.createPayment);
router.get('/payment/callback', paymentController.paymentCallback);
module.exports = router;
Middleware for Error Handlingโ
// middleware/errorHandler.js
const { OmiseError } = require('omise');
function errorHandler(err, req, res, next) {
if (err instanceof OmiseError) {
console.error('Omise Error:', {
message: err.message,
code: err.code,
statusCode: err.statusCode
});
return res.status(err.statusCode || 500).json({
error: err.message,
code: err.code
});
}
console.error('Unexpected error:', err);
res.status(500).json({ error: 'Internal server error' });
}
module.exports = errorHandler;
NestJS Integrationโ
Module Setupโ
// omise/omise.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { OmiseService } from './omise.service';
@Global()
@Module({
imports: [ConfigModule],
providers: [OmiseService],
exports: [OmiseService],
})
export class OmiseModule {}
Serviceโ
// omise/omise.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Omise from 'omise';
@Injectable()
export class OmiseService {
private omise: ReturnType<typeof Omise>;
constructor(private configService: ConfigService) {
this.omise = Omise({
secretKey: this.configService.get<string>('OMISE_SECRET_KEY')!,
publicKey: this.configService.get<string>('OMISE_PUBLIC_KEY')!,
omiseVersion: '2019-05-29'
});
}
async createCharge(params: Omise.Charges.CreatePayload): Promise<Omise.Charges.ICharge> {
return this.omise.charges.create(params);
}
async retrieveCharge(chargeId: string): Promise<Omise.Charges.ICharge> {
return this.omise.charges.retrieve(chargeId);
}
async createCustomer(params: Omise.Customers.CreatePayload): Promise<Omise.Customers.ICustomer> {
return this.omise.customers.create(params);
}
async refundCharge(chargeId: string, amount?: number): Promise<Omise.Refunds.IRefund> {
return this.omise.charges.refund(chargeId, amount ? { amount } : undefined);
}
}
Controllerโ
// payment/payment.controller.ts
import { Controller, Post, Get, Body, Param, Query, HttpException, HttpStatus } from '@nestjs/common';
import { OmiseService } from '../omise/omise.service';
import { PaymentService } from './payment.service';
@Controller('payment')
export class PaymentController {
constructor(
private omiseService: OmiseService,
private paymentService: PaymentService
) {}
@Post(':orderId')
async createPayment(
@Param('orderId') orderId: string,
@Body('omiseToken') omiseToken: string
) {
try {
const order = await this.paymentService.findOrder(orderId);
const charge = await this.omiseService.createCharge({
amount: Math.round(order.total * 100),
currency: 'THB',
card: omiseToken,
description: `Order #${order.id}`,
metadata: {
order_id: order.id,
customer_email: order.email
},
return_uri: `${process.env.APP_URL}/payment/callback`
});
await this.paymentService.savePayment({
orderId: order.id,
chargeId: charge.id,
amount: order.total,
status: charge.status,
paid: charge.paid
});
if (charge.paid) {
await this.paymentService.markOrderPaid(order.id);
return { success: true, charge };
} else if (charge.authorize_uri) {
return { redirect: charge.authorize_uri };
} else {
throw new HttpException(charge.failure_message, HttpStatus.BAD_REQUEST);
}
} catch (error) {
throw new HttpException('Payment failed', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Get('callback')
async paymentCallback(@Query('id') chargeId: string) {
const charge = await this.omiseService.retrieveCharge(chargeId);
await this.paymentService.updatePaymentStatus(charge.id, charge.status, charge.paid);
if (charge.paid) {
return { success: true, message: 'Payment successful' };
} else {
throw new HttpException(charge.failure_message, HttpStatus.BAD_REQUEST);
}
}
}
Best Practicesโ
1. Use Environment Variablesโ
require('dotenv').config();
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY,
publicKey: process.env.OMISE_PUBLIC_KEY
});
if (!process.env.OMISE_SECRET_KEY) {
throw new Error('OMISE_SECRET_KEY is not set');
}
2. Implement Idempotencyโ
async function createIdempotentCharge(params, orderId) {
// Use deterministic idempotency key based on orderId
// This ensures the same order always gets the same key
const idempotencyKey = `order-${orderId}`;
return omise.charges.create(params, {
headers: {
'Idempotency-Key': idempotencyKey
}
});
}
3. Use Retry Logicโ
async function createChargeWithRetry(params, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await omise.charges.create(params);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
4. Store Minimal Dataโ
class Payment {
constructor(chargeId) {
this.chargeId = chargeId;
this._charge = null;
}
async getCharge() {
if (!this._charge) {
this._charge = await omise.charges.retrieve(this.chargeId);
}
return this._charge;
}
async refreshCharge() {
this._charge = null;
return this.getCharge();
}
}
5. Implement Loggingโ
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
async function createChargeWithLogging(params) {
logger.info('Creating charge', { amount: params.amount, currency: params.currency });
try {
const charge = await omise.charges.create(params);
logger.info('Charge created', { chargeId: charge.id, paid: charge.paid });
return charge;
} catch (error) {
logger.error('Charge failed', { error: error.message, code: error.code });
throw error;
}
}
6. Validate Inputโ
const Joi = require('joi');
const chargeSchema = Joi.object({
amount: Joi.number().min(2000).required(),
currency: Joi.string().valid('THB', 'USD', 'SGD', 'JPY').required(),
token: Joi.string().required()
});
async function createValidatedCharge(params) {
const { error, value } = chargeSchema.validate(params);
if (error) {
throw new Error(`Validation error: ${error.message}`);
}
return omise.charges.create({
amount: value.amount,
currency: value.currency,
card: value.token
});
}
Testingโ
Jest Examplesโ
// __tests__/payment.test.js
const omise = require('../config/omise');
jest.mock('../config/omise');
describe('Payment Service', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should create a charge successfully', async () => {
const mockCharge = {
id: 'chrg_test_123',
amount: 100000,
currency: 'THB',
paid: true,
status: 'successful'
};
omise.charges.create.mockResolvedValue(mockCharge);
const result = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_123'
});
expect(result.id).toBe('chrg_test_123');
expect(result.paid).toBe(true);
});
test('should handle charge error', async () => {
const mockError = new Error('insufficient_fund');
mockError.code = 'insufficient_fund';
omise.charges.create.mockRejectedValue(mockError);
await expect(
omise.charges.create({ amount: 100000, currency: 'THB', card: 'tokn_test_123' })
).rejects.toThrow('insufficient_fund');
});
});
Integration Tests with Supertestโ
// __tests__/payment.integration.test.js
const request = require('supertest');
const app = require('../app');
const omise = require('../config/omise');
jest.mock('../config/omise');
describe('Payment API', () => {
test('POST /payment/:orderId should create a payment', async () => {
const mockCharge = {
id: 'chrg_test_123',
paid: true,
status: 'successful'
};
omise.charges.create.mockResolvedValue(mockCharge);
const response = await request(app)
.post('/payment/order_123')
.send({ omiseToken: 'tokn_test_123' });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.charge.id).toBe('chrg_test_123');
});
});
Troubleshootingโ
SSL Certificate Errorsโ
const https = require('https');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY,
agent: new https.Agent({
rejectUnauthorized: false // Only for development!
})
});
Connection Timeoutsโ
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY,
timeout: 60000 // 60 seconds
});
Debug Modeโ
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY,
debug: true // Logs request/response details
});
Webhook Signature Verificationโ
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express middleware
function webhookVerification(req, res, next) {
const payload = JSON.stringify(req.body);
const signature = req.headers['omise-signature'];
if (!verifyWebhookSignature(payload, signature, process.env.OMISE_WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
FAQโ
How do I handle webhooks in Express?โ
const express = require('express');
const router = express.Router();
router.post('/webhooks/omise', express.json(), (req, res) => {
const event = req.body;
switch (event.key) {
case 'charge.complete':
handleChargeComplete(event.data);
break;
case 'refund.create':
handleRefundCreate(event.data);
break;
}
res.json({ status: 'ok' });
});
async function handleChargeComplete(chargeData) {
const charge = await omise.charges.retrieve(chargeData.id);
// Update payment status
}
How do I test payments without real charges?โ
// Use test API keys
const omise = require('omise')({
secretKey: 'skey_test_123456789'
});
// Use test tokens
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_5086xl7ddjbases4sq3i' // Test token
});
console.log('Test mode:', !charge.livemode);
How do I handle currency conversion?โ
class Money {
constructor(amountBaht) {
this.amountSatang = Math.round(amountBaht * 100);
}
getSatang() {
return this.amountSatang;
}
getBaht() {
return this.amountSatang / 100;
}
static fromSatang(amountSatang) {
const instance = new Money(0);
instance.amountSatang = amountSatang;
return instance;
}
}
// Usage
const money = new Money(1000.00);
const charge = await omise.charges.create({
amount: money.getSatang(),
currency: 'THB',
card: 'tokn_test_123'
});
How do I implement retry logic with exponential backoff?โ
async function retry(fn, maxRetries = 3, baseDelay = 1000) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = baseDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
const charge = await retry(() =>
omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_123'
})
);
How do I handle partial refunds?โ
async function refundCharge(chargeId, refundAmount = null) {
const charge = await omise.charges.retrieve(chargeId);
const refundable = charge.amount - charge.refunded;
if (refundAmount && refundAmount > refundable) {
throw new Error('Refund amount exceeds refundable amount');
}
return omise.charges.refund(chargeId, {
amount: refundAmount
});
}
How do I save multiple cards for a customer?โ
async function addCardToCustomer(customerId, token) {
return omise.customers.update(customerId, { card: token });
}
async function listCustomerCards(customerId) {
const customer = await omise.customers.retrieve(customerId);
return customer.cards.data;
}
async function chargeSpecificCard(customerId, cardId, amount) {
return omise.charges.create({
amount,
currency: 'THB',
customer: customerId,
card: cardId
});
}
How do I implement subscription billing?โ
const cron = require('node-cron');
class SubscriptionManager {
constructor(customerId, planAmount) {
this.customerId = customerId;
this.planAmount = planAmount;
}
async chargeMonthly() {
const charge = await omise.charges.create({
amount: this.planAmount,
currency: 'THB',
customer: this.customerId,
description: `Subscription ${new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}`
});
return charge;
}
handleFailedPayment(charge) {
// Send notification
// Update subscription status
}
}
// Schedule monthly charges
cron.schedule('0 0 1 * *', async () => {
const subscriptions = await getActiveSubscriptions();
for (const subscription of subscriptions) {
const manager = new SubscriptionManager(
subscription.customerId,
subscription.planAmount
);
try {
await manager.chargeMonthly();
} catch (error) {
manager.handleFailedPayment(error);
}
}
});
Related Resourcesโ
- Omise API Documentation
- omise-node GitHub Repository
- npm Package
- TypeScript Definitions
- Omise Dashboard
- Webhooks Guide
- Testing Guide
Next Stepsโ
Supportโ
If you encounter issues with the Node.js library:
- Check the GitHub Issues
- Review the API documentation
- Contact support@omise.co with:
- Node.js version
- omise-node library version
- Error message and stack trace
- Steps to reproduce