🐛 Tried to fix fund claim cocurrency issue

This commit is contained in:
2025-11-17 00:18:57 +08:00
parent 6313f15375
commit 861fc7cafa

View File

@@ -609,99 +609,112 @@ public class PaymentService(
public async Task<SnWalletTransaction> ReceiveFundAsync(Guid recipientAccountId, Guid fundId) public async Task<SnWalletTransaction> ReceiveFundAsync(Guid recipientAccountId, Guid fundId)
{ {
var fund = await db.WalletFunds // Use a transaction to ensure atomicity
.Include(f => f.Recipients) await using var transactionScope = await db.Database.BeginTransactionAsync();
.FirstOrDefaultAsync(f => f.Id == fundId);
if (fund == null) try
throw new InvalidOperationException("Fund not found");
if (fund.Status is Shared.Models.FundStatus.Expired or Shared.Models.FundStatus.Refunded)
throw new InvalidOperationException("Fund is no longer available");
var recipient = fund.Recipients.FirstOrDefault(r => r.RecipientAccountId == recipientAccountId);
// Handle open mode fund - create recipient if not exists
if (recipient is null && fund.IsOpen)
{ {
// Check if recipient has already claimed from this fund // Load fund with proper locking to prevent concurrent modifications
var existingClaim = fund.Recipients.FirstOrDefault(r => r.RecipientAccountId == recipientAccountId); var fund = await db.WalletFunds
if (existingClaim != null) .Include(f => f.Recipients)
throw new InvalidOperationException("You have already claimed from this fund"); .FirstOrDefaultAsync(f => f.Id == fundId);
// Calculate amount for new recipient if (fund == null)
var amount = CalculateDynamicAmount(fund); throw new InvalidOperationException("Fund not found");
if (amount <= 0)
throw new InvalidOperationException("No funds remaining to claim");
// Create new recipient if (fund.Status is Shared.Models.FundStatus.Expired or Shared.Models.FundStatus.Refunded)
recipient = new SnWalletFundRecipient throw new InvalidOperationException("Fund is no longer available");
var recipient = fund.Recipients.FirstOrDefault(r => r.RecipientAccountId == recipientAccountId);
// Handle open mode fund - create recipient if not exists
if (recipient is null && fund.IsOpen)
{ {
RecipientAccountId = recipientAccountId, // Check if recipient has already claimed from this fund
Amount = amount, var existingClaim = fund.Recipients.FirstOrDefault(r => r.RecipientAccountId == recipientAccountId);
IsReceived = false if (existingClaim != null)
}; throw new InvalidOperationException("You have already claimed from this fund");
fund.Recipients.Add(recipient);
fund.RemainingAmount -= amount;
}
else if (recipient is null)
{
throw new InvalidOperationException("You are not a recipient of this fund");
}
// For closed mode funds, calculate amount dynamically if not already set // Calculate amount for new recipient
if (!fund.IsOpen && recipient.Amount == 0) var amount = CalculateDynamicAmount(fund);
{ if (amount <= 0)
var amount = CalculateDynamicAmount(fund); throw new InvalidOperationException("No funds remaining to claim");
if (amount <= 0)
throw new InvalidOperationException("No funds remaining to claim");
recipient.Amount = amount; // Create new recipient
fund.RemainingAmount -= amount; recipient = new SnWalletFundRecipient
} {
RecipientAccountId = recipientAccountId,
Amount = amount,
IsReceived = false
};
fund.Recipients.Add(recipient);
fund.RemainingAmount -= amount;
}
else if (recipient is null)
{
throw new InvalidOperationException("You are not a recipient of this fund");
}
if (recipient.IsReceived) // For closed mode funds, calculate amount dynamically if not already set
throw new InvalidOperationException("You have already received this fund"); if (!fund.IsOpen && recipient.Amount == 0)
{
var amount = CalculateDynamicAmount(fund);
if (amount <= 0)
throw new InvalidOperationException("No funds remaining to claim");
var recipientWallet = await wat.GetWalletAsync(recipientAccountId); recipient.Amount = amount;
if (recipientWallet == null) fund.RemainingAmount -= amount;
throw new InvalidOperationException("Recipient wallet not found"); }
// Create transaction to transfer funds to recipient if (recipient.IsReceived)
var transaction = await CreateTransactionAsync( throw new InvalidOperationException("You have already received this fund");
payerWalletId: null, // System transfer
payeeWalletId: recipientWallet.Id,
currency: fund.Currency,
amount: recipient.Amount,
remarks: $"Received fund portion from {fund.CreatorAccountId}",
type: Shared.Models.TransactionType.System,
silent: true
);
// Mark as received var recipientWallet = await wat.GetWalletAsync(recipientAccountId);
recipient.IsReceived = true; if (recipientWallet == null)
recipient.ReceivedAt = SystemClock.Instance.GetCurrentInstant(); throw new InvalidOperationException("Recipient wallet not found");
// Update fund status // Create transaction to transfer funds to recipient
if (fund.IsOpen) var walletTransaction = await CreateTransactionAsync(
{ payerWalletId: null, // System transfer
if (fund.RemainingAmount <= 0) payeeWalletId: recipientWallet.Id,
fund.Status = Shared.Models.FundStatus.FullyReceived; currency: fund.Currency,
amount: recipient.Amount,
remarks: $"Received fund portion from {fund.CreatorAccountId}",
type: Shared.Models.TransactionType.System,
silent: true
);
// Mark as received
recipient.IsReceived = true;
recipient.ReceivedAt = SystemClock.Instance.GetCurrentInstant();
// Update fund status
if (fund.IsOpen)
{
if (fund.RemainingAmount <= 0)
fund.Status = Shared.Models.FundStatus.FullyReceived;
else
fund.Status = Shared.Models.FundStatus.PartiallyReceived;
}
else else
fund.Status = Shared.Models.FundStatus.PartiallyReceived; {
var allReceived = fund.Recipients.All(r => r.IsReceived);
if (allReceived)
fund.Status = Shared.Models.FundStatus.FullyReceived;
else
fund.Status = Shared.Models.FundStatus.PartiallyReceived;
}
await db.SaveChangesAsync();
await transactionScope.CommitAsync();
return walletTransaction;
} }
else catch
{ {
var allReceived = fund.Recipients.All(r => r.IsReceived); await transactionScope.RollbackAsync();
if (allReceived) throw;
fund.Status = Shared.Models.FundStatus.FullyReceived;
else
fund.Status = Shared.Models.FundStatus.PartiallyReceived;
} }
await db.SaveChangesAsync();
return transaction;
} }
public async Task ProcessExpiredFundsAsync() public async Task ProcessExpiredFundsAsync()