private async Task UpdatePayoutsAwaitingForPayment(NewOnChainTransactionEvent newTransaction, AddressTrackedSource addressTrackedSource) { try { var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(newTransaction.CryptoCode); var destinationSum = newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network)); var destination = addressTrackedSource.Address.ToString(); var paymentMethodId = new PaymentMethodId(newTransaction.CryptoCode, BitcoinPaymentType.Instance); await using var ctx = _dbContextFactory.CreateContext(); var payouts = await ctx.Payouts .Include(o => o.StoreData) .Include(o => o.PullPaymentData) .Where(p => p.State == PayoutState.AwaitingPayment) .Where(p => p.PaymentMethodId == paymentMethodId.ToString()) #pragma warning disable CA1307 // Specify StringComparison .Where(p => destination.Equals(p.Destination)) #pragma warning restore CA1307 // Specify StringComparison .ToListAsync(); var payoutByDestination = payouts.ToDictionary(p => p.Destination); if (!payoutByDestination.TryGetValue(destination, out var payout)) { return; } var payoutBlob = payout.GetBlob(_jsonSerializerSettings); if (payoutBlob.CryptoAmount is null || // The round up here is not strictly necessary, this is temporary to fix existing payout before we // were properly roundup the crypto amount destinationSum != BTCPayServer.Extensions.RoundUp(payoutBlob.CryptoAmount.Value, network.Divisibility)) { return; } var derivationSchemeSettings = payout.StoreData .GetDerivationSchemeSettings(_btcPayNetworkProvider, newTransaction.CryptoCode).AccountDerivation; var storeWalletMatched = (await _explorerClientProvider.GetExplorerClient(newTransaction.CryptoCode) .GetTransactionAsync(derivationSchemeSettings, newTransaction.NewTransactionEvent.TransactionData.TransactionHash)); //if the wallet related to the store related to the payout does not have the tx: it is external var isInternal = storeWalletMatched is { }; var proof = ParseProof(payout) as PayoutTransactionOnChainBlob ?? new PayoutTransactionOnChainBlob() { Accounted = isInternal }; var txId = newTransaction.NewTransactionEvent.TransactionData.TransactionHash; if (!proof.Candidates.Add(txId)) { return; } if (isInternal) { payout.State = PayoutState.InProgress; var walletId = new WalletId(payout.StoreDataId, newTransaction.CryptoCode); _eventAggregator.Publish(new UpdateTransactionLabel(walletId, newTransaction.NewTransactionEvent.TransactionData.TransactionHash, UpdateTransactionLabel.PayoutTemplate(new () { { payout.PullPaymentDataId ?? "", new List <string> { payout.Id } } }, walletId.ToString()))); } else { await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId), new ExternalPayoutTransactionNotification() { PaymentMethod = payout.PaymentMethodId, PayoutId = payout.Id, StoreId = payout.StoreDataId }); } proof.TransactionId ??= txId; SetProofBlob(payout, proof); await ctx.SaveChangesAsync(); } catch (Exception ex) { Logs.PayServer.LogWarning(ex, "Error while processing a transaction in the pull payment hosted service"); } }
private async Task UpdatePayoutsAwaitingForPayment(NewOnChainTransactionEvent newTransaction) { try { var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(newTransaction.CryptoCode); Dictionary <string, decimal> destinations; if (newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource addressTrackedSource) { destinations = new Dictionary <string, decimal>() { { addressTrackedSource.Address.ToString(), newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network)) } }; } else { destinations = newTransaction.NewTransactionEvent.TransactionData.Transaction.Outputs .GroupBy(txout => txout.ScriptPubKey) .ToDictionary( txoutSet => txoutSet.Key.GetDestinationAddress(network.NBitcoinNetwork).ToString(), txoutSet => txoutSet.Sum(txout => txout.Value.ToDecimal(MoneyUnit.BTC))); } var paymentMethodId = new PaymentMethodId(newTransaction.CryptoCode, BitcoinPaymentType.Instance); using var ctx = _dbContextFactory.CreateContext(); var payouts = await ctx.Payouts .Include(o => o.PullPaymentData) .Where(p => p.State == PayoutState.AwaitingPayment) .Where(p => p.PaymentMethodId == paymentMethodId.ToString()) .Where(p => destinations.Keys.Contains(p.Destination)) .ToListAsync(); var payoutByDestination = payouts.ToDictionary(p => p.Destination); foreach (var destination in destinations) { if (!payoutByDestination.TryGetValue(destination.Key, out var payout)) { continue; } var payoutBlob = payout.GetBlob(_jsonSerializerSettings); if (payoutBlob.CryptoAmount is null || // The round up here is not strictly necessary, this is temporary to fix existing payout before we // were properly roundup the crypto amount destination.Value != BTCPayServer.Extensions.RoundUp(payoutBlob.CryptoAmount.Value, network.Divisibility)) { continue; } var proof = ParseProof(payout) as PayoutTransactionOnChainBlob; if (proof is null) { proof = new PayoutTransactionOnChainBlob() { Accounted = !(newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource), }; } var txId = newTransaction.NewTransactionEvent.TransactionData.TransactionHash; if (proof.Candidates.Add(txId)) { if (proof.Accounted is true) { payout.State = PayoutState.InProgress; var walletId = new WalletId(payout.PullPaymentData.StoreId, newTransaction.CryptoCode); _eventAggregator.Publish(new UpdateTransactionLabel(walletId, newTransaction.NewTransactionEvent.TransactionData.TransactionHash, UpdateTransactionLabel.PayoutTemplate(payout.Id, payout.PullPaymentDataId, walletId.ToString()))); } if (proof.TransactionId is null) { proof.TransactionId = txId; } SetProofBlob(payout, proof); } } await ctx.SaveChangesAsync(); } catch (Exception ex) { Logs.PayServer.LogWarning(ex, "Error while processing a transaction in the pull payment hosted service"); } }