Payment Flow Guide
Understanding the complete payment flow is crucial for successful Payme integration.
Overview
The Payme payment flow consists of several steps:
- CheckPerformTransaction (optional but recommended) - Validate the order
- CreateTransaction - Lock the order amount
- PerformTransaction - Complete the payment
- 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
paymeIdto 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
| State | Name | Description |
|---|---|---|
1 | Created | Transaction created, awaiting payment |
2 | Completed | Payment successful, money transferred |
-1 | Cancelled | Cancelled before payment |
-2 | Refunded | Cancelled 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 // 10Complete 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
- Always save
paymeIdto your database - Store transaction state and timestamps
- Implement idempotency (same request = same response)
- Handle all transaction states
- Set order status based on transaction state
- Implement proper error handling
- Log all transactions for reconciliation
❌ Don'ts
- Don't create transaction without checking first
- Don't perform transaction twice
- Don't cancel after goods/services delivered
- Don't forget to update order status
- Don't lose the
paymeId - 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
- Learn about Card Tokenization
- Explore Database Integration
- Check Error Handling
- See Complete Examples