🐛 Tried to fix fund claim cocurrency issue
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user