Skip to main content

Payout Flow

Send money to customers and vendors using the two-step intent flow.

import { ZenPays } from '@zenxdigitalholdings/zenpays'

async function payoutWithIntent() {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

// Step 1: Create payout intent with basic details
const intent = await zenpays.payouts.createPayoutIntent({
amount: 10000,
currency: 'INR',
country: 'IN',
beneficiaryName: 'John Doe',
beneficiaryEmail: 'john@example.com',
purpose: 'SALARY',
})

console.log('Intent created:', intent.intentId)
console.log('Status:', intent.status) // 'requires_confirmation'
console.log('Expires at:', intent.expiresAt)

// The response tells you exactly what fields to collect
console.log('Required fields:')
intent.requiredFields?.forEach(field => {
console.log(` - ${field.label} (${field.fieldName}) [${field.type}]`)
})

// Step 2: Confirm with the required fields
const result = await zenpays.payouts.confirmPayoutIntent(intent.intentId, {
beneficiaryAccount: '1234567890',
beneficiaryIfsc: 'HDFC0001234',
})

console.log('Payout confirmed:', result.payoutId)
console.log('Status:', result.status) // 'processing'

// Step 3: Poll for completion
const poll = async (id: string): Promise<void> => {
const current = await zenpays.payouts.getPayoutIntent(id)

if (current.status === 'succeeded') {
console.log('Payout completed!')
return
}
if (current.status === 'failed') {
console.log('Payout failed:', current.failureReason)
return
}

console.log('Status:', current.status)
await new Promise(r => setTimeout(r, 5000))
return poll(id)
}

await poll(intent.intentId)
}

// Crypto payout example
async function cryptoPayout() {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

// Step 1: Create intent for crypto (USDT)
const intent = await zenpays.payouts.createPayoutIntent({
amount: 500,
currency: 'USDT',
beneficiaryName: 'Crypto Wallet',
})

// For crypto, requiredFields will include "chain" and "walletAddress"
console.log(intent.requiredFields)
// [{ fieldName: "chain", type: "select", options: ["ethereum", "tron", ...] },
// { fieldName: "walletAddress", type: "text" }]

// Step 2: Confirm with chain and address
const result = await zenpays.payouts.confirmPayoutIntent(intent.intentId, {
chain: 'tron',
walletAddress: 'TXyz123abc...',
})

console.log('Crypto payout processing:', result.payoutId)
}

payoutWithIntent()

Legacy Direct Payout

Deprecated

The direct payout API is deprecated. Use the Payout Intent flow above instead.

import { ValidationError, ZenPays } from '@zenxdigitalholdings/zenpays'

async function processPayout() {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

// 1. Check available payout methods
const methods = await zenpays.payouts.getMethods()
console.log('Available payout methods:', methods.map(m => m.type).join(', '))

// 2. Check wallet balance
const balance = await zenpays.wallet.getBalance('USD')
console.log('Available balance:', balance)

// 3. Create payout
const payout = await zenpays.payouts.create({
customerId: 'cust_xxx',
amount: 10000, // $100.00
currency: 'USD',
payoutMethod: 'bank_transfer',
beneficiaryDetails: {
name: 'John Doe',
email: 'john@example.com',
bankName: 'Chase Bank',
accountNumber: '123456789',
accountType: 'checking',
routingNumber: '021000021',
},
description: 'Withdrawal request',
idempotencyKey: 'payout_unique_123', // Prevent duplicates
})

console.log('Payout created:', payout.id)
console.log('Status:', payout.status)

// 4. Monitor payout status
const waitForPayout = async (payoutId: string): Promise<boolean> => {
const p = await zenpays.payouts.get(payoutId)

switch (p.status) {
case 'completed':
console.log('Payout completed!')
console.log('Processed at:', p.processedAt)
return true

case 'failed':
console.log('Payout failed:', p.failureReason)
return false

case 'processing':
console.log('Payout processing...')
break

default:
console.log('Payout status:', p.status)
}

await new Promise(resolve => setTimeout(resolve, 5000))
return waitForPayout(payoutId)
}

return waitForPayout(payout.id)
}

// Retry a failed payout
async function retryFailedPayout(payoutId: string) {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

const payout = await zenpays.payouts.get(payoutId)

if (payout.status !== 'failed') {
console.log('Payout is not failed, cannot retry')
return
}

console.log('Retrying payout:', payoutId)
const retried = await zenpays.payouts.retry(payoutId)
console.log('Retry status:', retried.status)
}

// Get customer payouts
async function getCustomerPayouts(customerId: string) {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

const { data, total } = await zenpays.payouts.listByCustomer(customerId, {
limit: 10,
})

console.log(`Found ${total} payouts for customer ${customerId}:`)
data.forEach((p) => {
console.log(` ${p.id}: ${p.amount} ${p.currency} - ${p.status}`)
})
}

processPayout()

Cancel a Payout

Cancel a payout that hasn't started processing yet.

import { ZenPays } from '@zenxdigitalholdings/zenpays'

async function cancelPayout(payoutId: string) {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

// Check current status
const payout = await zenpays.payouts.get(payoutId)

if (payout.status !== 'pending') {
console.log(`Cannot cancel — payout is already ${payout.status}`)
return
}

// Cancel the payout
const cancelled = await zenpays.payouts.cancel(payoutId, 'Customer requested cancellation')
console.log('Cancelled:', cancelled.status) // 'cancelled'
}

cancelPayout('pay_xxx')

Batch Payout Flow

Send payouts to multiple beneficiaries in a single batch.

import { ZenPays } from '@zenxdigitalholdings/zenpays'

async function processBatchPayout() {
const zenpays = new ZenPays({
apiKey: process.env.ZENPAYS_API_KEY!,
})

// 1. Create batch payout
const batch = await zenpays.payouts.createBatch({
items: [
{
beneficiaryName: 'Alice Smith',
beneficiaryAccount: '1234567890',
beneficiaryIfsc: 'HDFC0001234',
amount: 5000,
currency: 'INR',
payoutType: 'imps',
purpose: 'SALARY',
},
{
beneficiaryName: 'Bob Johnson',
beneficiaryVpa: 'bob@upi',
amount: 3000,
currency: 'INR',
payoutType: 'upi',
purpose: 'COMMISSION',
},
{
beneficiaryName: 'Carol Williams',
beneficiaryAccount: '9876543210',
beneficiaryIfsc: 'ICIC0004567',
amount: 7000,
currency: 'INR',
payoutType: 'neft',
purpose: 'PAYOUT',
},
],
currency: 'INR',
webhookUrl: 'https://example.com/webhooks/batch',
})

console.log('Batch created:', batch.batchId)
console.log('Total amount:', batch.totalAmount, batch.currency)
console.log('Estimated fees:', batch.estimatedFees)
console.log('Status:', batch.status) // 'validated'

// 2. Confirm the batch to start processing
const confirmed = await zenpays.payouts.confirmBatch(batch.batchId)
console.log('Processing started:', confirmed.status) // 'processing'

// 3. Poll for completion
const waitForBatch = async (batchId: string): Promise<void> => {
const status = await zenpays.payouts.getBatch(batchId)
console.log(`Progress: ${status.progressPercentage}% (${status.successfulPayouts} succeeded, ${status.failedPayouts} failed)`)

if (status.status === 'completed' || status.status === 'partially_completed') {
console.log('Batch finished!')

// 4. Check individual results
const { data: payouts } = await zenpays.payouts.getBatchPayouts(batchId)
payouts.forEach(p => {
console.log(` ${p.id}: ${p.amount} ${p.currency}${p.status}`)
})

// 5. Check for failures
const { data: failed } = await zenpays.payouts.getBatchPayouts(batchId, { status: 'failed' })
if (failed.length > 0) {
console.log(`${failed.length} payouts failed:`)
failed.forEach(p => console.log(` ${p.id}: ${p.failureReason}`))
}
return
}

await new Promise(resolve => setTimeout(resolve, 5000))
return waitForBatch(batchId)
}

await waitForBatch(batch.batchId)
}

processBatchPayout()