Skip to main content

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 BrandCard NumberCVVExpiry Date3D SecureDescription
Visa4242424242424242Any 3 digitsAny future dateNot enrolledStandard successful payment
Visa4111111111111111Any 3 digitsAny future dateNot enrolledAlternative Visa test card
Mastercard5555555555554444Any 3 digitsAny future dateNot enrolledStandard successful payment
Mastercard5454545454545454Any 3 digitsAny future dateNot enrolledAlternative Mastercard test card
JCB3530111333300000Any 3 digitsAny future dateNot enrolledStandard JCB payment
American Express378282246310005Any 4 digitsAny future dateNot enrolledStandard Amex payment
American Express371449635398431Any 4 digitsAny future dateNot enrolledAlternative Amex test card
Diners Club36227206271667Any 3 digitsAny future dateNot enrolledStandard Diners payment
Discover6011111111111117Any 3 digitsAny future dateNot enrolledStandard 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 BrandCard NumberCVVExpiry DateAuthenticationDescription
Visa4000000000000002Any 3 digitsAny future dateSuccess3DS enrolled, successful auth
Mastercard5200000000000007Any 3 digitsAny future dateSuccess3DS enrolled, successful auth
JCB3530111333300001Any 3 digitsAny future dateSuccess3DS enrolled, successful auth

Failed 3D Secure Authenticationโ€‹

Cards that fail 3D Secure authentication:

Card BrandCard NumberCVVExpiry DateAuthenticationDescription
Visa4000000000000010Any 3 digitsAny future dateFailed3DS enrolled, auth failed
Mastercard5200000000000015Any 3 digitsAny future dateFailed3DS enrolled, auth failed
Visa4000000000000028Any 3 digitsAny future dateRejected3DS 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 NumberDecline ReasonDescription
4000000000000101Insufficient fundsCard has insufficient funds
4000000000000119Processing errorGeneric processing error
4000000000000127Lost cardCard has been reported lost
4000000000000135Stolen cardCard has been reported stolen
4000000000000143Expired cardCard has expired
4000000000000150Invalid CVVCVV verification failed
4000000000000168Invalid card numberCard number is invalid
4000000000000176Invalid expiry dateExpiry date is invalid
4000000000000184Transaction not permittedTransaction not allowed for this card
4000000000000192Suspected fraudTransaction flagged as fraudulent
4000000000000200Card restrictedCard has restrictions
4000000000000218Exceeds withdrawal limitAmount exceeds card limit
4000000000000226Issuer unavailableCard issuer system unavailable
4000000000000234Do not honorGeneric 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 BrandCard NumberCurrencyDescription
Visa4242424242424242THBStandard Thai Visa card
Mastercard5555555555554444THBStandard Thai Mastercard
JCB3530111333300000THBJCB card popular in Thailand
UnionPay6200000000000005THBUnionPay card for Chinese tourists

Singapore Test Cardsโ€‹

Card BrandCard NumberCurrencyDescription
Visa4242424242424242SGDSingapore Visa card
Mastercard5555555555554444SGDSingapore Mastercard

Malaysia Test Cardsโ€‹

Card BrandCard NumberCurrencyDescription
Visa4242424242424242MYRMalaysian Visa card
Mastercard5555555555554444MYRMalaysian Mastercard

Japan Test Cardsโ€‹

Card BrandCard NumberCurrencyDescription
Visa4242424242424242JPYJapanese Visa card
Mastercard5555555555554444JPYJapanese Mastercard
JCB3530111333300000JPYJapanese JCB card
JCB3566002020360505JPYAlternative JCB card

Alternative Payment Methods Test Dataโ€‹

PromptPay Test Dataโ€‹

Test PromptPay payments in the Thai market:

FieldTest ValueDescription
Phone Number0812345678Valid Thai mobile number
Phone Number+66812345678Valid with country code
National ID1234567890123Valid 13-digit Thai ID
AmountAny amountNo 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:

FieldTest ValueDescription
Phone Number0812345678Valid Thai mobile number
Amount20 - 1000000Amount 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:

FieldTest ValueDescription
Amount1 - 5000000Amount in THB subunits
CurrencyTHBThai 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 CodeBank NameTest AccountDescription
bblBangkok BankAny username/passwordSimulate successful payment
kbankKasikorn BankAny username/passwordSimulate successful payment
scbSiam Commercial BankAny username/passwordSimulate successful payment
bayBank of AyudhyaAny username/passwordSimulate successful payment
ktbKrung Thai BankAny username/passwordSimulate 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 CodeBank NameCurrencyDescription
mobile_banking_scbSCB EasyTHBSCB mobile banking
mobile_banking_kbankK PLUSTHBKasikorn mobile banking
mobile_banking_bayKrungsri MobileTHBBank of Ayudhya mobile
mobile_banking_bblBualuang mBankingTHBBangkok 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:

FieldTest ValueDescription
Amount100 - 10000000Amount in SGD subunits
CurrencySGDSingapore 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 CodeBank NameTest AccountDescription
fpx_mb2uMaybank2uAny credentialsSimulate successful payment
fpx_cimbclicksCIMB ClicksAny credentialsSimulate successful payment
fpx_pbbPublic BankAny credentialsSimulate successful payment
fpx_rhbRHB BankAny credentialsSimulate 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โ€‹

  1. Enable verbose logging:
const Omise = require('omise')({
secretKey: 'skey_test_xxxxxxxxxx',
omiseVersion: '2019-05-29'
});

// Log all requests and responses
Omise.setDebug(true);
  1. Check API response details:
import omise
omise.api_secret = 'skey_test_xxxxxxxxxx'

# Enable debug mode
import logging
logging.basicConfig(level=logging.DEBUG)
  1. Verify test mode:
// Always verify you're in test mode
if (!secretKey.startsWith('skey_test_')) {
throw new Error('Not in test mode!');
}
  1. 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.

Next Stepsโ€‹

  1. Test successful payments with standard test cards
  2. Test 3D Secure flows with enrolled test cards
  3. Test decline scenarios with specific decline cards
  4. Test regional payment methods for your target markets
  5. Set up webhook testing to handle payment events
  6. Implement error handling for all failure scenarios
  7. Test refund flows to ensure proper handling
  8. Run automated tests with your test suite

Ready to test error scenarios? Check out Simulating Failures.

Need to test webhooks? See Testing Webhooks.