> ## 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.

# Virtual Accounts Guide

> Create dedicated bank accounts for your customers with the Paystack TypeScript SDK

Dedicated Virtual Accounts (DVAs) allow you to assign unique bank account numbers to your customers. This enables direct bank transfers and automatic payment reconciliation.

## Overview

Virtual accounts provide:

* Unique account numbers for each customer
* Automatic payment reconciliation
* Support for multiple banks
* Real-time payment notifications via webhooks
* Custom split configurations

<Note>
  Virtual accounts are available in Nigeria (NGN) and Ghana (GHS). Contact Paystack for availability in other regions.
</Note>

## Create a Virtual Account

Assign a dedicated account number to a customer.

<Steps>
  <Step title="Initialize the SDK">
    ```typescript theme={null}
    import { Paystack } from '@efobi/paystack';

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

  <Step title="Create the virtual account">
    ```typescript theme={null}
    const { data, error } = await paystack.virtualAccount.create({
      customer: 'CUS_xxxxxxxxxx', // Customer code
      preferred_bank: 'wema-bank', // Optional: specific bank slug
      first_name: 'John',
      last_name: 'Doe',
      phone: '+2348012345678'
    });

    if (error) {
      console.error('Creation failed:', error);
      return;
    }

    console.log('Virtual account created!');
    console.log('Bank:', data.data.bank.name);
    console.log('Account Name:', data.data.account_name);
    console.log('Account Number:', data.data.account_number);
    console.log('Customer:', data.data.customer.email);
    ```
  </Step>

  <Step title="Display to customer">
    ```typescript theme={null}
    // Show the account details to your customer
    const accountInfo = {
      bank: data.data.bank.name,
      accountNumber: data.data.account_number,
      accountName: data.data.account_name
    };

    // Store in your database for future reference
    await db.virtualAccounts.create({
      customerId: customer.id,
      accountId: data.data.id,
      accountNumber: data.data.account_number,
      bankSlug: data.data.bank.slug
    });
    ```
  </Step>
</Steps>

### Create Parameters

<ParamField path="customer" type="string" required>
  Customer code or email address
</ParamField>

<ParamField path="preferred_bank" type="string">
  Slug of preferred bank provider (e.g., `wema-bank`, `titan-paystack`)
</ParamField>

<ParamField path="subaccount" type="string">
  Subaccount code for routing payments
</ParamField>

<ParamField path="split_code" type="string">
  Split code for payment splitting
</ParamField>

<ParamField path="first_name" type="string">
  Customer's first name (for account name generation)
</ParamField>

<ParamField path="last_name" type="string">
  Customer's last name (for account name generation)
</ParamField>

<ParamField path="phone" type="string">
  Customer's phone number
</ParamField>

## List Virtual Accounts

Retrieve all virtual accounts on your integration.

```typescript theme={null}
const { data, error } = await paystack.virtualAccount.list({
  perPage: 50,
  page: 1,
  active: true,
  currency: 'NGN'
});

if (data) {
  console.log(`Found ${data.meta.total} virtual accounts`);
  
  data.data.forEach(account => {
    console.log(`${account.account_number}:`, {
      customer: account.customer.email,
      bank: account.bank.name,
      active: account.active,
      assigned: account.assigned
    });
  });
}
```

### List Parameters

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

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

<ParamField path="active" type="boolean">
  Filter by active status
</ParamField>

<ParamField path="currency" type="string">
  Filter by currency: `NGN` or `GHS`
</ParamField>

## Fetch Single Virtual Account

Get details of a specific virtual account.

```typescript theme={null}
const accountId = '123456';

const { data, error } = await paystack.virtualAccount.fetch(accountId);

if (data) {
  console.log('Account details:', {
    bank: data.data.bank.name,
    accountNumber: data.data.account_number,
    accountName: data.data.account_name,
    customer: data.data.customer.email,
    active: data.data.active,
    currency: data.data.currency,
    splitConfig: data.data.split_config
  });
}
```

## Assign Virtual Account

Assign a dedicated virtual account to a customer.

```typescript theme={null}
const { data, error } = await paystack.virtualAccount.assign({
  email: 'customer@email.com',
  first_name: 'John',
  last_name: 'Doe',
  phone: '+2348012345678',
  preferred_bank: 'wema-bank',
  country: 'NG'
});

if (data && data.status) {
  console.log('Virtual account assigned successfully!');
} else if (error) {
  console.error('Assignment failed:', error.data?.message);
}
```

<Tip>
  Use the `assign` method when you want Paystack to handle customer creation automatically. Use `create` when you already have a customer code.
</Tip>

## Requery Virtual Account

Manually check for new transactions on a virtual account.

```typescript theme={null}
const { data, error } = await paystack.virtualAccount.requery({
  account_number: '1234567890',
  provider_slug: 'wema-bank'
});

if (data && data.status) {
  console.log('Account requeried successfully');
  console.log('Message:', data.message);
}
```

<Note>
  Requerying is useful when you suspect a payment was made but not automatically detected. Most payments are detected automatically.
</Note>

## Deactivate Virtual Account

Deactivate a virtual account when it's no longer needed.

```typescript theme={null}
const accountId = '123456';

const { data, error } = await paystack.virtualAccount.deactivate(accountId);

if (data) {
  console.log('Account deactivated');
  console.log('Active status:', data.data.active); // Should be false
  
  // Update your database
  await db.virtualAccounts.update(accountId, { active: false });
}
```

<Warning>
  Deactivating an account prevents it from receiving payments. This action cannot be undone through the API.
</Warning>

## Split Configuration

Add or remove split configurations from virtual accounts.

### Add Split

Route payments to multiple accounts automatically.

```typescript theme={null}
const { data, error } = await paystack.virtualAccount.addSplit({
  account_number: '1234567890',
  split_code: 'SPL_xxxxxxxxxx'
});

if (data) {
  console.log('Split added successfully!');
  console.log('Split config:', data.data.split_config);
}
```

### Remove Split

Remove split configuration from a virtual account.

```typescript theme={null}
const { data, error } = await paystack.virtualAccount.removeSplit({
  account_number: '1234567890'
});

if (data) {
  console.log('Split removed');
  console.log('Split config:', data.data.split_config); // Empty object
}
```

## Available Banks

Fetch list of available bank providers for virtual accounts.

```typescript theme={null}
const { data, error } = await paystack.virtualAccount.fetchBanks();

if (data) {
  console.log('Available banks:');
  
  data.data.forEach(bank => {
    console.log(`${bank.bank_name}:`, {
      slug: bank.provider_slug,
      bankId: bank.bank_id,
      id: bank.id
    });
  });
}
```

<Tip>
  Use the `provider_slug` when creating virtual accounts with a specific bank preference.
</Tip>

## Webhook Integration

Listen for payments to virtual accounts:

```typescript theme={null}
paystack.webhook
  .on('dedicatedaccount.assign.success', (data) => {
    console.log('Virtual account assigned!');
    
    const account = data.dedicated_account;
    // Store account details
    db.virtualAccounts.create({
      customerId: data.customer.id,
      accountNumber: account.account_number,
      bankName: account.bank.name,
      active: account.active
    });
  })
  .on('dedicatedaccount.assign.failed', (data) => {
    console.log('Assignment failed');
    // Handle failure
    notifyAdmin('virtual_account_assignment_failed', data);
  })
  .on('charge.success', (data) => {
    // Check if payment is from a virtual account
    if (data.channel === 'dedicated_nuban') {
      console.log('Payment received via virtual account!');
      console.log('Amount:', data.amount / 100);
      console.log('Customer:', data.customer.email);
      
      // Credit customer's account
      creditCustomerAccount(data.customer.email, data.amount / 100);
    }
  });
```

## Best Practices

<Tip>
  1. **Store account details** in your database with customer records
  2. **Verify customer identity** before creating virtual accounts
  3. **Display account info prominently** to customers
  4. **Handle webhook events** for automatic payment processing
  5. **Monitor account status** and reactivate when needed
  6. **Use split configurations** for automatic payment routing
  7. **Provide payment instructions** to customers
  8. **Test thoroughly** in test mode before going live
</Tip>

## Common Scenarios

### Wallet Funding

```typescript theme={null}
async function createWalletAccount(userId: string) {
  const user = await db.users.findById(userId);
  
  // Get or create Paystack customer
  let customer = await db.customers.findByEmail(user.email);
  
  if (!customer) {
    // Create customer first
    customer = await createPaystackCustomer(user);
  }

  // Create virtual account
  const { data, error } = await paystack.virtualAccount.create({
    customer: customer.code,
    first_name: user.firstName,
    last_name: user.lastName,
    phone: user.phone
  });

  if (error) {
    throw new Error('Failed to create wallet account');
  }

  // Store account details
  await db.walletAccounts.create({
    userId,
    accountNumber: data.data.account_number,
    bankName: data.data.bank.name,
    accountName: data.data.account_name,
    accountId: data.data.id
  });

  return {
    accountNumber: data.data.account_number,
    bankName: data.data.bank.name,
    accountName: data.data.account_name
  };
}

// Handle incoming payment
paystack.webhook.on('charge.success', async (data) => {
  if (data.channel === 'dedicated_nuban') {
    const account = await db.walletAccounts.findByAccountNumber(
      data.metadata.account_number
    );
    
    if (account) {
      await db.walletTransactions.create({
        userId: account.userId,
        amount: data.amount / 100,
        reference: data.reference,
        type: 'credit',
        status: 'completed'
      });
      
      await db.users.incrementBalance(account.userId, data.amount / 100);
      
      await notifyUser(account.userId, 'wallet_funded', {
        amount: data.amount / 100
      });
    }
  }
});
```

### Invoice Payments

```typescript theme={null}
async function generateInvoiceAccount(
  invoiceId: string,
  customerEmail: string
) {
  const invoice = await db.invoices.findById(invoiceId);
  
  // Create temporary customer for the invoice
  const customer = await createOrGetCustomer({
    email: customerEmail,
    metadata: { invoice_id: invoiceId }
  });

  // Create virtual account with split for commission
  const { data, error } = await paystack.virtualAccount.create({
    customer: customer.code,
    split_code: process.env.INVOICE_SPLIT_CODE // Pre-configured split
  });

  if (error) {
    throw new Error('Failed to generate payment account');
  }

  // Store and send to customer
  await db.invoices.update(invoiceId, {
    paymentAccountNumber: data.data.account_number,
    paymentBankName: data.data.bank.name,
    paymentAccountName: data.data.account_name
  });

  await sendInvoiceEmail(customerEmail, {
    invoiceId,
    amount: invoice.amount,
    accountNumber: data.data.account_number,
    bankName: data.data.bank.name,
    accountName: data.data.account_name
  });

  return data.data;
}
```

### Subscription Renewals

```typescript theme={null}
async function setupSubscriptionAccount(subscriptionId: string) {
  const subscription = await db.subscriptions.findById(subscriptionId);
  const user = await db.users.findById(subscription.userId);

  // Create or reuse virtual account
  let virtualAccount = await db.virtualAccounts.findByUserId(user.id);
  
  if (!virtualAccount) {
    const { data } = await paystack.virtualAccount.create({
      customer: user.paystackCustomerCode,
      first_name: user.firstName,
      last_name: user.lastName,
      phone: user.phone
    });
    
    virtualAccount = await db.virtualAccounts.create({
      userId: user.id,
      accountNumber: data.data.account_number,
      bankName: data.data.bank.name
    });
  }

  // Link to subscription
  await db.subscriptions.update(subscriptionId, {
    paymentAccountId: virtualAccount.id
  });

  return virtualAccount;
}

// Auto-renew when payment is received
paystack.webhook.on('charge.success', async (data) => {
  if (data.channel === 'dedicated_nuban' && data.metadata.subscription_id) {
    const subscriptionId = data.metadata.subscription_id;
    const amount = data.amount / 100;
    
    const subscription = await db.subscriptions.findById(subscriptionId);
    
    if (amount >= subscription.price) {
      await db.subscriptions.update(subscriptionId, {
        status: 'active',
        lastPaymentDate: new Date(),
        nextBillingDate: calculateNextBilling(subscription)
      });
      
      await notifyUser(subscription.userId, 'subscription_renewed');
    }
  }
});
```

## Error Handling

```typescript theme={null}
try {
  const { data, error } = await paystack.virtualAccount.create({
    customer: customerCode
  });

  if (error) {
    // Handle validation errors
    if (error.message.includes('customer')) {
      return { 
        success: false, 
        message: 'Invalid customer code' 
      };
    }
    
    return { success: false, message: error.message };
  }

  if (!data.status) {
    // Handle API errors
    return { success: false, message: data.message };
  }

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

} catch (err) {
  console.error('Unexpected error:', err);
  return { success: false, message: 'Failed to create virtual account' };
}
```

## Related Resources

* [Virtual Account API Reference](/api-reference/virtual-account) - Full API documentation
* [Webhooks Guide](/guides/webhooks) - Handle payment notifications
* [Transaction Splits](/api-reference/split) - Configure payment splitting
