2021-04-13 10:36:49 +02:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Text;
|
2022-11-22 20:17:29 +09:00
|
|
|
using System.Threading;
|
2021-04-13 10:36:49 +02:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
using BTCPayServer;
|
2021-07-16 09:57:37 +02:00
|
|
|
using BTCPayServer.Abstractions.Models;
|
2022-04-24 05:19:34 +02:00
|
|
|
using BTCPayServer.Client;
|
2021-04-13 10:36:49 +02:00
|
|
|
using BTCPayServer.Client.Models;
|
|
|
|
using BTCPayServer.Data;
|
|
|
|
using BTCPayServer.Events;
|
|
|
|
using BTCPayServer.HostedServices;
|
|
|
|
using BTCPayServer.Logging;
|
|
|
|
using BTCPayServer.Payments;
|
2024-04-04 16:31:04 +09:00
|
|
|
using BTCPayServer.Payments.Bitcoin;
|
2024-05-01 10:22:07 +09:00
|
|
|
using BTCPayServer.Payouts;
|
2021-04-13 10:36:49 +02:00
|
|
|
using BTCPayServer.Services;
|
2024-04-04 16:31:04 +09:00
|
|
|
using BTCPayServer.Services.Invoices;
|
2021-07-16 09:57:37 +02:00
|
|
|
using BTCPayServer.Services.Notifications;
|
|
|
|
using BTCPayServer.Services.Notifications.Blobs;
|
2021-10-18 05:37:59 +02:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2021-04-13 10:36:49 +02:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using NBitcoin;
|
|
|
|
using NBitcoin.Payment;
|
|
|
|
using NBitcoin.RPC;
|
|
|
|
using NBXplorer.Models;
|
|
|
|
using Newtonsoft.Json;
|
2021-06-10 11:43:45 +02:00
|
|
|
using Newtonsoft.Json.Linq;
|
2021-04-13 10:36:49 +02:00
|
|
|
using NewBlockEvent = BTCPayServer.Events.NewBlockEvent;
|
|
|
|
using PayoutData = BTCPayServer.Data.PayoutData;
|
2021-11-04 08:21:01 +01:00
|
|
|
using StoreData = BTCPayServer.Data.StoreData;
|
2021-04-13 10:36:49 +02:00
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
public string Currency { get; }
|
2021-04-13 10:36:49 +02:00
|
|
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
2024-05-01 10:22:07 +09:00
|
|
|
private readonly PaymentMethodHandlerDictionary _paymentHandlers;
|
2021-04-13 10:36:49 +02:00
|
|
|
private readonly ExplorerClientProvider _explorerClientProvider;
|
|
|
|
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
|
|
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
2021-07-16 09:57:37 +02:00
|
|
|
private readonly NotificationSender _notificationSender;
|
2021-11-22 17:16:08 +09:00
|
|
|
private readonly Logs Logs;
|
2023-12-01 10:50:05 +01:00
|
|
|
private readonly EventAggregator _eventAggregator;
|
2023-11-29 18:51:40 +09:00
|
|
|
private readonly TransactionLinkProviders _transactionLinkProviders;
|
2022-10-11 17:34:29 +09:00
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
PayoutMethodId IHandler<PayoutMethodId>.Id => PayoutMethodId;
|
|
|
|
public PayoutMethodId PayoutMethodId { get; }
|
|
|
|
public PaymentMethodId PaymentMethodId { get; }
|
|
|
|
public BTCPayNetwork Network { get; }
|
2024-05-09 17:20:24 +09:00
|
|
|
public string[] DefaultRateRules => Network.DefaultRateRules;
|
2022-10-11 17:34:29 +09:00
|
|
|
public WalletRepository WalletRepository { get; }
|
|
|
|
|
2021-04-13 10:36:49 +02:00
|
|
|
public BitcoinLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
2024-05-01 10:22:07 +09:00
|
|
|
PayoutMethodId payoutMethodId,
|
|
|
|
BTCPayNetwork network,
|
2024-04-04 16:31:04 +09:00
|
|
|
PaymentMethodHandlerDictionary handlers,
|
2022-10-11 17:34:29 +09:00
|
|
|
WalletRepository walletRepository,
|
2021-12-31 16:59:02 +09:00
|
|
|
ExplorerClientProvider explorerClientProvider,
|
2021-10-18 05:37:59 +02:00
|
|
|
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
2021-12-31 16:59:02 +09:00
|
|
|
ApplicationDbContextFactory dbContextFactory,
|
2021-11-22 17:16:08 +09:00
|
|
|
NotificationSender notificationSender,
|
2023-11-29 18:51:40 +09:00
|
|
|
Logs logs,
|
2023-12-01 10:50:05 +01:00
|
|
|
EventAggregator eventAggregator,
|
2023-11-29 18:51:40 +09:00
|
|
|
TransactionLinkProviders transactionLinkProviders)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
|
|
|
_btcPayNetworkProvider = btcPayNetworkProvider;
|
2024-05-01 10:22:07 +09:00
|
|
|
PayoutMethodId = payoutMethodId;
|
|
|
|
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
|
|
|
Network = network;
|
|
|
|
_paymentHandlers = handlers;
|
2022-10-11 17:34:29 +09:00
|
|
|
WalletRepository = walletRepository;
|
2021-04-13 10:36:49 +02:00
|
|
|
_explorerClientProvider = explorerClientProvider;
|
|
|
|
_jsonSerializerSettings = jsonSerializerSettings;
|
|
|
|
_dbContextFactory = dbContextFactory;
|
2021-07-16 09:57:37 +02:00
|
|
|
_notificationSender = notificationSender;
|
2024-05-01 10:22:07 +09:00
|
|
|
Currency = network.CryptoCode;
|
2021-11-22 17:16:08 +09:00
|
|
|
this.Logs = logs;
|
2023-12-01 10:50:05 +01:00
|
|
|
_eventAggregator = eventAggregator;
|
2023-11-29 18:51:40 +09:00
|
|
|
_transactionLinkProviders = transactionLinkProviders;
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
2023-04-07 08:58:41 +02:00
|
|
|
public async Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
2021-07-16 09:57:37 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
var explorerClient = _explorerClientProvider.GetExplorerClient(Network);
|
2023-04-07 08:58:41 +02:00
|
|
|
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
|
|
|
{
|
2023-04-10 11:07:03 +09:00
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
2023-04-07 08:58:41 +02:00
|
|
|
await WalletRepository.AddWalletTransactionAttachment(
|
2024-05-01 10:22:07 +09:00
|
|
|
new WalletId(claimRequest.StoreId, Network.CryptoCode),
|
2023-04-07 08:58:41 +02:00
|
|
|
bitcoinLikeClaimDestination.Address.ToString(),
|
|
|
|
Attachment.Payout(payoutData.PullPaymentDataId, payoutData.Id), WalletObjectData.Types.Address);
|
|
|
|
}
|
2021-07-16 09:57:37 +02:00
|
|
|
}
|
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(string destination, CancellationToken cancellationToken)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
|
|
|
destination = destination.Trim();
|
|
|
|
try
|
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
if (destination.StartsWith($"{Network.NBitcoinNetwork.UriScheme}:", StringComparison.OrdinalIgnoreCase))
|
2021-10-21 17:43:02 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
return Task.FromResult<(IClaimDestination, string)>((new UriClaimDestination(new BitcoinUrlBuilder(destination, Network.NBitcoinNetwork)), null));
|
2021-10-21 17:43:02 +02:00
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
return Task.FromResult<(IClaimDestination, string)>((new AddressClaimDestination(BitcoinAddress.Create(destination, Network.NBitcoinNetwork)), null));
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
2021-10-18 05:37:59 +02:00
|
|
|
return Task.FromResult<(IClaimDestination, string)>(
|
|
|
|
(null, "A valid address was not provided"));
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-24 20:17:09 +09:00
|
|
|
public (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob)
|
|
|
|
{
|
|
|
|
return (true, null);
|
|
|
|
}
|
|
|
|
|
2021-04-13 10:36:49 +02:00
|
|
|
public IPayoutProof ParseProof(PayoutData payout)
|
|
|
|
{
|
|
|
|
if (payout?.Proof is null)
|
|
|
|
return null;
|
2024-05-01 10:22:07 +09:00
|
|
|
var payoutMethodId = payout.GetPayoutMethodId();
|
|
|
|
if (payoutMethodId is null)
|
2024-04-04 16:31:04 +09:00
|
|
|
return null;
|
2024-05-01 10:22:07 +09:00
|
|
|
var cryptoCode = Network.CryptoCode;
|
2022-08-17 09:45:51 +02:00
|
|
|
ParseProofType(payout.Proof, out var raw, out var proofType);
|
2022-11-15 10:40:57 +01:00
|
|
|
if (proofType == PayoutTransactionOnChainBlob.Type)
|
2021-06-10 11:43:45 +02:00
|
|
|
{
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-11-15 10:40:57 +01:00
|
|
|
var res = raw.ToObject<PayoutTransactionOnChainBlob>(
|
2024-05-01 10:22:07 +09:00
|
|
|
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(payoutMethodId)));
|
2022-11-15 10:40:57 +01:00
|
|
|
if (res == null)
|
|
|
|
return null;
|
2024-04-04 16:31:04 +09:00
|
|
|
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(cryptoCode);
|
2022-11-15 10:40:57 +01:00
|
|
|
return res;
|
2021-06-10 11:43:45 +02:00
|
|
|
}
|
2022-11-15 10:40:57 +01:00
|
|
|
return raw.ToObject<ManualPayoutProof>();
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
2024-03-14 19:13:26 +09:00
|
|
|
public static void ParseProofType(string proof, out JObject obj, out string type)
|
2022-08-17 09:45:51 +02:00
|
|
|
{
|
|
|
|
type = null;
|
|
|
|
if (proof is null)
|
|
|
|
{
|
|
|
|
obj = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-14 19:13:26 +09:00
|
|
|
obj = JObject.Parse(proof);
|
2022-11-15 10:40:57 +01:00
|
|
|
TryParseProofType(obj, out type);
|
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-11-15 10:40:57 +01:00
|
|
|
public static bool TryParseProofType(JObject proof, out string type)
|
|
|
|
{
|
|
|
|
type = null;
|
|
|
|
if (proof is null)
|
2022-08-17 09:45:51 +02:00
|
|
|
{
|
2022-11-15 10:40:57 +01:00
|
|
|
return false;
|
2022-08-17 09:45:51 +02:00
|
|
|
}
|
2022-11-15 10:40:57 +01:00
|
|
|
|
|
|
|
if (!proof.TryGetValue("proofType", StringComparison.InvariantCultureIgnoreCase, out var proofType))
|
|
|
|
return false;
|
|
|
|
type = proofType.Value<string>();
|
|
|
|
return true;
|
2022-08-17 09:45:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-13 10:36:49 +02:00
|
|
|
public void StartBackgroundCheck(Action<Type[]> subscribe)
|
|
|
|
{
|
2021-12-31 16:59:02 +09:00
|
|
|
subscribe(new[] { typeof(NewOnChainTransactionEvent), typeof(NewBlockEvent) });
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async Task BackgroundCheck(object o)
|
|
|
|
{
|
2021-07-16 09:57:37 +02:00
|
|
|
if (o is NewOnChainTransactionEvent newTransaction && newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource addressTrackedSource)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2021-07-16 09:57:37 +02:00
|
|
|
await UpdatePayoutsAwaitingForPayment(newTransaction, addressTrackedSource);
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (o is NewBlockEvent || o is NewOnChainTransactionEvent)
|
|
|
|
{
|
|
|
|
await UpdatePayoutsInProgress();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
public Task<decimal> GetMinimumPayoutAmount(IClaimDestination claimDestination)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
if (Network
|
2021-04-13 10:36:49 +02:00
|
|
|
.NBitcoinNetwork?
|
|
|
|
.Consensus?
|
|
|
|
.ConsensusFactory?
|
|
|
|
.CreateTxOut() is TxOut txout &&
|
|
|
|
claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
|
|
|
{
|
|
|
|
txout.ScriptPubKey = bitcoinLikeClaimDestination.Address.ScriptPubKey;
|
2021-11-15 06:48:07 +02:00
|
|
|
return Task.FromResult(txout.GetDustThreshold().ToDecimal(MoneyUnit.BTC));
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Task.FromResult(0m);
|
|
|
|
}
|
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
|
|
|
|
public Dictionary<PayoutState, List<(string Action, string Text)>> GetPayoutSpecificActions()
|
|
|
|
{
|
|
|
|
return new Dictionary<PayoutState, List<(string Action, string Text)>>()
|
|
|
|
{
|
|
|
|
{PayoutState.AwaitingPayment, new List<(string Action, string Text)>()
|
|
|
|
{
|
|
|
|
("reject-payment", "Reject payout transaction")
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<StatusMessageModel> DoSpecificAction(string action, string[] payoutIds, string storeId)
|
|
|
|
{
|
|
|
|
switch (action)
|
|
|
|
{
|
2021-08-05 07:47:25 +02:00
|
|
|
case "mark-paid":
|
2021-07-16 09:57:37 +02:00
|
|
|
await using (var context = _dbContextFactory.CreateContext())
|
|
|
|
{
|
2023-02-07 08:51:20 +01:00
|
|
|
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
|
2023-04-10 11:07:03 +09:00
|
|
|
{
|
|
|
|
States = new[] { PayoutState.AwaitingPayment },
|
|
|
|
Stores = new[] { storeId },
|
|
|
|
PayoutIds = payoutIds
|
|
|
|
}, context)).Where(data =>
|
2024-05-01 10:22:07 +09:00
|
|
|
PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
|
|
|
|
payoutMethodId == PayoutMethodId)
|
2021-12-31 16:59:02 +09:00
|
|
|
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
|
2021-07-16 09:57:37 +02:00
|
|
|
foreach (var valueTuple in payouts)
|
|
|
|
{
|
|
|
|
valueTuple.Item2.Accounted = true;
|
|
|
|
valueTuple.data.State = PayoutState.InProgress;
|
|
|
|
SetProofBlob(valueTuple.data, valueTuple.Item2);
|
|
|
|
}
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
return new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = "Payout payments have been marked confirmed",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
|
|
|
};
|
|
|
|
case "reject-payment":
|
2023-04-10 11:07:03 +09:00
|
|
|
await using (var context = _dbContextFactory.CreateContext())
|
2021-07-16 09:57:37 +02:00
|
|
|
{
|
2023-02-07 08:51:20 +01:00
|
|
|
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
|
2023-04-10 11:07:03 +09:00
|
|
|
{
|
|
|
|
States = new[] { PayoutState.AwaitingPayment },
|
|
|
|
Stores = new[] { storeId },
|
|
|
|
PayoutIds = payoutIds
|
|
|
|
}, context)).Where(data =>
|
2024-05-01 10:22:07 +09:00
|
|
|
PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
|
|
|
|
payoutMethodId == PayoutMethodId)
|
2021-12-31 16:59:02 +09:00
|
|
|
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
|
2021-07-16 09:57:37 +02:00
|
|
|
foreach (var valueTuple in payouts)
|
|
|
|
{
|
|
|
|
valueTuple.Item2.TransactionId = null;
|
|
|
|
SetProofBlob(valueTuple.data, valueTuple.Item2);
|
|
|
|
}
|
2021-10-18 05:37:59 +02:00
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
await context.SaveChangesAsync();
|
|
|
|
}
|
2021-10-18 05:37:59 +02:00
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
return new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = "Payout payments have been unmarked",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-10-18 05:37:59 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
public bool IsSupported(StoreData storeData)
|
2021-10-18 05:37:59 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
return storeData.GetDerivationSchemeSettings(_paymentHandlers, Network.CryptoCode, true)?.AccountDerivation is not null;
|
2021-10-18 05:37:59 +02:00
|
|
|
}
|
|
|
|
|
2024-05-01 10:22:07 +09:00
|
|
|
public async Task<IActionResult> InitiatePayment(string[] payoutIds)
|
2021-10-18 05:37:59 +02:00
|
|
|
{
|
|
|
|
await using var ctx = this._dbContextFactory.CreateContext();
|
|
|
|
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
|
|
|
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
2021-12-31 16:59:02 +09:00
|
|
|
.Where(data => payoutIds.Contains(data.Id)
|
2024-05-01 10:22:07 +09:00
|
|
|
&& PayoutMethodId.ToString() == data.PaymentMethodId
|
2021-10-18 05:37:59 +02:00
|
|
|
&& data.State == PayoutState.AwaitingPayment)
|
|
|
|
.ToListAsync();
|
|
|
|
|
2023-01-06 14:18:07 +01:00
|
|
|
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().Where(s => s != null).ToArray();
|
2022-04-24 05:19:34 +02:00
|
|
|
var storeId = payouts.First().StoreDataId;
|
2021-12-31 16:59:02 +09:00
|
|
|
List<string> bip21 = new List<string>();
|
2021-10-18 05:37:59 +02:00
|
|
|
foreach (var payout in payouts)
|
|
|
|
{
|
|
|
|
if (payout.Proof != null)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var blob = payout.GetBlob(_jsonSerializerSettings);
|
2024-05-01 10:22:07 +09:00
|
|
|
if (payout.GetPayoutMethodId() != PayoutMethodId)
|
2021-10-18 05:37:59 +02:00
|
|
|
continue;
|
2024-05-01 10:22:07 +09:00
|
|
|
var claim = await ParseClaimDestination(blob.Destination, default);
|
2021-10-21 17:43:02 +02:00
|
|
|
switch (claim.destination)
|
|
|
|
{
|
|
|
|
case UriClaimDestination uriClaimDestination:
|
|
|
|
uriClaimDestination.BitcoinUrl.Amount = new Money(blob.CryptoAmount.Value, MoneyUnit.BTC);
|
2022-04-24 05:19:34 +02:00
|
|
|
var newUri = new UriBuilder(uriClaimDestination.BitcoinUrl.Uri);
|
|
|
|
BTCPayServerClient.AppendPayloadToQuery(newUri, new KeyValuePair<string, object>("payout", payout.Id));
|
|
|
|
bip21.Add(newUri.Uri.ToString());
|
2021-10-21 17:43:02 +02:00
|
|
|
break;
|
|
|
|
case AddressClaimDestination addressClaimDestination:
|
2024-05-01 10:22:07 +09:00
|
|
|
var bip21New = Network.GenerateBIP21(addressClaimDestination.Address.ToString(), blob.CryptoAmount.Value);
|
2022-04-24 05:19:34 +02:00
|
|
|
bip21New.QueryParams.Add("payout", payout.Id);
|
|
|
|
bip21.Add(bip21New.ToString());
|
2021-10-21 17:43:02 +02:00
|
|
|
break;
|
2021-12-31 16:59:02 +09:00
|
|
|
}
|
2021-10-18 05:37:59 +02:00
|
|
|
}
|
2021-12-31 16:59:02 +09:00
|
|
|
if (bip21.Any())
|
2024-05-01 10:22:07 +09:00
|
|
|
return new RedirectToActionResult("WalletSend", "UIWallets", new { walletId = new WalletId(storeId, Network.CryptoCode).ToString(), bip21 });
|
2022-01-07 12:32:00 +09:00
|
|
|
return new RedirectToActionResult("Payouts", "UIWallets", new
|
2021-07-16 09:57:37 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
walletId = new WalletId(storeId, Network.CryptoCode).ToString(),
|
2021-12-31 16:59:02 +09:00
|
|
|
pullPaymentId = pullPaymentIds.Length == 1 ? pullPaymentIds.First() : null
|
2021-10-18 05:37:59 +02:00
|
|
|
});
|
2021-07-16 09:57:37 +02:00
|
|
|
}
|
|
|
|
|
2021-04-13 10:36:49 +02:00
|
|
|
private async Task UpdatePayoutsInProgress()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2021-07-16 09:57:37 +02:00
|
|
|
await using var ctx = _dbContextFactory.CreateContext();
|
2021-04-13 10:36:49 +02:00
|
|
|
var payouts = await ctx.Payouts
|
|
|
|
.Include(p => p.PullPaymentData)
|
|
|
|
.Where(p => p.State == PayoutState.InProgress)
|
|
|
|
.ToListAsync();
|
2023-12-01 10:50:05 +01:00
|
|
|
List<PayoutData> updatedPayouts = new List<PayoutData>();
|
2021-04-13 10:36:49 +02:00
|
|
|
foreach (var payout in payouts)
|
|
|
|
{
|
|
|
|
var proof = ParseProof(payout) as PayoutTransactionOnChainBlob;
|
|
|
|
var payoutBlob = payout.GetBlob(this._jsonSerializerSettings);
|
|
|
|
if (proof is null || proof.Accounted is false)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
foreach (var txid in proof.Candidates.ToList())
|
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
var explorer = _explorerClientProvider.GetExplorerClient(Network.CryptoCode);
|
2021-04-13 10:36:49 +02:00
|
|
|
var tx = await explorer.GetTransactionAsync(txid);
|
|
|
|
if (tx is null)
|
|
|
|
{
|
|
|
|
proof.Candidates.Remove(txid);
|
|
|
|
}
|
|
|
|
else if (tx.Confirmations >= payoutBlob.MinimumConfirmation)
|
|
|
|
{
|
|
|
|
payout.State = PayoutState.Completed;
|
|
|
|
proof.TransactionId = tx.TransactionHash;
|
2023-12-01 10:50:05 +01:00
|
|
|
updatedPayouts.Add(payout);
|
2021-04-13 10:36:49 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var rebroadcasted = await explorer.BroadcastAsync(tx.Transaction);
|
|
|
|
if (rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_ERROR ||
|
|
|
|
rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_REJECTED)
|
|
|
|
{
|
|
|
|
proof.Candidates.Remove(txid);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
payout.State = PayoutState.InProgress;
|
|
|
|
proof.TransactionId = tx.TransactionHash;
|
2023-12-01 10:50:05 +01:00
|
|
|
updatedPayouts.Add(payout);
|
2021-04-13 10:36:49 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 14:18:07 +01:00
|
|
|
if (proof.TransactionId is not null && !proof.Candidates.Contains(proof.TransactionId))
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
|
|
|
proof.TransactionId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (proof.Candidates.Count == 0)
|
|
|
|
{
|
2023-12-01 10:50:05 +01:00
|
|
|
if (payout.State != PayoutState.AwaitingPayment)
|
|
|
|
{
|
|
|
|
updatedPayouts.Add(payout);
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
payout.State = PayoutState.AwaitingPayment;
|
|
|
|
}
|
|
|
|
else if (proof.TransactionId is null)
|
|
|
|
{
|
|
|
|
proof.TransactionId = proof.Candidates.First();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (payout.State == PayoutState.Completed)
|
|
|
|
proof.Candidates = null;
|
|
|
|
SetProofBlob(payout, proof);
|
|
|
|
}
|
|
|
|
|
|
|
|
await ctx.SaveChangesAsync();
|
2023-12-01 10:50:05 +01:00
|
|
|
foreach (PayoutData payoutData in updatedPayouts)
|
|
|
|
{
|
|
|
|
_eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated,payoutData));
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Logs.PayServer.LogWarning(ex, "Error while processing an update in the pull payment hosted service");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
private async Task UpdatePayoutsAwaitingForPayment(NewOnChainTransactionEvent newTransaction,
|
|
|
|
AddressTrackedSource addressTrackedSource)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(newTransaction.CryptoCode);
|
2021-07-16 09:57:37 +02:00
|
|
|
var destinationSum =
|
|
|
|
newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network));
|
|
|
|
var destination = addressTrackedSource.Address.ToString();
|
2024-04-04 16:31:04 +09:00
|
|
|
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(newTransaction.CryptoCode);
|
2021-04-13 10:36:49 +02:00
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
await using var ctx = _dbContextFactory.CreateContext();
|
2021-04-13 10:36:49 +02:00
|
|
|
var payouts = await ctx.Payouts
|
2022-04-24 05:19:34 +02:00
|
|
|
.Include(o => o.StoreData)
|
2021-04-13 10:36:49 +02:00
|
|
|
.Include(o => o.PullPaymentData)
|
|
|
|
.Where(p => p.State == PayoutState.AwaitingPayment)
|
|
|
|
.Where(p => p.PaymentMethodId == paymentMethodId.ToString())
|
2021-07-29 20:29:34 +09:00
|
|
|
#pragma warning disable CA1307 // Specify StringComparison
|
2021-07-16 09:57:37 +02:00
|
|
|
.Where(p => destination.Equals(p.Destination))
|
2021-07-29 20:29:34 +09:00
|
|
|
#pragma warning restore CA1307 // Specify StringComparison
|
2021-04-13 10:36:49 +02:00
|
|
|
.ToListAsync();
|
|
|
|
var payoutByDestination = payouts.ToDictionary(p => p.Destination);
|
2021-07-16 09:57:37 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-04-24 05:19:34 +02:00
|
|
|
var derivationSchemeSettings = payout.StoreData
|
2024-05-01 10:22:07 +09:00
|
|
|
.GetDerivationSchemeSettings(_paymentHandlers, newTransaction.CryptoCode)?.AccountDerivation;
|
2022-08-04 12:07:59 +09:00
|
|
|
if (derivationSchemeSettings is null)
|
|
|
|
return;
|
2021-07-16 09:57:37 +02:00
|
|
|
|
|
|
|
var storeWalletMatched = (await _explorerClientProvider.GetExplorerClient(newTransaction.CryptoCode)
|
|
|
|
.GetTransactionAsync(derivationSchemeSettings,
|
|
|
|
newTransaction.NewTransactionEvent.TransactionData.TransactionHash));
|
2022-11-19 00:04:46 +09:00
|
|
|
//if the wallet related to the store of the payout does not have the tx: it has been paid externally
|
|
|
|
var isInternal = storeWalletMatched is not null;
|
2021-07-16 09:57:37 +02:00
|
|
|
|
|
|
|
var proof = ParseProof(payout) as PayoutTransactionOnChainBlob ??
|
2021-12-31 16:59:02 +09:00
|
|
|
new PayoutTransactionOnChainBlob() { Accounted = isInternal };
|
2021-07-16 09:57:37 +02:00
|
|
|
var txId = newTransaction.NewTransactionEvent.TransactionData.TransactionHash;
|
2021-12-31 16:59:02 +09:00
|
|
|
if (!proof.Candidates.Add(txId))
|
|
|
|
return;
|
2021-07-16 09:57:37 +02:00
|
|
|
if (isInternal)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2021-07-16 09:57:37 +02:00
|
|
|
payout.State = PayoutState.InProgress;
|
2022-10-11 17:34:29 +09:00
|
|
|
await WalletRepository.AddWalletTransactionAttachment(
|
|
|
|
new WalletId(payout.StoreDataId, newTransaction.CryptoCode),
|
2021-07-16 09:57:37 +02:00
|
|
|
newTransaction.NewTransactionEvent.TransactionData.TransactionHash,
|
2022-10-11 17:34:29 +09:00
|
|
|
Attachment.Payout(payout.PullPaymentDataId, payout.Id));
|
2021-07-16 09:57:37 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-04-24 05:19:34 +02:00
|
|
|
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
|
2021-07-16 09:57:37 +02:00
|
|
|
new ExternalPayoutTransactionNotification()
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2021-07-16 09:57:37 +02:00
|
|
|
PaymentMethod = payout.PaymentMethodId,
|
|
|
|
PayoutId = payout.Id,
|
2022-04-24 05:19:34 +02:00
|
|
|
StoreId = payout.StoreDataId
|
2021-07-16 09:57:37 +02:00
|
|
|
});
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
2021-07-16 09:57:37 +02:00
|
|
|
proof.TransactionId ??= txId;
|
|
|
|
SetProofBlob(payout, proof);
|
2021-04-13 10:36:49 +02:00
|
|
|
await ctx.SaveChangesAsync();
|
2023-12-01 10:50:05 +01:00
|
|
|
_eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated,payout));
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Logs.PayServer.LogWarning(ex, "Error while processing a transaction in the pull payment hosted service");
|
|
|
|
}
|
|
|
|
}
|
2021-07-16 09:57:37 +02:00
|
|
|
|
2022-04-24 05:19:34 +02:00
|
|
|
public void SetProofBlob(PayoutData data, PayoutTransactionOnChainBlob blob)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2024-05-01 10:22:07 +09:00
|
|
|
data.SetProofBlob(blob, _jsonSerializerSettings.GetSerializer(data.GetPayoutMethodId()));
|
2022-08-17 09:45:51 +02:00
|
|
|
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
}
|