> ## Documentation Index
> Fetch the complete documentation index at: https://paystack-sdk.efobi.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Transfers Guide

> Send money to bank accounts with the Paystack TypeScript SDK

The Transfer API allows you to send money from your Paystack balance to bank accounts. This guide covers how to initiate and manage transfers.

## Overview

Transfers enable you to send funds to your customers, vendors, or employees. The process involves:

1. Creating transfer recipients
2. Initiating transfers to recipients
3. Finalizing transfers with OTP (when required)
4. Tracking transfer status

<Warning>
  Transfers require sufficient balance in your Paystack account. Ensure your account is funded before initiating transfers.
</Warning>

## Prerequisites

Before sending transfers, you need:

* A funded Paystack balance
* Transfer recipients created (see [Transfer Recipients API](/api-reference/recipient))
* OTP enabled (for first-time transfers)

## Initiate a Transfer

Send money to a transfer recipient.

<Steps>
  <Step title="Import and initialize">
    ```typescript theme={null}
    import { Paystack } from '@efobi/paystack';

    const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);
    ```
  </Step>

  <Step title="Initiate the transfer">
    ```typescript theme={null}
    const { data, error } = await paystack.transfer.initiate({
      source: 'balance',
      amount: 50000, // Amount in kobo (₦500.00)
      recipient: 'RCP_xxxxxxxxxx', // Recipient code
      reason: 'Payment for services',
      reference: `TRF-${Date.now()}`,
      currency: 'NGN'
    });

    if (error) {
      // Handle OTP requirement
      if (error.data?.code === 'REQUIRES_OTP') {
        console.log('OTP required. Check your email/phone.');
        console.log('Next step:', error.data?.meta?.nextStep);
        return;
      }
      
      console.error('Transfer failed:', error);
      return;
    }

    console.log('Transfer initiated!');
    console.log('Transfer code:', data.data.transfer_code);
    console.log('Status:', data.data.status);
    ```
  </Step>

  <Step title="Handle OTP (if required)">
    ```typescript theme={null}
    // If OTP is required, finalize with the code
    const { data: finalData, error: finalError } = 
      await paystack.transfer.finalize({
        transfer_code: data.data.transfer_code,
        otp: '123456' // OTP from email/SMS
      });

    if (finalData && finalData.data.status === 'success') {
      console.log('Transfer completed successfully!');
    }
    ```
  </Step>
</Steps>

### Transfer Parameters

<ParamField path="source" type="string" default="balance" required>
  Source of funds (currently only `balance` is supported)
</ParamField>

<ParamField path="amount" type="number" required>
  Amount to transfer in kobo (minimum: 1000 kobo = ₦10)
</ParamField>

<ParamField path="recipient" type="string" required>
  Transfer recipient code (starts with `RCP_`)
</ParamField>

<ParamField path="reason" type="string">
  Description of the transfer
</ParamField>

<ParamField path="reference" type="string" required>
  Unique transfer reference
</ParamField>

<ParamField path="currency" type="string" default="NGN">
  Currency code: `NGN`, `USD`, `GHS`, `ZAR`, `KES`, or `XOF`
</ParamField>

## Finalize Transfer with OTP

Complete a transfer that requires OTP verification.

```typescript theme={null}
const { data, error } = await paystack.transfer.finalize({
  transfer_code: 'TRF_xxxxxxxxxx',
  otp: '123456'
});

if (data && data.data.status === 'success') {
  console.log('Transfer finalized successfully!');
  console.log('Amount transferred:', data.data.amount / 100);
  console.log('Recipient:', data.data.recipient);
} else if (error) {
  console.error('OTP verification failed:', error.data?.message);
}
```

<Note>
  OTP is typically required for the first transfer or when transferring to new recipients. After successful verification, subsequent transfers may not require OTP.
</Note>

## Bulk Transfers

Initiate multiple transfers in a single request.

```typescript theme={null}
const { data, error } = await paystack.transfer.initiateBulk({
  source: 'balance',
  transfers: [
    {
      amount: 50000,
      recipient: 'RCP_xxxxxxxxxx',
      reference: `TRF-001-${Date.now()}`
    },
    {
      amount: 75000,
      recipient: 'RCP_yyyyyyyyyy',
      reference: `TRF-002-${Date.now()}`
    },
    {
      amount: 100000,
      recipient: 'RCP_zzzzzzzzzz',
      reference: `TRF-003-${Date.now()}`
    }
  ]
});

if (error && error.data?.code === 'REQUIRES_OTP') {
  console.log('Bulk transfer requires OTP');
  // Finalize with OTP
} else if (data) {
  console.log(`Initiated ${data.data.length} transfers`);
  
  data.data.forEach(transfer => {
    console.log(`${transfer.reference}: ${transfer.status}`);
  });
}
```

<Tip>
  Bulk transfers are processed asynchronously. Monitor transfer status using webhooks or by polling the transfer endpoints.
</Tip>

## List Transfers

Retrieve a paginated list of transfers.

```typescript theme={null}
const { data, error } = await paystack.transfer.list({
  perPage: 50,
  page: 1,
  status: 'success',
  from: '2024-01-01',
  to: '2024-12-31'
});

if (data) {
  console.log(`Found ${data.meta.total} transfers`);
  
  data.data.forEach(transfer => {
    console.log(`${transfer.reference}:`, {
      amount: transfer.amount / 100,
      status: transfer.status,
      recipient: transfer.recipient.name,
      createdAt: transfer.created_at
    });
  });
}
```

### List Parameters

<ParamField path="perPage" type="number" default="50">
  Number of transfers per page
</ParamField>

<ParamField path="page" type="number" default="1">
  Page number to retrieve
</ParamField>

<ParamField path="status" type="string">
  Filter by status: `pending`, `success`, `failed`, `reversed`
</ParamField>

<ParamField path="from" type="string">
  Start date (ISO 8601 format)
</ParamField>

<ParamField path="to" type="string">
  End date (ISO 8601 format)
</ParamField>

## Get Single Transfer

Fetch details of a specific transfer.

```typescript theme={null}
const { data, error } = await paystack.transfer.getTransferById(
  'TRF_xxxxxxxxxx' // Transfer code or ID
);

if (data) {
  console.log('Transfer details:', {
    amount: data.data.amount / 100,
    status: data.data.status,
    reference: data.data.reference,
    reason: data.data.reason,
    recipient: {
      name: data.data.recipient.name,
      accountNumber: data.data.recipient.details.account_number,
      bankName: data.data.recipient.details.bank_name
    },
    transferredAt: data.data.transferred_at
  });
}
```

## Verify Transfer

Confirm the status of a transfer by reference.

```typescript theme={null}
const reference = 'TRF-12345';

const { data, error } = await paystack.transfer.verify(reference);

if (data) {
  const transfer = data.data;
  
  if (transfer.status === 'success') {
    console.log('Transfer completed successfully!');
    await updatePaymentRecord(reference, 'completed');
  } else if (transfer.status === 'failed') {
    console.log('Transfer failed:', transfer.failures);
    await updatePaymentRecord(reference, 'failed');
  } else if (transfer.status === 'reversed') {
    console.log('Transfer was reversed');
    await updatePaymentRecord(reference, 'reversed');
  } else {
    console.log('Transfer is still pending');
  }
}
```

## Transfer Status

Transfers can have the following statuses:

* **pending** - Transfer is being processed
* **success** - Transfer completed successfully
* **failed** - Transfer failed (check `failures` field for reason)
* **reversed** - Transfer was reversed (funds returned to your balance)
* **processing** - Transfer is in progress

## Error Handling

Handle common transfer errors:

```typescript theme={null}
try {
  const { data, error } = await paystack.transfer.initiate({
    source: 'balance',
    amount: 50000,
    recipient: 'RCP_xxxxxxxxxx',
    reference: `TRF-${Date.now()}`
  });

  if (error) {
    // Handle specific error codes
    if (error.data?.code === 'REQUIRES_OTP') {
      return {
        requiresOtp: true,
        transferCode: error.data?.data?.transfer_code,
        message: 'Please enter the OTP sent to your email'
      };
    }

    if (error.data?.message?.includes('insufficient balance')) {
      return {
        success: false,
        message: 'Insufficient balance in your Paystack account'
      };
    }

    return { success: false, message: error.data?.message };
  }

  return { success: true, transfer: data.data };

} catch (err) {
  console.error('Unexpected error:', err);
  return { success: false, message: 'Transfer failed' };
}
```

## Best Practices

<Tip>
  1. **Validate recipient codes** before initiating transfers
  2. **Use unique references** for each transfer
  3. **Monitor your balance** before bulk transfers
  4. **Handle OTP gracefully** in your user interface
  5. **Store transfer records** in your database
  6. **Implement webhooks** to track transfer status in real-time
  7. **Test with small amounts** before going live
  8. **Set up notifications** for failed transfers
</Tip>

## Common Scenarios

### Payout to Vendor

```typescript theme={null}
async function payVendor(
  vendorId: string,
  amount: number,
  invoiceId: string
) {
  // Get vendor's recipient code from database
  const vendor = await db.vendors.findById(vendorId);
  
  const { data, error } = await paystack.transfer.initiate({
    source: 'balance',
    amount: amount * 100, // Convert to kobo
    recipient: vendor.recipientCode,
    reason: `Payment for invoice ${invoiceId}`,
    reference: `PAY-${invoiceId}-${Date.now()}`
  });

  if (error?.data?.code === 'REQUIRES_OTP') {
    // Store for later finalization
    await db.pendingTransfers.create({
      transferCode: error.data.data.transfer_code,
      vendorId,
      amount,
      invoiceId
    });
    
    return { requiresOtp: true, transferCode: error.data.data.transfer_code };
  }

  if (data && data.data.status === 'success') {
    await db.invoices.update(invoiceId, { status: 'paid' });
    await notifyVendor(vendorId, 'payment_sent', amount);
  }

  return { success: true, transfer: data.data };
}
```

### Salary Payments

```typescript theme={null}
async function processSalaryPayments(month: string, year: number) {
  const employees = await db.employees.findAll({ active: true });
  
  const transfers = employees.map(emp => ({
    amount: emp.salary * 100,
    recipient: emp.recipientCode,
    reference: `SAL-${emp.id}-${month}-${year}`
  }));

  const { data, error } = await paystack.transfer.initiateBulk({
    source: 'balance',
    transfers
  });

  if (error?.data?.code === 'REQUIRES_OTP') {
    console.log('Bulk salary payment requires OTP');
    await notifyAdmin('otp_required', { month, year });
    return;
  }

  if (data) {
    // Log all transfers
    for (const transfer of data.data) {
      await db.salaryPayments.create({
        employeeId: transfer.reference.split('-')[1],
        transferCode: transfer.transfer_code,
        amount: transfer.amount / 100,
        status: transfer.status,
        month,
        year
      });
    }
    
    console.log(`Processed ${data.data.length} salary payments`);
  }
}
```

### Refund Processing

```typescript theme={null}
async function processRefund(
  transactionId: string,
  refundAmount: number,
  reason: string
) {
  // Get customer's recipient code
  const transaction = await db.transactions.findById(transactionId);
  const customer = await db.customers.findByEmail(transaction.email);
  
  if (!customer.recipientCode) {
    // Create recipient first
    throw new Error('Customer recipient not set up');
  }

  const { data, error } = await paystack.transfer.initiate({
    source: 'balance',
    amount: refundAmount * 100,
    recipient: customer.recipientCode,
    reason: `Refund: ${reason}`,
    reference: `REF-${transactionId}-${Date.now()}`
  });

  if (data && data.data.status === 'success') {
    await db.refunds.create({
      transactionId,
      amount: refundAmount,
      transferCode: data.data.transfer_code,
      reason,
      status: 'completed'
    });
    
    await notifyCustomer(customer.email, 'refund_processed', {
      amount: refundAmount,
      reason
    });
  }

  return data?.data;
}
```

## Webhook Integration

Listen for transfer events in real-time:

```typescript theme={null}
paystack.webhook
  .on('transfer.success', (data) => {
    console.log('Transfer successful:', data.reference);
    // Update your records
    updateTransferStatus(data.reference, 'success');
  })
  .on('transfer.failed', (data) => {
    console.log('Transfer failed:', data.reference);
    // Handle failure
    updateTransferStatus(data.reference, 'failed');
    notifyAdminOfFailure(data);
  })
  .on('transfer.reversed', (data) => {
    console.log('Transfer reversed:', data.reference);
    // Handle reversal
    updateTransferStatus(data.reference, 'reversed');
  });
```

## Related Resources

* [Transfer Recipients API](/api-reference/recipient) - Create and manage recipients
* [Transfer API Reference](/api-reference/transfer) - Full API documentation
* [Webhooks Guide](/guides/webhooks) - Handle transfer events in real-time
* [Verification Guide](/guides/verification) - Verify bank account details
