Skip to content

Payment Flow Guide

Understanding the complete payment flow is crucial for successful Payme integration.

Overview

The Payme payment flow consists of several steps:

  1. CheckPerformTransaction (optional but recommended) - Validate the order
  2. CreateTransaction - Lock the order amount
  3. PerformTransaction - Complete the payment
  4. CheckTransaction - Verify final status

Transaction Flow Sequence

Step-by-Step Flow

Step 1: Check if Payment is Possible

Before creating a transaction, verify that the payment can be processed:

typescript
import { PaymeMerchant } from '@joyida/payme';

const payme = new PaymeMerchant({
  merchantId: process.env.PAYME_MERCHANT_ID!,
  secretKey: process.env.PAYME_SECRET_KEY!,
});

// Check if payment is allowed
const check = await payme.checkPerformTransaction({
  amount: 500000, // 5000 UZS in tiyin
  account: { order_id: 'ORD-123' }
});

if (!check.allow) {
  throw new Error('Payment not allowed');
}

console.log('✅ Payment can be processed');

What happens:

  • Validates account exists in your system
  • Checks if amount matches the order
  • Verifies all systems are operational
  • Does NOT create a transaction

Step 2: Create Transaction

Create a transaction to lock the order:

typescript
const paymeId = '5305e3bab097f420a62ced0b'; // 24-char ID from Payme

const created = await payme.createTransaction({
  id: paymeId,
  time: Date.now(),
  amount: 500000,
  account: { order_id: 'ORD-123' }
});

console.log('Transaction created:', created.transaction);
console.log('State:', created.state); // 1 (created)
console.log('Create time:', created.create_time);

What happens:

  • Order is locked (customer cannot modify)
  • Transaction is saved to your database
  • Order status changes to "awaiting payment"
  • Transaction expires after 12 hours if not performed

Important:

  • Save the paymeId to your database
  • Store the transaction state
  • Set order status to "pending payment"

Step 3: Perform Transaction

Complete the payment by transferring money:

typescript
const performed = await payme.performTransaction({
  id: paymeId
});

console.log('Payment completed:', performed.transaction);
console.log('State:', performed.state); // 2 (completed)
console.log('Perform time:', performed.perform_time);

What happens:

  • Money is transferred to merchant account
  • Order is marked as paid
  • Goods/services can be delivered
  • Transaction cannot be cancelled (only refunded)

Important:

  • Update order status to "paid"
  • Trigger fulfillment process
  • Send confirmation to customer

Step 4: Check Transaction Status

Verify the transaction status:

typescript
const status = await payme.checkTransaction({
  id: paymeId
});

console.log('Current state:', status.state);
console.log('Create time:', status.create_time);
console.log('Perform time:', status.perform_time);
console.log('Cancel time:', status.cancel_time);

Transaction States

StateNameDescription
1CreatedTransaction created, awaiting payment
2CompletedPayment successful, money transferred
-1CancelledCancelled before payment
-2RefundedCancelled after payment (refund)
typescript
import { TransactionStates } from '@joyida/payme';

if (status.state === TransactionStates.COMPLETED) {
  console.log('✅ Payment successful');
} else if (status.state === TransactionStates.CREATED) {
  console.log('⏳ Awaiting payment');
} else if (status.state === TransactionStates.CANCELLED) {
  console.log('❌ Payment cancelled');
} else if (status.state === TransactionStates.REFUNDED) {
  console.log('💰 Payment refunded');
}

Cancellation Flow

Cancel Before Payment

If transaction is in state 1 (created):

typescript
import { CancelReasons } from '@joyida/payme';

const cancelled = await payme.cancelTransaction({
  id: paymeId,
  reason: CancelReasons.TIMEOUT // or other reason
});

console.log('State:', cancelled.state); // -1 (cancelled)
console.log('Cancel time:', cancelled.cancel_time);

What happens:

  • Order is unlocked
  • Inventory is restored
  • Customer can modify order again
  • No money was transferred

Cancel After Payment (Refund)

If transaction is in state 2 (completed):

typescript
const refunded = await payme.cancelTransaction({
  id: paymeId,
  reason: CancelReasons.REFUND
});

console.log('State:', refunded.state); // -2 (refund)
console.log('Cancel time:', refunded.cancel_time);

What happens:

  • Refund process is initiated
  • Money will be returned to customer
  • Order status changes to "refunded"

Important:

  • Cannot cancel if goods/services already delivered
  • Refund may take 3-5 business days

Cancel Reasons

typescript
import { CancelReasons } from '@joyida/payme';

// Available reasons:
CancelReasons.RECIPIENT_NOT_FOUND  // 1
CancelReasons.DEBIT_ERROR          // 2
CancelReasons.EXECUTION_ERROR      // 3
CancelReasons.TIMEOUT              // 4 (12 hours)
CancelReasons.REFUND               // 5
CancelReasons.UNKNOWN              // 10

Complete Payment Example

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

const payme = new PaymeMerchant({
  merchantId: process.env.PAYME_MERCHANT_ID!,
  secretKey: process.env.PAYME_SECRET_KEY!,
});

const db = new Database('app.db');

async function processPayment(orderId: string, amount: number) {
  try {
    // 1. Check if payment is allowed
    const check = await payme.checkPerformTransaction({
      amount,
      account: { order_id: orderId }
    });
    
    if (!check.allow) {
      throw new Error('Payment not allowed');
    }
    
    // 2. Create transaction
    const paymeId = generatePaymeId(); // Your function to generate 24-char ID
    const created = await payme.createTransaction({
      id: paymeId,
      time: Date.now(),
      amount,
      account: { order_id: orderId }
    });
    
    // 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]
    );
    
    console.log('✅ Transaction created:', created.transaction);
    
    // 4. Perform transaction
    const performed = await payme.performTransaction({
      id: paymeId
    });
    
    // 5. Update database
    db.run(
      `UPDATE payme_transactions 
       SET state = ?, perform_time = ? 
       WHERE payme_id = ?`,
      [performed.state, performed.perform_time, paymeId]
    );
    
    if (performed.state === TransactionStates.COMPLETED) {
      console.log('✅ Payment successful!');
      
      // 6. Update order status
      db.run(
        `UPDATE orders SET status = 'paid' WHERE id = ?`,
        [orderId]
      );
      
      // 7. Trigger fulfillment
      await fulfillOrder(orderId);
    }
    
    return performed;
    
  } catch (error) {
    if (error instanceof PaymeError) {
      console.error('Payme error:', error.code, error.message);
      
      // Handle specific errors
      if (error.code === -31003) {
        console.error('Transaction not found');
      } else if (error.code === -31008) {
        console.error('Cannot perform operation');
      }
    }
    throw error;
  }
}

Transaction Timeout

Transactions automatically expire after 12 hours (43,200,000 milliseconds) if not performed:

typescript
const TRANSACTION_TIMEOUT = 12 * 60 * 60 * 1000; // 12 hours

// Check if transaction expired
const isExpired = (createTime: number) => {
  return Date.now() - createTime > TRANSACTION_TIMEOUT;
};

if (isExpired(created.create_time)) {
  console.log('⚠️ Transaction expired, will be auto-cancelled');
}

Best Practices

✅ Do's

  1. Always save paymeId to your database
  2. Store transaction state and timestamps
  3. Implement idempotency (same request = same response)
  4. Handle all transaction states
  5. Set order status based on transaction state
  6. Implement proper error handling
  7. Log all transactions for reconciliation

❌ Don'ts

  1. Don't create transaction without checking first
  2. Don't perform transaction twice
  3. Don't cancel after goods/services delivered
  4. Don't forget to update order status
  5. Don't lose the paymeId
  6. Don't ignore transaction timeouts

Error Handling

typescript
import { PaymeError, ValidationError, NetworkError } from '@joyida/payme';

try {
  await payme.createTransaction(params);
} catch (error) {
  if (error instanceof PaymeError) {
    // Payme API error
    console.error('Payme error:', error.code, error.message);
  } else if (error instanceof ValidationError) {
    // Input validation error
    console.error('Validation error:', error.message);
  } else if (error instanceof NetworkError) {
    // Network or timeout error
    console.error('Network error:', error.message);
  }
}

Next Steps

Released under MIT License.