2020-06-28 21:44:35 -05:00
using System ;
2020-06-24 10:34:09 +09:00
using System.Collections.Generic ;
using System.Linq ;
using System.Threading ;
using System.Threading.Channels ;
using System.Threading.Tasks ;
2022-06-28 16:02:17 +02:00
using BTCPayServer.Abstractions.Extensions ;
2021-04-13 10:36:49 +02:00
using BTCPayServer.Client.Models ;
2020-06-24 10:34:09 +09:00
using BTCPayServer.Data ;
2021-11-22 17:16:08 +09:00
using BTCPayServer.Logging ;
2022-06-28 16:02:17 +02:00
using BTCPayServer.Models.WalletViewModels ;
2020-06-24 10:34:09 +09:00
using BTCPayServer.Payments ;
2022-06-28 16:02:17 +02:00
using BTCPayServer.Rating ;
2020-06-24 10:34:09 +09:00
using BTCPayServer.Services ;
using BTCPayServer.Services.Notifications ;
using BTCPayServer.Services.Notifications.Blobs ;
using BTCPayServer.Services.Rates ;
using Microsoft.EntityFrameworkCore ;
2021-07-16 09:57:37 +02:00
using Microsoft.Extensions.Logging ;
2020-06-24 10:34:09 +09:00
using NBitcoin ;
using NBitcoin.DataEncoders ;
2021-04-13 10:36:49 +02:00
using NBXplorer ;
2022-11-15 10:40:57 +01:00
using Newtonsoft.Json.Linq ;
2021-04-13 10:36:49 +02:00
using PayoutData = BTCPayServer . Data . PayoutData ;
2022-06-28 16:02:17 +02:00
using PullPaymentData = BTCPayServer . Data . PullPaymentData ;
2021-04-13 10:36:49 +02:00
2020-06-24 10:34:09 +09:00
namespace BTCPayServer.HostedServices
{
public class CreatePullPayment
{
public DateTimeOffset ? ExpiresAt { get ; set ; }
public DateTimeOffset ? StartsAt { get ; set ; }
public string StoreId { get ; set ; }
public string Name { get ; set ; }
2022-02-09 21:54:00 -08:00
public string Description { get ; set ; }
2020-06-24 10:34:09 +09:00
public decimal Amount { get ; set ; }
public string Currency { get ; set ; }
2020-12-07 20:04:50 -08:00
public string CustomCSSLink { get ; set ; }
public string EmbeddedCSS { get ; set ; }
2020-06-24 10:34:09 +09:00
public PaymentMethodId [ ] PaymentMethodIds { get ; set ; }
public TimeSpan ? Period { get ; set ; }
2022-04-28 02:51:04 +02:00
public bool AutoApproveClaims { get ; set ; }
2022-01-24 20:17:09 +09:00
public TimeSpan ? BOLT11Expiration { get ; set ; }
2020-06-24 10:34:09 +09:00
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public class PullPaymentHostedService : BaseAsyncService
{
2023-06-16 03:56:17 +02:00
private readonly string [ ] _lnurlSupportedCurrencies = { "BTC" , "SATS" } ;
2020-06-24 10:34:09 +09:00
public class CancelRequest
{
public CancelRequest ( string pullPaymentId )
{
2021-12-28 17:39:54 +09:00
ArgumentNullException . ThrowIfNull ( pullPaymentId ) ;
2020-06-24 10:34:09 +09:00
PullPaymentId = pullPaymentId ;
}
2022-04-24 05:19:34 +02:00
2022-11-15 10:40:57 +01:00
public CancelRequest ( string [ ] payoutIds , string [ ] storeIds )
2020-06-24 10:34:09 +09:00
{
2021-12-28 17:39:54 +09:00
ArgumentNullException . ThrowIfNull ( payoutIds ) ;
2020-06-24 10:34:09 +09:00
PayoutIds = payoutIds ;
2022-11-15 10:40:57 +01:00
StoreIds = storeIds ;
2020-06-24 10:34:09 +09:00
}
2022-04-24 05:19:34 +02:00
2022-11-15 10:40:57 +01:00
public string [ ] StoreIds { get ; set ; }
2020-06-24 10:34:09 +09:00
public string PullPaymentId { get ; set ; }
public string [ ] PayoutIds { get ; set ; }
2022-11-15 10:40:57 +01:00
internal TaskCompletionSource < Dictionary < string , MarkPayoutRequest . PayoutPaidResult > > Completion { get ; set ; }
2020-06-24 10:34:09 +09:00
}
2022-04-24 05:19:34 +02:00
2020-06-24 13:44:26 +09:00
public class PayoutApproval
{
public enum Result
{
Ok ,
NotFound ,
InvalidState ,
TooLowAmount ,
OldRevision
}
2022-04-24 05:19:34 +02:00
2022-12-04 13:23:59 +01:00
public record ApprovalResult ( Result Result , decimal? CryptoAmount ) ;
2023-01-06 14:18:07 +01:00
2020-06-24 13:44:26 +09:00
public string PayoutId { get ; set ; }
public int Revision { get ; set ; }
public decimal Rate { get ; set ; }
2022-12-04 13:23:59 +01:00
internal TaskCompletionSource < ApprovalResult > Completion { get ; set ; }
2020-06-24 10:34:09 +09:00
2020-06-24 13:44:26 +09:00
public static string GetErrorMessage ( Result result )
{
switch ( result )
{
case PullPaymentHostedService . PayoutApproval . Result . Ok :
return "Ok" ;
case PullPaymentHostedService . PayoutApproval . Result . InvalidState :
return "The payout is not in a state that can be approved" ;
case PullPaymentHostedService . PayoutApproval . Result . TooLowAmount :
return "The crypto amount is too small." ;
case PullPaymentHostedService . PayoutApproval . Result . OldRevision :
return "The crypto amount is too small." ;
case PullPaymentHostedService . PayoutApproval . Result . NotFound :
return "The payout is not found" ;
default :
throw new NotSupportedException ( ) ;
}
}
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public async Task < string > CreatePullPayment ( CreatePullPayment create )
{
2021-12-28 17:39:54 +09:00
ArgumentNullException . ThrowIfNull ( create ) ;
2020-06-24 10:34:09 +09:00
if ( create . Amount < = 0.0 m )
throw new ArgumentException ( "Amount out of bound" , nameof ( create ) ) ;
using var ctx = this . _dbContextFactory . CreateContext ( ) ;
var o = new Data . PullPaymentData ( ) ;
2022-04-24 05:19:34 +02:00
o . StartDate = create . StartsAt is DateTimeOffset date
? date
: DateTimeOffset . UtcNow - TimeSpan . FromSeconds ( 1.0 ) ;
2020-06-24 10:34:09 +09:00
o . EndDate = create . ExpiresAt is DateTimeOffset date2 ? new DateTimeOffset ? ( date2 ) : null ;
o . Period = create . Period is TimeSpan period ? ( long? ) period . TotalSeconds : null ;
o . Id = Encoders . Base58 . EncodeData ( RandomUtils . GetBytes ( 20 ) ) ;
o . StoreId = create . StoreId ;
2023-04-10 11:07:03 +09:00
2020-06-24 10:34:09 +09:00
o . SetBlob ( new PullPaymentBlob ( )
{
Name = create . Name ? ? string . Empty ,
2022-02-09 21:54:00 -08:00
Description = create . Description ? ? string . Empty ,
2020-06-24 10:34:09 +09:00
Currency = create . Currency ,
Limit = create . Amount ,
Period = o . Period is long periodSeconds ? ( TimeSpan ? ) TimeSpan . FromSeconds ( periodSeconds ) : null ,
SupportedPaymentMethods = create . PaymentMethodIds ,
2022-04-28 02:51:04 +02:00
AutoApproveClaims = create . AutoApproveClaims ,
2021-04-13 10:36:49 +02:00
View = new PullPaymentBlob . PullPaymentView ( )
2020-06-24 10:34:09 +09:00
{
Title = create . Name ? ? string . Empty ,
2022-02-09 21:54:00 -08:00
Description = create . Description ? ? string . Empty ,
2020-12-07 20:04:50 -08:00
CustomCSSLink = create . CustomCSSLink ,
2020-06-24 10:34:09 +09:00
Email = null ,
2020-12-07 20:04:50 -08:00
EmbeddedCSS = create . EmbeddedCSS ,
2022-01-24 20:17:09 +09:00
} ,
BOLT11Expiration = create . BOLT11Expiration ? ? TimeSpan . FromDays ( 30.0 )
2020-06-24 10:34:09 +09:00
} ) ;
ctx . PullPayments . Add ( o ) ;
await ctx . SaveChangesAsync ( ) ;
return o . Id ;
}
2022-08-17 09:45:51 +02:00
public class PayoutQuery
{
public PayoutState [ ] States { get ; set ; }
public string [ ] PullPayments { get ; set ; }
public string [ ] PayoutIds { get ; set ; }
public string [ ] PaymentMethods { get ; set ; }
public string [ ] Stores { get ; set ; }
2023-02-07 08:51:20 +01:00
public bool IncludeArchived { get ; set ; }
public bool IncludeStoreData { get ; set ; }
public bool IncludePullPaymentData { get ; set ; }
2022-08-17 09:45:51 +02:00
}
public async Task < List < PayoutData > > GetPayouts ( PayoutQuery payoutQuery )
{
await using var ctx = _dbContextFactory . CreateContext ( ) ;
return await GetPayouts ( payoutQuery , ctx ) ;
}
2023-02-07 08:51:20 +01:00
public static async Task < List < PayoutData > > GetPayouts ( PayoutQuery payoutQuery , ApplicationDbContext ctx ,
CancellationToken cancellationToken = default )
2022-08-17 09:45:51 +02:00
{
var query = ctx . Payouts . AsQueryable ( ) ;
if ( payoutQuery . States is not null )
{
query = query . Where ( data = > payoutQuery . States . Contains ( data . State ) ) ;
}
if ( payoutQuery . PullPayments is not null )
{
query = query . Where ( data = > payoutQuery . PullPayments . Contains ( data . PullPaymentDataId ) ) ;
}
if ( payoutQuery . PayoutIds is not null )
{
2023-02-07 16:53:44 +09:00
if ( payoutQuery . PayoutIds . Length = = 1 )
{
var payoutId = payoutQuery . PayoutIds [ 0 ] ;
query = query . Where ( data = > data . Id = = payoutId ) ;
}
else
{
query = query . Where ( data = > payoutQuery . PayoutIds . Contains ( data . Id ) ) ;
}
2022-08-17 09:45:51 +02:00
}
if ( payoutQuery . PaymentMethods is not null )
{
query = query . Where ( data = > payoutQuery . PaymentMethods . Contains ( data . PaymentMethodId ) ) ;
}
if ( payoutQuery . Stores is not null )
{
query = query . Where ( data = > payoutQuery . Stores . Contains ( data . StoreDataId ) ) ;
}
2023-02-07 08:51:20 +01:00
if ( payoutQuery . IncludeStoreData )
{
query = query . Include ( data = > data . StoreData ) ;
}
2023-04-10 11:07:03 +09:00
2023-02-07 08:51:20 +01:00
if ( payoutQuery . IncludePullPaymentData | | ! payoutQuery . IncludeArchived )
{
query = query . Include ( data = > data . PullPaymentData ) ;
}
if ( ! payoutQuery . IncludeArchived )
{
query = query . Where ( data = >
data . PullPaymentData = = null | | ! data . PullPaymentData . Archived ) ;
}
2022-08-17 09:45:51 +02:00
2023-02-07 08:51:20 +01:00
return await query . ToListAsync ( cancellationToken ) ;
2022-08-17 09:45:51 +02:00
}
2021-06-10 18:54:27 +09:00
public async Task < Data . PullPaymentData > GetPullPayment ( string pullPaymentId , bool includePayouts )
2020-06-24 10:34:09 +09:00
{
2021-06-10 11:43:45 +02:00
await using var ctx = _dbContextFactory . CreateContext ( ) ;
2021-06-10 18:54:27 +09:00
IQueryable < Data . PullPaymentData > query = ctx . PullPayments ;
if ( includePayouts )
query = query . Include ( data = > data . Payouts ) ;
return await query . FirstOrDefaultAsync ( data = > data . Id = = pullPaymentId ) ;
2020-06-24 10:34:09 +09:00
}
class PayoutRequest
{
2022-04-24 05:19:34 +02:00
public PayoutRequest ( TaskCompletionSource < ClaimRequest . ClaimResponse > completionSource ,
ClaimRequest request )
2020-06-24 10:34:09 +09:00
{
2021-12-28 17:39:54 +09:00
ArgumentNullException . ThrowIfNull ( request ) ;
ArgumentNullException . ThrowIfNull ( completionSource ) ;
2020-06-24 10:34:09 +09:00
Completion = completionSource ;
ClaimRequest = request ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public TaskCompletionSource < ClaimRequest . ClaimResponse > Completion { get ; set ; }
public ClaimRequest ClaimRequest { get ; }
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public PullPaymentHostedService ( ApplicationDbContextFactory dbContextFactory ,
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings ,
EventAggregator eventAggregator ,
BTCPayNetworkProvider networkProvider ,
2020-06-24 13:44:26 +09:00
NotificationSender notificationSender ,
2021-04-13 10:36:49 +02:00
RateFetcher rateFetcher ,
2021-07-16 09:57:37 +02:00
IEnumerable < IPayoutHandler > payoutHandlers ,
2021-11-22 17:16:08 +09:00
ILogger < PullPaymentHostedService > logger ,
2022-06-28 16:02:17 +02:00
Logs logs ,
2023-03-13 02:12:58 +01:00
DisplayFormatter displayFormatter ,
2022-06-28 16:02:17 +02:00
CurrencyNameTable currencyNameTable ) : base ( logs )
2020-06-24 10:34:09 +09:00
{
_dbContextFactory = dbContextFactory ;
_jsonSerializerSettings = jsonSerializerSettings ;
_eventAggregator = eventAggregator ;
_networkProvider = networkProvider ;
_notificationSender = notificationSender ;
2020-06-24 13:44:26 +09:00
_rateFetcher = rateFetcher ;
2021-04-13 10:36:49 +02:00
_payoutHandlers = payoutHandlers ;
2021-07-16 09:57:37 +02:00
_logger = logger ;
2022-06-28 16:02:17 +02:00
_currencyNameTable = currencyNameTable ;
2023-03-13 02:12:58 +01:00
_displayFormatter = displayFormatter ;
2020-06-24 10:34:09 +09:00
}
Channel < object > _Channel ;
private readonly ApplicationDbContextFactory _dbContextFactory ;
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings ;
private readonly EventAggregator _eventAggregator ;
private readonly BTCPayNetworkProvider _networkProvider ;
private readonly NotificationSender _notificationSender ;
2020-06-24 13:44:26 +09:00
private readonly RateFetcher _rateFetcher ;
2021-04-13 10:36:49 +02:00
private readonly IEnumerable < IPayoutHandler > _payoutHandlers ;
2021-07-16 09:57:37 +02:00
private readonly ILogger < PullPaymentHostedService > _logger ;
2022-06-28 16:02:17 +02:00
private readonly CurrencyNameTable _currencyNameTable ;
2023-03-13 02:12:58 +01:00
private readonly DisplayFormatter _displayFormatter ;
2021-04-13 10:36:49 +02:00
private readonly CompositeDisposable _subscriptions = new CompositeDisposable ( ) ;
2020-06-24 10:34:09 +09:00
internal override Task [ ] InitializeTasks ( )
{
_Channel = Channel . CreateUnbounded < object > ( ) ;
2021-04-13 10:36:49 +02:00
foreach ( IPayoutHandler payoutHandler in _payoutHandlers )
{
payoutHandler . StartBackgroundCheck ( Subscribe ) ;
}
2022-04-24 05:19:34 +02:00
2023-01-06 14:18:07 +01:00
return new [ ] { Loop ( ) } ;
2020-06-24 10:34:09 +09:00
}
2021-04-13 10:36:49 +02:00
private void Subscribe ( params Type [ ] events )
{
foreach ( Type @event in events )
{
_eventAggregator . Subscribe ( @event , ( subscription , o ) = > _Channel . Writer . TryWrite ( o ) ) ;
}
}
2020-06-24 10:34:09 +09:00
private async Task Loop ( )
{
await foreach ( var o in _Channel . Reader . ReadAllAsync ( ) )
{
if ( o is PayoutRequest req )
{
await HandleCreatePayout ( req ) ;
}
2020-06-24 13:44:26 +09:00
if ( o is PayoutApproval approv )
{
await HandleApproval ( approv ) ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
if ( o is CancelRequest cancel )
{
await HandleCancel ( cancel ) ;
2021-06-10 18:54:27 +09:00
}
2022-04-24 05:19:34 +02:00
2021-06-10 11:43:45 +02:00
if ( o is InternalPayoutPaidRequest paid )
{
await HandleMarkPaid ( paid ) ;
2021-06-10 18:54:27 +09:00
}
2022-04-24 05:19:34 +02:00
2021-04-13 10:36:49 +02:00
foreach ( IPayoutHandler payoutHandler in _payoutHandlers )
2020-06-24 10:34:09 +09:00
{
2021-07-16 09:57:37 +02:00
try
{
await payoutHandler . BackgroundCheck ( o ) ;
}
catch ( Exception e )
{
_logger . LogError ( e , "PayoutHandler failed during BackgroundCheck" ) ;
}
2020-06-24 10:34:09 +09:00
}
}
}
2023-06-16 03:56:17 +02:00
public bool SupportsLNURL ( PullPaymentBlob blob )
{
var pms = blob . SupportedPaymentMethods . FirstOrDefault ( id = >
id . PaymentType = = LightningPaymentType . Instance & &
_networkProvider . DefaultNetwork . CryptoCode = = id . CryptoCode ) ;
return pms is not null & & _lnurlSupportedCurrencies . Contains ( blob . Currency ) ;
}
2020-06-24 13:44:26 +09:00
public Task < RateResult > GetRate ( PayoutData payout , string explicitRateRule , CancellationToken cancellationToken )
{
2022-04-24 05:19:34 +02:00
var ppBlob = payout . PullPaymentData ? . GetBlob ( ) ;
var payoutPaymentMethod = payout . GetPaymentMethodId ( ) ;
var currencyPair = new Rating . CurrencyPair ( payoutPaymentMethod . CryptoCode ,
ppBlob ? . Currency ? ? payoutPaymentMethod . CryptoCode ) ;
2020-06-24 13:44:26 +09:00
Rating . RateRule rule = null ;
try
{
if ( explicitRateRule is null )
{
2022-04-24 05:19:34 +02:00
var storeBlob = payout . StoreData . GetStoreBlob ( ) ;
2020-06-24 13:44:26 +09:00
var rules = storeBlob . GetRateRules ( _networkProvider ) ;
rules . Spread = 0.0 m ;
rule = rules . GetRuleFor ( currencyPair ) ;
}
else
{
rule = Rating . RateRule . CreateFromExpression ( explicitRateRule , currencyPair ) ;
}
}
catch ( Exception )
{
throw new FormatException ( "Invalid RateRule" ) ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 13:44:26 +09:00
return _rateFetcher . FetchRate ( rule , cancellationToken ) ;
}
2022-04-24 05:19:34 +02:00
2022-12-04 13:23:59 +01:00
public Task < PayoutApproval . ApprovalResult > Approve ( PayoutApproval approval )
2020-06-24 13:44:26 +09:00
{
2022-04-24 05:19:34 +02:00
approval . Completion =
2022-12-04 13:23:59 +01:00
new TaskCompletionSource < PayoutApproval . ApprovalResult > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
2020-06-24 13:44:26 +09:00
if ( ! _Channel . Writer . TryWrite ( approval ) )
throw new ObjectDisposedException ( nameof ( PullPaymentHostedService ) ) ;
return approval . Completion . Task ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 13:44:26 +09:00
private async Task HandleApproval ( PayoutApproval req )
{
try
{
2023-07-20 15:05:14 +02:00
await using var ctx = _dbContextFactory . CreateContext ( ) ;
2022-04-24 05:19:34 +02:00
var payout = await ctx . Payouts . Include ( p = > p . PullPaymentData ) . Where ( p = > p . Id = = req . PayoutId )
. FirstOrDefaultAsync ( ) ;
2020-06-24 13:44:26 +09:00
if ( payout is null )
{
2022-12-04 13:23:59 +01:00
req . Completion . SetResult ( new PayoutApproval . ApprovalResult ( PayoutApproval . Result . NotFound , null ) ) ;
2020-06-24 13:44:26 +09:00
return ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 13:44:26 +09:00
if ( payout . State ! = PayoutState . AwaitingApproval )
{
2022-12-04 13:23:59 +01:00
req . Completion . SetResult ( new PayoutApproval . ApprovalResult ( PayoutApproval . Result . InvalidState , null ) ) ;
2020-06-24 13:44:26 +09:00
return ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 13:44:26 +09:00
var payoutBlob = payout . GetBlob ( this . _jsonSerializerSettings ) ;
if ( payoutBlob . Revision ! = req . Revision )
{
2022-12-04 13:23:59 +01:00
req . Completion . SetResult ( new PayoutApproval . ApprovalResult ( PayoutApproval . Result . OldRevision , null ) ) ;
2020-06-24 13:44:26 +09:00
return ;
}
2022-04-24 05:19:34 +02:00
2021-09-24 07:16:25 +02:00
if ( ! PaymentMethodId . TryParse ( payout . PaymentMethodId , out var paymentMethod ) )
{
2022-12-04 13:23:59 +01:00
req . Completion . SetResult ( new PayoutApproval . ApprovalResult ( PayoutApproval . Result . NotFound , null ) ) ;
2021-09-24 07:16:25 +02:00
return ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 13:44:26 +09:00
payout . State = PayoutState . AwaitingPayment ;
2021-12-31 16:59:02 +09:00
2022-08-17 09:45:51 +02:00
if ( payout . PullPaymentData is null | |
paymentMethod . CryptoCode = = payout . PullPaymentData . GetBlob ( ) . Currency )
2020-06-24 13:44:26 +09:00
req . Rate = 1.0 m ;
2021-04-13 10:36:49 +02:00
var cryptoAmount = payoutBlob . Amount / req . Rate ;
2021-10-18 15:00:38 +09:00
var payoutHandler = _payoutHandlers . FindPayoutHandler ( paymentMethod ) ;
if ( payoutHandler is null )
throw new InvalidOperationException ( $"No payout handler for {paymentMethod}" ) ;
2022-11-22 20:17:29 +09:00
var dest = await payoutHandler . ParseClaimDestination ( paymentMethod , payoutBlob . Destination , default ) ;
2022-04-24 05:19:34 +02:00
decimal minimumCryptoAmount =
await payoutHandler . GetMinimumPayoutAmount ( paymentMethod , dest . destination ) ;
2021-04-13 10:36:49 +02:00
if ( cryptoAmount < minimumCryptoAmount )
2020-06-24 13:44:26 +09:00
{
2022-12-04 13:23:59 +01:00
req . Completion . TrySetResult ( new PayoutApproval . ApprovalResult ( PayoutApproval . Result . TooLowAmount , null ) ) ;
2020-06-24 13:44:26 +09:00
return ;
}
2022-04-24 05:19:34 +02:00
payoutBlob . CryptoAmount = Extensions . RoundUp ( cryptoAmount ,
_networkProvider . GetNetwork ( paymentMethod . CryptoCode ) . Divisibility ) ;
2021-04-13 10:36:49 +02:00
payout . SetBlob ( payoutBlob , _jsonSerializerSettings ) ;
2020-06-24 13:44:26 +09:00
await ctx . SaveChangesAsync ( ) ;
2022-04-24 05:19:34 +02:00
2023-07-20 15:05:14 +02:00
_eventAggregator . Publish ( new PayoutEvent ( PayoutEvent . PayoutEventType . Approved , payout ) ) ;
2022-12-04 13:23:59 +01:00
req . Completion . SetResult ( new PayoutApproval . ApprovalResult ( PayoutApproval . Result . Ok , payoutBlob . CryptoAmount ) ) ;
2020-06-24 13:44:26 +09:00
}
2020-06-28 17:55:27 +09:00
catch ( Exception ex )
2020-06-24 13:44:26 +09:00
{
req . Completion . TrySetException ( ex ) ;
}
}
2022-04-24 05:19:34 +02:00
2021-06-10 11:43:45 +02:00
private async Task HandleMarkPaid ( InternalPayoutPaidRequest req )
{
try
{
await using var ctx = _dbContextFactory . CreateContext ( ) ;
2022-04-24 05:19:34 +02:00
var payout = await ctx . Payouts . Include ( p = > p . PullPaymentData ) . Where ( p = > p . Id = = req . Request . PayoutId )
. FirstOrDefaultAsync ( ) ;
2021-06-10 11:43:45 +02:00
if ( payout is null )
{
2022-11-15 10:40:57 +01:00
req . Completion . SetResult ( MarkPayoutRequest . PayoutPaidResult . NotFound ) ;
2021-06-10 11:43:45 +02:00
return ;
}
2022-04-24 05:19:34 +02:00
2022-11-15 10:40:57 +01:00
if ( payout . State = = PayoutState . Completed )
2021-06-10 11:43:45 +02:00
{
2022-11-15 10:40:57 +01:00
req . Completion . SetResult ( MarkPayoutRequest . PayoutPaidResult . InvalidState ) ;
2021-06-10 11:43:45 +02:00
return ;
}
2022-11-15 10:40:57 +01:00
switch ( req . Request . State )
2021-06-10 11:43:45 +02:00
{
2022-11-15 10:40:57 +01:00
case PayoutState . Completed or PayoutState . InProgress
2023-01-06 14:18:07 +01:00
when payout . State is not PayoutState . AwaitingPayment and not PayoutState . Completed and not PayoutState . InProgress :
2022-11-15 10:40:57 +01:00
case PayoutState . AwaitingPayment when payout . State is not PayoutState . InProgress :
req . Completion . SetResult ( MarkPayoutRequest . PayoutPaidResult . InvalidState ) ;
return ;
case PayoutState . InProgress or PayoutState . Completed :
payout . SetProofBlob ( req . Request . Proof ) ;
break ;
default :
payout . SetProofBlob ( null ) ;
break ;
2021-06-10 11:43:45 +02:00
}
2022-11-15 10:40:57 +01:00
payout . State = req . Request . State ;
2021-06-10 11:43:45 +02:00
await ctx . SaveChangesAsync ( ) ;
2022-11-15 10:40:57 +01:00
req . Completion . SetResult ( MarkPayoutRequest . PayoutPaidResult . Ok ) ;
2021-06-10 11:43:45 +02:00
}
catch ( Exception ex )
{
req . Completion . TrySetException ( ex ) ;
}
}
2020-06-24 13:44:26 +09:00
2020-06-24 10:34:09 +09:00
private async Task HandleCreatePayout ( PayoutRequest req )
{
try
{
DateTimeOffset now = DateTimeOffset . UtcNow ;
2021-04-13 10:36:49 +02:00
await using var ctx = _dbContextFactory . CreateContext ( ) ;
2022-04-24 05:19:34 +02:00
var withoutPullPayment = req . ClaimRequest . PullPaymentId is null ;
var pp = string . IsNullOrEmpty ( req . ClaimRequest . PullPaymentId )
? null
: await ctx . PullPayments . FindAsync ( req . ClaimRequest . PullPaymentId ) ;
2021-05-13 10:50:08 +02:00
2022-04-24 05:19:34 +02:00
if ( ! withoutPullPayment & & ( pp is null | | pp . Archived ) )
2020-06-24 10:34:09 +09:00
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . Archived ) ) ;
return ;
}
2022-04-24 05:19:34 +02:00
PullPaymentBlob ppBlob = null ;
if ( ! withoutPullPayment )
2020-06-24 10:34:09 +09:00
{
2022-04-24 05:19:34 +02:00
if ( pp . IsExpired ( now ) )
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . Expired ) ) ;
return ;
}
if ( ! pp . HasStarted ( now ) )
{
req . Completion . TrySetResult (
new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . NotStarted ) ) ;
return ;
}
ppBlob = pp . GetBlob ( ) ;
if ( ! ppBlob . SupportedPaymentMethods . Contains ( req . ClaimRequest . PaymentMethodId ) )
{
req . Completion . TrySetResult (
new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . PaymentMethodNotSupported ) ) ;
return ;
}
2020-06-24 10:34:09 +09:00
}
2022-04-24 05:19:34 +02:00
2021-04-13 10:36:49 +02:00
var payoutHandler =
2021-10-18 15:00:38 +09:00
_payoutHandlers . FindPayoutHandler ( req . ClaimRequest . PaymentMethodId ) ;
2022-04-24 05:19:34 +02:00
if ( payoutHandler is null )
2020-06-24 10:34:09 +09:00
{
2022-04-24 05:19:34 +02:00
req . Completion . TrySetResult (
new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . PaymentMethodNotSupported ) ) ;
2020-06-24 10:34:09 +09:00
return ;
}
2021-10-21 17:43:02 +02:00
if ( req . ClaimRequest . Destination . Id ! = null )
{
if ( await ctx . Payouts . AnyAsync ( data = >
2022-04-24 05:19:34 +02:00
data . Destination . Equals ( req . ClaimRequest . Destination . Id ) & &
data . State ! = PayoutState . Completed & & data . State ! = PayoutState . Cancelled
2021-10-21 17:43:02 +02:00
) )
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . Duplicate ) ) ;
return ;
}
}
2021-11-08 08:14:49 +01:00
if ( req . ClaimRequest . Value <
await payoutHandler . GetMinimumPayoutAmount ( req . ClaimRequest . PaymentMethodId ,
req . ClaimRequest . Destination ) )
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . AmountTooLow ) ) ;
return ;
}
2022-04-24 05:19:34 +02:00
var payoutsRaw = withoutPullPayment
? null
: await ctx . Payouts . GetPayoutInPeriod ( pp , now )
. Where ( p = > p . State ! = PayoutState . Cancelled ) . ToListAsync ( ) ;
2023-01-06 14:18:07 +01:00
var payouts = payoutsRaw ? . Select ( o = > new { Entity = o , Blob = o . GetBlob ( _jsonSerializerSettings ) } ) ;
2022-04-24 05:19:34 +02:00
var limit = ppBlob ? . Limit ? ? 0 ;
var totalPayout = payouts ? . Select ( p = > p . Blob . Amount ) ? . Sum ( ) ;
var claimed = req . ClaimRequest . Value is decimal v ? v : limit - ( totalPayout ? ? 0 ) ;
if ( totalPayout is not null & & totalPayout + claimed > limit )
2020-06-24 10:34:09 +09:00
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . Overdraft ) ) ;
return ;
}
2022-04-24 05:19:34 +02:00
if ( ! withoutPullPayment & & ( claimed < ppBlob . MinimumClaim | | claimed = = 0.0 m ) )
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . AmountTooLow ) ) ;
return ;
}
2020-06-24 10:34:09 +09:00
var payout = new PayoutData ( )
{
Id = Encoders . Base58 . EncodeData ( RandomUtils . GetBytes ( 20 ) ) ,
Date = now ,
2022-04-28 02:51:04 +02:00
State = PayoutState . AwaitingApproval ,
2020-06-24 10:34:09 +09:00
PullPaymentDataId = req . ClaimRequest . PullPaymentId ,
PaymentMethodId = req . ClaimRequest . PaymentMethodId . ToString ( ) ,
2022-04-24 05:19:34 +02:00
Destination = req . ClaimRequest . Destination . Id ,
StoreDataId = req . ClaimRequest . StoreId ? ? pp ? . StoreId
2020-06-24 10:34:09 +09:00
} ;
var payoutBlob = new PayoutBlob ( )
{
2023-01-06 14:18:07 +01:00
Amount = claimed ,
2023-07-24 11:37:18 +02:00
Destination = req . ClaimRequest . Destination . ToString ( ) ,
Metadata = req . ClaimRequest . Metadata ? ? new JObject ( ) ,
2020-06-24 10:34:09 +09:00
} ;
payout . SetBlob ( payoutBlob , _jsonSerializerSettings ) ;
2021-04-13 10:36:49 +02:00
await ctx . Payouts . AddAsync ( payout ) ;
2020-06-24 10:34:09 +09:00
try
{
2023-04-07 08:58:41 +02:00
await payoutHandler . TrackClaim ( req . ClaimRequest , payout ) ;
2020-06-24 10:34:09 +09:00
await ctx . SaveChangesAsync ( ) ;
2023-07-20 15:05:14 +02:00
var response = new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . Ok , payout ) ;
_eventAggregator . Publish ( new PayoutEvent ( PayoutEvent . PayoutEventType . Created , payout ) ) ;
2022-08-17 09:45:51 +02:00
if ( req . ClaimRequest . PreApprove . GetValueOrDefault ( ppBlob ? . AutoApproveClaims is true ) )
2022-04-28 02:51:04 +02:00
{
payout . StoreData = await ctx . Stores . FindAsync ( payout . StoreDataId ) ;
var rateResult = await GetRate ( payout , null , CancellationToken . None ) ;
if ( rateResult . BidAsk ! = null )
{
2022-12-04 13:23:59 +01:00
var approveResultTask = new TaskCompletionSource < PayoutApproval . ApprovalResult > ( ) ;
2022-04-28 02:51:04 +02:00
await HandleApproval ( new PayoutApproval ( )
{
2022-08-17 09:45:51 +02:00
PayoutId = payout . Id ,
Revision = payoutBlob . Revision ,
Rate = rateResult . BidAsk . Ask ,
2022-12-04 13:23:59 +01:00
Completion = approveResultTask
2022-04-28 02:51:04 +02:00
} ) ;
2022-12-04 13:23:59 +01:00
var approveResult = await approveResultTask . Task ;
if ( approveResult . Result = = PayoutApproval . Result . Ok )
2022-04-28 02:51:04 +02:00
{
payout . State = PayoutState . AwaitingPayment ;
2022-12-04 13:23:59 +01:00
payoutBlob . CryptoAmount = approveResult . CryptoAmount ;
payout . SetBlob ( payoutBlob , _jsonSerializerSettings ) ;
2022-04-28 02:51:04 +02:00
}
}
}
2022-08-17 09:45:51 +02:00
2023-07-20 15:05:14 +02:00
req . Completion . TrySetResult ( response ) ;
2022-04-24 05:19:34 +02:00
await _notificationSender . SendNotification ( new StoreScope ( payout . StoreDataId ) ,
new PayoutNotification ( )
{
StoreId = payout . StoreDataId ,
Currency = ppBlob ? . Currency ? ? req . ClaimRequest . PaymentMethodId . CryptoCode ,
Status = payout . State ,
PaymentMethod = payout . PaymentMethodId ,
PayoutId = payout . Id
} ) ;
2020-06-24 10:34:09 +09:00
}
catch ( DbUpdateException )
{
req . Completion . TrySetResult ( new ClaimRequest . ClaimResponse ( ClaimRequest . ClaimResult . Duplicate ) ) ;
}
}
catch ( Exception ex )
{
req . Completion . TrySetException ( ex ) ;
}
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
private async Task HandleCancel ( CancelRequest cancel )
{
try
{
using var ctx = this . _dbContextFactory . CreateContext ( ) ;
List < PayoutData > payouts = null ;
if ( cancel . PullPaymentId ! = null )
{
2023-01-06 14:18:07 +01:00
ctx . PullPayments . Attach ( new Data . PullPaymentData ( ) { Id = cancel . PullPaymentId , Archived = true } )
2020-06-24 10:34:09 +09:00
. Property ( o = > o . Archived ) . IsModified = true ;
payouts = await ctx . Payouts
2022-04-24 05:19:34 +02:00
. Where ( p = > p . PullPaymentDataId = = cancel . PullPaymentId )
2023-01-06 14:18:07 +01:00
. Where ( p = > cancel . StoreIds = = null | | cancel . StoreIds . Contains ( p . StoreDataId ) )
2022-04-24 05:19:34 +02:00
. ToListAsync ( ) ;
2022-11-15 10:40:57 +01:00
cancel . PayoutIds = payouts . Select ( data = > data . Id ) . ToArray ( ) ;
2020-06-24 10:34:09 +09:00
}
else
{
var payoutIds = cancel . PayoutIds . ToHashSet ( ) ;
payouts = await ctx . Payouts
2022-04-24 05:19:34 +02:00
. Where ( p = > payoutIds . Contains ( p . Id ) )
2023-01-06 14:18:07 +01:00
. Where ( p = > cancel . StoreIds = = null | | cancel . StoreIds . Contains ( p . StoreDataId ) )
2022-04-24 05:19:34 +02:00
. ToListAsync ( ) ;
2020-06-24 10:34:09 +09:00
}
2022-11-15 10:40:57 +01:00
Dictionary < string , MarkPayoutRequest . PayoutPaidResult > result = new ( ) ;
2023-01-06 14:18:07 +01:00
2020-06-24 10:34:09 +09:00
foreach ( var payout in payouts )
{
if ( payout . State ! = PayoutState . Completed & & payout . State ! = PayoutState . InProgress )
2022-11-15 10:40:57 +01:00
{
2020-06-24 10:34:09 +09:00
payout . State = PayoutState . Cancelled ;
2023-01-06 14:18:07 +01:00
result . Add ( payout . Id , MarkPayoutRequest . PayoutPaidResult . Ok ) ;
2022-11-15 10:40:57 +01:00
}
else
{
2023-01-06 14:18:07 +01:00
result . Add ( payout . Id , MarkPayoutRequest . PayoutPaidResult . InvalidState ) ;
2022-11-15 10:40:57 +01:00
}
}
foreach ( string s1 in cancel . PayoutIds . Where ( s = > ! result . ContainsKey ( s ) ) )
{
result . Add ( s1 , MarkPayoutRequest . PayoutPaidResult . NotFound ) ;
2020-06-24 10:34:09 +09:00
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
await ctx . SaveChangesAsync ( ) ;
2022-11-15 10:40:57 +01:00
cancel . Completion . TrySetResult ( result ) ;
2020-06-24 10:34:09 +09:00
}
catch ( Exception ex )
{
cancel . Completion . TrySetException ( ex ) ;
}
}
2022-04-24 05:19:34 +02:00
2022-11-15 10:40:57 +01:00
public Task < Dictionary < string , MarkPayoutRequest . PayoutPaidResult > > Cancel ( CancelRequest cancelRequest )
2020-06-24 10:34:09 +09:00
{
CancellationToken . ThrowIfCancellationRequested ( ) ;
2023-01-06 14:18:07 +01:00
cancelRequest . Completion = new TaskCompletionSource < Dictionary < string , MarkPayoutRequest . PayoutPaidResult > > ( ) ;
2020-06-28 17:55:27 +09:00
if ( ! _Channel . Writer . TryWrite ( cancelRequest ) )
2020-06-24 13:44:26 +09:00
throw new ObjectDisposedException ( nameof ( PullPaymentHostedService ) ) ;
2023-01-06 14:18:07 +01:00
return cancelRequest . Completion . Task ;
2020-06-24 10:34:09 +09:00
}
public Task < ClaimRequest . ClaimResponse > Claim ( ClaimRequest request )
{
CancellationToken . ThrowIfCancellationRequested ( ) ;
2022-04-24 05:19:34 +02:00
var cts = new TaskCompletionSource < ClaimRequest . ClaimResponse > ( TaskCreationOptions
. RunContinuationsAsynchronously ) ;
2020-06-28 17:55:27 +09:00
if ( ! _Channel . Writer . TryWrite ( new PayoutRequest ( cts , request ) ) )
2020-06-24 13:44:26 +09:00
throw new ObjectDisposedException ( nameof ( PullPaymentHostedService ) ) ;
2020-06-24 10:34:09 +09:00
return cts . Task ;
}
public override Task StopAsync ( CancellationToken cancellationToken )
{
_Channel ? . Writer . Complete ( ) ;
2021-04-13 10:36:49 +02:00
_subscriptions . Dispose ( ) ;
2020-06-24 10:34:09 +09:00
return base . StopAsync ( cancellationToken ) ;
}
2021-06-10 11:43:45 +02:00
2022-11-15 10:40:57 +01:00
public Task < MarkPayoutRequest . PayoutPaidResult > MarkPaid ( MarkPayoutRequest request )
2021-06-10 11:43:45 +02:00
{
CancellationToken . ThrowIfCancellationRequested ( ) ;
2022-11-15 10:40:57 +01:00
var cts = new TaskCompletionSource < MarkPayoutRequest . PayoutPaidResult > ( TaskCreationOptions
2022-04-24 05:19:34 +02:00
. RunContinuationsAsynchronously ) ;
2021-06-10 11:43:45 +02:00
if ( ! _Channel . Writer . TryWrite ( new InternalPayoutPaidRequest ( cts , request ) ) )
throw new ObjectDisposedException ( nameof ( PullPaymentHostedService ) ) ;
return cts . Task ;
}
2022-08-17 09:45:51 +02:00
public PullPaymentsModel . PullPaymentModel . ProgressModel CalculatePullPaymentProgress ( PullPaymentData pp ,
DateTimeOffset now )
2022-06-28 16:02:17 +02:00
{
var ppBlob = pp . GetBlob ( ) ;
2022-08-17 09:45:51 +02:00
2022-06-28 16:02:17 +02:00
var ni = _currencyNameTable . GetCurrencyData ( ppBlob . Currency , true ) ;
var nfi = _currencyNameTable . GetNumberFormatInfo ( ppBlob . Currency , true ) ;
var totalCompleted = pp . Payouts . Where ( p = > ( p . State = = PayoutState . Completed | |
p . State = = PayoutState . InProgress ) & & p . IsInPeriod ( pp , now ) )
. Select ( o = > o . GetBlob ( _jsonSerializerSettings ) . Amount ) . Sum ( ) . RoundToSignificant ( ni . Divisibility ) ;
var period = pp . GetPeriod ( now ) ;
var totalAwaiting = pp . Payouts . Where ( p = > ( p . State = = PayoutState . AwaitingPayment | |
p . State = = PayoutState . AwaitingApproval ) & &
p . IsInPeriod ( pp , now ) ) . Select ( o = >
o . GetBlob ( _jsonSerializerSettings ) . Amount ) . Sum ( ) . RoundToSignificant ( ni . Divisibility ) ;
;
var currencyData = _currencyNameTable . GetCurrencyData ( ppBlob . Currency , true ) ;
return new PullPaymentsModel . PullPaymentModel . ProgressModel ( )
{
CompletedPercent = ( int ) ( totalCompleted / ppBlob . Limit * 100 m ) ,
AwaitingPercent = ( int ) ( totalAwaiting / ppBlob . Limit * 100 m ) ,
AwaitingFormatted = totalAwaiting . ToString ( "C" , nfi ) ,
Awaiting = totalAwaiting ,
Completed = totalCompleted ,
CompletedFormatted = totalCompleted . ToString ( "C" , nfi ) ,
Limit = ppBlob . Limit . RoundToSignificant ( currencyData . Divisibility ) ,
2023-03-13 02:12:58 +01:00
LimitFormatted = _displayFormatter . Currency ( ppBlob . Limit , ppBlob . Currency ) ,
2022-06-28 16:02:17 +02:00
ResetIn = period ? . End is { } nr ? ZeroIfNegative ( nr - now ) . TimeString ( ) : null ,
EndIn = pp . EndDate is { } end ? ZeroIfNegative ( end - now ) . TimeString ( ) : null ,
} ;
}
2022-08-17 09:45:51 +02:00
2022-06-28 16:02:17 +02:00
public TimeSpan ZeroIfNegative ( TimeSpan time )
{
if ( time < TimeSpan . Zero )
time = TimeSpan . Zero ;
return time ;
}
2022-08-17 09:45:51 +02:00
2022-06-28 16:02:17 +02:00
2021-06-10 11:43:45 +02:00
class InternalPayoutPaidRequest
{
2022-11-15 10:40:57 +01:00
public InternalPayoutPaidRequest ( TaskCompletionSource < MarkPayoutRequest . PayoutPaidResult > completionSource ,
MarkPayoutRequest request )
2021-06-10 11:43:45 +02:00
{
2021-12-28 17:39:54 +09:00
ArgumentNullException . ThrowIfNull ( request ) ;
ArgumentNullException . ThrowIfNull ( completionSource ) ;
2021-06-10 11:43:45 +02:00
Completion = completionSource ;
Request = request ;
}
2022-04-24 05:19:34 +02:00
2022-11-15 10:40:57 +01:00
public TaskCompletionSource < MarkPayoutRequest . PayoutPaidResult > Completion { get ; set ; }
public MarkPayoutRequest Request { get ; }
2021-06-10 11:43:45 +02:00
}
}
2022-11-15 10:40:57 +01:00
public class MarkPayoutRequest
2021-06-10 11:43:45 +02:00
{
public enum PayoutPaidResult
{
Ok ,
NotFound ,
InvalidState
}
2022-04-24 05:19:34 +02:00
2021-06-10 11:43:45 +02:00
public string PayoutId { get ; set ; }
2022-11-19 00:04:46 +09:00
public JObject Proof { get ; set ; }
2023-02-07 08:51:20 +01:00
public PayoutState State { get ; set ; } = PayoutState . Completed ;
2021-06-10 18:54:27 +09:00
2021-06-10 11:43:45 +02:00
public static string GetErrorMessage ( PayoutPaidResult result )
{
switch ( result )
{
case PayoutPaidResult . NotFound :
return "The payout is not found" ;
case PayoutPaidResult . Ok :
return "Ok" ;
case PayoutPaidResult . InvalidState :
2022-11-15 10:40:57 +01:00
return "The payout is not in a state that can be marked with the specified state" ;
2021-06-10 11:43:45 +02:00
default :
throw new NotSupportedException ( ) ;
}
}
2020-06-24 10:34:09 +09:00
}
public class ClaimRequest
{
2023-07-24 13:40:26 +02:00
public static ( string error , decimal? amount ) IsPayoutAmountOk ( IClaimDestination destination , decimal? amount , string payoutCurrency = null , string ppCurrency = null )
{
return amount switch
{
null when destination . Amount is null & & ppCurrency is null = > ( "Amount is not specified in destination or payout request" , null ) ,
null when destination . Amount is null = > ( null , null ) ,
null when destination . Amount ! = null = > ( null , destination . Amount ) ,
not null when destination . Amount is null = > ( null , amount ) ,
not null when destination . Amount ! = null & & amount ! = destination . Amount & &
destination . IsExplicitAmountMinimum & &
payoutCurrency = = "BTC" & & ppCurrency = = "SATS" & &
new Money ( amount . Value , MoneyUnit . Satoshi ) . ToUnit ( MoneyUnit . BTC ) < destination . Amount = >
( $"Amount is implied in both destination ({destination.Amount}) and payout request ({amount}), but the payout request amount is less than the destination amount" , null ) ,
not null when destination . Amount ! = null & & amount ! = destination . Amount & &
destination . IsExplicitAmountMinimum & &
! ( payoutCurrency = = "BTC" & & ppCurrency = = "SATS" ) & &
amount < destination . Amount = >
( $"Amount is implied in both destination ({destination.Amount}) and payout request ({amount}), but the payout request amount is less than the destination amount" , null ) ,
not null when destination . Amount ! = null & & amount ! = destination . Amount & &
! destination . IsExplicitAmountMinimum = >
( $"Amount is implied in destination ({destination.Amount}) that does not match the payout amount provided {amount})" , null ) ,
_ = > ( null , amount )
} ;
}
2020-06-24 10:34:09 +09:00
public static string GetErrorMessage ( ClaimResult result )
{
switch ( result )
{
case ClaimResult . Ok :
break ;
case ClaimResult . Duplicate :
return "This address is already used for another payout" ;
case ClaimResult . Expired :
return "This pull payment is expired" ;
case ClaimResult . NotStarted :
return "This pull payment has yet started" ;
case ClaimResult . Archived :
return "This pull payment has been archived" ;
case ClaimResult . Overdraft :
return "The payout amount overdraft the pull payment's limit" ;
case ClaimResult . AmountTooLow :
return "The requested payout amount is too low" ;
case ClaimResult . PaymentMethodNotSupported :
return "This payment method is not supported by the pull payment" ;
default :
throw new NotSupportedException ( "Unsupported ClaimResult" ) ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
return null ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public class ClaimResponse
{
public ClaimResponse ( ClaimResult result , PayoutData payoutData = null )
{
Result = result ;
PayoutData = payoutData ;
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public ClaimResult Result { get ; set ; }
public PayoutData PayoutData { get ; set ; }
}
2022-04-24 05:19:34 +02:00
2020-06-24 10:34:09 +09:00
public enum ClaimResult
{
Ok ,
Duplicate ,
Expired ,
Archived ,
NotStarted ,
Overdraft ,
AmountTooLow ,
PaymentMethodNotSupported ,
}
public PaymentMethodId PaymentMethodId { get ; set ; }
public string PullPaymentId { get ; set ; }
public decimal? Value { get ; set ; }
public IClaimDestination Destination { get ; set ; }
2022-04-24 05:19:34 +02:00
public string StoreId { get ; set ; }
2022-04-28 02:51:04 +02:00
public bool? PreApprove { get ; set ; }
2023-07-24 11:37:18 +02:00
public JObject Metadata { get ; set ; }
2020-06-24 10:34:09 +09:00
}
2023-07-20 15:05:14 +02:00
public record PayoutEvent ( PayoutEvent . PayoutEventType Type , PayoutData Payout )
{
public enum PayoutEventType
{
Created ,
Approved
}
}
2020-06-24 10:34:09 +09:00
}