Checkout Flow
Detailed documentation of the crypto payment checkout process from start to finish.
Overview
The checkout flow consists of several stages:
- Checkout creation
- Payment initiation
- Payment detection
- Confirmation tracking
- Merchant settlement
Checkout Lifecycle
Stage 1: Checkout Creation
API Call:
bash
POST /api/v1/checkout/cryptoRequest:
json
{
"amount": 100.00,
"currency": "USDT",
"network": "BSC",
"customer_email": "customer@example.com",
"reference": "ORDER-12345",
"callback_url": "https://yoursite.com/webhooks/payment",
"metadata": {
"custom_field": "value"
}
}Response:
json
{
"checkout_id": "checkout_xyz789",
"deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"amount": "100.00",
"currency": "USDT",
"network": "BSC",
"qr_code": "data:image/png;base64,iVBORw0KGgo...",
"status": "pending",
"expires_at": "2025-12-22T05:00:00Z",
"created_at": "2025-12-22T04:30:00Z"
}Stage 2: Payment Display
Display payment information to the customer with these key elements:
Required Information
- ✅ Deposit Address - Exact address to send to
- ✅ Amount - Precise amount to send
- ✅ Currency - USDT or USDC
- ✅ Network - BSC, ETH, POL, TRX, or SOL
- ✅ QR Code - For mobile wallet scanning
- ✅ Expiration Time - Countdown timer
Critical Warning
Always display a prominent warning about the network:
⚠️ IMPORTANT: Only send USDT on BSC network!
Sending on the wrong network will result in permanent loss of funds.Stage 3: Payment Detection
Rach monitors the deposit address for incoming transactions:
Detection Methods
- Blockchain Polling (every 30 seconds)
- Webhook Notifications (if Tatum configured)
Payment Detected Event
When a payment is detected, you'll receive a webhook:
json
{
"event": "payment.detected",
"checkout_id": "checkout_xyz789",
"txn_hash": "0x1234abc...",
"amount": "100.00",
"currency": "USDT",
"network": "BSC",
"confirmations": 0,
"status": "paid",
"timestamp": "2025-12-22T04:35:00Z"
}Stage 4: Confirmation Tracking
Network Requirements
| Network | Min Confirmations | Avg Time |
|---|---|---|
| BSC | 12 | ~36 seconds |
| Ethereum | 12 | ~3 minutes |
| Polygon | 128 | ~4 minutes |
| Tron | 20 | ~1 minute |
| Solana | 32 | ~20 seconds |
Confirmation Updates
You'll receive webhook updates as confirmations increase:
json
{
"event": "payment.confirming",
"checkout_id": "checkout_xyz789",
"confirmations": 6,
"required_confirmations": 12,
"status": "confirming"
}Fully Confirmed
json
{
"event": "payment.confirmed",
"checkout_id": "checkout_xyz789",
"txn_hash": "0x1234abc...",
"amount": "100.00",
"confirmations": 12,
"status": "confirmed",
"timestamp": "2025-12-22T04:36:00Z"
}Stage 5: Merchant Settlement
Once confirmed, the merchant balance is automatically credited:
Settlement Details:
- Amount credited: Exact payment amount
- Balance type: USDT or USDC
- Network: Same as payment network
- Instant: Credits immediately upon confirmation
Check Balance:
bash
GET /api/v1/balance/{merchant_id}Edge Cases
Underpayment
If customer sends less than the required amount:
- Status remains
pending - Customer can send additional payment
- Once total matches, payment proceeds
Overpayment
If customer sends more than required:
- Payment completes for the checkout amount
- Excess goes to merchant balance
- Merchant can refund manually if needed
Wrong Network
If customer sends on wrong network:
- Payment NOT detected by Rach
- Funds may be permanently lost
- Always display network warnings prominently
Expired Checkout
If payment arrives after expiration:
- Payment still detected
- Funds go to merchant balance
- Webhook sent with
expiredstatus - Manual handling required
Status Transitions
Implementation Example
javascript
// Complete checkout flow implementation
class CryptoCheckoutFlow {
constructor(rachPaymentService, orderService) {
this.rachPaymentService = rachPaymentService;
this.orderService = orderService;
}
async initiateCheckout(order) {
// 1. Create checkout
const checkout = await this.rachPaymentService.createCheckout({
amount: order.total,
currency: 'USDT',
network: 'BSC',
customer_email: order.customerEmail,
reference: order.id,
callback_url: `${process.env.BASE_URL}/webhooks/payment`
});
// 2. Store checkout_id with order
await this.orderService.update(order.id, {
checkout_id: checkout.checkout_id,
payment_status: 'pending',
expires_at: checkout.expires_at
});
// 3. Return payment details for display
return {
depositAddress: checkout.deposit_address,
qrCode: checkout.qr_code,
amount: checkout.amount,
currency: checkout.currency,
network: checkout.network,
expiresAt: checkout.expires_at
};
}
async handlePaymentWebhook(webhookPayload) {
const { event, checkout_id, reference } = webhookPayload;
switch (event) {
case 'payment.detected':
await this.orderService.update(reference, {
payment_status: 'detected',
txn_hash: webhookPayload.txn_hash
});
break;
case 'payment.confirmed':
await this.orderService.update(reference, {
payment_status: 'confirmed',
paid_at: new Date()
});
// Fulfill order
await this.orderService.fulfill(reference);
break;
case 'payment.expired':
await this.orderService.update(reference, {
payment_status: 'expired'
});
break;
}
}
}Best Practices
Checkout Flow Best Practices
- ✅ Always generate unique deposit addresses per checkout
- ✅ Store checkout_id with your order for tracking
- ✅ Display countdown timer for checkout expiration
- ✅ Show network prominently with warnings
- ✅ Implement webhook handlers for all events
- ✅ Handle partial payments gracefully
- ✅ Provide customer support for stuck payments
- ✅ Monitor for expired checkouts with payments
Troubleshooting
Payment Not Detected
- Verify customer sent to correct address
- Check transaction on block explorer
- Verify correct network used
- Check if enough confirmations
Payment Stuck
- Check blockchain congestion
- Verify transaction included in block
- Check confirmation count
- Contact support if needed
Wrong Amount
- Check for partial payments
- Verify decimals (USDT has 6 decimals on most networks)
- Check for multiple transactions
