Virtual Account Details Rendering

Dynamic UI rendering for virtual account deposit instructions based on schema types - automatically display the correct bank details, crypto addresses, or payment information for each virtual account.

When virtual accounts are created, they return schema-specific details that need to be displayed to users as deposit instructions. This guide shows how to dynamically render the appropriate UI based on the schema type and details returned.

Base URL
https://api.due.network/v1

Authentication required: Include your API key in Authorization header and account ID in Due-Account-Id header.


🎯 Overview

Virtual accounts return different details objects based on their schemaIn type:

  • Bank schemas (bank_sepa, bank_us, bank_uk) - Return bank account details
  • Crypto schemas (evm, tron, starknet) - Return crypto addresses

The key is to:

  1. Get schema definitions from /schemas endpoint
  2. Match virtual account schema to the schema definition
  3. Render appropriate UI based on schema category and fields

📋 Get Schema Definitions

First, fetch all available schemas to understand field requirements:

curl -H "Authorization: Bearer your_api_key" \
  -H "Due-Account-Id: your_account_id" \
  https://api.due.network/v1/schemas

Schema Response Structure:

[
  {
    "id": "bank_sepa",
    "category": "bank",
    "fields": [
      {
        "key": "accountType",
        "type": "string",
        "oneOf": ["individual", "business"],
        "required": true
      },
      {
        "key": "IBAN",
        "type": "iban",
        "required": true
      },
      {
        "key": "companyName",
        "type": "string",
        "requiredIf": "accountType=business"
      }
    ]
  },
  {
    "id": "evm",
    "category": "crypto",
    "fields": [
      {
        "key": "address",
        "type": "eth_addr",
        "required": true
      }
    ]
  }
]

🏦 Bank Schema Rendering

SEPA (European) Virtual Accounts

Virtual Account Response:

{
  "schemaIn": "bank_sepa",
  "currencyIn": "EUR",
  "details": {
    "accountType": "business",
    "companyName": "Your Company Ltd",
    "IBAN": "DE89370400440532013000",
    "bankName": "Commerzbank AG",
    "swiftCode": "COBADEFF"
  }
}

Rendered UI:

<div class="deposit-instructions sepa">
  <h3>💶 EUR Deposit Instructions</h3>
  <div class="bank-details">
    <div class="detail-row">
      <label>IBAN:</label>
      <span class="copyable" data-copy="DE89370400440532013000">
        DE89 3704 0044 0532 0130 00
        <button class="copy-btn">📋</button>
      </span>
    </div>
    <div class="detail-row">
      <label>Bank Name:</label>
      <span>Commerzbank AG</span>
    </div>
    <div class="detail-row">
      <label>SWIFT/BIC:</label>
      <span class="copyable" data-copy="COBADEFF">COBADEFF</span>
    </div>
    <div class="detail-row">
      <label>Beneficiary:</label>
      <span>Your Company Ltd</span>
    </div>
  </div>
  <div class="important-note">
    <p>⚠️ <strong>Important:</strong> Only send EUR to this account. Other currencies will be rejected.</p>
  </div>
</div>

US Bank Virtual Accounts

Virtual Account Response:

{
  "schemaIn": "bank_us",
  "currencyIn": "USD",
  "details": {
    "bankName": "JPMorgan Chase Bank",
    "accountName": "Your Company LLC",
    "accountNumber": "123456789012",
    "routingNumber": "021000021"
  }
}

Rendered UI:

<div class="deposit-instructions us-bank">
  <h3>💵 USD Deposit Instructions</h3>
  <div class="bank-details">
    <div class="detail-row">
      <label>Account Number:</label>
      <span class="copyable" data-copy="123456789012">
        123456789012
        <button class="copy-btn">📋</button>
      </span>
    </div>
    <div class="detail-row">
      <label>Routing Number:</label>
      <span class="copyable" data-copy="021000021">021000021</span>
    </div>
    <div class="detail-row">
      <label>Bank Name:</label>
      <span>JPMorgan Chase Bank</span>
    </div>
    <div class="detail-row">
      <label>Account Name:</label>
      <span>Your Company LLC</span>
    </div>
  </div>
  <div class="transfer-types">
    <p><strong>Supported:</strong> ACH Transfer, Wire Transfer</p>
    <p><strong>Processing Time:</strong> ACH (1-2 days), Wire (same day)</p>
  </div>
</div>

UK Bank Virtual Accounts

Virtual Account Response:

{
  "schemaIn": "bank_uk",
  "currencyIn": "GBP",
  "details": {
    "accountType": "business",
    "companyName": "Your Company Ltd",
    "accountNumber": "12345678",
    "sortCode": "123456",
    "bankName": "Barclays Bank PLC"
  }
}

Rendered UI:

<div class="deposit-instructions uk-bank">
  <h3>💷 GBP Deposit Instructions</h3>
  <div class="bank-details">
    <div class="detail-row">
      <label>Account Number:</label>
      <span class="copyable" data-copy="12345678">12345678</span>
    </div>
    <div class="detail-row">
      <label>Sort Code:</label>
      <span class="copyable" data-copy="123456">12-34-56</span>
    </div>
    <div class="detail-row">
      <label>Account Name:</label>
      <span>Your Company Ltd</span>
    </div>
    <div class="detail-row">
      <label>Bank:</label>
      <span>Barclays Bank PLC</span>
    </div>
  </div>
  <div class="payment-info">
    <p><strong>Faster Payments:</strong> Instant transfer (up to £1M)</p>
    <p><strong>CHAPS:</strong> Same day (for larger amounts)</p>
  </div>
</div>

Mexican Bank Virtual Accounts

Virtual Account Response:

{
  "schemaIn": "bank_mexico",
  "currencyIn": "MXN",
  "details": {
    "clabe": "012345678901234567",
    "beneficiaryName": "Your Company Mexico SA",
    "bankName": "BBVA Mexico"
  }
}

Rendered UI:

<div class="deposit-instructions mexico-bank">
  <h3>🇲🇽 MXN Deposit Instructions</h3>
  <div class="bank-details">
    <div class="detail-row">
      <label>CLABE:</label>
      <span class="copyable clabe" data-copy="012345678901234567">
        0123 4567 8901 2345 67
        <button class="copy-btn">📋</button>
      </span>
    </div>
    <div class="detail-row">
      <label>Beneficiary:</label>
      <span>Your Company Mexico SA</span>
    </div>
    <div class="detail-row">
      <label>Bank:</label>
      <span>BBVA Mexico</span>
    </div>
  </div>
  <div class="spei-info">
    <p><strong>SPEI Transfer:</strong> Instant processing 24/7</p>
    <p><strong>Maximum:</strong> No limit for SPEI transfers</p>
  </div>
</div>

💎 Crypto Schema Rendering

Ethereum/EVM Virtual Accounts

Virtual Account Response:

{
  "schemaIn": "evm",
  "currencyIn": "USDC",
  "railOut": "ethereum",
  "details": {
    "address": "0x1234567890abcdef1234567890abcdef12345678"
  }
}

Rendered UI:

<div class="deposit-instructions crypto evm">
  <h3>⚡ USDC Deposit Instructions</h3>
  <div class="crypto-details">
    <div class="network-info">
      <span class="network-badge ethereum">Ethereum Network</span>
      <span class="token-badge usdc">USDC</span>
    </div>
    <div class="address-section">
      <label>Deposit Address:</label>
      <div class="address-container">
        <span class="address copyable" data-copy="0x1234567890abcdef1234567890abcdef12345678">
          0x1234567890abcdef1234567890abcdef12345678
        </span>
        <button class="copy-btn">📋 Copy</button>
      </div>
      <div class="qr-code">
        <img src="data:image/png;base64,..." alt="QR Code" />
        <p>Scan QR code with wallet</p>
      </div>
    </div>
  </div>
  <div class="crypto-warnings">
    <div class="warning">
      <p>⚠️ <strong>Only send USDC on Ethereum network</strong></p>
      <p>• Other tokens will be lost permanently</p>
      <p>• Minimum deposit: $1 USDC</p>
      <p>• Network fees apply</p>
    </div>
  </div>
</div>

Tron Virtual Accounts

Virtual Account Response:

{
  "schemaIn": "tron",
  "currencyIn": "USDT",
  "railOut": "arbitrum",
  "details": {
    "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"
  }
}

Rendered UI:

<div class="deposit-instructions crypto tron">
  <h3>🟢 USDT Deposit Instructions</h3>
  <div class="crypto-details">
    <div class="network-info">
      <span class="network-badge tron">Tron Network (TRC-20)</span>
      <span class="token-badge usdt">USDT</span>
      <span class="conversion-info">→ Auto-converts to USDC on Arbitrum</span>
    </div>
    <div class="address-section">
      <label>Deposit Address:</label>
      <div class="address-container">
        <span class="address copyable" data-copy="TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE">
          TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
        </span>
        <button class="copy-btn">📋 Copy</button>
      </div>
    </div>
  </div>
  <div class="bridge-info">
    <div class="conversion-flow">
      <span class="step">USDT (Tron)</span>
      <span class="arrow">→</span>
      <span class="step">Auto-Bridge</span>
      <span class="arrow">→</span>
      <span class="step">USDC (Arbitrum)</span>
    </div>
    <p><strong>Processing:</strong> ~5 minutes after confirmation</p>
  </div>
</div>

💻 Dynamic Rendering Implementation

React Component

import React, { useState, useEffect } from 'react';

const VirtualAccountDetails = ({ virtualAccount, onCopy }) => {
  const [schemas, setSchemas] = useState([]);
  
  useEffect(() => {
    // Fetch schemas on component mount
    fetchSchemas();
  }, []);
  
  const fetchSchemas = async () => {
    try {
      const response = await fetch('/api/due/schemas', {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Due-Account-Id': accountId
        }
      });
      const schemaData = await response.json();
      setSchemas(schemaData);
    } catch (error) {
      console.error('Failed to fetch schemas:', error);
    }
  };
  
  const getSchemaInfo = (schemaId) => {
    return schemas.find(s => s.id === schemaId);
  };
  
  const formatAddress = (address, type) => {
    if (type === 'iban') {
      return address.replace(/(.{4})/g, '$1 ').trim();
    } else if (type === 'clabe') {
      return address.replace(/(.{4})/g, '$1 ').trim();
    } else if (type === 'sort_code') {
      return address.replace(/(.{2})/g, '$1-').slice(0, -1);
    }
    return address;
  };
  
  const handleCopy = async (text, label) => {
    try {
      await navigator.clipboard.writeText(text);
      onCopy?.(label);
    } catch (error) {
      console.error('Failed to copy:', error);
    }
  };
  
  const renderBankDetails = () => {
    const { details, currencyIn, schemaIn } = virtualAccount;
    const schema = getSchemaInfo(schemaIn);
    
    const getCurrencyIcon = (currency) => {
      const icons = {
        'EUR': '💶',
        'USD': '💵', 
        'GBP': '💷',
        'MXN': '🇲🇽'
      };
      return icons[currency] || '💰';
    };
    
    const getNetworkName = (schemaId) => {
      const networks = {
        'bank_sepa': 'SEPA Transfer',
        'bank_us': 'ACH / Wire Transfer',
        'bank_uk': 'Faster Payments',
        'bank_mexico': 'SPEI Transfer'
      };
      return networks[schemaId] || 'Bank Transfer';
    };
    
    return (
      <div className={`deposit-instructions bank ${schemaIn}`}>
        <h3>{getCurrencyIcon(currencyIn)} {currencyIn} Deposit Instructions</h3>
        
        <div className="bank-details">
          {/* IBAN for SEPA */}
          {details.IBAN && (
            <div className="detail-row">
              <label>IBAN:</label>
              <div className="copyable-field">
                <span className="value">{formatAddress(details.IBAN, 'iban')}</span>
                <button 
                  className="copy-btn"
                  onClick={() => handleCopy(details.IBAN, 'IBAN')}
                >
                  📋
                </button>
              </div>
            </div>
          )}
          
          {/* Account Number for US/UK */}
          {details.accountNumber && (
            <div className="detail-row">
              <label>Account Number:</label>
              <div className="copyable-field">
                <span className="value">{details.accountNumber}</span>
                <button 
                  className="copy-btn"
                  onClick={() => handleCopy(details.accountNumber, 'Account Number')}
                >
                  📋
                </button>
              </div>
            </div>
          )}
          
          {/* Routing Number for US */}
          {details.routingNumber && (
            <div className="detail-row">
              <label>Routing Number:</label>
              <div className="copyable-field">
                <span className="value">{details.routingNumber}</span>
                <button 
                  className="copy-btn"
                  onClick={() => handleCopy(details.routingNumber, 'Routing Number')}
                >
                  📋
                </button>
              </div>
            </div>
          )}
          
          {/* Sort Code for UK */}
          {details.sortCode && (
            <div className="detail-row">
              <label>Sort Code:</label>
              <div className="copyable-field">
                <span className="value">{formatAddress(details.sortCode, 'sort_code')}</span>
                <button 
                  className="copy-btn"
                  onClick={() => handleCopy(details.sortCode, 'Sort Code')}
                >
                  📋
                </button>
              </div>
            </div>
          )}
          
          {/* CLABE for Mexico */}
          {details.clabe && (
            <div className="detail-row">
              <label>CLABE:</label>
              <div className="copyable-field">
                <span className="value">{formatAddress(details.clabe, 'clabe')}</span>
                <button 
                  className="copy-btn"
                  onClick={() => handleCopy(details.clabe, 'CLABE')}
                >
                  📋
                </button>
              </div>
            </div>
          )}
          
          {/* Bank Name */}
          {details.bankName && (
            <div className="detail-row">
              <label>Bank:</label>
              <span className="value">{details.bankName}</span>
            </div>
          )}
          
          {/* Beneficiary Name */}
          {(details.accountName || details.companyName || details.beneficiaryName) && (
            <div className="detail-row">
              <label>Beneficiary:</label>
              <span className="value">
                {details.accountName || details.companyName || details.beneficiaryName}
              </span>
            </div>
          )}
          
          {/* SWIFT Code */}
          {details.swiftCode && (
            <div className="detail-row">
              <label>SWIFT/BIC:</label>
              <div className="copyable-field">
                <span className="value">{details.swiftCode}</span>
                <button 
                  className="copy-btn"
                  onClick={() => handleCopy(details.swiftCode, 'SWIFT Code')}
                >
                  📋
                </button>
              </div>
            </div>
          )}
        </div>
        
        <div className="payment-info">
          <p><strong>Payment Method:</strong> {getNetworkName(schemaIn)}</p>
          <p className="currency-warning">
            ⚠️ Only send {currencyIn} to this account. Other currencies will be rejected.
          </p>
        </div>
      </div>
    );
  };
  
  const renderCryptoDetails = () => {
    const { details, currencyIn, railOut, schemaIn } = virtualAccount;
    
    const getNetworkInfo = (schema, rail) => {
      if (schema === 'tron') {
        return { name: 'Tron', badge: 'tron', color: '#ff0000' };
      } else if (schema === 'evm') {
        const networks = {
          'ethereum': { name: 'Ethereum', badge: 'ethereum', color: '#627eea' },
          'polygon': { name: 'Polygon', badge: 'polygon', color: '#8247e5' },
          'arbitrum': { name: 'Arbitrum', badge: 'arbitrum', color: '#28a0f0' },
          'base': { name: 'Base', badge: 'base', color: '#0052ff' }
        };
        return networks[rail] || { name: 'EVM', badge: 'evm', color: '#627eea' };
      }
      return { name: 'Crypto', badge: 'crypto', color: '#f7931a' };
    };
    
    const getCurrencyIcon = (currency) => {
      const icons = {
        'USDC': '🔵',
        'USDT': '🟢',
        'EURC': '🔵'
      };
      return icons[currency] || '💎';
    };
    
    const networkInfo = getNetworkInfo(schemaIn, railOut);
    
    return (
      <div className={`deposit-instructions crypto ${schemaIn}`}>
        <h3>{getCurrencyIcon(currencyIn)} {currencyIn} Deposit Instructions</h3>
        
        <div className="crypto-details">
          <div className="network-info">
            <span className={`network-badge ${networkInfo.badge}`} style={{backgroundColor: networkInfo.color}}>
              {networkInfo.name} Network
            </span>
            <span className={`token-badge ${currencyIn.toLowerCase()}`}>
              {currencyIn}
            </span>
          </div>
          
          <div className="address-section">
            <label>Deposit Address:</label>
            <div className="address-container">
              <span className="address">{details.address}</span>
              <button 
                className="copy-btn primary"
                onClick={() => handleCopy(details.address, 'Address')}
              >
                📋 Copy Address
              </button>
            </div>
            
            <div className="qr-section">
              <QRCodeGenerator value={details.address} />
              <p>Scan with your wallet app</p>
            </div>
          </div>
        </div>
        
        <div className="crypto-warnings">
          <div className="warning">
            <p><strong>⚠️ Important:</strong></p>
            <ul>
              <li>Only send {currencyIn} on {networkInfo.name} network</li>
              <li>Other tokens or networks will result in permanent loss</li>
              <li>Minimum deposit: 1 {currencyIn}</li>
              <li>Network fees apply</li>
            </ul>
          </div>
        </div>
      </div>
    );
  };
  
  if (!virtualAccount) {
    return <div className="loading">Loading deposit instructions...</div>;
  }
  
  const schema = getSchemaInfo(virtualAccount.schemaIn);
  
  if (!schema) {
    return <div className="error">Unable to load deposit instructions</div>;
  }
  
  return (
    <div className="virtual-account-details">
      {schema.category === 'bank' && renderBankDetails()}
      {schema.category === 'crypto' && renderCryptoDetails()}
      
      {virtualAccount.railOut && virtualAccount.currencyOut && (
        <div className="conversion-info">
          <h4>💱 Auto-Conversion</h4>
          <div className="conversion-flow">
            <span>{virtualAccount.currencyIn}</span>
            <span className="arrow">→</span>
            <span>{virtualAccount.currencyOut}</span>
            <span className="network">on {virtualAccount.railOut}</span>
          </div>
          <p>Funds automatically convert upon deposit confirmation</p>
        </div>
      )}
    </div>
  );
};

// QR Code component
const QRCodeGenerator = ({ value }) => {
  const [qrUrl, setQrUrl] = useState('');
  
  useEffect(() => {
    // Generate QR code (using a library like qrcode)
    import('qrcode').then(QRCode => {
      QRCode.toDataURL(value, { width: 200 }).then(setQrUrl);
    });
  }, [value]);
  
  return qrUrl ? <img src={qrUrl} alt="QR Code" className="qr-code" /> : null;
};

export default VirtualAccountDetails;

Vanilla JavaScript Implementation

class VirtualAccountRenderer {
  constructor(apiKey, accountId) {
    this.apiKey = apiKey;
    this.accountId = accountId;
    this.schemas = [];
  }
  
  async init() {
    await this.fetchSchemas();
  }
  
  async fetchSchemas() {
    try {
      const response = await fetch('https://api.due.network/v1/schemas', {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Due-Account-Id': this.accountId
        }
      });
      this.schemas = await response.json();
    } catch (error) {
      console.error('Failed to fetch schemas:', error);
    }
  }
  
  getSchema(schemaId) {
    return this.schemas.find(s => s.id === schemaId);
  }
  
  render(virtualAccount, container) {
    const schema = this.getSchema(virtualAccount.schemaIn);
    
    if (!schema) {
      container.innerHTML = '<div class="error">Schema not found</div>';
      return;
    }
    
    if (schema.category === 'bank') {
      this.renderBankAccount(virtualAccount, container);
    } else if (schema.category === 'crypto') {
      this.renderCryptoAccount(virtualAccount, container);
    }
    
    this.addCopyListeners(container);
  }
  
  renderBankAccount(virtualAccount, container) {
    const { details, currencyIn, schemaIn } = virtualAccount;
    
    const html = `
      <div class="deposit-instructions bank ${schemaIn}">
        <h3>${this.getCurrencyIcon(currencyIn)} ${currencyIn} Deposit Instructions</h3>
        <div class="bank-details">
          ${this.renderBankFields(details, schemaIn)}
        </div>
        <div class="payment-info">
          <p><strong>Payment Method:</strong> ${this.getPaymentMethod(schemaIn)}</p>
          <p class="warning">⚠️ Only send ${currencyIn} to this account</p>
        </div>
      </div>
    `;
    
    container.innerHTML = html;
  }
  
  renderCryptoAccount(virtualAccount, container) {
    const { details, currencyIn, schemaIn, railOut } = virtualAccount;
    
    const html = `
      <div class="deposit-instructions crypto ${schemaIn}">
        <h3>${this.getCurrencyIcon(currencyIn)} ${currencyIn} Deposit Instructions</h3>
        <div class="crypto-details">
          <div class="network-info">
            <span class="network-badge">${this.getNetworkName(schemaIn, railOut)}</span>
            <span class="token-badge">${currencyIn}</span>
          </div>
          <div class="address-section">
            <label>Deposit Address:</label>
            <div class="address-container">
              <span class="address" data-copy="${details.address}">${details.address}</span>
              <button class="copy-btn" data-copy="${details.address}">📋 Copy</button>
            </div>
          </div>
        </div>
        <div class="crypto-warnings">
          <p><strong>⚠️ Important:</strong> Only send ${currencyIn} on ${this.getNetworkName(schemaIn, railOut)} network</p>
        </div>
      </div>
    `;
    
    container.innerHTML = html;
  }
  
  renderBankFields(details, schemaIn) {
    let html = '';
    
    // IBAN for SEPA
    if (details.IBAN) {
      html += `
        <div class="detail-row">
          <label>IBAN:</label>
          <span class="value copyable" data-copy="${details.IBAN}">
            ${this.formatIBAN(details.IBAN)}
            <button class="copy-btn" data-copy="${details.IBAN}">📋</button>
          </span>
        </div>
      `;
    }
    
    // Account Number for US/UK
    if (details.accountNumber) {
      html += `
        <div class="detail-row">
          <label>Account Number:</label>
          <span class="value copyable" data-copy="${details.accountNumber}">
            ${details.accountNumber}
            <button class="copy-btn" data-copy="${details.accountNumber}">📋</button>
          </span>
        </div>
      `;
    }
    
    // Routing Number for US
    if (details.routingNumber) {
      html += `
        <div class="detail-row">
          <label>Routing Number:</label>
          <span class="value copyable" data-copy="${details.routingNumber}">
            ${details.routingNumber}
            <button class="copy-btn" data-copy="${details.routingNumber}">📋</button>
          </span>
        </div>
      `;
    }
    
    // Sort Code for UK
    if (details.sortCode) {
      html += `
        <div class="detail-row">
          <label>Sort Code:</label>
          <span class="value copyable" data-copy="${details.sortCode}">
            ${this.formatSortCode(details.sortCode)}
            <button class="copy-btn" data-copy="${details.sortCode}">📋</button>
          </span>
        </div>
      `;
    }
    
    // CLABE for Mexico
    if (details.clabe) {
      html += `
        <div class="detail-row">
          <label>CLABE:</label>
          <span class="value copyable" data-copy="${details.clabe}">
            ${this.formatCLABE(details.clabe)}
            <button class="copy-btn" data-copy="${details.clabe}">📋</button>
          </span>
        </div>
      `;
    }
    
    // Bank Name
    if (details.bankName) {
      html += `
        <div class="detail-row">
          <label>Bank:</label>
          <span class="value">${details.bankName}</span>
        </div>
      `;
    }
    
    // Beneficiary
    const beneficiary = details.accountName || details.companyName || details.beneficiaryName;
    if (beneficiary) {
      html += `
        <div class="detail-row">
          <label>Beneficiary:</label>
          <span class="value">${beneficiary}</span>
        </div>
      `;
    }
    
    // SWIFT Code
    if (details.swiftCode) {
      html += `
        <div class="detail-row">
          <label>SWIFT/BIC:</label>
          <span class="value copyable