Overview
The Banks API provides access to UAE banking institutions for store owners to configure their payout bank accounts. This system supports all major UAE banks and is used for financial operations and payout processing. Location:convex/shared/banks.ts
Get Banks
Retrieves all available banks in the UAE banking system.Copy
const banks = await convex.query(api.shared.banks.getBanks);
Copy
[
{
"_id": "b123456789",
"name": "Emirates NBD",
"nameArabic": "بنك الإمارات دبي الوطني",
"code": "EBILAEAD",
"isActive": true,
"sortOrder": 1,
"_creationTime": 1640995200000
},
{
"_id": "b987654321",
"name": "First Abu Dhabi Bank",
"nameArabic": "بنك أبوظبي الأول",
"code": "NBADAEAD",
"isActive": true,
"sortOrder": 2,
"_creationTime": 1640995200000
},
{
"_id": "b555666777",
"name": "Dubai Islamic Bank",
"nameArabic": "بنك دبي الإسلامي",
"code": "DUIBAE2X",
"isActive": true,
"sortOrder": 3,
"_creationTime": 1640995200000
},
{
"_id": "b111222333",
"name": "Abu Dhabi Commercial Bank",
"nameArabic": "بنك أبوظبي التجاري",
"code": "ADCBAEAA",
"isActive": true,
"sortOrder": 4,
"_creationTime": 1640995200000
},
{
"_id": "b444555666",
"name": "Mashreq Bank",
"nameArabic": "بنك المشرق",
"code": "BOMLAEAD",
"isActive": true,
"sortOrder": 5,
"_creationTime": 1640995200000
}
]
Seed Banks
Seeds the database with initial bank data (admin function).Copy
await convex.mutation(api.shared.banks.seedBanks);
Copy
null
Bank Selector Component
Here’s a reusable bank selector component for your frontend:Copy
import { useQuery } from 'convex/react';
import { api } from './convex/_generated/api';
interface BankSelectorProps {
selectedBankId?: string;
onBankSelect: (bankId: string, bankName: string) => void;
placeholder?: string;
required?: boolean;
}
function BankSelector({ selectedBankId, onBankSelect, placeholder = "Select a bank", required = false }: BankSelectorProps) {
const banks = useQuery(api.shared.banks.getBanks);
if (!banks) {
return <div>Loading banks...</div>;
}
return (
<div className="bank-selector">
<label className="block text-sm font-medium text-gray-700 mb-2">
Bank {required && <span className="text-red-500">*</span>}
</label>
<select
value={selectedBankId || ''}
onChange={(e) => {
const selectedBank = banks.find(bank => bank._id === e.target.value);
if (selectedBank) {
onBankSelect(selectedBank._id, selectedBank.name);
}
}}
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required={required}
>
<option value="">{placeholder}</option>
{banks.map((bank) => (
<option key={bank._id} value={bank._id}>
{bank.name} - {bank.nameArabic}
</option>
))}
</select>
{selectedBankId && (
<div className="mt-2 text-sm text-gray-600">
Selected: {banks.find(b => b._id === selectedBankId)?.name}
</div>
)}
</div>
);
}
export default BankSelector;
Bank Account Setup Form
Complete form for setting up bank account details:Copy
import { useState } from 'react';
import { useMutation } from 'convex/react';
import { api } from './convex/_generated/api';
import BankSelector from './BankSelector';
interface BankAccountFormProps {
storeId: string;
onSuccess?: () => void;
}
function BankAccountForm({ storeId, onSuccess }: BankAccountFormProps) {
const [formData, setFormData] = useState({
bankId: '',
accountHolderName: '',
IBAN: '',
accountNumber: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const updateStore = useMutation(api.stores.operations.updateStore);
const validateIBAN = (iban: string): boolean => {
// UAE IBAN format: AE + 2 digits + 19 digits = 23 characters
const uaeIBANRegex = /^AE\d{21}$/;
return uaeIBANRegex.test(iban.replace(/\s/g, ''));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setErrors({});
// Validation
const newErrors: Record<string, string> = {};
if (!formData.bankId) newErrors.bankId = 'Please select a bank';
if (!formData.accountHolderName) newErrors.accountHolderName = 'Account holder name is required';
if (!formData.IBAN) {
newErrors.IBAN = 'IBAN is required';
} else if (!validateIBAN(formData.IBAN)) {
newErrors.IBAN = 'Invalid UAE IBAN format (e.g., AE123456789012345678901)';
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
setIsSubmitting(false);
return;
}
try {
await updateStore({
bankAccount: {
bankId: formData.bankId,
accountHolderName: formData.accountHolderName,
IBAN: formData.IBAN.replace(/\s/g, ''), // Remove spaces
accountNumber: formData.accountNumber
}
});
onSuccess?.();
} catch (error) {
console.error('Failed to update bank account:', error);
setErrors({ submit: 'Failed to save bank account details' });
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="bg-blue-50 p-4 rounded-lg">
<h3 className="font-medium text-blue-900 mb-2">Bank Account Information</h3>
<p className="text-sm text-blue-700">
This information will be used for processing your store payouts.
Ensure all details are accurate to avoid payment delays.
</p>
</div>
<BankSelector
selectedBankId={formData.bankId}
onBankSelect={(bankId, bankName) => {
setFormData(prev => ({ ...prev, bankId }));
setErrors(prev => ({ ...prev, bankId: '' }));
}}
required
/>
{errors.bankId && <p className="text-red-500 text-sm">{errors.bankId}</p>}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Account Holder Name <span className="text-red-500">*</span>
</label>
<input
type="text"
value={formData.accountHolderName}
onChange={(e) => {
setFormData(prev => ({ ...prev, accountHolderName: e.target.value }));
setErrors(prev => ({ ...prev, accountHolderName: '' }));
}}
placeholder="Enter account holder name as per bank records"
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required
/>
{errors.accountHolderName && <p className="text-red-500 text-sm">{errors.accountHolderName}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
IBAN <span className="text-red-500">*</span>
</label>
<input
type="text"
value={formData.IBAN}
onChange={(e) => {
const value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
setFormData(prev => ({ ...prev, IBAN: value }));
setErrors(prev => ({ ...prev, IBAN: '' }));
}}
placeholder="AE123456789012345678901"
maxLength={23}
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono"
required
/>
<p className="text-xs text-gray-500 mt-1">
UAE IBAN format: AE followed by 21 digits
</p>
{errors.IBAN && <p className="text-red-500 text-sm">{errors.IBAN}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Account Number (Optional)
</label>
<input
type="text"
value={formData.accountNumber}
onChange={(e) => setFormData(prev => ({ ...prev, accountNumber: e.target.value }))}
placeholder="Additional account number if required"
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{errors.submit && (
<div className="bg-red-50 p-4 rounded-lg">
<p className="text-red-700 text-sm">{errors.submit}</p>
</div>
)}
<div className="flex justify-end">
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? 'Saving...' : 'Save Bank Details'}
</button>
</div>
</form>
);
}
export default BankAccountForm;
Supported UAE Banks
The system includes all major UAE banks:Major Banks
- Emirates NBD (بنك الإمارات دبي الوطني)
- First Abu Dhabi Bank (بنك أبوظبي الأول)
- Dubai Islamic Bank (بنك دبي الإسلامي)
- Abu Dhabi Commercial Bank (بنك أبوظبي التجاري)
Other Banks
- Mashreq Bank (بنك المشرق)
- RAKBANK (بنك رأس الخيمة الوطني)
- ADIB (بنك أبوظبي الإسلامي)
- CBD (بنك دبي التجاري)
IBAN Validation
UAE IBAN format validation:Copy
function validateUAEIBAN(iban: string): boolean {
// Remove spaces and convert to uppercase
const cleanIBAN = iban.replace(/\s/g, '').toUpperCase();
// Check format: AE + 21 digits
if (!/^AE\d{21}$/.test(cleanIBAN)) {
return false;
}
// IBAN check digit validation (simplified)
const rearranged = cleanIBAN.slice(4) + cleanIBAN.slice(0, 4);
const numericString = rearranged.replace(/[A-Z]/g, (char) =>
(char.charCodeAt(0) - 55).toString()
);
// Mod 97 check
let remainder = 0;
for (let i = 0; i < numericString.length; i++) {
remainder = (remainder * 10 + parseInt(numericString[i])) % 97;
}
return remainder === 1;
}
Error Handling
No banks found
No banks found
Status Code:
200 (Success with empty array)Copy
[]
Bank seeding failed
Bank seeding failed
Status Code:
500Copy
{
"error": "Failed to seed bank data",
"details": "Database connection error"
}
Invalid bank ID
Invalid bank ID
Status Code:
404Copy
{
"error": "Bank not found",
"bankId": "b123456789"
}
Bank data is pre-seeded during system initialization and includes all major UAE banking institutions with their official Arabic names and SWIFT codes.
Always validate IBAN format on the client side before submission to provide immediate feedback to users and reduce server-side validation errors.
