2020-06-29 04:44:35 +02:00
using System ;
2021-04-13 10:36:49 +02:00
using System.Collections.Generic ;
2020-06-24 03:34:09 +02:00
using System.Linq ;
2022-11-22 12:17:29 +01:00
using System.Threading ;
2020-06-24 03:34:09 +02:00
using System.Threading.Tasks ;
2022-08-11 14:30:42 +02:00
using BTCPayServer.Abstractions.Constants ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Extensions ;
using BTCPayServer.Abstractions.Models ;
2022-08-11 14:30:42 +02:00
using BTCPayServer.Client ;
2021-04-13 10:36:49 +02:00
using BTCPayServer.Client.Models ;
2020-06-24 03:34:09 +02:00
using BTCPayServer.Data ;
using BTCPayServer.HostedServices ;
using BTCPayServer.Models ;
2022-08-11 14:30:42 +02:00
using BTCPayServer.Models.WalletViewModels ;
2020-06-24 03:34:09 +02:00
using BTCPayServer.Payments ;
using BTCPayServer.Services ;
using BTCPayServer.Services.Rates ;
2023-01-30 09:23:49 +01:00
using BTCPayServer.Services.Stores ;
2020-06-24 06:44:26 +02:00
using Microsoft.AspNetCore.Authorization ;
2020-06-24 03:34:09 +02:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.EntityFrameworkCore ;
2022-05-25 12:59:28 +02:00
using NBitcoin ;
2020-06-24 03:34:09 +02:00
namespace BTCPayServer.Controllers
{
2022-01-07 04:32:00 +01:00
public class UIPullPaymentController : Controller
2020-06-24 03:34:09 +02:00
{
private readonly ApplicationDbContextFactory _dbContextFactory ;
private readonly CurrencyNameTable _currencyNameTable ;
2023-03-13 02:12:58 +01:00
private readonly DisplayFormatter _displayFormatter ;
2020-06-24 03:34:09 +02:00
private readonly PullPaymentHostedService _pullPaymentHostedService ;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings ;
2021-04-13 10:36:49 +02:00
private readonly IEnumerable < IPayoutHandler > _payoutHandlers ;
2023-01-30 09:23:49 +01:00
private readonly StoreRepository _storeRepository ;
2020-06-24 03:34:09 +02:00
2022-01-07 04:32:00 +01:00
public UIPullPaymentController ( ApplicationDbContextFactory dbContextFactory ,
2020-06-24 03:34:09 +02:00
CurrencyNameTable currencyNameTable ,
2023-03-13 02:12:58 +01:00
DisplayFormatter displayFormatter ,
2020-06-24 03:34:09 +02:00
PullPaymentHostedService pullPaymentHostedService ,
2021-04-13 10:36:49 +02:00
BTCPayNetworkJsonSerializerSettings serializerSettings ,
2023-01-30 09:23:49 +01:00
IEnumerable < IPayoutHandler > payoutHandlers ,
StoreRepository storeRepository )
2020-06-24 03:34:09 +02:00
{
_dbContextFactory = dbContextFactory ;
_currencyNameTable = currencyNameTable ;
2023-03-13 02:12:58 +01:00
_displayFormatter = displayFormatter ;
2020-06-24 03:34:09 +02:00
_pullPaymentHostedService = pullPaymentHostedService ;
_serializerSettings = serializerSettings ;
2021-04-13 10:36:49 +02:00
_payoutHandlers = payoutHandlers ;
2023-01-30 09:23:49 +01:00
_storeRepository = storeRepository ;
2020-06-24 03:34:09 +02:00
}
2021-12-31 08:59:02 +01:00
2022-08-11 14:30:42 +02:00
[AllowAnonymous]
[HttpGet("pull-payments/{pullPaymentId}")]
2020-06-24 03:34:09 +02:00
public async Task < IActionResult > ViewPullPayment ( string pullPaymentId )
{
using var ctx = _dbContextFactory . CreateContext ( ) ;
var pp = await ctx . PullPayments . FindAsync ( pullPaymentId ) ;
if ( pp is null )
return NotFound ( ) ;
var blob = pp . GetBlob ( ) ;
2023-01-30 09:23:49 +01:00
var store = await _storeRepository . FindStore ( pp . StoreId ) ;
if ( store is null )
return NotFound ( ) ;
var storeBlob = store . GetStoreBlob ( ) ;
2020-06-24 03:34:09 +02:00
var payouts = ( await ctx . Payouts . GetPayoutInPeriod ( pp )
. OrderByDescending ( o = > o . Date )
. ToListAsync ( ) )
. Select ( o = > new
{
Entity = o ,
Blob = o . GetBlob ( _serializerSettings ) ,
2021-10-18 08:00:38 +02:00
ProofBlob = _payoutHandlers . FindPayoutHandler ( o . GetPaymentMethodId ( ) ) ? . ParseProof ( o )
2020-06-24 03:34:09 +02:00
} ) ;
var cd = _currencyNameTable . GetCurrencyData ( blob . Currency , false ) ;
var totalPaid = payouts . Where ( p = > p . Entity . State ! = PayoutState . Cancelled ) . Select ( p = > p . Blob . Amount ) . Sum ( ) ;
var amountDue = blob . Limit - totalPaid ;
2023-01-06 14:18:07 +01:00
ViewPullPaymentModel vm = new ( pp , DateTimeOffset . UtcNow )
2020-06-24 03:34:09 +02:00
{
2023-01-30 09:23:49 +01:00
BrandColor = storeBlob . BrandColor ,
CssFileId = storeBlob . CssFileId ,
2020-06-24 03:34:09 +02:00
AmountCollected = totalPaid ,
AmountDue = amountDue ,
ClaimedAmount = amountDue ,
CurrencyData = cd ,
2021-03-07 23:51:50 +01:00
StartDate = pp . StartDate ,
2021-12-17 07:31:06 +01:00
LastRefreshed = DateTime . UtcNow ,
2020-06-24 03:34:09 +02:00
Payouts = payouts
2020-10-13 09:58:46 +02:00
. Select ( entity = > new ViewPullPaymentModel . PayoutLine
2020-06-28 10:55:27 +02:00
{
Id = entity . Entity . Id ,
Amount = entity . Blob . Amount ,
Currency = blob . Currency ,
2021-04-13 10:36:49 +02:00
Status = entity . Entity . State ,
Destination = entity . Blob . Destination ,
2021-10-18 05:37:59 +02:00
PaymentMethod = PaymentMethodId . Parse ( entity . Entity . PaymentMethodId ) ,
2022-05-18 14:49:42 +02:00
Link = entity . ProofBlob ? . Link ,
TransactionId = entity . ProofBlob ? . Id
2020-06-28 10:55:27 +02:00
} ) . ToList ( )
2020-06-24 03:34:09 +02:00
} ;
vm . IsPending & = vm . AmountDue > 0.0 m ;
return View ( nameof ( ViewPullPayment ) , vm ) ;
}
2022-08-11 14:30:42 +02:00
[HttpGet("stores/{storeId}/pull-payments/edit/{pullPaymentId}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task < IActionResult > EditPullPayment ( string storeId , string pullPaymentId )
{
using var ctx = _dbContextFactory . CreateContext ( ) ;
Data . PullPaymentData pp = await ctx . PullPayments . FindAsync ( pullPaymentId ) ;
if ( pp = = null & & ! string . IsNullOrEmpty ( pullPaymentId ) )
{
return NotFound ( ) ;
}
var vm = new UpdatePullPaymentModel ( pp ) ;
return View ( vm ) ;
}
[HttpPost("stores/{storeId}/pull-payments/edit/{pullPaymentId}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task < IActionResult > EditPullPayment ( string storeId , string pullPaymentId , UpdatePullPaymentModel viewModel )
{
using var ctx = _dbContextFactory . CreateContext ( ) ;
var pp = await ctx . PullPayments . FindAsync ( pullPaymentId ) ;
if ( pp = = null & & ! string . IsNullOrEmpty ( pullPaymentId ) )
{
return NotFound ( ) ;
}
if ( ! ModelState . IsValid )
{
return View ( viewModel ) ;
}
var blob = pp . GetBlob ( ) ;
blob . Description = viewModel . Description ? ? string . Empty ;
blob . Name = viewModel . Name ? ? string . Empty ;
blob . View = new PullPaymentBlob . PullPaymentView ( )
{
Title = viewModel . Name ? ? string . Empty ,
Description = viewModel . Description ? ? string . Empty ,
CustomCSSLink = viewModel . CustomCSSLink ,
Email = null ,
EmbeddedCSS = viewModel . EmbeddedCSS ,
} ;
2023-01-06 14:18:07 +01:00
2022-08-11 14:30:42 +02:00
pp . SetBlob ( blob ) ;
ctx . PullPayments . Update ( pp ) ;
await ctx . SaveChangesAsync ( ) ;
2023-01-06 14:18:07 +01:00
2022-08-11 14:30:42 +02:00
TempData . SetStatusMessageModel ( new StatusMessageModel
{
Message = "Pull payment updated successfully" ,
Severity = StatusMessageModel . StatusSeverity . Success
} ) ;
return RedirectToAction ( nameof ( UIStorePullPaymentsController . PullPayments ) , "UIStorePullPayments" , new { storeId , pullPaymentId } ) ;
}
[AllowAnonymous]
2022-05-25 12:59:28 +02:00
[HttpPost("pull-payments/{pullPaymentId}/claim")]
2022-11-22 12:17:29 +01:00
public async Task < IActionResult > ClaimPullPayment ( string pullPaymentId , ViewPullPaymentModel vm , CancellationToken cancellationToken )
2020-06-24 03:34:09 +02:00
{
using var ctx = _dbContextFactory . CreateContext ( ) ;
var pp = await ctx . PullPayments . FindAsync ( pullPaymentId ) ;
if ( pp is null )
{
ModelState . AddModelError ( nameof ( pullPaymentId ) , "This pull payment does not exists" ) ;
}
2021-12-31 08:59:02 +01:00
2020-06-24 03:34:09 +02:00
var ppBlob = pp . GetBlob ( ) ;
2021-12-31 08:59:02 +01:00
2021-10-18 05:37:59 +02:00
var paymentMethodId = ppBlob . SupportedPaymentMethods . FirstOrDefault ( id = > vm . SelectedPaymentMethod = = id . ToString ( ) ) ;
2021-12-31 08:59:02 +01:00
var payoutHandler = paymentMethodId is null ? null : _payoutHandlers . FindPayoutHandler ( paymentMethodId ) ;
2021-10-18 05:37:59 +02:00
if ( payoutHandler is null )
2020-06-24 03:34:09 +02:00
{
2022-05-18 14:40:26 +02:00
ModelState . AddModelError ( nameof ( vm . SelectedPaymentMethod ) , "Invalid destination with selected payment method" ) ;
2021-10-18 05:37:59 +02:00
return await ViewPullPayment ( pullPaymentId ) ;
}
2022-11-22 12:17:29 +01:00
var destination = await payoutHandler . ParseAndValidateClaimDestination ( paymentMethodId , vm . Destination , ppBlob , cancellationToken ) ;
2021-10-18 05:37:59 +02:00
if ( destination . destination is null )
{
2021-12-31 08:59:02 +01:00
ModelState . AddModelError ( nameof ( vm . Destination ) , destination . error ? ? "Invalid destination with selected payment method" ) ;
2021-10-18 05:37:59 +02:00
return await ViewPullPayment ( pullPaymentId ) ;
}
if ( vm . ClaimedAmount = = 0 )
{
2022-05-18 14:40:26 +02:00
ModelState . AddModelError ( nameof ( vm . ClaimedAmount ) , "Amount is required" ) ;
2021-10-18 05:37:59 +02:00
}
2022-05-25 12:59:28 +02:00
else
2021-10-18 05:37:59 +02:00
{
2022-05-25 12:59:28 +02:00
var amount = ppBlob . Currency = = "SATS" ? new Money ( vm . ClaimedAmount , MoneyUnit . Satoshi ) . ToUnit ( MoneyUnit . BTC ) : vm . ClaimedAmount ;
if ( destination . destination . Amount ! = null & & amount ! = destination . destination . Amount )
{
2023-03-13 02:12:58 +01:00
var implied = _displayFormatter . Currency ( destination . destination . Amount . Value , paymentMethodId . CryptoCode , DisplayFormatter . CurrencyFormat . Symbol ) ;
var provided = _displayFormatter . Currency ( vm . ClaimedAmount , ppBlob . Currency , DisplayFormatter . CurrencyFormat . Symbol ) ;
2023-01-06 14:18:07 +01:00
ModelState . AddModelError ( nameof ( vm . ClaimedAmount ) ,
2022-05-25 12:59:28 +02:00
$"Amount implied in destination ({implied}) does not match the payout amount provided ({provided})." ) ;
}
2020-06-24 03:34:09 +02:00
}
if ( ! ModelState . IsValid )
{
return await ViewPullPayment ( pullPaymentId ) ;
}
var result = await _pullPaymentHostedService . Claim ( new ClaimRequest ( )
{
2021-10-18 05:37:59 +02:00
Destination = destination . destination ,
2020-06-24 03:34:09 +02:00
PullPaymentId = pullPaymentId ,
Value = vm . ClaimedAmount ,
2021-10-18 05:37:59 +02:00
PaymentMethodId = paymentMethodId
2020-06-24 03:34:09 +02:00
} ) ;
if ( result . Result ! = ClaimRequest . ClaimResult . Ok )
{
if ( result . Result = = ClaimRequest . ClaimResult . AmountTooLow )
{
ModelState . AddModelError ( nameof ( vm . ClaimedAmount ) , ClaimRequest . GetErrorMessage ( result . Result ) ) ;
}
else
{
ModelState . AddModelError ( string . Empty , ClaimRequest . GetErrorMessage ( result . Result ) ) ;
}
return await ViewPullPayment ( pullPaymentId ) ;
}
2023-01-06 14:18:07 +01:00
2022-05-18 14:40:26 +02:00
TempData . SetStatusMessageModel ( new StatusMessageModel
2020-06-24 03:34:09 +02:00
{
2023-03-13 02:12:58 +01:00
Message = $"Your claim request of {_displayFormatter.Currency(vm.ClaimedAmount, ppBlob.Currency, DisplayFormatter.CurrencyFormat.Symbol)} to {vm.Destination} has been submitted and is awaiting {(result.PayoutData.State == PayoutState.AwaitingApproval ? " approval " : " payment ")}." ,
2022-05-18 14:40:26 +02:00
Severity = StatusMessageModel . StatusSeverity . Success
} ) ;
2023-01-06 14:18:07 +01:00
2022-05-18 14:40:26 +02:00
return RedirectToAction ( nameof ( ViewPullPayment ) , new { pullPaymentId } ) ;
2020-06-24 03:34:09 +02:00
}
}
}