Skip to content

Basic Payment Example

Complete example of processing a payment using Payme Merchant API.

Overview

This example demonstrates:

  • Checking if payment is possible
  • Creating a transaction
  • Performing the transaction
  • Handling errors
  • Database integration

Prerequisites

bash
bun add @joyida/payme

Environment Setup

Create .env file:

bash
PAYME_MERCHANT_ID=your_merchant_id
PAYME_SECRET_KEY=your_secret_key
DATABASE_URL=./app.db

Database Schema

sql
-- Create transactions table
CREATE TABLE IF NOT EXISTS 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,
  reason INTEGER,
  create_time INTEGER NOT NULL,
  perform_time INTEGER DEFAULT 0,
  cancel_time INTEGER DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Create orders table
CREATE TABLE IF NOT EXISTS orders (
  id TEXT PRIMARY KEY,
  user_id INTEGER NOT NULL,
  amount INTEGER NOT NULL,
  status TEXT DEFAULT 'pending',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Create indexes
CREATE INDEX idx_payme_transactions_payme_id ON payme_transactions(payme_id);
CREATE INDEX idx_payme_transactions_order_id ON payme_transactions(order_id);
CREATE INDEX idx_orders_user_id ON orders(user_id);

Complete Implementation

typescript
import { PaymeMerchant, TransactionStates, PaymeError, ValidationError, NetworkError } from '@joyida/payme';
import { Database } from 'bun:sqlite';

// Initialize Payme client
const payme = new PaymeMerchant({
  merchantId: process.env.PAYME_MERCHANT_ID!,
  secretKey: process.env.PAYME_SECRET_KEY!,
  timeout: 30000
});

// Initialize database
const db = new Database(process.env.DATABASE_URL!);

// Generate 24-character Payme transaction ID
function generatePaymeId(): string {
  const chars = '0123456789abcdef';
  let id = '';
  for (let i = 0; i < 24; i++) {
    id += chars[Math.floor(Math.random() * chars.length)];
  }
  return id;
}

// Check if order exists and get details
function getOrder(orderId: string) {
  const order = db.query(`
    SELECT * FROM orders WHERE id = ? AND status = 'pending'
  `).get(orderId);
  
  if (!order) {
    throw new Error('Order not found or already processed');
  }
  
  return order;
}

// Save transaction to database
function saveTransaction(data: {
  paymeId: string;
  orderId: string;
  amount: number;
  state: number;
  createTime: number;
}) {
  db.run(`
    INSERT INTO payme_transactions 
    (payme_id, order_id, amount, state, create_time)
    VALUES (?, ?, ?, ?, ?)
  `, [
    data.paymeId,
    data.orderId,
    data.amount,
    data.state,
    data.createTime
  ]);
}

// Update transaction state
function updateTransactionState(paymeId: string, state: number, performTime?: number) {
  if (performTime) {
    db.run(`
      UPDATE payme_transactions 
      SET state = ?, perform_time = ?, updated_at = CURRENT_TIMESTAMP
      WHERE payme_id = ?
    `, [state, performTime, paymeId]);
  } else {
    db.run(`
      UPDATE payme_transactions 
      SET state = ?, updated_at = CURRENT_TIMESTAMP
      WHERE payme_id = ?
    `, [state, paymeId]);
  }
}

// Update order status
function updateOrderStatus(orderId: string, status: string) {
  db.run(`
    UPDATE orders 
    SET status = ?, updated_at = CURRENT_TIMESTAMP
    WHERE id = ?
  `, [status, orderId]);
}

// Main payment processing function
async function processPayment(orderId: string) {
  console.log(`\n🔄 Processing payment for order: ${orderId}`);
  
  try {
    // 1. Get order details
    console.log('📋 Fetching order details...');
    const order = getOrder(orderId);
    console.log(`✅ Order found: ${order.amount} tiyin`);
    
    // 2. Check if payment is possible
    console.log('\n🔍 Checking if payment is possible...');
    const check = await payme.checkPerformTransaction({
      amount: order.amount,
      account: { order_id: orderId }
    });
    
    if (!check.allow) {
      throw new Error('Payment not allowed');
    }
    console.log('✅ Payment allowed');
    
    // 3. Create transaction
    console.log('\n📝 Creating transaction...');
    const paymeId = generatePaymeId();
    console.log(`Generated Payme ID: ${paymeId}`);
    
    const created = await payme.createTransaction({
      id: paymeId,
      time: Date.now(),
      amount: order.amount,
      account: { order_id: orderId }
    });
    
    console.log(`✅ Transaction created: ${created.transaction}`);
    console.log(`   State: ${created.state} (created)`);
    console.log(`   Create time: ${new Date(created.create_time).toISOString()}`);
    
    // 4. Save to database
    console.log('\n💾 Saving to database...');
    saveTransaction({
      paymeId,
      orderId,
      amount: order.amount,
      state: created.state,
      createTime: created.create_time
    });
    updateOrderStatus(orderId, 'awaiting_payment');
    console.log('✅ Saved to database');
    
    // 5. Perform transaction (complete payment)
    console.log('\n💳 Performing transaction...');
    const performed = await payme.performTransaction({
      id: paymeId
    });
    
    console.log(`✅ Transaction performed: ${performed.transaction}`);
    console.log(`   State: ${performed.state} (completed)`);
    console.log(`   Perform time: ${new Date(performed.perform_time).toISOString()}`);
    
    // 6. Update database
    console.log('\n💾 Updating database...');
    updateTransactionState(paymeId, performed.state, performed.perform_time);
    updateOrderStatus(orderId, 'paid');
    console.log('✅ Database updated');
    
    // 7. Check final status
    console.log('\n🔍 Checking final status...');
    const status = await payme.checkTransaction({
      id: paymeId
    });
    
    console.log('✅ Final status:');
    console.log(`   State: ${status.state}`);
    console.log(`   Created: ${new Date(status.create_time).toISOString()}`);
    console.log(`   Performed: ${new Date(status.perform_time).toISOString()}`);
    
    if (status.state === TransactionStates.COMPLETED) {
      console.log('\n🎉 Payment successful!');
      return {
        success: true,
        paymeId,
        transactionId: status.transaction,
        amount: order.amount
      };
    } else {
      throw new Error(`Unexpected transaction state: ${status.state}`);
    }
    
  } catch (error) {
    console.error('\n❌ Payment failed!');
    
    if (error instanceof ValidationError) {
      console.error('Validation error:', error.message);
      throw new Error(`Invalid input: ${error.message}`);
      
    } else if (error instanceof PaymeError) {
      console.error('Payme error:', error.code, error.message);
      
      switch (error.code) {
        case -32504:
          throw new Error('Payment system configuration error');
        case -31001:
          throw new Error('Invalid payment amount');
        case -31003:
          throw new Error('Transaction not found');
        case -31050:
          throw new Error('Order not found');
        case -32400:
          throw new Error('Payment system temporarily unavailable');
        default:
          throw new Error(`Payment error: ${error.message}`);
      }
      
    } else if (error instanceof NetworkError) {
      console.error('Network error:', error.message);
      if (error.timeout) {
        throw new Error(`Request timed out after ${error.timeout}ms`);
      } else {
        throw new Error('Network connection error');
      }
      
    } else {
      console.error('Unknown error:', error);
      throw error;
    }
  }
}

// Example usage
async function main() {
  // Create a test order
  const orderId = `ORD-${Date.now()}`;
  const amount = 500000; // 5000 UZS in tiyin
  
  db.run(`
    INSERT INTO orders (id, user_id, amount, status)
    VALUES (?, ?, ?, ?)
  `, [orderId, 1, amount, 'pending']);
  
  console.log(`Created test order: ${orderId}`);
  
  // Process payment
  try {
    const result = await processPayment(orderId);
    console.log('\n✅ Payment result:', result);
  } catch (error) {
    console.error('\n❌ Payment error:', error.message);
    process.exit(1);
  }
}

// Run example
main();

Running the Example

bash
# Install dependencies
bun install

# Set environment variables
export PAYME_MERCHANT_ID=your_merchant_id
export PAYME_SECRET_KEY=your_secret_key

# Run the example
bun run example.ts

Expected Output

Created test order: ORD-1234567890

🔄 Processing payment for order: ORD-1234567890
📋 Fetching order details...
✅ Order found: 500000 tiyin

🔍 Checking if payment is possible...
✅ Payment allowed

📝 Creating transaction...
Generated Payme ID: 5305e3bab097f420a62ced0b
✅ Transaction created: 5123
   State: 1 (created)
   Create time: 2024-01-15T10:30:00.000Z

💾 Saving to database...
✅ Saved to database

💳 Performing transaction...
✅ Transaction performed: 5123
   State: 2 (completed)
   Perform time: 2024-01-15T10:30:01.000Z

💾 Updating database...
✅ Database updated

🔍 Checking final status...
✅ Final status:
   State: 2
   Created: 2024-01-15T10:30:00.000Z
   Performed: 2024-01-15T10:30:01.000Z

🎉 Payment successful!

✅ Payment result: {
  success: true,
  paymeId: '5305e3bab097f420a62ced0b',
  transactionId: '5123',
  amount: 500000
}

Error Handling Examples

Invalid Amount

typescript
// This will throw ValidationError
await payme.createTransaction({
  id: paymeId,
  time: Date.now(),
  amount: -100, // ❌ Negative amount
  account: { order_id: orderId }
});

// Output:
// ❌ Payment failed!
// Validation error: Amount must be a positive integer

Order Not Found

typescript
// This will throw PaymeError with code -31050
await payme.checkPerformTransaction({
  amount: 500000,
  account: { order_id: 'NON_EXISTENT' } // ❌ Order doesn't exist
});

// Output:
// ❌ Payment failed!
// Payme error: -31050 Order not found

Network Timeout

typescript
// This will throw NetworkError
const payme = new PaymeMerchant({
  merchantId: process.env.PAYME_MERCHANT_ID!,
  secretKey: process.env.PAYME_SECRET_KEY!,
  timeout: 1 // ❌ Very short timeout
});

// Output:
// ❌ Payment failed!
// Network error: Request timed out after 1ms

Next Steps

Released under MIT License.