2020-05-23 21:13:18 +02:00
using System ;
2020-03-24 16:18:43 +01:00
using System.Collections.Generic ;
using System.Linq ;
2020-03-31 14:43:35 +02:00
using System.Threading.Tasks ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Constants ;
2022-02-24 09:00:44 +01:00
using BTCPayServer.Abstractions.Extensions ;
2020-03-31 10:47:13 +02:00
using BTCPayServer.Client ;
2020-04-01 08:21:54 +02:00
using BTCPayServer.Client.Models ;
2020-03-31 14:43:35 +02:00
using BTCPayServer.Data ;
2020-12-29 10:33:44 +01:00
using BTCPayServer.Payments ;
2020-03-24 16:18:43 +01:00
using BTCPayServer.Security ;
2020-03-31 14:43:35 +02:00
using BTCPayServer.Services.Stores ;
2020-03-24 16:18:43 +01:00
using Microsoft.AspNetCore.Authorization ;
2020-06-30 08:26:19 +02:00
using Microsoft.AspNetCore.Cors ;
2020-03-31 14:43:35 +02:00
using Microsoft.AspNetCore.Identity ;
2020-03-24 16:18:43 +01:00
using Microsoft.AspNetCore.Mvc ;
2021-07-27 14:11:47 +02:00
using StoreData = BTCPayServer . Data . StoreData ;
2020-03-24 16:18:43 +01:00
2022-01-14 05:05:23 +01:00
namespace BTCPayServer.Controllers.Greenfield
2020-03-24 16:18:43 +01:00
{
[ApiController]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
2020-06-30 08:26:19 +02:00
[EnableCors(CorsPolicies.All)]
2022-01-07 04:17:59 +01:00
public class GreenfieldStoresController : ControllerBase
2020-03-24 16:18:43 +01:00
{
2020-03-31 14:43:35 +02:00
private readonly StoreRepository _storeRepository ;
private readonly UserManager < ApplicationUser > _userManager ;
2020-12-29 10:33:44 +01:00
private readonly BTCPayNetworkProvider _btcPayNetworkProvider ;
2020-03-31 14:43:35 +02:00
2022-01-07 04:17:59 +01:00
public GreenfieldStoresController ( StoreRepository storeRepository , UserManager < ApplicationUser > userManager , BTCPayNetworkProvider btcPayNetworkProvider )
2020-03-31 14:43:35 +02:00
{
_storeRepository = storeRepository ;
_userManager = userManager ;
2020-12-29 10:33:44 +01:00
_btcPayNetworkProvider = btcPayNetworkProvider ;
2020-03-31 14:43:35 +02:00
}
2020-03-24 16:18:43 +01:00
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores")]
2021-07-27 14:11:47 +02:00
public Task < ActionResult < IEnumerable < Client . Models . StoreData > > > GetStores ( )
2020-03-24 16:18:43 +01:00
{
2020-06-28 10:55:27 +02:00
var stores = HttpContext . GetStoresData ( ) ;
2021-07-27 14:11:47 +02:00
return Task . FromResult < ActionResult < IEnumerable < Client . Models . StoreData > > > ( Ok ( stores . Select ( FromModel ) ) ) ;
2020-03-24 16:18:43 +01:00
}
2020-06-28 10:55:27 +02:00
2020-03-31 10:47:13 +02:00
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}")]
2021-12-23 05:32:08 +01:00
public IActionResult GetStore ( string storeId )
2020-03-31 10:47:13 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
{
2021-12-23 05:32:08 +01:00
return StoreNotFound ( ) ;
2020-03-31 10:47:13 +02:00
}
2021-12-23 05:32:08 +01:00
return Ok ( FromModel ( store ) ) ;
2020-03-31 10:47:13 +02:00
}
2020-06-28 10:55:27 +02:00
2020-03-31 14:43:35 +02:00
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}")]
2020-06-03 11:58:49 +02:00
public async Task < IActionResult > RemoveStore ( string storeId )
2020-03-31 14:43:35 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
{
2021-12-23 05:32:08 +01:00
return StoreNotFound ( ) ;
2020-03-31 14:43:35 +02:00
}
if ( ! _storeRepository . CanDeleteStores ( ) )
{
2020-06-08 16:40:58 +02:00
return this . CreateAPIError ( "unsupported" ,
2020-06-03 11:58:49 +02:00
"BTCPay Server is using a database server that does not allow you to remove stores." ) ;
2020-03-31 14:43:35 +02:00
}
await _storeRepository . RemoveStore ( storeId , _userManager . GetUserId ( User ) ) ;
return Ok ( ) ;
}
2020-06-28 10:55:27 +02:00
2020-04-01 08:21:54 +02:00
[HttpPost("~/api/v1/stores")]
2020-06-27 08:34:03 +02:00
[Authorize(Policy = Policies.CanModifyStoreSettingsUnscoped, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
2020-04-30 16:43:16 +02:00
public async Task < IActionResult > CreateStore ( CreateStoreRequest request )
2020-04-01 08:21:54 +02:00
{
2020-04-30 16:43:16 +02:00
var validationResult = Validate ( request ) ;
if ( validationResult ! = null )
{
return validationResult ;
}
var store = new Data . StoreData ( ) ;
2021-12-31 08:59:02 +01:00
2021-10-25 06:15:08 +02:00
PaymentMethodId . TryParse ( request . DefaultPaymentMethod , out var defaultPaymentMethodId ) ;
ToModel ( request , store , defaultPaymentMethodId ) ;
2020-04-30 16:43:16 +02:00
await _storeRepository . CreateStore ( _userManager . GetUserId ( User ) , store ) ;
2020-04-01 08:21:54 +02:00
return Ok ( FromModel ( store ) ) ;
}
2020-06-28 10:55:27 +02:00
2020-04-27 13:13:20 +02:00
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPut("~/api/v1/stores/{storeId}")]
2020-04-30 16:43:16 +02:00
public async Task < IActionResult > UpdateStore ( string storeId , UpdateStoreRequest request )
2020-04-27 13:13:20 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
{
2021-12-23 05:32:08 +01:00
return StoreNotFound ( ) ;
2020-04-27 13:13:20 +02:00
}
2020-04-30 16:43:16 +02:00
var validationResult = Validate ( request ) ;
if ( validationResult ! = null )
{
return validationResult ;
}
2020-04-27 13:13:20 +02:00
2021-10-25 06:15:08 +02:00
PaymentMethodId . TryParse ( request . DefaultPaymentMethod , out var defaultPaymentMethodId ) ;
2021-10-20 16:17:40 +02:00
2021-10-25 06:15:08 +02:00
ToModel ( request , store , defaultPaymentMethodId ) ;
2020-04-27 13:13:20 +02:00
await _storeRepository . UpdateStore ( store ) ;
return Ok ( FromModel ( store ) ) ;
}
2020-04-01 08:21:54 +02:00
2020-12-29 10:33:44 +01:00
private Client . Models . StoreData FromModel ( Data . StoreData data )
2020-03-24 16:18:43 +01:00
{
2020-05-23 21:13:18 +02:00
var storeBlob = data . GetStoreBlob ( ) ;
2020-03-24 16:18:43 +01:00
return new Client . Models . StoreData ( )
{
2020-06-28 10:55:27 +02:00
Id = data . Id ,
2020-05-23 21:13:18 +02:00
Name = data . StoreName ,
Website = data . StoreWebsite ,
SpeedPolicy = data . SpeedPolicy ,
2021-10-18 09:56:47 +02:00
DefaultPaymentMethod = data . GetDefaultPaymentId ( ) ? . ToStringNormalized ( ) ,
2020-05-23 21:13:18 +02:00
//blob
//we do not include DefaultCurrencyPairs,Spread, PreferredExchange, RateScripting, RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
2020-12-29 10:33:44 +01:00
//we do not include PaymentMethodCriteria because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
2020-05-23 21:13:18 +02:00
NetworkFeeMode = storeBlob . NetworkFeeMode ,
RequiresRefundEmail = storeBlob . RequiresRefundEmail ,
2020-11-09 08:11:03 +01:00
LightningAmountInSatoshi = storeBlob . LightningAmountInSatoshi ,
LightningPrivateRouteHints = storeBlob . LightningPrivateRouteHints ,
OnChainWithLnInvoiceFallback = storeBlob . OnChainWithLnInvoiceFallback ,
RedirectAutomatically = storeBlob . RedirectAutomatically ,
2021-04-07 06:08:42 +02:00
LazyPaymentMethods = storeBlob . LazyPaymentMethods ,
2020-05-23 21:13:18 +02:00
ShowRecommendedFee = storeBlob . ShowRecommendedFee ,
RecommendedFeeBlockTarget = storeBlob . RecommendedFeeBlockTarget ,
DefaultLang = storeBlob . DefaultLang ,
2020-08-26 07:01:39 +02:00
MonitoringExpiration = storeBlob . MonitoringExpiration ,
InvoiceExpiration = storeBlob . InvoiceExpiration ,
2020-05-23 21:13:18 +02:00
CustomLogo = storeBlob . CustomLogo ,
CustomCSS = storeBlob . CustomCSS ,
HtmlTitle = storeBlob . HtmlTitle ,
2020-05-31 04:42:49 +02:00
AnyoneCanCreateInvoice = storeBlob . AnyoneCanInvoice ,
2020-05-23 21:13:18 +02:00
LightningDescriptionTemplate = storeBlob . LightningDescriptionTemplate ,
PaymentTolerance = storeBlob . PaymentTolerance ,
2020-11-09 08:11:03 +01:00
PayJoinEnabled = storeBlob . PayJoinEnabled
2020-03-24 16:18:43 +01:00
} ;
}
2020-05-23 21:13:18 +02:00
2020-12-29 10:33:44 +01:00
private static void ToModel ( StoreBaseData restModel , Data . StoreData model , PaymentMethodId defaultPaymentMethod )
2020-04-22 14:42:30 +02:00
{
2020-05-23 21:13:18 +02:00
var blob = model . GetStoreBlob ( ) ;
model . StoreName = restModel . Name ;
2020-04-30 16:43:16 +02:00
model . StoreName = restModel . Name ;
2020-05-23 21:13:18 +02:00
model . StoreWebsite = restModel . Website ;
model . SpeedPolicy = restModel . SpeedPolicy ;
2020-12-29 10:33:44 +01:00
model . SetDefaultPaymentId ( defaultPaymentMethod ) ;
2020-05-23 21:13:18 +02:00
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
//blob
//we do not include DefaultCurrencyPairs;Spread; PreferredExchange; RateScripting; RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
//we do not include OnChainMinValue and LightningMaxValue because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
blob . NetworkFeeMode = restModel . NetworkFeeMode ;
2021-10-20 16:17:40 +02:00
blob . DefaultCurrency = restModel . DefaultCurrency ;
2020-05-23 21:13:18 +02:00
blob . RequiresRefundEmail = restModel . RequiresRefundEmail ;
2020-11-09 08:11:03 +01:00
blob . LightningAmountInSatoshi = restModel . LightningAmountInSatoshi ;
blob . LightningPrivateRouteHints = restModel . LightningPrivateRouteHints ;
blob . OnChainWithLnInvoiceFallback = restModel . OnChainWithLnInvoiceFallback ;
2021-04-07 06:08:42 +02:00
blob . LazyPaymentMethods = restModel . LazyPaymentMethods ;
2020-11-09 08:11:03 +01:00
blob . RedirectAutomatically = restModel . RedirectAutomatically ;
2020-05-23 21:13:18 +02:00
blob . ShowRecommendedFee = restModel . ShowRecommendedFee ;
blob . RecommendedFeeBlockTarget = restModel . RecommendedFeeBlockTarget ;
blob . DefaultLang = restModel . DefaultLang ;
2020-08-26 07:01:39 +02:00
blob . MonitoringExpiration = restModel . MonitoringExpiration ;
blob . InvoiceExpiration = restModel . InvoiceExpiration ;
2020-05-23 21:13:18 +02:00
blob . CustomLogo = restModel . CustomLogo ;
blob . CustomCSS = restModel . CustomCSS ;
blob . HtmlTitle = restModel . HtmlTitle ;
2020-05-31 04:42:49 +02:00
blob . AnyoneCanInvoice = restModel . AnyoneCanCreateInvoice ;
2020-05-23 21:13:18 +02:00
blob . LightningDescriptionTemplate = restModel . LightningDescriptionTemplate ;
blob . PaymentTolerance = restModel . PaymentTolerance ;
blob . PayJoinEnabled = restModel . PayJoinEnabled ;
model . SetStoreBlob ( blob ) ;
2020-04-30 16:43:16 +02:00
}
private IActionResult Validate ( StoreBaseData request )
{
2020-05-23 21:13:18 +02:00
if ( request is null )
{
return BadRequest ( ) ;
}
2020-06-28 10:55:27 +02:00
2020-12-29 10:33:44 +01:00
if ( ! string . IsNullOrEmpty ( request . DefaultPaymentMethod ) & &
2021-10-25 06:15:08 +02:00
! PaymentMethodId . TryParse ( request . DefaultPaymentMethod , out var defaultPaymentMethodId ) )
2020-12-29 10:33:44 +01:00
{
ModelState . AddModelError ( nameof ( request . Name ) , "DefaultPaymentMethod is invalid" ) ;
}
2021-12-31 08:59:02 +01:00
2020-05-23 21:13:18 +02:00
if ( string . IsNullOrEmpty ( request . Name ) )
2020-04-30 16:43:16 +02:00
ModelState . AddModelError ( nameof ( request . Name ) , "Name is missing" ) ;
2020-06-28 10:55:27 +02:00
else if ( request . Name . Length < 1 | | request . Name . Length > 50 )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . Name ) , "Name can only be between 1 and 50 characters" ) ;
if ( ! string . IsNullOrEmpty ( request . Website ) & & ! Uri . TryCreate ( request . Website , UriKind . Absolute , out _ ) )
{
ModelState . AddModelError ( nameof ( request . Website ) , "Website is not a valid url" ) ;
}
2020-06-28 10:55:27 +02:00
if ( request . InvoiceExpiration < TimeSpan . FromMinutes ( 1 ) & & request . InvoiceExpiration > TimeSpan . FromMinutes ( 60 * 24 * 24 ) )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . InvoiceExpiration ) , "InvoiceExpiration can only be between 1 and 34560 mins" ) ;
2020-06-28 10:55:27 +02:00
if ( request . MonitoringExpiration < TimeSpan . FromMinutes ( 10 ) & & request . MonitoringExpiration > TimeSpan . FromMinutes ( 60 * 24 * 24 ) )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . MonitoringExpiration ) , "InvoiceExpiration can only be between 10 and 34560 mins" ) ;
2020-06-28 10:55:27 +02:00
if ( request . PaymentTolerance < 0 & & request . PaymentTolerance > 100 )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . PaymentTolerance ) , "PaymentTolerance can only be between 0 and 100 percent" ) ;
2020-06-03 11:58:49 +02:00
2020-06-08 16:40:58 +02:00
return ! ModelState . IsValid ? this . CreateValidationError ( ModelState ) : null ;
2020-04-22 14:42:30 +02:00
}
2021-12-31 08:59:02 +01:00
2021-12-23 05:32:08 +01:00
private IActionResult StoreNotFound ( )
{
return this . CreateAPIError ( 404 , "store-not-found" , "The store was not found" ) ;
}
2020-03-24 16:18:43 +01:00
}
}