添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

交易系统是现代商业和金融活动的核心,涵盖从电子商务订单处理到金融机构的实时清算等多种场景。这类系统通常需要高并发处理能力、实时数据存储和高效检索功能,同时需要在数据一致性与性能之间找到平衡。随着交易数据的规模和复杂性不断增长,传统交易系统架构在应对这些挑战时,往往因固定的表结构和横向扩展能力的不足而受限。

MongoDB 作为一款分布式文档型数据库,以其灵活的架构、高吞吐能力和内置的事务支持,为构建复杂、高效的交易系统提供了一种现代化的解决方案,能够满足多样化的业务需求。

问题描述与挑战

交易系统上的核心功能包括账户管理、余额查询、充值、消费以及交易记录的管理和查询。如下图所示,显示用户的账户余额:

以及显示用户的交易记录:

构建一个高效、稳定的现代交易系统面临诸多挑战,主要体现在以下几个方面:

交易系统需要能够处理大规模的并发请求,尤其是在金融领域和电子商务中,用户同时进行交易、查询余额和其他操作的场景非常普遍。如果系统无法承载高并发,可能会导致响应延迟甚至系统崩溃。

  • 数据一致性与事务管理 交易系统必须确保数据的一致性,尤其是涉及账户余额、交易记录和消费等敏感操作时。一旦数据不一致,可能会引发财务风险和用户信任问题。分布式环境下的事务管理进一步加大了实现难度。

  • 灵活的数据建模 传统关系型数据库通常使用固定表结构,难以应对交易系统中复杂多变的业务逻辑和频繁变化的需求。例如,新增业务功能可能需要调整数据模型,这会导致系统修改成本高昂,甚至中断服务。

  • 实时数据处理与查询性能 在海量交易数据的背景下,系统需要能够高效存储、检索并实时处理用户的查询请求,例如账户余额查询和历史交易明细的检索。这要求底层数据库在提供高吞吐能力的同时,也要有极快的查询性能。

  • 系统可扩展性 随着业务的增长,交易系统的数据规模会持续增加。这就要求系统具备良好的横向扩展能力,以确保在扩展服务器或存储节点时,性能能够线性提升。

  • 为达到大规模数据存储和高并发读写的目的,数据建模是问题的关键。MongoDB 的文档模型以灵活和高效著称,可以轻松适应复杂多变的业务需求。

    交易系统的核心功能,包括以下 6 个 API:

  • api/CreateAccount 创建用户账户,包括初始化用户基本信息和初始余额。

  • api/GetBalance 查询账户余额,需要实时返回准确的数据,确保一致性。

  • api/GetTransactionById 根据交易 ID 查询具体交易详情,支持快速精准的查询。

  • api/GetTransactions 根据账户 ID 获取历史交易记录,支持分页和时间范围过滤,便于用户查询大规模数据。

  • api/Recharge 用户账户充值操作,涉及事务处理,确保账户余额更新和交易记录的同步。

  • api/Purchase 消费功能,涉及事务处理,并记录消费详情。

  • 针对上述需求,设计两张表存储用户的资金状态和资金变动的详细信息。两张表通过 Uuid 关联交易,实现高效查询和更新操作。

  • AccountEntry 表示系统中每个用户的账户基本信息,主要包含以下字段:
  • Uuid (string) 每个账户的唯一标识符,用于在系统中唯一定位一个账户,全局唯一。

  • Balance (decimal) 账户当前的余额,精确到小数,用于记录用户可用的资金总额。

  • 代码定义如下:

    public class AccountEntry
        public string Uuid { get; set; }
        public decimal Balance { get; set; }
        public DateTime CreateTime { get; set; }
        public DateTime UpdateTime { get; set; }
    
  • TransEntry记录系统中的每一笔交易,支持不同类型的操作,其字段描述如下:
  • Tid (string) 每笔交易的唯一标识符,唯一定位某一笔具体交易。

  • Uuid (string) 对应账户的唯一标识符,作为外键关联到 AccountEntry,表示这笔交易属于哪个账户。

  • Amount (decimal) 交易金额,记录本次交易涉及的资金数额。

  • NewBalance (decimal) 交易完成后的账户余额快照,便于追踪资金流动,提供账单审计功能。

  • Type (TransType) 交易类型枚举,表示交易的性质。

  • 代码定义如下:

    public class TransEntry
        public string Tid { get; set; }
        public string Uuid { get; set; }
        public decimal Amount { get; set; }
        public decimal NewBalance { get; set; }
        public TransType Type { get; set; }
        public DateTime CreateTime { get; set; }
        public DateTime UpdateTime { get; set; }
    public enum TransType
        None = 0,
        Recharge = 1,
        Purchase = 2,
    

    这里具体实现各个API,并通过实际的读取方式来确定所需要的索引和片键。由简单到复杂,逐一介绍。

    CreateAccount API

    创建账户非常简单,此处并未使用事务处理,原因在于用户的uuid是主键,如果同样uuid的账户存在,则会触发插入失败:

    public async Task<AccountEntry> CreateAccountAsync(string uuid, CancellationToken cancellationToken = default)
        using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
            var newAccount = new AccountEntry
                Uuid = uuid,
                Balance = 0,
                CreateTime = DateTime.UtcNow,
                UpdateTime = DateTime.UtcNow
            await _accountCollection.InsertOneAsync(newAccount, cancellationToken: cts.Token);
            return newAccount;
    

    GetBalance API

    获取余额的实现很直观,需要注意的是此处IClientSessionHandle是可选参数,当传入该参数时,可实现复杂事务:

    public async Task<decimal> GetBalanceAsync(string uuid, CancellationToken ct, IClientSessionHandle s = null)
        AccountEntry account;
        if (s == null)
            account = await _accountCollection.Find(x => x.Uuid == uuid).FirstOrDefaultAsync(ct);
            account = await _accountCollection.Find(s, x => x.Uuid == uuid).FirstOrDefaultAsync(ct);
        if (account == null)
            throw new KeyNotFoundException($"Account '{uuid}' does not exist.");
        return account.Balance;
    

    此API需要Account表索引:(Uuid)。

    AddBalance与DecreaseBalance

    对于余额的操作也是构建后续交易API的基础,这两个API的实现使用MongoDB的Inc原语进行原子操作,同时支持事务。它们不会被单独使用,因为增加或扣减余额都需要有对应的交易记录,因此,IClientSessionHandle是必选参数:

    private async Task<AccountEntry> AddBalanceAsync(string accountId, decimal amount, CancellationToken
    
    
    
    
        
     ct, IClientSessionHandle s)
        if (amount <= 0)
            throw new ArgumentException("Amount should > 0");
        var filter = Builders<AccountEntry>.Filter.Eq(x => x.Uuid, accountId);
        var update = Builders<AccountEntry>.Update
            .Inc(x => x.Balance, amount)
            .Set(x => x.UpdateTime, DateTime.UtcNow);
        var options = new FindOneAndUpdateOptions<AccountEntry> { ReturnDocument = ReturnDocument.After, IsUpsert = true };
        return await _accountCollection.FindOneAndUpdateAsync(s, filter, update, options, cancellationToken: ct);
    private async Task<AccountEntry> DecreaseBalanceAsync(string accountId, decimal amount, CancellationToken ct, IClientSessionHandle s)
        if (amount <= 0)
            throw new ArgumentException("Amount should > 0");
        var filter = Builders<AccountEntry>.Filter.And(
            Builders<AccountEntry>.Filter.Eq(x => x.Uuid, accountId),
            Builders<AccountEntry>.Filter.Gte(x => x.Balance, amount)
        var update = Builders<AccountEntry>.Update
            .Inc(x => x.Balance, -amount)
            .Set(x => x.UpdateTime, DateTime.UtcNow);
        var options = new FindOneAndUpdateOptions<AccountEntry> { ReturnDocument = ReturnDocument.After };
        return await _accountCollection.FindOneAndUpdateAsync(s, filter, update, options, cancellationToken: ct);
    

    值得一提的是,在我们的设计中,这两个API中的amount都是正数,好处是在扣减余额可以加入Filter.Gte(x => x.Balance, amount),进一步保证扣除余额的正确性。

    这两个API需要Account表索引:(Uuid)。

    GetTransactionByTid与GetTransactions

    这两个API可以获取交易记录,非常简单:

    public async Task<TransEntry> GetTransactionByTidAsync(string tid, CancellationToken ct, IClientSessionHandle s = null)
        if (s == null)
            return await _transCollection.Find(x => x.Tid == tid).FirstOrDefaultAsync(ct);
        return await _transCollection.Find(s, x => x.Tid == tid).FirstOrDefaultAsync(ct);
    public async Task<List<TransEntry>> GetTransactionsAsync(string accountId, CancellationToken ct, int n = 10)
        var result = await _transCollection.Find(x => x.Uuid == accountId).SortBy(x => x.CreateTime).Limit(n).ToListAsync(ct);
        return result;
    

    这两个API需要Trans表索引:(Uuid, CreateTime),(Tid)。

    Recharge API

    下面到了最复杂的两个交易API:Recharge与Purchase。注意这两个API除了保证一致性,还需要保证幂等性,也就是说支持多次重复调用,不影响最终结果。

    对于Recharge,其中涉及三个操作,需要使用事务:

  • GetTransactionByTid: 调用方需要传入Tid,防止重复交易,幂等性由这步操作进行保证。实际应用中,该Tid可以使用第三方金融系统返回的id作为唯一标识。
  • AddBalance:前面已经实现了该API。
  • InsertTransaction:增加一条充值交易记录。
  • 上述三个操作的任意一步失败,都可以利用MongoDB的事务进行回滚,从而保证交易的强一致性。

    public async Task<TransResult> RechargeAsync(string accountId, string tid, decimal amount)
        using (var session = await _mongoClient.StartSessionAsync())
        using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
            return await session.WithTransactionAsync(
                async (s, ct) =>
                    var existingTransEntry = await GetTransactionByTidAsync(tid, ct, s);
                    if (existingTransEntry != null)
                        return new TransResult { Code = TransCode.DuplicateTrans };
                    var newBalanceEntry = await AddBalanceAsync(accountId, amount, ct, s);
                    var newTransEntry = new TransEntry
                        Tid = GenerateTid(),
                        Uuid = accountId,
                        Amount = amount,
                        NewBalance = newBalanceEntry.Balance,
                        Type = TransType.Recharge,
                        CreateTime = DateTime.UtcNow,
                        UpdateTime = DateTime.UtcNow,
    
    
    
    
        
    
                    await _transCollection.InsertOneAsync(s, newTransEntry, cancellationToken: ct);
                    return new TransResult
                        NewBalance = newTransEntry.NewBalance,
                        Tid = newTransEntry.Tid
                }, cancellationToken: cts.Token);
    

    Purchase API

    Purchase API的实现与Recharge基本相同,把AddBalance换为DecreaseBalance

    public async Task<TransResult> PurchaseAsync(string accountId, string tid, decimal amount)
        using (var session = await _mongoClient.StartSessionAsync())
        using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
            return await session.WithTransactionAsync(
                async (s, ct) =>
                    var existingTransEntry = await GetTransactionByTidAsync(tid, ct, s);
                    if (existingTransEntry != null)
                        return new TransResult { Code = TransCode.DuplicateTrans };
                    var newBalanceEntry = await DecreaseBalanceAsync(accountId, amount, ct, s);
                    if (newBalanceEntry == null)
                        return new TransResult { Code = TransCode.InsufficientBalance };
                    var newTransEntry = new TransEntry
                        Tid = GenerateTid(),
                        Uuid = accountId,
                        Amount = amount,
                        NewBalance = newBalanceEntry.Balance,
                        Type = TransType.Purchase,
                        CreateTime = DateTime.UtcNow,
                        UpdateTime = DateTime.UtcNow,
                    await _transCollection.InsertOneAsync(s, newTransEntry, cancellationToken: ct);
                    return new TransResult
                        NewBalance = newTransEntry.NewBalance,
                        Tid = newTransEntry.Tid
                cancellationToken: cts.Token);
    

    在实践中,可以通过修改Purchase API,在其中加入业务逻辑,实现业务逻辑与交易系统的一致性。

    索引与片键

    Account表需要索引:

  • (Uuid)
  • Trans表需要索引:

  • (Uuid, CreateTime)
  • (Tid)
  • 创建 10 个账户,并为每个账户进行初始充值 1000。对每个账户生成 5 笔交易,随机决定交易类型(充值或消费),交易金额在 0~300 之间。 打印每个账户的当前余额,以及最近10笔交易的详情。

    public async Task RunAsync()
        var random = new Random(42);
        // Create 10 accounts
        var accountIds = new List<string>();
        for (int i = 0; i < 10; i++)
            var accountId = GenerateAccountUuid();
            var account = await CreateAccountAsync(accountId);
            accountIds.Add(account.Uuid);
            await RechargeAsync(account.Uuid, GenerateTid(), 1000);
            Console.WriteLine($"Created account {account.Uuid} with initial balance of 1000.");
        Console.WriteLine("------------------------------------------------------------------------------");
        foreach (var accountId in accountIds)
            for (int i = 0; i < 5; i++)
                var isRecharge = random.Next(0, 2) == 0;
                var amount = (decimal)(random.NextDouble() * 300);
                var tid = GenerateTid();
                if (isRecharge)
                    var result = await RechargeAsync(accountId, tid, amount);
                    if (result.Code == TransCode.Success)
                        Console.WriteLine($"Recharge: Account {accountId}, Amount: {Math.Round(amount, 2)}, New Balance: {Math.Round(result.NewBalance, 2)}, Tid: {tid}");
                        Console.WriteLine($"Recharge failed: {result.Code}");
                    var result = await PurchaseAsync(accountId,
    
    
    
    
        
     tid, amount);
                    if (result.Code == TransCode.Success)
                        Console.WriteLine($"Purchase: Account {accountId}, Amount: {Math.Round(amount, 2)}, New Balance: {Math.Round(result.NewBalance, 2)}, Tid: {tid}");
                        Console.WriteLine($"Purchase failed: {result.Code}");
        Console.WriteLine("------------------------------------------------------------------------------");
        foreach (var accountId in accountIds)
            var balance = await GetBalanceAsync(accountId, CancellationToken.None);
            var transactions = await GetTransactionsAsync(accountId, CancellationToken.None);
            Console.WriteLine($"Account {accountId} Balance: {Math.Round(balance, 2)}");
            Console.WriteLine("Recent Transactions:");
            foreach (var transaction in transactions)
                Console.WriteLine($"Tid: {transaction.Tid}, Type: {transaction.Type}, Amount: {Math.Round(transaction.Amount, 2)}, New Balance: {Math.Round(transaction.NewBalance, 2)}, Time: {transaction.CreateTime}");
            Console.WriteLine("****************************************************************************");
    

    部分运行结果:

    Account Acc_BE2E2B2FD3A2490CBEEA9625C3BD7C18 Balance: 1267.83 Recent Transactions: Tid: T_6DA267BECAC945E2973903C7EE36C599, Type: Recharge, Amount: 1000, New Balance: 1000, Time: 11/24/2024 4:00:42 AM Tid: T_6BB3C3B2E9CD4197B609957C2937D218, Type: Purchase, Amount: 42.27, New Balance: 957.73, Time: 11/24/2024 4:00:43 AM Tid: T_0D63FB52BCBF4FF382D7C611252329F4, Type: Recharge, Amount: 156.83, New Balance: 1114.56, Time: 11/24/2024 4:00:43 AM Tid: T_30201F58B3AA4BD6B14C4B9DC0A20272, Type: Recharge, Amount: 78.78, New Balance: 1193.33, Time: 11/24/2024 4:00:43 AM Tid: T_42837124561748BFBECA1677FD951338, Type: Purchase, Amount: 153.88, New Balance: 1039.46, Time: 11/24/2024 4:00:43 AM Tid: T_16DC47F772814D4A8A3E1DBCF26B661D, Type: Recharge, Amount: 228.38, New Balance: 1267.83, Time: 11/24/2024 4:00:43 AM

    使用MongoDB Compass连接部署在MongoDB Atlas上的DB:

    本文基于 MongoDB 提供了完整的账户管理与交易支持功能,适合于中小型交易系统,易于扩展到更加复杂的应用场景,例如多货币支持、国际支付等。每个功能模块通过事务、过滤器和幂等性处理相互配合,构建了一个安全、高效且易于扩展的交易系统框架。这种设计能够满足实际业务中高并发、数据一致性和复杂交易的需求,同时提供了明确的错误处理路径,便于维护与扩展。

    Demo代码

    填充ConnectionString即可:

    using MongoDB.Bson.Serialization.Conventions;
    using MongoDB.Driver;
    namespace MongoTest
        class Program
            static void Main(string[] args)
                var model = new TransModel();
                model.RunAsync().Wait();
                Console.WriteLine("Done");
        public class AccountEntry
            public string Uuid { get; set; }
            public decimal Balance { get; set; }
            public DateTime CreateTime { get; set; }
            public DateTime UpdateTime { get; set; }
        public class TransEntry
            public string Tid { get; set; }
            public string Uuid { get; set; }
            public decimal Amount { get; set; }
            public decimal NewBalance { get; set; }
            public TransType Type { get; set; }
            public DateTime CreateTime { get; set; }
            public DateTime UpdateTime { get; set; }
        public enum TransType
            None = 0,
            Recharge = 1,
            Purchase = 2,
        public class TransResult
            public string Tid { get; set; }
            public decimal NewBalance { get; set; }
            public TransCode Code { get; set; }
        public enum TransCode
            Success = 0,
            DuplicateTrans = 1,
            Purchase = 2,
            InsufficientBalance = 3
        public class TransModel
            private readonly IMongoClient _mongoClient;
            private
    
    
    
    
        
     readonly IMongoCollection<AccountEntry> _accountCollection;
            private readonly IMongoCollection<TransEntry> _transCollection;
            private const string DatabaseName = "Transaction";
            private const string AccountCollectionName = "Account";
            private const string TransCollectionName = "Trans";
            public const string ConnectionString = "";
            public TransModel()
                _mongoClient = new MongoClient(ConnectionString);
                var database = _mongoClient.GetDatabase(DatabaseName);
                var ignoreExtraElementsConvention = new ConventionPack { new IgnoreExtraElementsConvention(true) };
                ConventionRegistry.Register("IgnoreExtraElements", ignoreExtraElementsConvention, type => true);
                _accountCollection = database.GetCollection<AccountEntry>(AccountCollectionName);
                _transCollection = database.GetCollection<TransEntry>(TransCollectionName);
            public static string GenerateTid() => $"T_{Guid.NewGuid().ToString("N").ToUpper()}";
            public static string GenerateAccountUuid() => $"Acc_{Guid.NewGuid().ToString("N").ToUpper()}";
            public async Task<AccountEntry> CreateAccountAsync(string uuid, CancellationToken cancellationToken = default)
                using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
                    var newAccount = new AccountEntry
                        Uuid = uuid,
                        Balance = 0,
                        CreateTime = DateTime.UtcNow,
                        UpdateTime = DateTime.UtcNow
                    await _accountCollection.InsertOneAsync(newAccount, cancellationToken: cts.Token);
                    return newAccount;
            public async Task<decimal> GetBalanceAsync(string uuid, CancellationToken ct, IClientSessionHandle s = null)
                AccountEntry account;
                if (s == null)
                    account = await _accountCollection.Find(x => x.Uuid == uuid).FirstOrDefaultAsync(ct);
                    account = await _accountCollection.Find(s, x => x.Uuid == uuid).FirstOrDefaultAsync(ct);
                if (account == null)
                    throw new KeyNotFoundException($"Account '{uuid}' does not exist.");
                return account.Balance;
            private async Task<AccountEntry> AddBalanceAsync(string accountId, decimal amount, CancellationToken ct, IClientSessionHandle s)
                if (amount <= 0)
                    throw new ArgumentException("Amount should > 0");
                var filter = Builders<AccountEntry>.Filter.Eq(x => x.Uuid, accountId);
                var update = Builders<AccountEntry>.Update
                    .Inc(x => x.Balance, amount)
                    .Set(x => x.UpdateTime, DateTime.UtcNow);
                var options = new FindOneAndUpdateOptions<AccountEntry> { ReturnDocument = ReturnDocument.After, IsUpsert = true };
                return await _accountCollection.FindOneAndUpdateAsync(s, filter, update, options, cancellationToken: ct);
            private async Task<AccountEntry> DecreaseBalanceAsync(string accountId, decimal amount, CancellationToken ct, IClientSessionHandle s)
                if (amount <= 0)
                    throw new ArgumentException(
    
    
    
    
        
    "Amount should > 0");
                var filter = Builders<AccountEntry>.Filter.And(
                    Builders<AccountEntry>.Filter.Eq(x => x.Uuid, accountId),
                    Builders<AccountEntry>.Filter.Gte(x => x.Balance, amount)
                var update = Builders<AccountEntry>.Update
                    .Inc(x => x.Balance, -amount)
                    .Set(x => x.UpdateTime, DateTime.UtcNow);
                var options = new FindOneAndUpdateOptions<AccountEntry> { ReturnDocument = ReturnDocument.After };
                return await _accountCollection.FindOneAndUpdateAsync(s, filter, update, options, cancellationToken: ct);
            public async Task<TransEntry> GetTransactionByTidAsync(string tid, CancellationToken ct, IClientSessionHandle s = null)
                if (s == null)
                    return await _transCollection.Find(x => x.Tid == tid).FirstOrDefaultAsync(ct);
                return await _transCollection.Find(s, x => x.Tid == tid).FirstOrDefaultAsync(ct);
            public async Task<List<TransEntry>> GetTransactionsAsync(string accountId, CancellationToken ct, int n = 10)
                var result = await _transCollection.Find(x => x.Uuid == accountId).SortBy(x => x.CreateTime).Limit(n).ToListAsync(ct);
                return result;
            public async Task<TransResult> RechargeAsync(string accountId, string tid, decimal amount)
                using (var session = await _mongoClient.StartSessionAsync())
                using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
                    return await session.WithTransactionAsync(
                        async (s, ct) =>
                            var existingTransEntry = await GetTransactionByTidAsync(tid, ct, s);
                            if (existingTransEntry != null)
                                return new TransResult { Code = TransCode.DuplicateTrans };
                            var newBalanceEntry = await AddBalanceAsync(accountId, amount, ct, s);
                            var newTransEntry = new TransEntry
                                Tid = GenerateTid(),
                                Uuid = accountId,
                                Amount = amount,
                                NewBalance = newBalanceEntry.Balance,
                                Type = TransType.Recharge,
                                CreateTime = DateTime.UtcNow,
                                UpdateTime = DateTime.UtcNow,
                            await _transCollection.InsertOneAsync(s, newTransEntry, cancellationToken: ct);
                            return new TransResult
                                NewBalance = newTransEntry.NewBalance,
                                Tid = newTransEntry.Tid
                        }, cancellationToken: cts.Token);
            public async Task<TransResult> PurchaseAsync(string accountId, string tid, decimal amount)
                using (var session = await _mongoClient.StartSessionAsync())
                using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
                    return await session.WithTransactionAsync(
                        async (s, ct) =>
                            var existingTransEntry = await GetTransactionByTidAsync(tid, ct, s);
                            if (existingTransEntry != null)
                                return new TransResult { Code = TransCode.DuplicateTrans };
                            var newBalanceEntry = await DecreaseBalanceAsync(accountId, amount, ct, s);
                            if (
    
    
    
    
        
    newBalanceEntry == null)
                                return new TransResult { Code = TransCode.InsufficientBalance };
                            var newTransEntry = new TransEntry
                                Tid = GenerateTid(),
                                Uuid = accountId,
                                Amount = amount,
                                NewBalance = newBalanceEntry.Balance,
                                Type = TransType.Purchase,
                                CreateTime = DateTime.UtcNow,
                                UpdateTime = DateTime.UtcNow,
                            await _transCollection.InsertOneAsync(s, newTransEntry, cancellationToken: ct);
                            return new TransResult
                                NewBalance = newTransEntry.NewBalance,
                                Tid = newTransEntry.Tid
                        cancellationToken: cts.Token);
            public async Task RunAsync()
                var random = new Random(42);
                // Create 10 accounts
                var accountIds = new List<string>();
                for (int i = 0; i < 10; i++)
                    var accountId = GenerateAccountUuid();
                    var account = await CreateAccountAsync(accountId);
                    accountIds.Add(account.Uuid);
                    await RechargeAsync(account.Uuid, GenerateTid(), 1000);
                    Console.WriteLine($"Created account {account.Uuid} with initial balance of 1000.");
                Console.WriteLine("------------------------------------------------------------------------------");
                foreach (var accountId in accountIds)
                    for (int i = 0; i < 5; i++)
                        var isRecharge = random.Next(0, 2) == 0;
                        var amount = (decimal)(random.NextDouble() * 300);
                        var tid = GenerateTid();
                        if (isRecharge)
                            var result = await RechargeAsync(accountId, tid, amount);
                            if (result.Code == TransCode.Success)
                                Console.WriteLine($"Recharge: Account {accountId}, Amount: {Math.Round(amount, 2)}, New Balance: {Math.Round(result.NewBalance, 2)}, Tid: {tid}");
                                Console.WriteLine($"Recharge failed: {result.Code}");
                            var result = await PurchaseAsync(accountId, tid, amount);
                            if (result.Code == TransCode.Success)
                                Console.WriteLine($"Purchase: Account {accountId}, Amount: {Math.Round(amount, 2)}, New Balance: {Math.Round(result.NewBalance, 2)}, Tid: {tid}");
                                Console.WriteLine($"Purchase failed: {result.Code}");
                Console.WriteLine("------------------------------------------------------------------------------");
                foreach (var accountId in accountIds)
                    var balance = await GetBalanceAsync(accountId, CancellationToken.None);
                    var transactions = await GetTransactionsAsync(accountId, CancellationToken.None);
                    Console.WriteLine($"Account {accountId} Balance: {Math.Round(balance, 2)}");
                    Console.WriteLine("Recent Transactions:");
                    foreach (var transaction in transactions)
                        Console.WriteLine($"Tid: {transaction.Tid}, Type: {transaction.Type}, Amount: {Math.Round(transaction.Amount, 2)}, New Balance: {Math.Round(transaction.NewBalance, 2)}, Time: {transaction.CreateTime}");
                    Console.WriteLine("****************************************************************************");