Testing Guide
Learn how to test your Payme integration.
Test Environment
Payme provides a test environment for development:
typescript
const payme = new PaymeMerchant({
merchantId: process.env.PAYME_MERCHANT_ID_TEST!,
secretKey: process.env.PAYME_SECRET_KEY_TEST!,
baseURL: 'https://checkout.test.paycom.uz/api' // Test environment
});Test Credentials
To access the test merchant dashboard:
| Parameter | Value |
|---|---|
| Login (Phone) | phone |
| Password | qwerty |
| SMS Code | 666666 |
SMS Code for All Test Cards
The SMS verification code for all test cards is 666666. This code works regardless of the card type or expected behavior.
Subscribe API Test Cards
Use these test card numbers in the test environment for card tokenization:
| Card Number | Expiry | Comment |
|---|---|---|
| 8600 0609 2109 0842 | 03/99 | SMS-info not connected |
| 3333 3364 1580 4657 | 03/99 | Card expired |
| 4444 4459 8745 9073 | 03/99 | Card blocked |
| 8600 1434 1777 0323 | 03/99 | Unknown system error |
| 8600 1343 0184 9596 | 03/99 | 10s delay simulation (ends in error) |
| 8600 4954 7331 6478 | 03/99 | Success card |
| 8600 0691 9540 6311 | 03/99 | Success card |
Test Scenarios
- Success Cards (
8600 4954 7331 6478,8600 0691 9540 6311) - Complete tokenization and payment successfully - Expired Card (
3333 3364 1580 4657) - Verify proper handling of expired cards - Blocked Card (
4444 4459 8745 9073) - Test blocked card error handling - SMS Not Connected (
8600 0609 2109 0842) - Test scenario when SMS verification is unavailable - Delay Simulation (
8600 1343 0184 9596) - Test timeout handling (10 second delay before error) - Unknown Error (
8600 1434 1777 0323) - Test handling of unexpected errors
Unit Testing
Testing with Mocks
typescript
import { describe, test, expect, mock } from 'bun:test';
import { PaymeMerchant } from '@joyida/payme';
describe('Payment Processing', () => {
test('should create transaction', async () => {
const payme = new PaymeMerchant({
merchantId: 'test_merchant_id',
secretKey: 'test_secret_key'
});
// Mock the HTTP client
const mockRpcCall = mock(() => Promise.resolve({
create_time: Date.now(),
transaction: '123',
state: 1,
receivers: null
}));
payme['httpClient'].rpcCall = mockRpcCall;
const result = await payme.createTransaction({
id: '5305e3bab097f420a62ced0b',
time: Date.now(),
amount: 500000,
account: { order_id: 'ORD-123' }
});
expect(result.state).toBe(1);
expect(result.transaction).toBe('123');
expect(mockRpcCall).toHaveBeenCalled();
});
});Testing Validation
typescript
import { describe, test, expect } from 'bun:test';
import { Validator, ValidationError } from '@joyida/payme';
describe('Validator', () => {
test('should validate amount', () => {
expect(() => Validator.validateAmount(500000)).not.toThrow();
expect(() => Validator.validateAmount(-100)).toThrow(ValidationError);
expect(() => Validator.validateAmount(0)).toThrow(ValidationError);
});
test('should validate card number', () => {
expect(() => Validator.validateCardNumber('8600069195406311')).not.toThrow();
expect(() => Validator.validateCardNumber('123')).toThrow(ValidationError);
});
test('should validate card expiry', () => {
expect(() => Validator.validateCardExpiry('0399')).not.toThrow();
expect(() => Validator.validateCardExpiry('1323')).toThrow(ValidationError);
});
});Testing Error Handling
typescript
import { describe, test, expect } from 'bun:test';
import { PaymeError, ValidationError } from '@joyida/payme';
describe('Error Handling', () => {
test('should handle PaymeError', async () => {
const payme = new PaymeMerchant({
merchantId: 'invalid',
secretKey: 'invalid'
});
try {
await payme.checkPerformTransaction({
amount: 500000,
account: { order_id: 'ORD-123' }
});
expect(true).toBe(false); // Should not reach here
} catch (error) {
expect(error).toBeInstanceOf(PaymeError);
expect(error.code).toBe(-32504);
}
});
test('should handle ValidationError', () => {
expect(() => {
Validator.validateAmount(-100);
}).toThrow(ValidationError);
});
});Integration Testing
Testing Payment Flow
typescript
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { PaymeMerchant, TransactionStates } from '@joyida/payme';
import { Database } from 'bun:sqlite';
describe('Payment Flow Integration', () => {
let db: Database;
let payme: PaymeMerchant;
beforeAll(() => {
// Setup test database
db = new Database(':memory:');
db.run(`
CREATE TABLE payme_transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
payme_id TEXT UNIQUE NOT NULL,
order_id TEXT NOT NULL,
amount INTEGER NOT NULL,
state INTEGER NOT NULL DEFAULT 1,
create_time INTEGER NOT NULL
)
`);
// Setup Payme client
payme = new PaymeMerchant({
merchantId: process.env.PAYME_MERCHANT_ID_TEST!,
secretKey: process.env.PAYME_SECRET_KEY_TEST!,
baseURL: 'https://checkout.test.paycom.uz/api'
});
});
afterAll(() => {
db.close();
});
test('should complete payment flow', async () => {
const orderId = `ORD-${Date.now()}`;
const amount = 500000;
const paymeId = generatePaymeId();
// 1. Check if payment is possible
const check = await payme.checkPerformTransaction({
amount,
account: { order_id: orderId }
});
expect(check.allow).toBe(true);
// 2. Create transaction
const created = await payme.createTransaction({
id: paymeId,
time: Date.now(),
amount,
account: { order_id: orderId }
});
expect(created.state).toBe(1);
// 3. Save to database
db.run(`
INSERT INTO payme_transactions
(payme_id, order_id, amount, state, create_time)
VALUES (?, ?, ?, ?, ?)
`, [paymeId, orderId, amount, created.state, created.create_time]);
// 4. Perform transaction
const performed = await payme.performTransaction({
id: paymeId
});
expect(performed.state).toBe(TransactionStates.COMPLETED);
// 5. Verify in database
const dbTransaction = db.query(`
SELECT * FROM payme_transactions WHERE payme_id = ?
`).get(paymeId);
expect(dbTransaction).toBeDefined();
expect(dbTransaction.order_id).toBe(orderId);
});
});Testing Card Tokenization
typescript
import { describe, test, expect } from 'bun:test';
import { PaymeSubscribe } from '@joyida/payme';
describe('Card Tokenization', () => {
test('should tokenize card', async () => {
const subscribeClient = new PaymeSubscribe({
merchantId: process.env.PAYME_MERCHANT_ID_TEST!
}, 'client');
// Create card token
const created = await subscribeClient.cardsCreate({
card: {
number: '8600069195406311',
expire: '0399'
},
save: true
});
expect(created.card.token).toBeDefined();
expect(created.card.verify).toBe(false);
// Request SMS code
const sms = await subscribeClient.cardsGetVerifyCode({
token: created.card.token
});
expect(sms.sent).toBe(true);
expect(sms.phone).toBeDefined();
// Verify card (use test code)
const verified = await subscribeClient.cardsVerify({
token: created.card.token,
code: '666666' // Test code
});
expect(verified.card.verify).toBe(true);
});
});Manual Testing
Test Checklist
- [ ] Create transaction
- [ ] Perform transaction
- [ ] Cancel transaction before perform
- [ ] Cancel transaction after perform (refund)
- [ ] Check transaction status
- [ ] Get statement
- [ ] Create card token
- [ ] Verify card
- [ ] Check card status
- [ ] Remove card token
- [ ] Create receipt
- [ ] Pay receipt
- [ ] Send receipt SMS
- [ ] Cancel receipt
- [ ] Get receipt details
Test Script
typescript
import { PaymeMerchant, PaymeSubscribe } from '@joyida/payme';
async function runTests() {
const payme = new PaymeMerchant({
merchantId: process.env.PAYME_MERCHANT_ID_TEST!,
secretKey: process.env.PAYME_SECRET_KEY_TEST!,
baseURL: 'https://checkout.test.paycom.uz/api'
});
console.log('🧪 Running Payme integration tests...\n');
// Test 1: Check perform transaction
console.log('1️⃣ Testing CheckPerformTransaction...');
try {
const check = await payme.checkPerformTransaction({
amount: 500000,
account: { order_id: 'TEST-001' }
});
console.log('✅ CheckPerformTransaction:', check.allow);
} catch (error) {
console.error('❌ CheckPerformTransaction failed:', error.message);
}
// Test 2: Create transaction
console.log('\n2️⃣ Testing CreateTransaction...');
const paymeId = generatePaymeId();
try {
const created = await payme.createTransaction({
id: paymeId,
time: Date.now(),
amount: 500000,
account: { order_id: 'TEST-001' }
});
console.log('✅ CreateTransaction:', created.state);
} catch (error) {
console.error('❌ CreateTransaction failed:', error.message);
}
// Test 3: Perform transaction
console.log('\n3️⃣ Testing PerformTransaction...');
try {
const performed = await payme.performTransaction({
id: paymeId
});
console.log('✅ PerformTransaction:', performed.state);
} catch (error) {
console.error('❌ PerformTransaction failed:', error.message);
}
// Test 4: Check transaction
console.log('\n4️⃣ Testing CheckTransaction...');
try {
const status = await payme.checkTransaction({
id: paymeId
});
console.log('✅ CheckTransaction:', status.state);
} catch (error) {
console.error('❌ CheckTransaction failed:', error.message);
}
console.log('\n✅ All tests completed!');
}
runTests();Performance Testing
Load Testing
typescript
import { PaymeMerchant } from '@joyida/payme';
async function loadTest(concurrency: number, requests: number) {
const payme = new PaymeMerchant({
merchantId: process.env.PAYME_MERCHANT_ID_TEST!,
secretKey: process.env.PAYME_SECRET_KEY_TEST!
});
const startTime = Date.now();
const promises = [];
for (let i = 0; i < requests; i++) {
promises.push(
payme.checkPerformTransaction({
amount: 500000,
account: { order_id: `TEST-${i}` }
})
);
// Control concurrency
if (promises.length >= concurrency) {
await Promise.all(promises);
promises.length = 0;
}
}
// Wait for remaining requests
if (promises.length > 0) {
await Promise.all(promises);
}
const endTime = Date.now();
const duration = endTime - startTime;
const rps = (requests / duration) * 1000;
console.log(`Completed ${requests} requests in ${duration}ms`);
console.log(`Average: ${rps.toFixed(2)} requests/second`);
}
// Run load test
loadTest(10, 100); // 10 concurrent requests, 100 totalBest Practices
✅ Do's
- Use test environment for development
- Test all error scenarios
- Test with different card numbers
- Test timeout handling
- Test idempotency
- Test database integration
- Run integration tests before deployment
❌ Don'ts
- Don't test in production
- Don't skip error testing
- Don't ignore timeout tests
- Don't forget to test cancellation
- Don't skip validation tests
Next Steps
- Learn about Payment Flow
- Explore Error Handling
- Check Complete Examples
- See Database Integration