Test Card Numbers & Test Data
Complete test card numbers and payment data for testing all payment scenarios in Omise's test environment. Use these cards to simulate successful payments, declines, 3D Secure authentication, and various failure scenarios.
Overviewโ
Omise provides comprehensive test data for all supported payment methods. These test cards and payment methods allow you to test your integration thoroughly before going live, covering success scenarios, decline reasons, fraud detection, 3D Secure authentication, and regional payment methods.
Key Featuresโ
- Complete Card Coverage: Test cards for all major card brands
- 3D Secure Testing: Test both successful and failed authentication
- Decline Scenarios: Test specific decline reasons and responses
- Regional Methods: Test cards and payment methods for each supported market
- Real-World Scenarios: Test edge cases and special conditions
- CVV & Expiry Testing: Test validation and security checks
Standard Test Cardsโ
Successful Payment Cardsโ
Use these cards to test successful payment scenarios:
| Card Brand | Card Number | CVV | Expiry Date | 3D Secure | Description |
|---|---|---|---|---|---|
| Visa | 4242424242424242 | Any 3 digits | Any future date | Not enrolled | Standard successful payment |
| Visa | 4111111111111111 | Any 3 digits | Any future date | Not enrolled | Alternative Visa test card |
| Mastercard | 5555555555554444 | Any 3 digits | Any future date | Not enrolled | Standard successful payment |
| Mastercard | 5454545454545454 | Any 3 digits | Any future date | Not enrolled | Alternative Mastercard test card |
| JCB | 3530111333300000 | Any 3 digits | Any future date | Not enrolled | Standard JCB payment |
| American Express | 378282246310005 | Any 4 digits | Any future date | Not enrolled | Standard Amex payment |
| American Express | 371449635398431 | Any 4 digits | Any future date | Not enrolled | Alternative Amex test card |
| Diners Club | 36227206271667 | Any 3 digits | Any future date | Not enrolled | Standard Diners payment |
| Discover | 6011111111111117 | Any 3 digits | Any future date | Not enrolled | Standard Discover payment |
Important Notesโ
- CVV: Use any valid CVV (3 digits for most cards, 4 for American Express)
- Expiry Date: Use any future date in MM/YY or MM/YYYY format
- Cardholder Name: Use any name (minimum 2 characters)
- Amount: Use any valid amount to test different payment values
3D Secure Test Cardsโ
Successful 3D Secure Authenticationโ
Cards that require and successfully complete 3D Secure authentication:
| Card Brand | Card Number | CVV | Expiry Date | Authentication | Description |
|---|---|---|---|---|---|
| Visa | 4000000000000002 | Any 3 digits | Any future date | Success | 3DS enrolled, successful auth |
| Mastercard | 5200000000000007 | Any 3 digits | Any future date | Success | 3DS enrolled, successful auth |
| JCB | 3530111333300001 | Any 3 digits | Any future date | Success | 3DS enrolled, successful auth |
Failed 3D Secure Authenticationโ
Cards that fail 3D Secure authentication:
| Card Brand | Card Number | CVV | Expiry Date | Authentication | Description |
|---|---|---|---|---|---|
| Visa | 4000000000000010 | Any 3 digits | Any future date | Failed | 3DS enrolled, auth failed |
| Mastercard | 5200000000000015 | Any 3 digits | Any future date | Failed | 3DS enrolled, auth failed |
| Visa | 4000000000000028 | Any 3 digits | Any future date | Rejected | 3DS enrolled, rejected by issuer |
3D Secure Authentication Flowโ
// JavaScript - Testing 3D Secure payments
const Omise = require('omise')({
secretKey: 'skey_test_xxxxxxxxxx',
omiseVersion: '2019-05-29'
});
async function test3DSecurePayment() {
try {
// 1. Create a token with 3DS test card
const token = await Omise.tokens.create({
card: {
name: 'Test User',
number: '4000000000000002', // 3DS enrolled card
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
});
// 2. Create charge (will return authorize_uri for 3DS)
const charge = await Omise.charges.create({
amount: 100000, // 1,000.00 THB
currency: 'THB',
card: token.id,
return_uri: 'https://example.com/orders/complete'
});
console.log('Charge Status:', charge.status); // 'pending'
console.log('3DS Authorization URL:', charge.authorize_uri);
// Customer completes 3DS authentication at authorize_uri
// After completion, they're redirected to return_uri
// 3. Retrieve charge to check final status
const updatedCharge = await Omise.charges.retrieve(charge.id);
console.log('Final Status:', updatedCharge.status); // 'successful'
} catch (error) {
console.error('Error:', error.message);
}
}
test3DSecurePayment();
# Python - Testing 3D Secure payments
import omise
omise.api_secret = 'skey_test_xxxxxxxxxx'
omise.api_version = '2019-05-29'
def test_3ds_payment():
try:
# 1. Create a token with 3DS test card
token = omise.Token.create(
card={
'name': 'Test User',
'number': '4000000000000002', # 3DS enrolled card
'expiration_month': 12,
'expiration_year': 2026,
'security_code': '123'
}
)
# 2. Create charge (will return authorize_uri for 3DS)
charge = omise.Charge.create(
amount=100000, # 1,000.00 THB
currency='THB',
card=token.id,
return_uri='https://example.com/orders/complete'
)
print(f'Charge Status: {charge.status}') # 'pending'
print(f'3DS Authorization URL: {charge.authorize_uri}')
# Customer completes 3DS authentication at authorize_uri
# After completion, they're redirected to return_uri
# 3. Retrieve charge to check final status
updated_charge = omise.Charge.retrieve(charge.id)
print(f'Final Status: {updated_charge.status}') # 'successful'
except omise.errors.BaseError as e:
print(f'Error: {e.message}')
test_3ds_payment()
# Ruby - Testing 3D Secure payments
require 'omise'
Omise.api_key = 'skey_test_xxxxxxxxxx'
Omise.api_version = '2019-05-29'
def test_3ds_payment
begin
# 1. Create a token with 3DS test card
token = Omise::Token.create(
card: {
name: 'Test User',
number: '4000000000000002', # 3DS enrolled card
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
)
# 2. Create charge (will return authorize_uri for 3DS)
charge = Omise::Charge.create(
amount: 100_000, # 1,000.00 THB
currency: 'THB',
card: token.id,
return_uri: 'https://example.com/orders/complete'
)
puts "Charge Status: #{charge.status}" # 'pending'
puts "3DS Authorization URL: #{charge.authorize_uri}"
# Customer completes 3DS authentication at authorize_uri
# After completion, they're redirected to return_uri
# 3. Retrieve charge to check final status
updated_charge = Omise::Charge.retrieve(charge.id)
puts "Final Status: #{updated_charge.status}" # 'successful'
rescue Omise::Error => e
puts "Error: #{e.message}"
end
end
test_3ds_payment
<?php
// PHP - Testing 3D Secure payments
require_once 'vendor/autoload.php';
define('OMISE_API_VERSION', '2019-05-29');
define('OMISE_SECRET_KEY', 'skey_test_xxxxxxxxxx');
function test3DSecurePayment() {
try {
// 1. Create a token with 3DS test card
$token = OmiseToken::create([
'card' => [
'name' => 'Test User',
'number' => '4000000000000002', // 3DS enrolled card
'expiration_month' => 12,
'expiration_year' => 2026,
'security_code' => '123'
]
]);
// 2. Create charge (will return authorize_uri for 3DS)
$charge = OmiseCharge::create([
'amount' => 100000, // 1,000.00 THB
'currency' => 'THB',
'card' => $token['id'],
'return_uri' => 'https://example.com/orders/complete'
]);
echo "Charge Status: {$charge['status']}\n"; // 'pending'
echo "3DS Authorization URL: {$charge['authorize_uri']}\n";
// Customer completes 3DS authentication at authorize_uri
// After completion, they're redirected to return_uri
// 3. Retrieve charge to check final status
$updatedCharge = OmiseCharge::retrieve($charge['id']);
echo "Final Status: {$updatedCharge['status']}\n"; // 'successful'
} catch (Exception $e) {
echo "Error: {$e->getMessage()}\n";
}
}
test3DSecurePayment();
?>
// Go - Testing 3D Secure payments
package main
import (
"fmt"
"github.com/omise/omise-go"
"github.com/omise/omise-go/operations"
)
func test3DSecurePayment() {
client, _ := omise.NewClient(
"pkey_test_xxxxxxxxxx",
"skey_test_xxxxxxxxxx",
)
client.APIVersion = "2019-05-29"
// 1. Create a token with 3DS test card
token, createTokenErr := client.CreateToken(&operations.CreateToken{
Name: "Test User",
Number: "4000000000000002", // 3DS enrolled card
ExpirationMonth: 12,
ExpirationYear: 2026,
SecurityCode: "123",
})
if createTokenErr != nil {
fmt.Printf("Error creating token: %v\n", createTokenErr)
return
}
// 2. Create charge (will return authorize_uri for 3DS)
charge, createChargeErr := client.CreateCharge(&operations.CreateCharge{
Amount: 100000, // 1,000.00 THB
Currency: "THB",
Card: token.ID,
ReturnURI: "https://example.com/orders/complete",
})
if createChargeErr != nil {
fmt.Printf("Error creating charge: %v\n", createChargeErr)
return
}
fmt.Printf("Charge Status: %s\n", charge.Status) // 'pending'
fmt.Printf("3DS Authorization URL: %s\n", charge.AuthorizeURI)
// Customer completes 3DS authentication at authorize_uri
// After completion, they're redirected to return_uri
// 3. Retrieve charge to check final status
updatedCharge, retrieveErr := client.GetCharge(charge.ID)
if retrieveErr != nil {
fmt.Printf("Error retrieving charge: %v\n", retrieveErr)
return
}
fmt.Printf("Final Status: %s\n", updatedCharge.Status) // 'successful'
}
func main() {
test3DSecurePayment()
}
Declined Card Test Numbersโ
Specific Decline Reasonsโ
Test specific decline scenarios with these cards:
| Card Number | Decline Reason | Description |
|---|---|---|
4000000000000101 | Insufficient funds | Card has insufficient funds |
4000000000000119 | Processing error | Generic processing error |
4000000000000127 | Lost card | Card has been reported lost |
4000000000000135 | Stolen card | Card has been reported stolen |
4000000000000143 | Expired card | Card has expired |
4000000000000150 | Invalid CVV | CVV verification failed |
4000000000000168 | Invalid card number | Card number is invalid |
4000000000000176 | Invalid expiry date | Expiry date is invalid |
4000000000000184 | Transaction not permitted | Transaction not allowed for this card |
4000000000000192 | Suspected fraud | Transaction flagged as fraudulent |
4000000000000200 | Card restricted | Card has restrictions |
4000000000000218 | Exceeds withdrawal limit | Amount exceeds card limit |
4000000000000226 | Issuer unavailable | Card issuer system unavailable |
4000000000000234 | Do not honor | Generic decline from issuer |
Testing Declined Paymentsโ
// JavaScript - Testing declined payments
const Omise = require('omise')({
secretKey: 'skey_test_xxxxxxxxxx'
});
async function testDeclinedPayment() {
try {
const token = await Omise.tokens.create({
card: {
name: 'Test User',
number: '4000000000000101', // Insufficient funds
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
});
const charge = await Omise.charges.create({
amount: 100000,
currency: 'THB',
card: token.id
});
console.log('Charge Status:', charge.status); // 'failed'
console.log('Failure Code:', charge.failure_code); // 'insufficient_funds'
console.log('Failure Message:', charge.failure_message);
} catch (error) {
console.error('Error:', error.message);
}
}
testDeclinedPayment();
# Python - Testing declined payments with proper error handling
import omise
omise.api_secret = 'skey_test_xxxxxxxxxx'
def test_declined_payment():
try:
token = omise.Token.create(
card={
'name': 'Test User',
'number': '4000000000000101', # Insufficient funds
'expiration_month': 12,
'expiration_year': 2026,
'security_code': '123'
}
)
charge = omise.Charge.create(
amount=100000,
currency='THB',
card=token.id
)
print(f'Charge Status: {charge.status}') # 'failed'
print(f'Failure Code: {charge.failure_code}') # 'insufficient_funds'
print(f'Failure Message: {charge.failure_message}')
except omise.errors.BaseError as e:
print(f'Error: {e.message}')
test_declined_payment()
Regional Test Cardsโ
Thailand Test Cardsโ
Cards specifically for testing in the Thai market:
| Card Brand | Card Number | Currency | Description |
|---|---|---|---|
| Visa | 4242424242424242 | THB | Standard Thai Visa card |
| Mastercard | 5555555555554444 | THB | Standard Thai Mastercard |
| JCB | 3530111333300000 | THB | JCB card popular in Thailand |
| UnionPay | 6200000000000005 | THB | UnionPay card for Chinese tourists |
Singapore Test Cardsโ
| Card Brand | Card Number | Currency | Description |
|---|---|---|---|
| Visa | 4242424242424242 | SGD | Singapore Visa card |
| Mastercard | 5555555555554444 | SGD | Singapore Mastercard |
Malaysia Test Cardsโ
| Card Brand | Card Number | Currency | Description |
|---|---|---|---|
| Visa | 4242424242424242 | MYR | Malaysian Visa card |
| Mastercard | 5555555555554444 | MYR | Malaysian Mastercard |
Japan Test Cardsโ
| Card Brand | Card Number | Currency | Description |
|---|---|---|---|
| Visa | 4242424242424242 | JPY | Japanese Visa card |
| Mastercard | 5555555555554444 | JPY | Japanese Mastercard |
| JCB | 3530111333300000 | JPY | Japanese JCB card |
| JCB | 3566002020360505 | JPY | Alternative JCB card |
Alternative Payment Methods Test Dataโ
PromptPay Test Dataโ
Test PromptPay payments in the Thai market:
| Field | Test Value | Description |
|---|---|---|
| Phone Number | 0812345678 | Valid Thai mobile number |
| Phone Number | +66812345678 | Valid with country code |
| National ID | 1234567890123 | Valid 13-digit Thai ID |
| Amount | Any amount | No minimum for testing |
// JavaScript - Testing PromptPay payments
const Omise = require('omise')({
secretKey: 'skey_test_xxxxxxxxxx'
});
async function testPromptPayPayment() {
try {
const source = await Omise.sources.create({
type: 'promptpay',
amount: 100000,
currency: 'THB'
});
console.log('Source ID:', source.id);
console.log('QR Code URL:', source.scannable_code.image.download_uri);
const charge = await Omise.charges.create({
amount: 100000,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/orders/complete'
});
console.log('Charge Status:', charge.status); // 'pending'
// In test mode, you can manually mark the payment as successful
// or it will auto-expire after the timeout period
} catch (error) {
console.error('Error:', error.message);
}
}
testPromptPayPayment();
TrueMoney Wallet Test Dataโ
Test TrueMoney Wallet payments:
| Field | Test Value | Description |
|---|---|---|
| Phone Number | 0812345678 | Valid Thai mobile number |
| Amount | 20 - 1000000 | Amount in THB subunits |
// JavaScript - Testing TrueMoney Wallet payments
async function testTrueMoneyPayment() {
try {
const source = await Omise.sources.create({
type: 'truemoney',
amount: 100000,
currency: 'THB',
phone_number: '0812345678'
});
const charge = await Omise.charges.create({
amount: 100000,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/orders/complete'
});
console.log('Charge Status:', charge.status);
console.log('Authorization URL:', charge.authorize_uri);
} catch (error) {
console.error('Error:', error.message);
}
}
testTrueMoneyPayment();
Rabbit LINE Pay Test Dataโ
Test Rabbit LINE Pay payments:
| Field | Test Value | Description |
|---|---|---|
| Amount | 1 - 5000000 | Amount in THB subunits |
| Currency | THB | Thai Baht only |
# Python - Testing Rabbit LINE Pay
import omise
omise.api_secret = 'skey_test_xxxxxxxxxx'
def test_rabbit_linepay():
try:
source = omise.Source.create(
type='rabbit_linepay',
amount=100000,
currency='THB'
)
charge = omise.Charge.create(
amount=100000,
currency='THB',
source=source.id,
return_uri='https://example.com/orders/complete'
)
print(f'Charge Status: {charge.status}')
print(f'Authorization URL: {charge.authorize_uri}')
except omise.errors.BaseError as e:
print(f'Error: {e.message}')
test_rabbit_linepay()
Internet Banking Test Dataโ
Test Internet Banking payments for supported Thai banks:
| Bank Code | Bank Name | Test Account | Description |
|---|---|---|---|
bbl | Bangkok Bank | Any username/password | Simulate successful payment |
kbank | Kasikorn Bank | Any username/password | Simulate successful payment |
scb | Siam Commercial Bank | Any username/password | Simulate successful payment |
bay | Bank of Ayudhya | Any username/password | Simulate successful payment |
ktb | Krung Thai Bank | Any username/password | Simulate successful payment |
# Ruby - Testing Internet Banking payments
require 'omise'
Omise.api_key = 'skey_test_xxxxxxxxxx'
def test_internet_banking
begin
source = Omise::Source.create(
type: 'internet_banking_bbl',
amount: 100_000,
currency: 'THB'
)
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/orders/complete'
)
puts "Charge Status: #{charge.status}"
puts "Authorization URL: #{charge.authorize_uri}"
rescue Omise::Error => e
puts "Error: #{e.message}"
end
end
test_internet_banking
Mobile Banking Test Dataโ
Test mobile banking payments:
| Bank Code | Bank Name | Currency | Description |
|---|---|---|---|
mobile_banking_scb | SCB Easy | THB | SCB mobile banking |
mobile_banking_kbank | K PLUS | THB | Kasikorn mobile banking |
mobile_banking_bay | Krungsri Mobile | THB | Bank of Ayudhya mobile |
mobile_banking_bbl | Bualuang mBanking | THB | Bangkok Bank mobile |
<?php
// PHP - Testing Mobile Banking payments
require_once 'vendor/autoload.php';
define('OMISE_SECRET_KEY', 'skey_test_xxxxxxxxxx');
function testMobileBanking() {
try {
$source = OmiseSource::create([
'type' => 'mobile_banking_scb',
'amount' => 100000,
'currency' => 'THB'
]);
$charge = OmiseCharge::create([
'amount' => 100000,
'currency' => 'THB',
'source' => $source['id'],
'return_uri' => 'https://example.com/orders/complete'
]);
echo "Charge Status: {$charge['status']}\n";
echo "Authorization URL: {$charge['authorize_uri']}\n";
} catch (Exception $e) {
echo "Error: {$e->getMessage()}\n";
}
}
testMobileBanking();
?>
PayNow Test Data (Singapore)โ
Test PayNow payments for Singapore:
| Field | Test Value | Description |
|---|---|---|
| Amount | 100 - 10000000 | Amount in SGD subunits |
| Currency | SGD | Singapore Dollar only |
// Go - Testing PayNow payments
package main
import (
"fmt"
"github.com/omise/omise-go"
"github.com/omise/omise-go/operations"
)
func testPayNow() {
client, _ := omise.NewClient(
"pkey_test_xxxxxxxxxx",
"skey_test_xxxxxxxxxx",
)
// Create PayNow source
source, createErr := client.CreateSource(&operations.CreateSource{
Type: "paynow",
Amount: 100000, // 1,000.00 SGD
Currency: "SGD",
})
if createErr != nil {
fmt.Printf("Error creating source: %v\n", createErr)
return
}
// Create charge
charge, chargeErr := client.CreateCharge(&operations.CreateCharge{
Amount: 100000,
Currency: "SGD",
Source: source.ID,
ReturnURI: "https://example.com/orders/complete",
})
if chargeErr != nil {
fmt.Printf("Error creating charge: %v\n", chargeErr)
return
}
fmt.Printf("Charge Status: %s\n", charge.Status)
fmt.Printf("QR Code URL: %s\n", charge.Source.ScannableCode.Image.DownloadURI)
}
func main() {
testPayNow()
}
FPX Test Data (Malaysia)โ
Test FPX (Financial Process Exchange) for Malaysian banks:
| Bank Code | Bank Name | Test Account | Description |
|---|---|---|---|
fpx_mb2u | Maybank2u | Any credentials | Simulate successful payment |
fpx_cimbclicks | CIMB Clicks | Any credentials | Simulate successful payment |
fpx_pbb | Public Bank | Any credentials | Simulate successful payment |
fpx_rhb | RHB Bank | Any credentials | Simulate successful payment |
Real-World Testing Scenariosโ
Scenario 1: Multi-Currency Testingโ
Test the same transaction across different currencies:
// JavaScript - Multi-currency testing
const currencies = ['THB', 'SGD', 'MYR', 'JPY', 'USD', 'EUR'];
const testCard = '4242424242424242';
async function testMultiCurrency() {
for (const currency of currencies) {
try {
const token = await Omise.tokens.create({
card: {
name: 'Test User',
number: testCard,
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
});
const amount = currency === 'JPY' ? 10000 : 100000;
const charge = await Omise.charges.create({
amount: amount,
currency: currency,
card: token.id,
description: `Test payment in ${currency}`
});
console.log(`${currency}: ${charge.status} - ${charge.id}`);
} catch (error) {
console.error(`${currency} failed:`, error.message);
}
}
}
testMultiCurrency();
Scenario 2: Testing Payment Limitsโ
Test minimum and maximum payment amounts:
# Python - Testing payment limits
import omise
omise.api_secret = 'skey_test_xxxxxxxxxx'
def test_payment_limits():
# Test amounts for different scenarios
test_amounts = {
'minimum': 2000, # 20.00 THB
'small': 10000, # 100.00 THB
'medium': 100000, # 1,000.00 THB
'large': 1000000, # 10,000.00 THB
'very_large': 10000000 # 100,000.00 THB
}
for test_name, amount in test_amounts.items():
try:
token = omise.Token.create(
card={
'name': 'Test User',
'number': '4242424242424242',
'expiration_month': 12,
'expiration_year': 2026,
'security_code': '123'
}
)
charge = omise.Charge.create(
amount=amount,
currency='THB',
card=token.id,
description=f'Test {test_name} amount'
)
print(f'{test_name} ({amount}): {charge.status}')
except omise.errors.BaseError as e:
print(f'{test_name} failed: {e.message}')
test_payment_limits()
Scenario 3: Testing Concurrent Paymentsโ
Test multiple simultaneous payments:
// JavaScript - Testing concurrent payments
async function testConcurrentPayments() {
const numberOfPayments = 5;
const promises = [];
for (let i = 0; i < numberOfPayments; i++) {
const promise = (async () => {
const token = await Omise.tokens.create({
card: {
name: `Test User ${i}`,
number: '4242424242424242',
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
});
const charge = await Omise.charges.create({
amount: 100000,
currency: 'THB',
card: token.id,
description: `Concurrent payment ${i}`
});
return { index: i, status: charge.status, id: charge.id };
})();
promises.push(promise);
}
try {
const results = await Promise.all(promises);
results.forEach(result => {
console.log(`Payment ${result.index}: ${result.status} (${result.id})`);
});
} catch (error) {
console.error('Error in concurrent payments:', error.message);
}
}
testConcurrentPayments();
Scenario 4: Testing Refund Flowsโ
Test full and partial refunds:
# Ruby - Testing refund flows
require 'omise'
Omise.api_key = 'skey_test_xxxxxxxxxx'
def test_refund_flow
begin
# Create successful charge
token = Omise::Token.create(
card: {
name: 'Test User',
number: '4242424242424242',
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
)
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: token.id
)
puts "Charge created: #{charge.id} - #{charge.status}"
# Test full refund
full_refund = Omise::Refund.create(charge.id)
puts "Full refund: #{full_refund.id} - #{full_refund.amount}"
# Create another charge for partial refund test
token2 = Omise::Token.create(
card: {
name: 'Test User',
number: '4242424242424242',
expiration_month: 12,
expiration_year: 2026,
security_code: '123'
}
)
charge2 = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: token2.id
)
# Test partial refund (50%)
partial_refund = Omise::Refund.create(charge2.id, amount: 50_000)
puts "Partial refund: #{partial_refund.id} - #{partial_refund.amount}"
rescue Omise::Error => e
puts "Error: #{e.message}"
end
end
test_refund_flow
Best Practices for Testingโ
1. Test All Payment Statesโ
Ensure your integration handles all possible payment states:
- Successful: Payment completed successfully
- Pending: Waiting for customer action (3DS, redirect)
- Failed: Payment declined or failed
- Expired: Payment timed out without completion
- Reversed: Payment was reversed (rare)
2. Test Different Card Typesโ
Test with multiple card brands to ensure compatibility:
const testCards = [
{ brand: 'Visa', number: '4242424242424242' },
{ brand: 'Mastercard', number: '5555555555554444' },
{ brand: 'JCB', number: '3530111333300000' },
{ brand: 'Amex', number: '378282246310005' }
];
async function testAllCardBrands() {
for (const card of testCards) {
console.log(`Testing ${card.brand}...`);
// Create token and charge
}
}
3. Test Edge Casesโ
Test uncommon but valid scenarios:
- Amounts with decimal precision
- International characters in cardholder names
- Maximum field lengths
- Minimum amounts
- Same card multiple times
- Rapid successive payments
4. Test Error Handlingโ
Ensure your application handles errors gracefully:
# Python - Comprehensive error handling
def test_with_error_handling():
try:
token = omise.Token.create(
card={
'name': 'Test User',
'number': '4000000000000101', # Will decline
'expiration_month': 12,
'expiration_year': 2026,
'security_code': '123'
}
)
charge = omise.Charge.create(
amount=100000,
currency='THB',
card=token.id
)
if charge.status == 'failed':
# Handle failed payment
print(f'Payment failed: {charge.failure_code}')
print(f'Message: {charge.failure_message}')
except omise.errors.InvalidRequestError as e:
# Handle invalid request (validation errors)
print(f'Invalid request: {e.message}')
except omise.errors.AuthenticationError as e:
# Handle authentication errors
print(f'Authentication error: {e.message}')
except omise.errors.BaseError as e:
# Handle other Omise errors
print(f'Omise error: {e.message}')
except Exception as e:
# Handle unexpected errors
print(f'Unexpected error: {str(e)}')
5. Use Descriptive Metadataโ
Add metadata to test charges for easier debugging:
const charge = await Omise.charges.create({
amount: 100000,
currency: 'THB',
card: token.id,
metadata: {
test_scenario: 'successful_visa_payment',
test_run_id: 'test_' + Date.now(),
environment: 'automated_testing'
}
});
6. Test Idempotencyโ
Ensure duplicate requests are handled correctly:
# Ruby - Testing idempotency
def test_idempotency
idempotency_key = "test_#{Time.now.to_i}"
# Make the same request twice
2.times do
charge = Omise::Charge.create(
{
amount: 100_000,
currency: 'THB',
card: token.id
},
{ 'Idempotency-Key': idempotency_key }
)
puts "Charge ID: #{charge.id}"
end
# Both requests should return the same charge ID
end
Troubleshootingโ
Common Issuesโ
Issue: "Card number is invalid"โ
Cause: Using production card numbers in test mode
Solution: Use test card numbers from this documentation. Test cards always start with specific prefixes (e.g., 4242, 5555, 3530).
// Wrong - real card
number: '4532015112830366'
// Correct - test card
number: '4242424242424242'
Issue: 3D Secure redirect not workingโ
Cause: Missing or invalid return_uri
Solution: Always provide a valid return_uri for 3DS payments:
const charge = await Omise.charges.create({
amount: 100000,
currency: 'THB',
card: token.id,
return_uri: 'https://example.com/complete' // Required for 3DS
});
Issue: "Amount is below minimum"โ
Cause: Test amount too low for currency
Solution: Respect minimum amounts:
- THB: 2000 (20.00 THB)
- SGD: 100 (1.00 SGD)
- MYR: 100 (1.00 MYR)
- JPY: 100 (100 JPY)
Issue: Token already usedโ
Cause: Attempting to reuse a token
Solution: Create a new token for each payment:
# Wrong - reusing token
charge1 = omise.Charge.create(amount=100000, currency='THB', card=token.id)
charge2 = omise.Charge.create(amount=100000, currency='THB', card=token.id) # Error!
# Correct - new token for each charge
token1 = omise.Token.create(card={...})
charge1 = omise.Charge.create(amount=100000, currency='THB', card=token1.id)
token2 = omise.Token.create(card={...})
charge2 = omise.Charge.create(amount=100000, currency='THB', card=token2.id)
Issue: PromptPay QR code not generatingโ
Cause: Incorrect source parameters
Solution: Ensure correct source creation:
const source = await Omise.sources.create({
type: 'promptpay',
amount: 100000, // Required
currency: 'THB' // Required
});
Issue: Test payment stuck in pendingโ
Cause: Normal behavior in test mode for redirect-based payments
Solution:
- For testing, payments requiring redirect will remain pending until timeout
- Use webhooks to handle status changes
- Check charge status after simulated customer action
Debugging Tipsโ
- Enable verbose logging:
const Omise = require('omise')({
secretKey: 'skey_test_xxxxxxxxxx',
omiseVersion: '2019-05-29'
});
// Log all requests and responses
Omise.setDebug(true);
- Check API response details:
import omise
omise.api_secret = 'skey_test_xxxxxxxxxx'
# Enable debug mode
import logging
logging.basicConfig(level=logging.DEBUG)
- Verify test mode:
// Always verify you're in test mode
if (!secretKey.startsWith('skey_test_')) {
throw new Error('Not in test mode!');
}
- Use metadata for tracking:
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: token.id,
metadata: {
debug_info: 'test_run_123',
timestamp: Time.now.to_s
}
)
Frequently Asked Questionsโ
Can I use real card numbers in test mode?โ
No, test mode only accepts test card numbers. Real card numbers will be rejected with an "invalid card number" error. This is a security measure to prevent accidental real transactions in test mode.
Do test payments create real transactions?โ
No, test payments are completely isolated from production. No money is actually charged, no real bank connections are made, and test data never affects production systems.
How long do test tokens remain valid?โ
Test tokens expire 30 minutes after creation, just like production tokens. Always create tokens immediately before use.
Can I test webhooks with test data?โ
Yes, test mode sends webhooks to your configured endpoint just like production. All webhook events work in test mode. See the Testing Webhooks guide for details.
Do 3D Secure test cards require real authentication?โ
No, 3D Secure test cards simulate the authentication flow without requiring real bank authentication. The test environment automatically handles the 3DS process.
Can I test refunds in test mode?โ
Yes, all refund operations work in test mode. You can create full and partial refunds on test charges. Refunds are processed immediately in test mode (no delays).
How do I test different decline reasons?โ
Use the specific test card numbers listed in the Declined Card Test Numbers section. Each card triggers a specific decline reason.
Can I test with amounts below the minimum?โ
No, minimum amounts are enforced in test mode to match production behavior. Testing with amounts below the minimum helps ensure your validation works correctly.
Do test payment methods expire?โ
Yes, test payment methods that require customer action (PromptPay, Internet Banking, etc.) will expire after the same timeout period as production (typically 15-30 minutes).
Can I delete test charges?โ
No, charges cannot be deleted in either test or production mode. However, test charges don't count toward your transaction volume and can be refunded if needed.
How do I test international cards?โ
Use the standard test cards with different currency codes. The card brand and number are the same; specify the appropriate currency when creating the charge.
Can I test recurring payments?โ
Yes, use test cards to create customers and schedules. All recurring payment features work in test mode. See the Recurring Payments guide for details.
Related Resourcesโ
- Test Mode Guide - Complete guide to using test mode
- Simulating Failures - Testing error scenarios and edge cases
- Testing Webhooks - Webhook testing strategies and tools
- API Authentication - Understanding test vs production keys
- Error Codes Reference - Complete list of error codes and meanings
- 3D Secure Guide - Understanding 3D Secure authentication
- Payment Methods - All supported payment methods
Next Stepsโ
- Test successful payments with standard test cards
- Test 3D Secure flows with enrolled test cards
- Test decline scenarios with specific decline cards
- Test regional payment methods for your target markets
- Set up webhook testing to handle payment events
- Implement error handling for all failure scenarios
- Test refund flows to ensure proper handling
- Run automated tests with your test suite
Ready to test error scenarios? Check out Simulating Failures.
Need to test webhooks? See Testing Webhooks.