Skip to content

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:

ParameterValue
Login (Phone)phone
Passwordqwerty
SMS Code666666

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 NumberExpiryComment
8600 0609 2109 084203/99SMS-info not connected
3333 3364 1580 465703/99Card expired
4444 4459 8745 907303/99Card blocked
8600 1434 1777 032303/99Unknown system error
8600 1343 0184 959603/9910s delay simulation (ends in error)
8600 4954 7331 647803/99Success card
8600 0691 9540 631103/99Success card

Test Scenarios

  1. Success Cards (8600 4954 7331 6478, 8600 0691 9540 6311) - Complete tokenization and payment successfully
  2. Expired Card (3333 3364 1580 4657) - Verify proper handling of expired cards
  3. Blocked Card (4444 4459 8745 9073) - Test blocked card error handling
  4. SMS Not Connected (8600 0609 2109 0842) - Test scenario when SMS verification is unavailable
  5. Delay Simulation (8600 1343 0184 9596) - Test timeout handling (10 second delay before error)
  6. 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 total

Best Practices

✅ Do's

  1. Use test environment for development
  2. Test all error scenarios
  3. Test with different card numbers
  4. Test timeout handling
  5. Test idempotency
  6. Test database integration
  7. Run integration tests before deployment

❌ Don'ts

  1. Don't test in production
  2. Don't skip error testing
  3. Don't ignore timeout tests
  4. Don't forget to test cancellation
  5. Don't skip validation tests

Next Steps

Released under MIT License.