Testing
Comprehensive guide to testing your Omise integration using test mode, test cards, and simulated payment scenarios across all payment methods.
Overviewโ
Omise provides a complete testing environment that mirrors production behavior without processing real transactions. Test mode allows you to validate your integration, test error handling, and simulate various payment scenarios before going live.
Key Features:
- โ Separate test keys - Dedicated API keys for testing
- โ Test cards - Simulate success, decline, and error scenarios
- โ All payment methods - Test QR, wallets, banking, and more
- โ Webhook testing - Receive test webhooks
- โ No real money - Safe testing environment
- โ Full feature parity - Test environment mirrors production
Test Mode vs Live Modeโ
| Feature | Test Mode | Live Mode |
|---|---|---|
| API Keys | pkey_test_..., skey_test_... | pkey_..., skey_... |
| Real Money | No | Yes |
| Test Cards | Accepted | Rejected |
| Real Cards | Rejected | Accepted |
| Webhooks | Delivered | Delivered |
| Dashboard | Test data only | Live transactions |
Test and live modes are completely isolated. Test keys cannot process real transactions, and live keys cannot use test data.
Getting Test API Keysโ
From Dashboardโ
- Log in to Omise Dashboard
- Switch to Test Mode using the toggle
- Navigate to Settings โ API Keys
- Copy your test keys:
- Public Key:
pkey_test_...(client-side) - Secret Key:
skey_test_...(server-side)
- Public Key:
Test Key Formatโ
# Public key (safe to use client-side)
pkey_test_5rt6s9vah5lkvi1rh9c
# Secret key (server-side only, never expose)
skey_test_5rt6s9vah5lkvi1rh9c
Never commit test keys to public repositories or expose them in client-side code. While test keys don't process real money, they can still access sensitive test data.
Test Cardsโ
Success Scenariosโ
| Card Number | Brand | 3DS | Result |
|---|---|---|---|
| 4242 4242 4242 4242 | Visa | No | Success |
| 4242 4242 4242 4242 | Visa | Yes | Success (with 3DS) |
| 5555 5555 5555 4444 | Mastercard | No | Success |
| 5555 5555 5555 4444 | Mastercard | Yes | Success (with 3DS) |
| 3566 1111 1111 1111 | JCB | No | Success |
| 3782 822463 10005 | Amex | No | Success |
Decline Scenariosโ
| Card Number | Error Code | Message |
|---|---|---|
| 4000 0000 0000 0002 | payment_rejected | Charge was rejected |
| 4000 0000 0000 0010 | insufficient_fund | Insufficient funds |
| 4000 0000 0000 0028 | stolen_or_lost_card | Card reported stolen/lost |
| 4000 0000 0000 9995 | failed_processing | Processing error |
Fraud Detectionโ
| Card Number | Fraud Result | Behavior |
|---|---|---|
| 4000 0000 0000 0101 | Flagged | Marked for manual review |
| 4000 0000 0000 0044 | Blocked | Transaction blocked by fraud rules |
3D Secure Testingโ
3DS testing requires a 3DS-enabled test account. Contact support@omise.co to enable.
| Card Number | 3DS Behavior | Result |
|---|---|---|
| 4242 4242 4242 4242 | Frictionless (no challenge) | Success |
| 4111 1111 1115 0002 | Enrollment failure | Declined |
| 4111 1111 1114 0003 | Authentication failure | Declined |
Test 3DS Authentication:
- Any expiration date in the future works
- Use any 3-4 digit CVV
- Use any cardholder name
Card Details for Testingโ
// Success card
{
name: "John Doe",
number: "4242424242424242",
expiration_month: 12,
expiration_year: 2027,
security_code: "123"
}
// Decline card
{
name: "Jane Smith",
number: "4000000000000002",
expiration_month: 12,
expiration_year: 2027,
security_code: "123"
}
Any future expiration date works for test cards. We recommend using dates at least 1 year in the future.
Testing Payment Methodsโ
Credit/Debit Cardsโ
// Using Omise.js
Omise.setPublicKey("pkey_test_YOUR_KEY");
Omise.createToken("card", {
name: "Test Card",
number: "4242424242424242",
expiration_month: 12,
expiration_year: 2027,
security_code: "123"
}, function(statusCode, response) {
if (statusCode === 200) {
console.log('Token created:', response.id);
// Use token to create charge
}
});
QR Payments (PromptPay, PayNow, etc.)โ
// Create test PromptPay source
const source = await omise.sources.create({
type: 'promptpay',
amount: 35000,
currency: 'THB'
});
// QR code is generated
console.log('QR Code URL:', source.scannable_code.image.download_uri);
// Simulate payment in dashboard
// 1. Go to dashboard โ Charges
// 2. Find the pending charge
// 3. Click "Mark as Successful" or "Mark as Failed"
E-Wallets (TrueMoney, GrabPay, etc.)โ
// Create test wallet source
const source = await omise.sources.create({
type: 'truemoney',
amount: 50000,
currency: 'THB',
phone_number: '+66876543210' // Any Thai number works in test
});
// Simulate in dashboard
// Dashboard โ Charges โ Mark as Successful
Mobile Bankingโ
// Create test mobile banking source
const source = await omise.sources.create({
type: 'mobile_banking_kbank',
amount: 100000,
currency: 'THB'
});
// Simulate in dashboard
// Dashboard โ Charges โ Actions โ Mark as Successful
Simulating Payment Outcomesโ
Via Dashboardโ
- Log in to Test Dashboard
- Ensure Test Mode is enabled
- Navigate to Charges
- Find your pending charge
- Click "Actions" dropdown
- Select:
- "Mark as Successful" - Simulate successful payment
- "Mark as Failed" - Simulate payment failure
Via API (Test Webhooks)โ
// Charges automatically trigger webhooks
// Check your webhook endpoint logs
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
console.log('Test webhook received:', event.key);
console.log('Charge ID:', event.data.id);
console.log('Status:', event.data.status);
res.sendStatus(200);
});
Testing Webhooksโ
Local Testing with ngrokโ
# Install ngrok
npm install -g ngrok
# Start your local server
node server.js # Runs on port 3000
# Create tunnel
ngrok http 3000
# Output:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000
Configure webhook in dashboard:
- Copy ngrok HTTPS URL
- Go to Settings โ Webhooks
- Add endpoint:
https://abc123.ngrok.io/webhooks/omise - Select test events
- Save
Test Webhook Deliveryโ
// Webhook handler
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
// Log for debugging
console.log('=== Webhook Received ===');
console.log('Event:', event.key);
console.log('Object:', event.object);
console.log('Data:', JSON.stringify(event.data, null, 2));
// Verify signature (important!)
if (!verifyWebhookSignature(req)) {
console.error('Invalid signature!');
return res.sendStatus(401);
}
// Handle event
switch (event.key) {
case 'charge.complete':
handleChargeComplete(event.data);
break;
case 'charge.failed':
handleChargeFailed(event.data);
break;
}
res.sendStatus(200);
});
Manual Webhook Testingโ
# Simulate webhook with cURL
curl -X POST http://localhost:3000/webhooks/omise \
-H "Content-Type: application/json" \
-d '{
"object": "event",
"id": "evnt_test_123",
"key": "charge.complete",
"data": {
"object": "charge",
"id": "chrg_test_123",
"status": "successful",
"amount": 100000
}
}'
Testing Refundsโ
Full Refundโ
// Create charge in test mode
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: testTokenId
});
// Wait for successful status
// Then refund
const refund = await omise.charges.refund(charge.id, {
amount: 100000
});
console.log('Refund status:', refund.status);
Partial Refundโ
// Partial refund (50%)
const partialRefund = await omise.charges.refund(charge.id, {
amount: 50000
});
// Multiple partial refunds (up to 15 per charge)
const refund2 = await omise.charges.refund(charge.id, {
amount: 25000
});
Void vs Refundโ
// Void (within 24 hours) - instant cancellation
const voidRefund = await omise.charges.refund(charge.id, {
amount: 100000
});
if (voidRefund.voided) {
console.log('Charge was voided (no settlement)');
} else {
console.log('Charge was refunded (settled then refunded)');
}
Testing 3D Secureโ
Enable 3DS in Testโ
// Include return_uri to trigger 3DS
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: tokenId,
return_uri: 'http://localhost:3000/payment/callback'
});
if (charge.authorize_uri) {
console.log('3DS required, redirect to:', charge.authorize_uri);
// In browser: window.location = charge.authorize_uri
}
Test 3DS Flowsโ
// Frictionless flow (no challenge)
const token = await Omise.createToken("card", {
number: "4242424242424242", // Passes without challenge
// ... other details
});
// Challenge flow (authentication required)
const token2 = await Omise.createToken("card", {
number: "4000000000003220", // Requires authentication
// ... other details
});
// Failed authentication
const token3 = await Omise.createToken("card", {
number: "4000000000003238", // Fails 3DS
// ... other details
});
Testing Error Handlingโ
Network Errorsโ
async function createChargeWithRetry(chargeData, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const charge = await omise.charges.create(chargeData);
return charge;
} catch (error) {
attempt++;
if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
console.log(`Network error, retry ${attempt}/${maxRetries}`);
await sleep(1000 * attempt); // Exponential backoff
} else {
throw error; // Don't retry other errors
}
}
}
throw new Error('Max retries exceeded');
}
API Errorsโ
try {
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: invalidToken
});
} catch (error) {
// Handle specific errors
switch (error.code) {
case 'authentication_failure':
console.error('Invalid API key');
break;
case 'invalid_card':
console.error('Card declined');
showCardError(error.message);
break;
case 'insufficient_fund':
console.error('Insufficient funds');
offerAlternativeMethod();
break;
default:
console.error('Payment error:', error.message);
}
}
Validation Errorsโ
// Test missing required fields
try {
const charge = await omise.charges.create({
amount: 100000
// Missing currency and payment source
});
} catch (error) {
console.log('Validation error:', error.message);
// Error: currency is required
}
// Test invalid amount
try {
const charge = await omise.charges.create({
amount: -100, // Negative amount
currency: 'THB',
card: tokenId
});
} catch (error) {
console.log('Invalid amount:', error.message);
}
Testing Customers APIโ
Create Test Customerโ
// Create customer with test card
const customer = await omise.customers.create({
email: 'test@example.com',
description: 'Test Customer',
card: testTokenId
});
console.log('Customer ID:', customer.id);
console.log('Default card:', customer.default_card);
Update Customer Cardโ
// Update with new test card
const updatedCustomer = await omise.customers.update(customer.id, {
card: newTestTokenId
});
console.log('New default card:', updatedCustomer.default_card);
Charge Saved Cardโ
// Charge customer without token
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
customer: customer.id
});
console.log('Charged saved card:', charge.card.last_digits);
Testing Checklistโ
Before going live, ensure you've tested:
Basic Integrationโ
- Create successful charge with test card (4242...)
- Create declined charge (4000 0000 0000 0002)
- Handle declined card gracefully
- Display clear error messages to user
- Verify charge status via API
- Test webhook delivery and handling
Payment Methodsโ
- Test all enabled payment methods
- QR code display (if applicable)
- Mobile banking redirect flow
- E-wallet integration
- Return URL handling for redirect methods
3D Secure (if enabled)โ
- Test frictionless flow (no challenge)
- Test challenge flow (authentication required)
- Test failed authentication
- Verify return_uri handling
- Check webhook triggers correctly
Refundsโ
- Create full refund
- Create partial refund
- Test void (within 24 hours)
- Verify refund webhooks
- Check refund status in dashboard
Error Handlingโ
- Invalid API key
- Expired token
- Insufficient funds
- Network timeout
- Invalid charge amount
- Missing required fields
Securityโ
- Verify webhook signatures
- Test with HTTPS only
- Validate user input
- Check PCI compliance (no card data on server)
- Test rate limiting
Customer Experienceโ
- Mobile responsive checkout
- Clear payment instructions
- Loading states during processing
- Success confirmation page
- Failure handling with retry option
- Email confirmations (test emails)
Test Data Best Practicesโ
1. Use Realistic Test Dataโ
// Good - realistic test data
{
email: 'test.customer@example.com',
name: 'Test Customer',
description: 'Test order #12345'
}
// Bad - obvious test data that might get flagged
{
email: 'test@test.com',
name: 'Test',
description: 'test'
}
2. Clean Up Test Data Regularlyโ
// Delete old test charges (via dashboard)
// Or keep test and live data separate
3. Tag Test Transactionsโ
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: tokenId,
metadata: {
environment: 'test',
test_scenario: 'successful_payment',
tester: 'john@company.com'
}
});
4. Document Test Scenariosโ
// Test scenario documentation
const TEST_SCENARIOS = {
successful_payment: {
card: '4242424242424242',
expected: 'successful',
description: 'Standard successful payment'
},
declined_payment: {
card: '4000000000000002',
expected: 'failed',
description: 'Card declined by bank'
},
insufficient_funds: {
card: '4000000000000010',
expected: 'failed',
description: 'Insufficient funds'
}
};
Troubleshootingโ
Common Testing Issuesโ
Issue: Test Charges Not Appearing in Dashboardโ
Symptoms:
- Created charges via API but don't see them in dashboard
- Dashboard shows empty test data
Causes:
- Using live keys instead of test keys
- Viewing wrong mode in dashboard
- API errors not caught
Solutions:
// 1. Verify you're using test keys
const omise = require('omise')({
secretKey: 'skey_test_...' // Must start with skey_test_
});
// 2. Check for API errors
try {
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: tokenId
});
console.log('Charge created:', charge.id);
} catch (error) {
console.error('API Error:', error.message);
console.error('Error code:', error.code);
}
// 3. Ensure dashboard is in test mode
// Dashboard โ Toggle "Test Mode" at top
Issue: Test Cards Rejectedโ
Symptoms:
- Test card numbers not working
- Getting "invalid card" errors
- Charges failing in test mode
Causes:
- Using real card numbers in test mode
- Incorrect test card number format
- Invalid expiration date or CVV
Solutions:
// โ CORRECT: Use test card numbers
const testToken = await Omise.createToken("card", {
number: "4242424242424242", // Test card
expiration_month: 12,
expiration_year: 2027, // Future date
security_code: "123"
});
// โ INCORRECT: Real card numbers don't work in test
const realCard = await Omise.createToken("card", {
number: "5555111122223333", // Real card = rejected
// ...
});
// Verify test mode
if (!Omise.getPublicKey().includes('test')) {
console.error('Not using test public key!');
}
Valid Test Cards:
const TEST_CARDS = {
visa_success: '4242424242424242',
mastercard_success: '5555555555554444',
declined: '4000000000000002',
insufficient_funds: '4000000000000010'
};
Issue: Webhooks Not Received in Test Modeโ
Symptoms:
- No webhook deliveries to endpoint
- Missing webhook events
- Timeout errors in dashboard
Causes:
- Endpoint not publicly accessible
- HTTPS certificate invalid
- Webhook signature verification failing
- Firewall blocking requests
Solutions:
// 1. Use ngrok for local testing
// Terminal 1: Start server
node server.js // Port 3000
// Terminal 2: Start ngrok
ngrok http 3000
// Use: https://abc123.ngrok.io/webhooks/omise
// 2. Check endpoint accessibility
curl https://your-domain.com/webhooks/omise
// Should return 405 Method Not Allowed (POST required)
// 3. Verify SSL certificate
// Use: https://www.ssllabs.com/ssltest/
// 4. Test webhook handler locally
curl -X POST http://localhost:3000/webhooks/omise \
-H "Content-Type: application/json" \
-d '{"object":"event","key":"charge.complete"}'
// 5. Check signature verification
app.post('/webhooks/omise', (req, res) => {
console.log('Webhook received');
console.log('Signature:', req.headers['omise-signature']);
console.log('Body:', req.body);
// Don't verify signature during initial testing
// if (!verifyWebhookSignature(req)) {
// return res.sendStatus(401);
// }
res.sendStatus(200);
});
Issue: API Key Authentication Failuresโ
Symptoms:
- "authentication_failure" errors
- 401 Unauthorized responses
- "Invalid API key" messages
Causes:
- Wrong API key format
- Using public key instead of secret key
- Keys from wrong environment
- API key not properly encoded
Solutions:
// โ CORRECT: Use secret key for server-side
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY // skey_test_...
});
// โ INCORRECT: Public key on server
const omise = require('omise')({
secretKey: 'pkey_test_...' // Wrong! Public key
});
// Check key format
function validateApiKey(key) {
if (key.startsWith('skey_test_')) {
return 'test_secret';
} else if (key.startsWith('skey_')) {
return 'live_secret';
} else if (key.startsWith('pkey_test_')) {
return 'test_public';
} else if (key.startsWith('pkey_')) {
return 'live_public';
}
return 'invalid';
}
// Verify in .env file
console.log('Key type:', validateApiKey(process.env.OMISE_SECRET_KEY));
// Test authentication
try {
const balance = await omise.balance.retrieve();
console.log('Authentication successful');
console.log('Available:', balance.available / 100);
} catch (error) {
console.error('Auth failed:', error.message);
}
Issue: Test Card Payment Failuresโ
Symptoms:
- All test cards failing
- Unexpected error codes
- Inconsistent test results
Causes:
- Incorrect charge parameters
- Invalid amount format
- Missing required fields
- Token expired or already used
Solutions:
// Common mistakes and fixes
// โ INCORRECT: Amount as decimal
const charge = await omise.charges.create({
amount: 100.00, // Wrong!
currency: 'THB'
});
// โ CORRECT: Amount in smallest unit (satang)
const charge = await omise.charges.create({
amount: 10000, // เธฟ100.00
currency: 'THB',
card: tokenId
});
// โ INCORRECT: Reusing token
const token = 'tokn_test_123';
await omise.charges.create({ amount: 10000, currency: 'THB', card: token });
await omise.charges.create({ amount: 10000, currency: 'THB', card: token }); // Fails!
// โ CORRECT: Create new token for each charge
const token1 = await createNewToken();
await omise.charges.create({ amount: 10000, currency: 'THB', card: token1 });
const token2 = await createNewToken();
await omise.charges.create({ amount: 10000, currency: 'THB', card: token2 });
// โ INCORRECT: Missing required fields
await omise.charges.create({
amount: 10000
// Missing: currency, payment source
});
// โ CORRECT: All required fields
await omise.charges.create({
amount: 10000,
currency: 'THB',
card: tokenId // or customer, or source
});
Issue: Webhook Testing Problemsโ
Symptoms:
- Webhook signature verification always fails
- Duplicate webhook events
- Missing webhook events
Causes:
- Body parser modifying request body
- Not storing processed event IDs
- Network issues causing retries
Solutions:
// Problem 1: Signature verification fails
// โ INCORRECT: Body already parsed
app.use(express.json()); // Parses body
app.post('/webhooks', (req, res) => {
verifySignature(req.body); // Body modified!
});
// โ CORRECT: Preserve raw body
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
app.post('/webhooks', (req, res) => {
verifySignature(req.rawBody); // Use raw body
});
// Problem 2: Duplicate events
// โ CORRECT: Track processed events
const processedEvents = new Set();
app.post('/webhooks', (req, res) => {
const event = req.body;
// Check if already processed
if (processedEvents.has(event.id)) {
console.log('Duplicate event:', event.id);
return res.sendStatus(200); // Still return 200
}
// Process event
handleEvent(event);
// Mark as processed
processedEvents.add(event.id);
res.sendStatus(200);
});
// Problem 3: Debugging webhooks
app.post('/webhooks', (req, res) => {
console.log('=== WEBHOOK DEBUG ===');
console.log('Headers:', req.headers);
console.log('Body:', JSON.stringify(req.body, null, 2));
console.log('Raw Body:', req.rawBody);
console.log('Event ID:', req.body.id);
console.log('Event Key:', req.body.key);
console.log('====================');
res.sendStatus(200);
});
Quick Troubleshooting Checklistโ
When things go wrong, check:
API Keys:
- Using test keys (start with
test_) - Using secret key on server (not public key)
- Keys loaded from environment variables
- Dashboard in correct mode (test/live)
Test Cards:
- Using valid test card numbers
- Future expiration date
- Valid CVV (any 3-4 digits)
- Not reusing tokens
Webhooks:
- Endpoint is HTTPS with valid certificate
- Endpoint is publicly accessible
- Returning 200 OK status
- Signature verification correct
- Handling duplicate events
Common Errors:
- Amount in correct format (smallest unit)
- All required fields provided
- Valid currency code
- Token not expired
Getting Helpโ
If issues persist:
- Check API Logs:
// Enable debug logging
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY,
debug: true
});
-
Review Dashboard Logs:
- Go to Dashboard โ API Logs
- Check request/response details
- Look for error messages
-
Test with cURL:
curl https://api.omise.co/charges \
-u skey_test_YOUR_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..."
- Contact Support:
- Email: support@omise.co
- Include: Test charge IDs, error messages, code samples
- Specify: Test mode, SDK version, programming language
FAQโ
Can I use real cards in test mode?
No, test mode only accepts test card numbers. Real cards will be rejected. This prevents accidental real charges during development.
Do test mode webhooks actually deliver?
Yes! Test webhooks are delivered to your endpoints just like live webhooks. Use ngrok for local testing or deploy to a staging server.
How do I test payment methods without test cards?
For QR codes, e-wallets, and mobile banking in test mode:
- Create the source/charge
- Go to your test dashboard
- Find the pending charge
- Click "Mark as Successful" or "Mark as Failed"
Are test API keys safe to commit to git?
While test keys don't process real money, they should still be kept in environment variables and not committed to public repositories. They can access test data and be used to create test charges.
Can I switch between test and live mode easily?
Yes, use environment variables:
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY // Switch via env
});
How long do test charges remain in the dashboard?
Test data remains indefinitely but can be manually deleted. Consider cleaning up old test data periodically to keep your test dashboard organized.
Do test transactions affect my live analytics?
No, test and live modes are completely separate. Test transactions don't appear in live analytics or settlement reports.
Related Resourcesโ
- Test Cards Reference - Complete test card list
- Webhooks Testing - Webhook testing guide
- Going Live - Launch checklist
- Authentication - API keys guide
- Dashboard Guide - Using test dashboard