2020-06-29 04:44:35 +02:00
using System ;
2024-05-09 02:18:02 +02:00
using Dapper ;
2020-06-28 10:55:27 +02:00
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
2017-09-13 08:47:34 +02:00
using System.Linq ;
2020-06-28 10:55:27 +02:00
using System.Net ;
using System.Net.Http ;
using System.Text ;
using System.Text.RegularExpressions ;
2017-09-13 08:47:34 +02:00
using System.Threading ;
2020-06-28 10:55:27 +02:00
using System.Threading.Tasks ;
2022-02-21 15:46:43 +01:00
using BTCPayServer.Abstractions.Constants ;
2020-12-29 09:58:35 +01:00
using BTCPayServer.Abstractions.Contracts ;
2022-03-02 18:28:34 +01:00
using BTCPayServer.Abstractions.Extensions ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Models ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Client ;
using BTCPayServer.Client.Models ;
2023-11-30 10:12:44 +01:00
using BTCPayServer.Configuration ;
2017-10-11 05:20:44 +02:00
using BTCPayServer.Controllers ;
2017-10-24 18:41:01 +02:00
using BTCPayServer.Data ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Events ;
2021-04-28 06:14:15 +02:00
using BTCPayServer.Fido2 ;
using BTCPayServer.Fido2.Models ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.HostedServices ;
2020-12-29 09:58:35 +01:00
using BTCPayServer.Hosting ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Lightning ;
2023-11-21 10:55:02 +01:00
using BTCPayServer.Lightning.Charge ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models ;
using BTCPayServer.Models.AccountViewModels ;
using BTCPayServer.Models.AppViewModels ;
using BTCPayServer.Models.InvoicingModels ;
2021-09-13 03:16:52 +02:00
using BTCPayServer.Models.ManageViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models.ServerViewModels ;
2018-01-17 07:59:31 +01:00
using BTCPayServer.Models.StoreViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models.WalletViewModels ;
2018-02-18 18:38:03 +01:00
using BTCPayServer.Payments ;
2018-02-19 10:54:21 +01:00
using BTCPayServer.Payments.Bitcoin ;
2018-02-25 16:48:12 +01:00
using BTCPayServer.Payments.Lightning ;
2021-03-01 14:44:53 +01:00
using BTCPayServer.Payments.PayJoin.Sender ;
2023-03-17 03:56:32 +01:00
using BTCPayServer.Plugins.PayButton ;
using BTCPayServer.Plugins.PointOfSale ;
2022-07-18 20:51:53 +02:00
using BTCPayServer.Plugins.PointOfSale.Controllers ;
2024-04-04 09:31:04 +02:00
using BTCPayServer.Rating ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Security.Bitpay ;
2023-06-16 03:47:58 +02:00
using BTCPayServer.Security.Greenfield ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services ;
2018-04-03 09:53:55 +02:00
using BTCPayServer.Services.Apps ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services.Invoices ;
2022-10-11 10:34:29 +02:00
using BTCPayServer.Services.Labels ;
2020-12-08 08:12:29 +01:00
using BTCPayServer.Services.Mails ;
2023-11-30 10:12:44 +01:00
using BTCPayServer.Services.Notifications ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services.Rates ;
2021-11-23 04:53:05 +01:00
using BTCPayServer.Storage.Models ;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration ;
using BTCPayServer.Storage.ViewModels ;
2021-04-28 06:14:15 +02:00
using Fido2NetLib ;
2021-11-23 04:53:05 +01:00
using Microsoft.AspNetCore.Http ;
2020-06-28 10:55:27 +02:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.EntityFrameworkCore ;
2020-12-29 09:58:35 +01:00
using Microsoft.Extensions.DependencyInjection ;
2022-10-11 10:34:29 +02:00
using Microsoft.Extensions.Hosting ;
2023-11-30 10:12:44 +01:00
using Microsoft.Extensions.Logging ;
2020-06-28 10:55:27 +02:00
using NBitcoin ;
using NBitcoin.DataEncoders ;
using NBitcoin.Payment ;
using NBitpayClient ;
2020-12-29 09:58:35 +01:00
using NBXplorer ;
2020-06-28 10:55:27 +02:00
using NBXplorer.Models ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
2020-03-18 12:08:09 +01:00
using Newtonsoft.Json.Schema ;
2024-04-04 09:31:04 +02:00
using Npgsql ;
2020-06-28 10:55:27 +02:00
using Xunit ;
using Xunit.Abstractions ;
using Xunit.Sdk ;
2023-11-21 10:55:02 +01:00
using CreateInvoiceRequest = BTCPayServer . Client . Models . CreateInvoiceRequest ;
2024-02-23 09:44:42 +01:00
using CreatePaymentRequestRequest = BTCPayServer . Client . Models . CreatePaymentRequestRequest ;
using MarkPayoutRequest = BTCPayServer . Client . Models . MarkPayoutRequest ;
using PaymentRequestData = BTCPayServer . Client . Models . PaymentRequestData ;
2020-06-28 10:55:27 +02:00
using RatesViewModel = BTCPayServer . Models . StoreViewModels . RatesViewModel ;
2024-05-09 02:18:02 +02:00
using Microsoft.Extensions.Caching.Memory ;
2024-06-26 10:42:22 +02:00
using PosViewType = BTCPayServer . Client . Models . PosViewType ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Tests
{
2021-11-23 05:57:45 +01:00
[Collection(nameof(NonParallelizableCollectionDefinition))]
2021-11-22 09:16:08 +01:00
public class UnitTest1 : UnitTestBase
2017-10-27 10:53:04 +02:00
{
2021-04-18 04:26:06 +02:00
public const int LongRunningTestTimeout = 60_000 ; // 60s
2020-04-05 13:48:00 +02:00
2021-11-22 09:16:08 +01:00
public UnitTest1 ( ITestOutputHelper helper ) : base ( helper )
2017-10-27 10:53:04 +02:00
{
}
2020-07-29 15:12:46 +02:00
class DockerImage
{
public string User { get ; private set ; }
public string Name { get ; private set ; }
public string Tag { get ; private set ; }
public string Source { get ; set ; }
public static DockerImage Parse ( string str )
{
//${BTCPAY_IMAGE: -btcpayserver / btcpayserver:1.0.3.21}
var variableMatch = Regex . Match ( str , @"\$\{[^-]+-([^\}]+)\}" ) ;
if ( variableMatch . Success )
{
str = variableMatch . Groups [ 1 ] . Value ;
}
DockerImage img = new DockerImage ( ) ;
var match = Regex . Match ( str , "([^/]*/)?([^:]+):?(.*)" ) ;
if ( ! match . Success )
throw new FormatException ( ) ;
img . User = match . Groups [ 1 ] . Length = = 0 ? string . Empty : match . Groups [ 1 ] . Value . Substring ( 0 , match . Groups [ 1 ] . Value . Length - 1 ) ;
img . Name = match . Groups [ 2 ] . Value ;
img . Tag = match . Groups [ 3 ] . Value ;
if ( img . Tag = = string . Empty )
img . Tag = "latest" ;
return img ;
}
public override string ToString ( )
{
return ToString ( true ) ;
}
public string ToString ( bool includeTag )
{
StringBuilder builder = new StringBuilder ( ) ;
if ( ! String . IsNullOrWhiteSpace ( User ) )
builder . Append ( $"{User}/" ) ;
builder . Append ( $"{Name}" ) ;
if ( includeTag )
{
if ( ! String . IsNullOrWhiteSpace ( Tag ) )
builder . Append ( $":{Tag}" ) ;
}
return builder . ToString ( ) ;
}
}
2020-03-18 12:08:09 +01:00
[Fact]
2020-04-05 09:43:49 +02:00
[Trait("Integration", "Integration")]
2020-03-18 12:08:09 +01:00
public async Task CheckSwaggerIsConformToSchema ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
var sresp = Assert
. IsType < JsonResult > ( await tester . PayTester . GetController < UIHomeController > ( acc . UserId , acc . StoreId )
2022-03-29 20:29:27 +02:00
. Swagger ( tester . PayTester . GetService < IEnumerable < ISwaggerProvider > > ( ) ) ) . Value . ToJson ( ) ;
2022-01-14 09:50:29 +01:00
JObject swagger = JObject . Parse ( sresp ) ;
var schema = JSchema . Parse ( File . ReadAllText ( TestUtils . GetTestDataFullPath ( "OpenAPI-Specification-schema.json" ) ) ) ;
IList < ValidationError > errors ;
bool valid = swagger . IsValid ( schema , out errors ) ;
2023-01-21 20:51:36 +01:00
//the schema is not fully compliant to the spec. We ARE allowed to have multiple security schemas.
2022-01-14 09:50:29 +01:00
var matchedError = errors . Where ( error = >
error . Path = = "components.securitySchemes.Basic" & & error . ErrorType = = ErrorType . OneOf ) . ToList ( ) ;
foreach ( ValidationError validationError in matchedError )
2020-03-20 17:37:39 +01:00
{
2022-01-14 09:50:29 +01:00
errors . Remove ( validationError ) ;
2020-03-20 17:37:39 +01:00
}
2024-07-11 02:28:24 +02:00
if ( errors . Any ( ) )
{
foreach ( ValidationError error in errors )
{
TestLogs . LogInformation ( $"Error Type: {error.ErrorType} - {error.Path}: {error.Message} - Value: {error.Value}" ) ;
}
}
2022-01-14 09:50:29 +01:00
Assert . Empty ( errors ) ;
2020-03-18 12:08:09 +01:00
}
2021-12-31 08:59:02 +01:00
2020-11-24 10:10:32 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task EnsureSwaggerPermissionsDocumented ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
2023-02-24 08:19:03 +01:00
var description = UtilitiesTests . GetSecuritySchemeDescription ( ) ;
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( description ) ;
var sresp = Assert
. IsType < JsonResult > ( await tester . PayTester . GetController < UIHomeController > ( acc . UserId , acc . StoreId )
2022-03-29 20:29:27 +02:00
. Swagger ( tester . PayTester . GetService < IEnumerable < ISwaggerProvider > > ( ) ) ) . Value . ToJson ( ) ;
2022-01-14 09:50:29 +01:00
JObject json = JObject . Parse ( sresp ) ;
2023-02-24 08:19:03 +01:00
// If this test fail, run `UpdateSwagger` once.
if ( description ! = json [ "components" ] [ "securitySchemes" ] [ "API_Key" ] [ "description" ] . Value < string > ( ) )
{
2023-11-28 15:20:03 +01:00
Assert . Fail ( "Please run manually the test `UpdateSwagger` once" ) ;
2023-02-24 08:19:03 +01:00
}
2020-11-24 10:10:32 +01:00
}
2023-01-06 14:18:07 +01:00
2022-06-13 06:36:47 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async void CanStoreArbitrarySettingsWithStore ( )
{
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
var settingsRepo = tester . PayTester . ServiceProvider . GetRequiredService < IStoreRepository > ( ) ;
2023-01-06 14:18:07 +01:00
var arbValue = await settingsRepo . GetSettingAsync < string > ( user . StoreId , "arbitrary" ) ;
2022-06-13 06:36:47 +02:00
Assert . Null ( arbValue ) ;
await settingsRepo . UpdateSetting ( user . StoreId , "arbitrary" , "saved" ) ;
2023-01-06 14:18:07 +01:00
arbValue = await settingsRepo . GetSettingAsync < string > ( user . StoreId , "arbitrary" ) ;
2022-06-13 06:36:47 +02:00
Assert . Equal ( "saved" , arbValue ) ;
await settingsRepo . UpdateSetting < TestData > ( user . StoreId , "arbitrary" , new TestData ( ) { Name = "hello" } ) ;
var arbData = await settingsRepo . GetSettingAsync < TestData > ( user . StoreId , "arbitrary" ) ;
Assert . Equal ( "hello" , arbData . Name ) ;
var client = await user . CreateClient ( ) ;
await client . RemoveStore ( user . StoreId ) ;
tester . Stores . Clear ( ) ;
arbValue = await settingsRepo . GetSettingAsync < string > ( user . StoreId , "arbitrary" ) ;
Assert . Null ( arbValue ) ;
}
class TestData
{
public string Name { get ; set ; }
}
2020-03-18 12:08:09 +01:00
2021-11-22 09:16:08 +01:00
private async Task CheckDeadLinks ( Regex regex , HttpClient httpClient , string file )
2019-09-02 09:32:38 +02:00
{
List < Task > checkLinks = new List < Task > ( ) ;
var text = await File . ReadAllTextAsync ( file ) ;
2020-03-27 00:58:12 +01:00
var urlBlacklist = new string [ ]
{
2020-03-31 11:13:22 +02:00
"https://www.btse.com" , // not allowing to be hit from circleci
2021-06-14 11:32:04 +02:00
"https://www.bitpay.com" , // not allowing to be hit from circleci
2023-01-21 20:51:36 +01:00
"https://support.bitpay.com"
2020-03-27 00:58:12 +01:00
} ;
2019-09-02 09:32:38 +02:00
foreach ( var match in regex . Matches ( text ) . OfType < Match > ( ) )
{
2020-03-27 00:58:12 +01:00
var url = match . Groups [ 1 ] . Value ;
2021-10-15 07:19:40 +02:00
if ( urlBlacklist . Any ( a = > url . StartsWith ( a . ToLowerInvariant ( ) ) ) )
2020-03-27 00:58:12 +01:00
continue ;
checkLinks . Add ( AssertLinkNotDead ( httpClient , url , file ) ) ;
2019-09-02 09:32:38 +02:00
}
2020-04-05 13:48:00 +02:00
2019-09-02 09:32:38 +02:00
await Task . WhenAll ( checkLinks ) ;
}
2021-11-22 09:16:08 +01:00
private async Task AssertLinkNotDead ( HttpClient httpClient , string url , string file )
2019-09-02 09:32:38 +02:00
{
2020-06-23 17:51:25 +02:00
var uri = new Uri ( url ) ;
2019-09-02 09:32:38 +02:00
try
{
2020-06-23 17:51:25 +02:00
using var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
2020-04-05 13:48:00 +02:00
request . Headers . TryAddWithoutValidation ( "Accept" ,
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" ) ;
request . Headers . TryAddWithoutValidation ( "User-Agent" ,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0" ) ;
2020-06-23 17:51:25 +02:00
var response = await httpClient . SendAsync ( request ) ;
2021-11-04 16:36:05 +01:00
if ( response . StatusCode = = HttpStatusCode . ServiceUnavailable ) // Temporary issue
{
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Unavailable: {url} ({file})" ) ;
2021-11-04 16:36:05 +01:00
return ;
}
2020-06-23 17:51:25 +02:00
Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
if ( uri . Fragment . Length ! = 0 )
{
var fragment = uri . Fragment . Substring ( 1 ) ;
var contents = await response . Content . ReadAsStringAsync ( ) ;
Assert . Matches ( $"id=\" { fragment } \ "" , contents ) ;
}
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"OK: {url} ({file})" ) ;
2019-09-02 09:32:38 +02:00
}
2020-06-23 17:51:25 +02:00
catch ( Exception ex ) when ( ex is MatchesException )
{
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"FAILED: {url} ({file}) – anchor not found: {uri.Fragment}" ) ;
2020-06-23 17:51:25 +02:00
throw ;
}
2020-04-18 19:33:53 +02:00
catch ( Exception ex )
2019-09-02 09:32:38 +02:00
{
2023-11-21 06:11:17 +01:00
var details = ex . Message ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"FAILED: {url} ({file}) {details}" ) ;
2020-06-24 03:34:09 +02:00
2019-09-02 09:32:38 +02:00
throw ;
}
}
2018-05-05 17:40:44 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanAcceptInvoiceWithTolerance2 ( )
2018-05-05 17:40:44 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
// Set tolerance to 50%
var stores = user . GetController < UIStoresController > ( ) ;
2024-09-30 12:13:51 +02:00
var response = await stores . GeneralSettings ( user . StoreId ) ;
2022-01-20 12:52:31 +01:00
var vm = Assert . IsType < GeneralSettingsViewModel > ( Assert . IsType < ViewResult > ( response ) . Model ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( 0.0 , vm . PaymentTolerance ) ;
vm . PaymentTolerance = 50.0 ;
2022-01-20 12:52:31 +01:00
Assert . IsType < RedirectToActionResult > ( stores . GeneralSettings ( vm ) . Result ) ;
2022-01-14 09:50:29 +01:00
2022-01-20 12:52:31 +01:00
var invoice = await user . BitPay . CreateInvoiceAsync (
2022-01-14 09:50:29 +01:00
new Invoice ( )
{
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-05-05 17:40:44 +02:00
2022-01-14 09:50:29 +01:00
// Pays 75%
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , tester . ExplorerNode . Network ) ;
2022-01-20 12:52:31 +01:00
await tester . ExplorerNode . SendToAddressAsync ( invoiceAddress ,
2022-01-14 09:50:29 +01:00
Money . Satoshis ( invoice . BtcDue . Satoshi * 0.75 m ) ) ;
2018-05-05 17:40:44 +02:00
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
} ) ;
2018-05-05 17:40:44 +02:00
}
2020-02-03 09:21:03 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanThrowBitpay404Error ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2020-02-03 09:21:03 +01:00
2022-01-14 09:50:29 +01:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
2020-02-03 09:21:03 +01:00
{
2022-01-14 09:50:29 +01:00
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
try
{
2024-01-18 01:47:39 +01:00
user . BitPay . GetInvoice ( invoice . Id + "123" ) ;
2022-01-14 09:50:29 +01:00
}
catch ( BitPayException ex )
{
Assert . Equal ( "Object not found" , ex . Errors . First ( ) ) ;
2020-02-03 09:21:03 +01:00
}
2022-01-14 09:50:29 +01:00
var req = new HttpRequestMessage ( HttpMethod . Get , "/invoices/Cy9jfK82eeEED1T3qhwF3Y" ) ;
req . Headers . TryAddWithoutValidation ( "Authorization" , "Basic dGVzdA==" ) ;
req . Content = new StringContent ( "{}" , Encoding . UTF8 , "application/json" ) ;
var result = await tester . PayTester . HttpClient . SendAsync ( req ) ;
Assert . Equal ( HttpStatusCode . Unauthorized , result . StatusCode ) ;
Assert . Equal ( 0 , result . Content . Headers . ContentLength . Value ) ;
2020-02-03 09:21:03 +01:00
}
2021-12-31 08:59:02 +01:00
2020-09-15 13:46:45 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task EnsureNewLightningInvoiceOnPartialPayment ( )
{
2021-11-22 09:16:08 +01:00
using var tester = CreateServerTester ( ) ;
2020-09-15 13:46:45 +02:00
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
2021-03-02 03:11:58 +01:00
await user . GrantAccessAsync ( true ) ;
2020-09-15 13:46:45 +02:00
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
await user . RegisterLightningNodeAsync ( "BTC" , LightningConnectionType . CLightning ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( NetworkFeeMode . Never ) ;
2024-09-30 12:13:51 +02:00
await user . ModifyGeneralSettings ( p = > p . SpeedPolicy = SpeedPolicy . HighSpeed ) ;
2020-09-24 12:18:19 +02:00
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 0.0001 m , "BTC" ) ) ;
2020-10-01 13:15:46 +02:00
await tester . WaitForEvent < InvoiceNewPaymentDetailsEvent > ( async ( ) = >
2020-09-15 13:46:45 +02:00
{
await tester . ExplorerNode . SendToAddressAsync (
2024-04-04 09:31:04 +02:00
BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) , Money . Coins ( 0.00005 m ) , new NBitcoin . RPC . SendToAddressParameters ( )
{
Replaceable = false
} ) ;
} , e = > e . InvoiceId = = invoice . Id & & e . PaymentMethodId = = PaymentTypes . LN . GetPaymentMethodId ( "BTC" ) ) ;
2021-07-16 09:57:37 +02:00
Invoice newInvoice = null ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
2021-08-23 12:34:36 +02:00
await Task . Delay ( 1000 ) ; // wait a bit for payment to process before fetching new invoice
2021-07-16 09:57:37 +02:00
newInvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
var newBolt11 = newInvoice . CryptoInfo . First ( o = > o . PaymentUrls . BOLT11 ! = null ) . PaymentUrls . BOLT11 ;
var oldBolt11 = invoice . CryptoInfo . First ( o = > o . PaymentUrls . BOLT11 ! = null ) . PaymentUrls . BOLT11 ;
Assert . NotEqual ( newBolt11 , oldBolt11 ) ;
2023-10-11 14:12:33 +02:00
Assert . Equal ( newInvoice . BtcDue . ToDecimal ( MoneyUnit . BTC ) ,
2021-07-16 09:57:37 +02:00
BOLT11PaymentRequest . Parse ( newBolt11 , Network . RegTest ) . MinimumAmount . ToDecimal ( LightMoneyUnit . BTC ) ) ;
2021-08-23 12:34:36 +02:00
} , 40000 ) ;
2021-12-31 08:59:02 +01:00
2023-12-20 10:41:28 +01:00
TestLogs . LogInformation ( $"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue((BTCPayNetwork)tester.DefaultNetwork)} via lightning" ) ;
2020-09-16 14:50:06 +02:00
var evt = await tester . WaitForEvent < InvoiceDataChangedEvent > ( async ( ) = >
{
await tester . SendLightningPaymentAsync ( newInvoice ) ;
2020-09-24 12:18:19 +02:00
} , evt = > evt . InvoiceId = = invoice . Id ) ;
var fetchedInvoice = await tester . PayTester . InvoiceRepository . GetInvoice ( evt . InvoiceId ) ;
2024-05-17 07:46:17 +02:00
Assert . Equal ( InvoiceStatus . Settled , fetchedInvoice . Status ) ;
2020-09-24 12:18:19 +02:00
Assert . Equal ( InvoiceExceptionStatus . None , fetchedInvoice . ExceptionStatus ) ;
2020-11-09 08:48:45 +01:00
2021-10-25 08:18:02 +02:00
//BTCPay will attempt to cancel previous bolt11 invoices so that there are less weird edge case scenarios
2022-05-18 07:57:36 +02:00
TestLogs . LogInformation ( $"Attempting to pay invoice {invoice.Id} original full amount bolt11 invoice" ) ;
var res = await tester . SendLightningPaymentAsync ( invoice ) ;
Assert . Equal ( PayResult . Error , res . Result ) ;
2021-12-31 08:59:02 +01:00
2021-10-25 08:18:02 +02:00
//NOTE: Eclair does not support cancelling invoice so the below test case would make sense for it
2021-11-22 09:16:08 +01:00
// TestLogs.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
2021-10-25 08:18:02 +02:00
// evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
// {
// await tester.SendLightningPaymentAsync(invoice);
// }, evt => evt.InvoiceId == invoice.Id);
// Assert.Equal(evt.InvoiceId, invoice.Id);
// fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
// Assert.Equal(3, fetchedInvoice.Payments.Count);
2020-09-15 13:46:45 +02:00
}
2019-04-05 09:28:18 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-10-28 14:46:03 +01:00
public async Task CanSetLightningServer ( )
2018-02-25 16:48:12 +01:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
2022-01-20 12:52:31 +01:00
await user . GrantAccessAsync ( true ) ;
2022-01-14 09:50:29 +01:00
var storeController = user . GetController < UIStoresController > ( ) ;
2024-09-30 12:13:51 +02:00
var storeResponse = await storeController . GeneralSettings ( user . StoreId ) ;
2022-01-14 09:50:29 +01:00
Assert . IsType < ViewResult > ( storeResponse ) ;
2022-05-24 06:18:16 +02:00
Assert . IsType < ViewResult > ( storeController . SetupLightningNode ( user . StoreId , "BTC" ) ) ;
2022-01-14 09:50:29 +01:00
2022-01-20 12:52:31 +01:00
storeController . SetupLightningNode ( user . StoreId , new LightningNodeViewModel
2018-02-25 16:48:12 +01:00
{
2022-01-14 09:50:29 +01:00
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true" ,
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
} , "test" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . False ( storeController . TempData . ContainsKey ( WellKnownTempData . ErrorMessage ) ) ;
storeController . TempData . Clear ( ) ;
Assert . True ( storeController . ModelState . IsValid ) ;
Assert . IsType < RedirectToActionResult > ( storeController . SetupLightningNode ( user . StoreId ,
new LightningNodeViewModel
2018-02-25 16:48:12 +01:00
{
2022-01-14 09:50:29 +01:00
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
} , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
// Make sure old connection string format does not work
Assert . IsType < RedirectToActionResult > ( storeController . SetupLightningNode ( user . StoreId ,
new LightningNodeViewModel { ConnectionString = tester . MerchantCharge . Client . Uri . AbsoluteUri } ,
"save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
2022-05-24 06:18:16 +02:00
storeResponse = storeController . LightningSettings ( user . StoreId , "BTC" ) ;
2022-01-14 09:50:29 +01:00
var storeVm =
2022-01-19 12:58:02 +01:00
Assert . IsType < LightningSettingsViewModel > ( Assert
2022-01-14 09:50:29 +01:00
. IsType < ViewResult > ( storeResponse ) . Model ) ;
2022-01-19 12:58:02 +01:00
Assert . NotEmpty ( storeVm . ConnectionString ) ;
2017-12-13 07:49:19 +01:00
}
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentCLightning ( )
2018-03-20 16:31:19 +01:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . CLightning ) ;
2018-03-20 16:31:19 +01:00
}
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentLnd ( )
2018-05-31 23:07:59 +02:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . LndREST ) ;
2018-05-31 23:07:59 +02:00
}
2018-05-25 19:18:47 +02:00
2023-11-21 10:55:02 +01:00
async Task ProcessLightningPayment ( string type )
2018-05-25 19:18:47 +02:00
{
2018-06-15 20:57:39 +02:00
// For easier debugging and testing
2018-06-16 00:20:56 +02:00
// LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue;
2018-02-23 07:21:42 +01:00
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( true ) ;
user . RegisterLightningNode ( "BTC" , type ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
await CanSendLightningPaymentCore ( tester , user ) ;
await Task . WhenAll ( Enumerable . Range ( 0 , 5 )
. Select ( _ = > CanSendLightningPaymentCore ( tester , user ) )
. ToArray ( ) ) ;
2018-02-23 07:21:42 +01:00
}
2018-02-26 05:29:23 +01:00
async Task CanSendLightningPaymentCore ( ServerTester tester , TestAccount user )
{
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 0.01 m ,
2018-02-26 05:29:23 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description"
} ) ;
2018-10-28 16:22:30 +01:00
await Task . Delay ( TimeSpan . FromMilliseconds ( 1000 ) ) ; // Give time to listen the new invoices
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Trying to send Lightning payment to {invoice.Id}" ) ;
2018-02-26 05:29:23 +01:00
await tester . SendLightningPaymentAsync ( invoice ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Lightning payment to {invoice.Id} is sent" ) ;
2019-01-05 19:47:39 +01:00
await TestUtils . EventuallyAsync ( async ( ) = >
2018-02-26 05:29:23 +01:00
{
var localInvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . Equal ( "complete" , localInvoice . Status ) ;
2020-01-10 03:13:54 +01:00
// C-Lightning may overpay for privacy
2020-06-24 03:34:09 +02:00
Assert . Contains ( localInvoice . ExceptionStatus . ToString ( ) , new [ ] { "False" , "paidOver" } ) ;
2018-02-26 05:29:23 +01:00
} ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanUseServerInitiatedPairingCode ( )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
acc . CreateStore ( ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
var controller = acc . GetController < UIStoresController > ( ) ;
var token = ( RedirectToActionResult ) await controller . CreateToken2 (
new Models . StoreViewModels . CreateTokenViewModel ( )
{
Label = "bla" ,
PublicKey = null ,
StoreId = acc . StoreId
} ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
var pairingCode = ( string ) token . RouteValues [ "pairingCode" ] ;
2023-11-28 15:20:03 +01:00
await acc . BitPay . AuthorizeClient ( new PairingCode ( pairingCode ) ) ;
2022-01-14 09:50:29 +01:00
Assert . True ( acc . BitPay . TestAccess ( Facade . Merchant ) ) ;
2017-10-27 10:53:04 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-01-06 07:04:30 +01:00
public async Task CanSendIPN ( )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
using var callbackServer = new CustomServer ( ) ;
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
2024-09-30 12:13:51 +02:00
await acc . GrantAccessAsync ( ) ;
2022-01-14 09:50:29 +01:00
acc . RegisterDerivationScheme ( "BTC" ) ;
2024-09-30 12:13:51 +02:00
await acc . ModifyGeneralSettings ( p = > p . SpeedPolicy = SpeedPolicy . LowSpeed ) ;
var invoice = await acc . BitPay . CreateInvoiceAsync ( new Invoice
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
Price = 5.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
NotificationURL = callbackServer . GetUri ( ) . AbsoluteUri ,
ItemDesc = "Some description" ,
FullNotifications = true ,
ExtendedNotifications = true
} ) ;
2021-11-22 16:49:51 +01:00
#pragma warning disable CS0618
2022-01-14 09:50:29 +01:00
BitcoinUrlBuilder url = new BitcoinUrlBuilder ( invoice . PaymentUrls . BIP21 ,
tester . NetworkProvider . BTC . NBitcoinNetwork ) ;
bool receivedPayment = false ;
bool paid = false ;
bool confirmed = false ;
bool completed = false ;
2022-01-17 08:59:37 +01:00
while ( ! completed | | ! confirmed | | ! receivedPayment )
2022-01-14 09:50:29 +01:00
{
var request = await callbackServer . GetNextRequest ( ) ;
if ( request . ContainsKey ( "event" ) )
{
var evtName = request [ "event" ] [ "name" ] . Value < string > ( ) ;
switch ( evtName )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
case InvoiceEvent . Created :
tester . ExplorerNode . SendToAddress ( url . Address , url . Amount ) ;
break ;
case InvoiceEvent . ReceivedPayment :
receivedPayment = true ;
break ;
case InvoiceEvent . PaidInFull :
2022-01-17 08:59:37 +01:00
// TODO, we should check that ReceivedPayment is sent after PaidInFull
// for now, we can't ensure this because the ReceivedPayment events isn't sent by the
// InvoiceWatcher, contrary to all other events
2022-01-14 09:50:29 +01:00
tester . ExplorerNode . Generate ( 6 ) ;
paid = true ;
break ;
case InvoiceEvent . Confirmed :
Assert . True ( paid ) ;
confirmed = true ;
break ;
case InvoiceEvent . Completed :
Assert . True (
paid ) ; //TODO: Fix, out of order event mean we can receive invoice_confirmed after invoice_complete
completed = true ;
break ;
default :
2023-11-28 15:20:03 +01:00
Assert . Fail ( $"{evtName} was not expected" ) ;
2022-01-14 09:50:29 +01:00
break ;
2019-01-06 07:04:30 +01:00
}
2017-10-27 10:53:04 +02:00
}
}
2022-01-14 09:50:29 +01:00
var invoice2 = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . NotNull ( invoice2 ) ;
2017-10-27 10:53:04 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CantPairTwiceWithSamePubkey ( )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
acc . CreateStore ( ) ;
var store = acc . GetController < UIStoresController > ( ) ;
var pairingCode = acc . BitPay . RequestClientAuthorization ( "test" , Facade . Merchant ) ;
Assert . IsType < RedirectToActionResult > ( store . Pair ( pairingCode . ToString ( ) , acc . StoreId ) . GetAwaiter ( )
. GetResult ( ) ) ;
pairingCode = acc . BitPay . RequestClientAuthorization ( "test1" , Facade . Merchant ) ;
acc . CreateStore ( ) ;
var store2 = acc . GetController < UIStoresController > ( ) ;
await store2 . Pair ( pairingCode . ToString ( ) , store2 . CurrentStore . Id ) ;
Assert . Contains ( nameof ( PairingResult . ReusedKey ) ,
2024-10-17 15:51:40 +02:00
store2 . TempData [ WellKnownTempData . ErrorMessage ] . ToString ( ) , StringComparison . CurrentCultureIgnoreCase ) ;
2017-10-27 10:53:04 +02:00
}
2022-10-07 09:04:22 +02:00
[Fact(Timeout = LongRunningTestTimeout * 2)]
2022-11-22 13:58:15 +01:00
[Trait("Flaky", "Flaky")]
2020-04-08 15:40:41 +02:00
public async Task CanUseTorClient ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var httpFactory = tester . PayTester . GetService < IHttpClientFactory > ( ) ;
var client = httpFactory . CreateClient ( PayjoinServerCommunicator . PayjoinOnionNamedClient ) ;
Assert . NotNull ( client ) ;
TestLogs . LogInformation ( "Querying an clearnet site over tor" ) ;
var response = await client . GetAsync ( "https://check.torproject.org/" ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadAsStringAsync ( ) ;
Assert . DoesNotContain ( "You are not using Tor." , result ) ;
Assert . Contains ( "Congratulations. This browser is configured to use Tor." , result ) ;
TestLogs . LogInformation ( "Querying a tor website" ) ;
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
2022-01-25 04:27:44 +01:00
if ( response . IsSuccessStatusCode ) // Sometimes the site goes down
{
result = await response . Content . ReadAsStringAsync ( ) ;
Assert . Contains ( "Bitcoin" , result ) ;
}
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( "...twice" ) ;
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
client . Dispose ( ) ;
TestLogs . LogInformation ( "...three times, but with a new httpclient" ) ;
client = httpFactory . CreateClient ( PayjoinServerCommunicator . PayjoinOnionNamedClient ) ;
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
TestLogs . LogInformation ( "Querying an onion address which can't be found" ) ;
await Assert . ThrowsAsync < HttpRequestException > ( ( ) = > client . GetAsync ( "http://dwoduwoi.onion/" ) ) ;
2022-01-25 04:27:44 +01:00
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( "Querying valid onion but unreachable" ) ;
2022-01-25 04:27:44 +01:00
using var cts = new CancellationTokenSource ( 10_000 ) ;
try
{
await client . GetAsync ( "http://nzwsosflsoquxirwb2zikz6uxr3u5n5u73l33umtdx4hq5mzm5dycuqd.onion/" , cts . Token ) ;
}
catch ( HttpRequestException )
{
}
catch when ( cts . Token . IsCancellationRequested ) // Ignore timeouts
{
}
2020-04-08 15:40:41 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-08-02 17:42:30 +02:00
public async Task CanRescanWallet ( )
2018-10-26 16:07:39 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" , ScriptPubKeyType . Segwit ) ;
var btcDerivationScheme = acc . DerivationScheme ;
var walletController = acc . GetController < UIWalletsController > ( ) ;
var walletId = new WalletId ( acc . StoreId , "BTC" ) ;
acc . IsAdmin = true ;
walletController = acc . GetController < UIWalletsController > ( ) ;
var rescan =
Assert . IsType < RescanWalletModel > ( Assert
. IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
Assert . True ( rescan . Ok ) ;
Assert . True ( rescan . IsFullySync ) ;
Assert . True ( rescan . IsSupportedByCurrency ) ;
Assert . True ( rescan . IsServerAdmin ) ;
rescan . GapLimit = 100 ;
// Sending a coin
var txId = tester . ExplorerNode . SendToAddress (
btcDerivationScheme . GetDerivation ( new KeyPath ( "0/90" ) ) . ScriptPubKey , Money . Coins ( 1.0 m ) ) ;
tester . ExplorerNode . Generate ( 1 ) ;
var transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
2023-06-22 09:09:53 +02:00
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId , loadTransactions : true ) . Result ) . Model ) ;
2022-01-14 09:50:29 +01:00
Assert . Empty ( transactions . Transactions ) ;
Assert . IsType < RedirectToActionResult > ( walletController . WalletRescan ( walletId , rescan ) . Result ) ;
while ( true )
2018-10-26 16:07:39 +02:00
{
2022-01-14 09:50:29 +01:00
rescan = Assert . IsType < RescanWalletModel > ( Assert
. IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
if ( rescan . Progress = = null & & rescan . LastSuccess ! = null )
2018-10-26 16:07:39 +02:00
{
2022-01-14 09:50:29 +01:00
if ( rescan . LastSuccess . Found = = 0 )
continue ;
// Scan over
break ;
}
else
{
Assert . Null ( rescan . TimeOfScan ) ;
Assert . NotNull ( rescan . RemainingTime ) ;
Assert . NotNull ( rescan . Progress ) ;
Thread . Sleep ( 100 ) ;
2018-10-26 16:07:39 +02:00
}
}
2022-01-14 09:50:29 +01:00
Assert . Null ( rescan . PreviousError ) ;
Assert . NotNull ( rescan . TimeOfScan ) ;
Assert . Equal ( 1 , rescan . LastSuccess . Found ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
2023-06-22 09:09:53 +02:00
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId , loadTransactions : true ) . Result ) . Model ) ;
2022-01-14 09:50:29 +01:00
var tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( tx . Id , txId . ToString ( ) ) ;
// Hijack the test to see if we can add label and comments
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addcomment : "hello-pouet" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addlabel : "test" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addlabelclick : "test2" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addcomment : "hello" ) ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
2023-06-22 09:09:53 +02:00
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId , loadTransactions : true ) . Result ) . Model ) ;
2022-01-14 09:50:29 +01:00
tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( "hello" , tx . Comment ) ;
2022-10-11 10:34:29 +02:00
Assert . Contains ( "test" , tx . Tags . Select ( l = > l . Text ) ) ;
Assert . Contains ( "test2" , tx . Tags . Select ( l = > l . Text ) ) ;
Assert . Equal ( 2 , tx . Tags . GroupBy ( l = > l . Color ) . Count ( ) ) ;
2022-01-14 09:50:29 +01:00
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , removelabel : "test2" ) ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
2023-06-22 09:09:53 +02:00
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId , loadTransactions : true ) . Result ) . Model ) ;
2022-01-14 09:50:29 +01:00
tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( "hello" , tx . Comment ) ;
2022-10-11 10:34:29 +02:00
Assert . Contains ( "test" , tx . Tags . Select ( l = > l . Text ) ) ;
Assert . DoesNotContain ( "test2" , tx . Tags . Select ( l = > l . Text ) ) ;
Assert . Single ( tx . Tags . GroupBy ( l = > l . Color ) ) ;
2018-10-26 16:07:39 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanListInvoices ( )
2018-05-06 06:16:39 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
await acc . GrantAccessAsync ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
var invoice = await acc . BitPay . CreateInvoiceAsync (
new Invoice
2018-05-06 06:16:39 +02:00
{
2022-01-14 09:50:29 +01:00
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-05-06 06:16:39 +02:00
2022-01-14 09:50:29 +01:00
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - Money . Satoshis ( 10 ) ;
await cashCow . SendToAddressAsync ( invoiceAddress , firstPayment ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( firstPayment , invoice . CryptoInfo [ 0 ] . Paid ) ;
} ) ;
AssertSearchInvoice ( acc , true , invoice . Id , null ) ;
AssertSearchInvoice ( acc , true , invoice . Id , null , acc . StoreId ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"storeid:{acc.StoreId}" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , "storeid:doesnotexist" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"{invoice.Id}" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , "exceptionstatus:paidPartial" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , "exceptionstatus:paidOver" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , "unusual:true" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , "unusual:false" ) ;
var time = invoice . InvoiceTime ;
AssertSearchInvoice ( acc , true , invoice . Id , $"startdate:{time.ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
2024-05-09 02:18:02 +02:00
AssertSearchInvoice ( acc , true , invoice . Id , $"enddate:{time.ToString().ToLowerInvariant()}" ) ;
2022-01-14 09:50:29 +01:00
AssertSearchInvoice ( acc , false , invoice . Id ,
$"startdate:{time.AddSeconds(1).ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
AssertSearchInvoice ( acc , false , invoice . Id ,
$"enddate:{time.AddSeconds(-1).ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
2018-05-06 06:16:39 +02:00
}
2018-05-29 17:12:07 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanGetRates ( )
2018-05-29 17:12:07 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
var rateController = acc . GetController < BitpayRateController > ( ) ;
var GetBaseCurrencyRatesResult = JObject . Parse ( ( ( JsonResult ) rateController
. GetBaseCurrencyRates ( "BTC" , default )
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
Assert . NotNull ( GetBaseCurrencyRatesResult ) ;
Assert . NotNull ( GetBaseCurrencyRatesResult . Data ) ;
var rate = Assert . Single ( GetBaseCurrencyRatesResult . Data ) ;
Assert . Equal ( "BTC" , rate . Code ) ;
var GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , default )
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
// We don't have any default currencies, so this should be failing
Assert . Null ( GetRatesResult ? . Data ) ;
var store = acc . GetController < UIStoresController > ( ) ;
var ratesVM = ( RatesViewModel ) ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
ratesVM . DefaultCurrencyPairs = "BTC_USD,LTC_USD" ;
await store . Rates ( ratesVM ) ;
store = acc . GetController < UIStoresController > ( ) ;
rateController = acc . GetController < BitpayRateController > ( ) ;
GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , default )
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
// Now we should have a result
Assert . NotNull ( GetRatesResult ) ;
Assert . NotNull ( GetRatesResult . Data ) ;
Assert . Equal ( 2 , GetRatesResult . Data . Length ) ;
var GetCurrencyPairRateResult = JObject . Parse ( ( ( JsonResult ) rateController
. GetCurrencyPairRate ( "BTC" , "LTC" , default )
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate > > ( ) ;
Assert . NotNull ( GetCurrencyPairRateResult ) ;
Assert . NotNull ( GetCurrencyPairRateResult . Data ) ;
Assert . Equal ( "LTC" , GetCurrencyPairRateResult . Data . Code ) ;
// Should be OK because the request is signed, so we can know the store
2024-01-18 01:47:39 +01:00
acc . BitPay . GetRates ( ) ;
2022-01-14 09:50:29 +01:00
HttpClient client = new HttpClient ( ) ;
// Unauthentified requests should also be ok
var response =
await client . GetAsync ( $"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}" ) ;
response . EnsureSuccessStatusCode ( ) ;
response = await client . GetAsync (
$"http://127.0.0.1:{tester.PayTester.Port}/rates?storeId={acc.StoreId}" ) ;
response . EnsureSuccessStatusCode ( ) ;
2018-05-29 17:12:07 +02:00
}
2021-12-31 08:36:38 +01:00
private void AssertSearchInvoice ( TestAccount acc , bool expected , string invoiceId , string filter , string storeId = null )
2018-05-06 06:16:39 +02:00
{
2020-04-05 13:48:00 +02:00
var result =
2022-02-21 06:53:48 +01:00
( InvoicesModel ) ( ( ViewResult ) acc . GetController < UIInvoiceController > ( storeId is not null )
2021-12-31 08:36:38 +01:00
. ListInvoices ( new InvoicesModel { SearchTerm = filter , StoreId = storeId } ) . Result ) . Model ;
2018-05-06 06:16:39 +02:00
Assert . Equal ( expected , result . Invoices . Any ( i = > i . InvoiceId = = invoiceId ) ) ;
}
2020-04-05 13:48:00 +02:00
// [Fact(Timeout = TestTimeout)]
[Fact()]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanRBFPayment ( )
2017-11-06 09:31:02 +01:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
await user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
var invoice =
user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } , Facade . Merchant ) ;
var payment1 = invoice . BtcDue + Money . Coins ( 0.0001 m ) ;
var payment2 = invoice . BtcDue ;
var tx1 = new uint256 ( tester . ExplorerNode . SendCommand ( "sendtoaddress" , new object [ ]
2017-11-06 09:31:02 +01:00
{
2020-04-05 13:48:00 +02:00
invoice . BitcoinAddress , payment1 . ToString ( ) , null , //comment
2017-11-06 09:31:02 +01:00
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
2022-01-14 09:50:29 +01:00
} ) . ResultString ) ;
TestLogs . LogInformation (
$"Let's send a first payment of {payment1} for the {invoice.BtcDue} invoice ({tx1})" ) ;
var invoiceAddress =
BitcoinAddress . Create ( invoice . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
TestLogs . LogInformation ( $"The invoice should be paidOver" ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "paidOver" , invoice . ExceptionStatus . ToString ( ) ) ;
invoiceAddress =
2020-04-05 13:48:00 +02:00
BitcoinAddress . Create ( invoice . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
2022-01-14 09:50:29 +01:00
} ) ;
2017-11-06 09:31:02 +01:00
2022-01-14 09:50:29 +01:00
var tx = tester . ExplorerNode . GetRawTransaction ( new uint256 ( tx1 ) ) ;
foreach ( var input in tx . Inputs )
{
input . ScriptSig = Script . Empty ; //Strip signatures
}
2017-11-06 09:31:02 +01:00
2022-01-14 09:50:29 +01:00
var output = tx . Outputs . First ( o = > o . Value = = payment1 ) ;
output . Value = payment2 ;
output . ScriptPubKey = invoiceAddress . ScriptPubKey ;
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
using ( var cts = new CancellationTokenSource ( 10000 ) )
using ( var listener = tester . ExplorerClient . CreateWebsocketNotificationSession ( ) )
{
listener . ListenAllDerivationSchemes ( ) ;
var replaced = tester . ExplorerNode . SignRawTransaction ( tx ) ;
Thread . Sleep ( 1000 ) ; // Make sure the replacement has a different timestamp
var tx2 = tester . ExplorerNode . SendRawTransaction ( replaced ) ;
TestLogs . LogInformation (
$"Let's RBF with a payment of {payment2} ({tx2}), waiting for NBXplorer to pick it up" ) ;
Assert . Equal ( tx2 ,
( ( NewTransactionEvent ) listener . NextEvent ( cts . Token ) ) . TransactionData . TransactionHash ) ;
}
2018-10-28 15:43:48 +01:00
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( $"The invoice should now not be paidOver anymore" ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment2 , invoice . BtcPaid ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
} ) ;
2020-04-05 13:48:00 +02:00
2020-03-11 12:46:37 +01:00
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation (
$"Let's test out rbf payments where the payment gets sent elsehwere instead" ) ;
var invoice2 =
user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 0.01 m , Currency = "BTC" } , Facade . Merchant ) ;
2020-03-11 12:46:37 +01:00
2022-01-14 09:50:29 +01:00
var invoice2Address =
BitcoinAddress . Create ( invoice2 . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
uint256 invoice2tx1Id =
await tester . ExplorerNode . SendToAddressAsync ( invoice2Address , invoice2 . BtcDue , new NBitcoin . RPC . SendToAddressParameters ( )
2020-03-11 12:46:37 +01:00
{
2022-01-14 09:50:29 +01:00
Replaceable = true
2020-03-11 12:46:37 +01:00
} ) ;
2022-01-14 09:50:29 +01:00
Transaction invoice2Tx1 = null ;
TestUtils . Eventually ( ( ) = >
{
invoice2 = user . BitPay . GetInvoice ( invoice2 . Id ) ;
Assert . Equal ( "paid" , invoice2 . Status ) ;
invoice2Tx1 = tester . ExplorerNode . GetRawTransaction ( new uint256 ( invoice2tx1Id ) ) ;
} ) ;
var invoice2Tx2 = invoice2Tx1 . Clone ( ) ;
foreach ( var input in invoice2Tx2 . Inputs )
{
input . ScriptSig = Script . Empty ; //Strip signatures
input . WitScript = WitScript . Empty ; //Strip signatures
}
2020-03-11 12:46:37 +01:00
2022-01-14 09:50:29 +01:00
output = invoice2Tx2 . Outputs . First ( o = >
o . ScriptPubKey = = invoice2Address . ScriptPubKey ) ;
output . Value - = new Money ( 10_000 , MoneyUnit . Satoshi ) ;
output . ScriptPubKey = new Key ( ) . GetScriptPubKey ( ScriptPubKeyType . Legacy ) ;
invoice2Tx2 = await tester . ExplorerNode . SignRawTransactionAsync ( invoice2Tx2 ) ;
await tester . ExplorerNode . SendRawTransactionAsync ( invoice2Tx2 ) ;
tester . ExplorerNode . Generate ( 1 ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
var i = await tester . PayTester . InvoiceRepository . GetInvoice ( invoice2 . Id ) ;
2024-05-15 00:49:53 +02:00
Assert . Equal ( InvoiceStatus . New , i . Status ) ;
2022-01-14 09:50:29 +01:00
Assert . Single ( i . GetPayments ( false ) ) ;
Assert . False ( i . GetPayments ( false ) . First ( ) . Accounted ) ;
} ) ;
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( "Let's test if we can RBF a normal payment without adding fees to the invoice" ) ;
await user . SetNetworkFeeMode ( NetworkFeeMode . MultiplePaymentsOnly ) ;
invoice = user . BitPay . CreateInvoice ( new Invoice { Price = 5000.0 m , Currency = "USD" } , Facade . Merchant ) ;
payment1 = invoice . BtcDue ;
tx1 = new uint256 ( tester . ExplorerNode . SendCommand ( "sendtoaddress" , new object [ ]
{
2020-04-05 13:48:00 +02:00
invoice . BitcoinAddress , payment1 . ToString ( ) , null , //comment
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
2022-01-14 09:50:29 +01:00
} ) . ResultString ) ;
TestLogs . LogInformation ( $"Paid {tx1}" ) ;
TestUtils . Eventually ( ( ) = >
2020-04-05 13:48:00 +02:00
{
2022-01-14 09:50:29 +01:00
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
}
) ;
var tx1Bump = new uint256 ( tester . ExplorerNode . SendCommand ( "bumpfee" , new object [ ]
{
2020-04-05 13:48:00 +02:00
tx1 . ToString ( ) ,
2022-01-14 09:50:29 +01:00
} ) . Result [ "txid" ] . Value < string > ( ) ) ;
TestLogs . LogInformation ( $"Bumped with {tx1Bump}" ) ;
2024-04-04 09:31:04 +02:00
var handler = tester . PayTester . GetService < PaymentMethodHandlerDictionary > ( ) . GetBitcoinHandler ( "BTC" ) ;
2022-01-14 09:50:29 +01:00
await TestUtils . EventuallyAsync ( async ( ) = >
{
var invoiceEntity = await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) ;
2024-04-04 09:31:04 +02:00
var btcPayments = invoiceEntity . GetAllBitcoinPaymentData ( handler , false ) . ToArray ( ) ;
2022-01-14 09:50:29 +01:00
var payments = invoiceEntity . GetPayments ( false ) . ToArray ( ) ;
Assert . Equal ( tx1 , btcPayments [ 0 ] . Outpoint . Hash ) ;
Assert . False ( payments [ 0 ] . Accounted ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( tx1Bump , btcPayments [ 1 ] . Outpoint . Hash ) ;
2022-01-14 09:50:29 +01:00
Assert . True ( payments [ 1 ] . Accounted ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( 0.0 m , payments [ 1 ] . PaymentMethodFee ) ;
2022-01-14 09:50:29 +01:00
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
}
) ;
2017-11-06 09:31:02 +01:00
}
2020-11-09 08:48:45 +01:00
2022-01-15 06:15:03 +01:00
[Fact(Timeout = TestUtils.TestTimeout)]
2020-09-16 11:49:47 +02:00
[Trait("Integration", "Integration")]
public async Task CanSaveKeyPathForOnChainPayments ( )
{
2021-11-22 09:16:08 +01:00
using var tester = CreateServerTester ( ) ;
2020-09-16 11:49:47 +02:00
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 0.01 m , "BTC" ) ) ;
await tester . WaitForEvent < InvoiceEvent > ( async ( ) = >
{
2024-01-18 01:47:39 +01:00
await tester . ExplorerNode . SendToAddressAsync (
2020-09-16 11:49:47 +02:00
BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) ,
Money . Coins ( 0.01 m ) ) ;
} ) ;
2020-11-09 08:48:45 +01:00
2020-09-16 11:49:47 +02:00
var payments = Assert . IsType < InvoiceDetailsModel > (
2022-01-07 04:32:00 +01:00
Assert . IsType < ViewResult > ( await user . GetController < UIInvoiceController > ( ) . Invoice ( invoice . Id ) ) . Model )
2020-09-16 11:49:47 +02:00
. Payments ;
Assert . Single ( payments ) ;
2024-04-04 09:31:04 +02:00
var paymentData = payments . First ( ) . Details ;
Assert . NotNull ( paymentData [ "keyPath" ] ) ;
2020-09-16 11:49:47 +02:00
}
2017-11-06 09:31:02 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-02-02 08:12:51 +01:00
[Trait("Integration", "Integration")]
public async void CheckCORSSetOnBitpayAPI ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
foreach ( var req in new [ ] { "invoices/" , "invoices" , "rates" , "tokens" } . Select ( async path = >
{
using HttpClient client = new HttpClient ( ) ;
HttpRequestMessage message = new HttpRequestMessage ( HttpMethod . Options ,
tester . PayTester . ServerUri . AbsoluteUri + path ) ;
message . Headers . Add ( "Access-Control-Request-Headers" , "test" ) ;
var response = await client . SendAsync ( message ) ;
response . EnsureSuccessStatusCode ( ) ;
Assert . True ( response . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val ) ) ;
Assert . Equal ( "*" , val . FirstOrDefault ( ) ) ;
Assert . True ( response . Headers . TryGetValues ( "Access-Control-Allow-Headers" , out val ) ) ;
Assert . Equal ( "test" , val . FirstOrDefault ( ) ) ;
} ) . ToList ( ) )
2019-02-02 08:12:51 +01:00
{
2022-01-14 09:50:29 +01:00
await req ;
2019-02-02 08:12:51 +01:00
}
2022-01-14 09:50:29 +01:00
HttpClient client2 = new HttpClient ( ) ;
HttpRequestMessage message2 = new HttpRequestMessage ( HttpMethod . Options ,
tester . PayTester . ServerUri . AbsoluteUri + "rates" ) ;
var response2 = await client2 . SendAsync ( message2 ) ;
Assert . True ( response2 . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val2 ) ) ;
Assert . Equal ( "*" , val2 . FirstOrDefault ( ) ) ;
2019-02-02 08:12:51 +01:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task TestAccessBitpayAPI ( )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
Assert . False ( user . BitPay . TestAccess ( Facade . Merchant ) ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
Assert . True ( user . BitPay . TestAccess ( Facade . Merchant ) ) ;
// Test request pairing code client side
var storeController = user . GetController < UIStoresController > ( ) ;
storeController
. CreateToken ( user . StoreId , new CreateTokenViewModel ( ) { Label = "test2" , StoreId = user . StoreId } )
. GetAwaiter ( ) . GetResult ( ) ;
Assert . NotNull ( storeController . GeneratedPairingCode ) ;
var k = new Key ( ) ;
var bitpay = new Bitpay ( k , tester . PayTester . ServerUri ) ;
bitpay . AuthorizeClient ( new PairingCode ( storeController . GeneratedPairingCode ) ) . Wait ( ) ;
Assert . True ( bitpay . TestAccess ( Facade . Merchant ) ) ;
Assert . True ( bitpay . TestAccess ( Facade . PointOfSale ) ) ;
// Same with new instance
bitpay = new Bitpay ( k , tester . PayTester . ServerUri ) ;
Assert . True ( bitpay . TestAccess ( Facade . Merchant ) ) ;
Assert . True ( bitpay . TestAccess ( Facade . PointOfSale ) ) ;
2024-01-18 06:08:07 +01:00
HttpClient client = new HttpClient ( ) ;
var token = ( await bitpay . GetAccessTokenAsync ( Facade . Merchant ) ) . Value ;
var getRates = tester . PayTester . ServerUri . AbsoluteUri + $"rates/?cryptoCode=BTC&token={token}" ;
var req = new HttpRequestMessage ( HttpMethod . Get , getRates ) ;
req . Headers . Add ( "x-signature" , NBitpayClient . Extensions . BitIdExtensions . GetBitIDSignature ( k , getRates , null ) ) ;
req . Headers . Add ( "x-identity" , k . PubKey . ToHex ( ) ) ;
var resp = await client . SendAsync ( req ) ;
resp . EnsureSuccessStatusCode ( ) ;
2022-01-14 09:50:29 +01:00
// Can generate API Key
var repo = tester . PayTester . GetService < TokenRepository > ( ) ;
Assert . Empty ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( )
. GenerateAPIKey ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
var apiKey = Assert . Single ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
///////
// Generating a new one remove the previous
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( )
. GenerateAPIKey ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
var apiKey2 = Assert . Single ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
Assert . NotEqual ( apiKey , apiKey2 ) ;
////////
apiKey = apiKey2 ;
// Can create an invoice with this new API Key
HttpRequestMessage message = new HttpRequestMessage ( HttpMethod . Post ,
tester . PayTester . ServerUri . AbsoluteUri + "invoices" ) ;
message . Headers . Authorization = new System . Net . Http . Headers . AuthenticationHeaderValue ( "Basic" ,
Encoders . Base64 . EncodeData ( Encoders . ASCII . DecodeData ( apiKey ) ) ) ;
var invoice = new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } ;
message . Content = new StringContent ( JsonConvert . SerializeObject ( invoice ) , Encoding . UTF8 ,
"application/json" ) ;
var result = client . SendAsync ( message ) . GetAwaiter ( ) . GetResult ( ) ;
result . EnsureSuccessStatusCode ( ) ;
/////////////////////
// Have error 403 with bad signature
client = new HttpClient ( ) ;
HttpRequestMessage mess =
new HttpRequestMessage ( HttpMethod . Get , tester . PayTester . ServerUri . AbsoluteUri + "tokens" ) ;
mess . Content = new StringContent ( string . Empty , Encoding . UTF8 , "application/json" ) ;
mess . Headers . Add ( "x-signature" ,
"3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe" ) ;
mess . Headers . Add ( "x-identity" ,
"04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99" ) ;
mess . Method = HttpMethod . Get ;
result = client . SendAsync ( mess ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Equal ( System . Net . HttpStatusCode . Unauthorized , result . StatusCode ) ;
//
2018-01-07 18:36:41 +01:00
}
2018-01-13 14:01:09 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-03-25 04:59:42 +01:00
[Trait("Integration", "Integration")]
public async Task CanUseAnyoneCanCreateInvoice ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2019-03-25 04:59:42 +01:00
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( "StoreId without anyone can create invoice = 403" ) ;
var response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId={user.StoreId}" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
Assert . Equal ( 403 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation (
"No store without anyone can create invoice = 404 because the bitpay API can't know the storeid" ) ;
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
Assert . Equal ( 404 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
2022-01-14 09:50:29 +01:00
await user . ModifyPayment ( p = > p . AnyoneCanCreateInvoice = true ) ;
TestLogs . LogInformation ( "Bad store with anyone can create invoice = 403" ) ;
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId=badid" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
Assert . Equal ( 403 , ( int ) response . StatusCode ) ;
TestLogs . LogInformation ( "Good store with anyone can create invoice = 200" ) ;
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId={user.StoreId}" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
Assert . Equal ( 200 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanTweakRate ( )
2018-01-17 07:59:31 +01:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
var invoice1 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) , invoice1 . BtcPrice ) ;
2018-01-17 07:59:31 +01:00
2022-01-14 09:50:29 +01:00
var storeController = user . GetController < UIStoresController > ( ) ;
var vm = ( RatesViewModel ) ( ( ViewResult ) storeController . Rates ( ) ) . Model ;
Assert . Equal ( 0.0 , vm . Spread ) ;
vm . Spread = 40 ;
await storeController . Rates ( vm ) ;
2018-01-17 07:59:31 +01:00
2022-01-14 09:50:29 +01:00
var invoice2 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-01-17 07:59:31 +01:00
2022-01-14 09:50:29 +01:00
var expectedRate = 5000.0 m * 0.6 m ;
var expectedCoins = invoice2 . Price / expectedRate ;
Assert . True ( invoice2 . BtcPrice . Almost ( Money . Coins ( expectedCoins ) , 0.00001 m ) ) ;
2018-01-17 07:59:31 +01:00
}
2021-08-03 10:03:00 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateTopupInvoices ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
2023-12-01 10:50:05 +01:00
await user . GrantAccessAsync ( ) ;
2022-01-14 09:50:29 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2023-12-20 10:41:28 +01:00
await tester . ExplorerNode . EnsureGenerateAsync ( 1 ) ;
2022-01-14 09:50:29 +01:00
var rng = new Random ( ) ;
foreach ( var networkFeeMode in Enum . GetValues ( typeof ( NetworkFeeMode ) ) . Cast < NetworkFeeMode > ( ) )
2021-08-03 10:03:00 +02:00
{
2022-01-14 09:50:29 +01:00
await user . SetNetworkFeeMode ( networkFeeMode ) ;
await AssertTopUpBtcPrice ( tester , user , Money . Coins ( 1.0 m ) , 5000.0 m , networkFeeMode ) ;
await AssertTopUpBtcPrice ( tester , user , Money . Coins ( 1.23456789 m ) , 5000.0 m * 1.23456789 m , networkFeeMode ) ;
// Check if there is no strange roundup issues
var v = ( decimal ) ( rng . NextDouble ( ) + 1.0 ) ;
v = Money . Coins ( v ) . ToDecimal ( MoneyUnit . BTC ) ;
await AssertTopUpBtcPrice ( tester , user , Money . Coins ( v ) , 5000.0 m * v , networkFeeMode ) ;
2021-08-03 10:03:00 +02:00
}
}
2023-12-20 10:41:28 +01:00
private async Task AssertTopUpBtcPrice ( ServerTester tester , TestAccount user , Money btcSent , decimal expectedPriceWithoutNetworkFee , NetworkFeeMode networkFeeMode )
2021-08-03 10:03:00 +02:00
{
var cashCow = tester . ExplorerNode ;
// First we try payment with a merchant having only BTC
var client = await user . CreateClient ( ) ;
var invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = null ,
Currency = "USD"
} ) ;
Assert . Equal ( 0 m , invoice . Amount ) ;
Assert . Equal ( InvoiceType . TopUp , invoice . Type ) ;
var btcmethod = ( await client . GetInvoicePaymentMethods ( user . StoreId , invoice . Id ) ) [ 0 ] ;
var paid = btcSent ;
var invoiceAddress = BitcoinAddress . Create ( btcmethod . Destination , cashCow . Network ) ;
2024-04-04 09:31:04 +02:00
var btc = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) ;
2021-08-03 10:03:00 +02:00
var networkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
2024-04-04 09:31:04 +02:00
. GetPaymentPrompt ( btc )
. PaymentMethodFee ;
2021-08-03 10:03:00 +02:00
if ( networkFeeMode ! = NetworkFeeMode . Always )
{
networkFee = 0.0 m ;
}
2023-12-01 10:50:05 +01:00
await cashCow . SendToAddressAsync ( invoiceAddress , paid ) ;
2021-08-03 10:03:00 +02:00
await TestUtils . EventuallyAsync ( async ( ) = >
{
try
{
var bitpayinvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . NotEqual ( 0.0 m , bitpayinvoice . Price ) ;
var due = Money . Parse ( bitpayinvoice . CryptoInfo [ 0 ] . CryptoPaid ) ;
Assert . Equal ( paid , due ) ;
Assert . Equal ( expectedPriceWithoutNetworkFee - networkFee * bitpayinvoice . Rate , bitpayinvoice . Price ) ;
Assert . Equal ( Money . Zero , bitpayinvoice . BtcDue ) ;
Assert . Equal ( "paid" , bitpayinvoice . Status ) ;
Assert . Equal ( "False" , bitpayinvoice . ExceptionStatus . ToString ( ) ) ;
// Check if we index by price correctly once we know it
2024-04-04 09:31:04 +02:00
var invoices = await client . GetInvoices ( user . StoreId , textSearch : bitpayinvoice . Price . ToString ( CultureInfo . InvariantCulture ) . Split ( '.' ) [ 0 ] ) ;
2021-08-03 10:03:00 +02:00
Assert . Contains ( invoices , inv = > inv . Id = = bitpayinvoice . Id ) ;
}
catch ( JsonSerializationException )
{
2023-11-28 15:20:03 +01:00
Assert . Fail ( "The bitpay's amount is not set" ) ;
2021-08-03 10:03:00 +02:00
}
} ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanModifyRates ( )
2018-05-03 18:46:52 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var store = user . GetController < UIStoresController > ( ) ;
var rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
Assert . False ( rateVm . ShowScripting ) ;
Assert . Equal ( CoinGeckoRateProvider . CoinGeckoName , rateVm . PreferredExchange ) ;
Assert . Equal ( 0.0 , rateVm . Spread ) ;
Assert . Null ( rateVm . TestRateRules ) ;
rateVm . PreferredExchange = "bitflyer" ;
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
Assert . Equal ( "bitflyer" , rateVm . PreferredExchange ) ;
rateVm . ScriptTest = "BTC_JPY,BTC_CAD" ;
rateVm . Spread = 10 ;
store = user . GetController < UIStoresController > ( ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
Assert . NotNull ( rateVm . TestRateRules ) ;
Assert . Equal ( 2 , rateVm . TestRateRules . Count ) ;
Assert . False ( rateVm . TestRateRules [ 0 ] . Error ) ;
Assert . StartsWith ( "(bitflyer(BTC_JPY)) * (0.9, 1.1) =" , rateVm . TestRateRules [ 0 ] . Rule ,
StringComparison . OrdinalIgnoreCase ) ;
Assert . True ( rateVm . TestRateRules [ 1 ] . Error ) ;
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
Assert . IsType < RedirectToActionResult > ( store . ShowRateRulesPost ( true ) . Result ) ;
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
store = user . GetController < UIStoresController > ( ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
Assert . Equal ( rateVm . StoreId , user . StoreId ) ;
Assert . Equal ( rateVm . DefaultScript , rateVm . Script ) ;
Assert . True ( rateVm . ShowScripting ) ;
rateVm . ScriptTest = "BTC_JPY" ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
Assert . True ( rateVm . ShowScripting ) ;
Assert . Contains ( "(bitflyer(BTC_JPY)) * (0.9, 1.1) = " , rateVm . TestRateRules [ 0 ] . Rule ,
StringComparison . OrdinalIgnoreCase ) ;
rateVm . ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD" ;
2023-12-12 17:38:28 +01:00
rateVm . Script = "DOGE_X = bitpay(DOGE_BTC) * BTC_X;\n" +
2022-01-14 09:50:29 +01:00
"X_CAD = ndax(X_CAD);\n" +
"X_X = coingecko(X_X);" ;
rateVm . Spread = 50 ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
Assert . True ( rateVm . TestRateRules . All ( t = > ! t . Error ) ) ;
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
store = user . GetController < UIStoresController > ( ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
Assert . Equal ( 50 , rateVm . Spread ) ;
Assert . True ( rateVm . ShowScripting ) ;
Assert . Contains ( "DOGE_X" , rateVm . Script , StringComparison . OrdinalIgnoreCase ) ;
2018-05-03 18:46:52 +02:00
}
2024-09-02 11:37:39 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanTopUpPullPayment ( )
{
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( true ) ;
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
var client = await user . CreateClient ( ) ;
var pp = await client . CreatePullPayment ( user . StoreId , new ( )
{
Currency = "BTC" ,
Amount = 1.0 m ,
2024-09-26 04:25:45 +02:00
PayoutMethods = [ "BTC-CHAIN" ]
2024-09-02 11:37:39 +02:00
} ) ;
var controller = user . GetController < UIInvoiceController > ( ) ;
var invoice = await controller . CreateInvoiceCoreRaw ( new ( )
{
Amount = 0.5 m ,
Currency = "BTC" ,
} , controller . HttpContext . GetStoreData ( ) , controller . Url . Link ( null , null ) , [ PullPaymentHostedService . GetInternalTag ( pp . Id ) ] ) ;
await client . MarkInvoiceStatus ( user . StoreId , invoice . Id , new ( ) { Status = InvoiceStatus . Settled } ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
var payouts = await client . GetPayouts ( pp . Id ) ;
var payout = Assert . Single ( payouts ) ;
2024-09-06 06:24:33 +02:00
Assert . Equal ( "TOPUP" , payout . PayoutMethodId ) ;
2024-09-02 11:37:39 +02:00
Assert . Equal ( invoice . Id , payout . Destination ) ;
2024-09-26 04:25:45 +02:00
Assert . Equal ( - 0.5 m , payout . OriginalAmount ) ;
2024-09-02 11:37:39 +02:00
} ) ;
}
2021-10-20 16:17:40 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanUseDefaultCurrency ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( true ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
await user . ModifyPayment ( s = >
2021-10-20 16:17:40 +02:00
{
2022-01-14 09:50:29 +01:00
Assert . Equal ( "USD" , s . DefaultCurrency ) ;
s . DefaultCurrency = "EUR" ;
} ) ;
var client = await user . CreateClient ( ) ;
// with greenfield
var invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( ) ) ;
Assert . Equal ( "EUR" , invoice . Currency ) ;
Assert . Equal ( InvoiceType . TopUp , invoice . Type ) ;
// with bitpay api
var invoice2 = await user . BitPay . CreateInvoiceAsync ( new Invoice ( ) ) ;
Assert . Equal ( "EUR" , invoice2 . Currency ) ;
// via UI
var controller = user . GetController < UIInvoiceController > ( ) ;
2024-01-18 01:47:39 +01:00
await controller . CreateInvoice ( ) ;
2022-01-14 09:50:29 +01:00
( await controller . CreateInvoice ( new CreateInvoiceModel ( ) , default ) ) . AssertType < RedirectToActionResult > ( ) ;
invoice = await client . GetInvoice ( user . StoreId , controller . CreatedInvoiceId ) ;
Assert . Equal ( "EUR" , invoice . Currency ) ;
Assert . Equal ( InvoiceType . TopUp , invoice . Type ) ;
// Check that the SendWallet use the default currency
var walletController = user . GetController < UIWalletsController > ( ) ;
var walletSend = await walletController . WalletSend ( new WalletId ( user . StoreId , "BTC" ) ) . AssertViewModelAsync < WalletSendModel > ( ) ;
Assert . Equal ( "EUR" , walletSend . Fiat ) ;
2021-10-20 16:17:40 +02:00
}
2019-12-26 06:22:36 +01:00
[Fact]
2021-10-18 09:56:47 +02:00
[Trait("Lightning", "Lightning")]
2024-03-13 14:29:25 +01:00
[Trait("Integration", "Integration")]
2018-10-28 14:46:03 +01:00
public async Task CanSetPaymentMethodLimits ( )
2018-04-03 10:39:28 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( true ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
await user . RegisterLightningNodeAsync ( "BTC" ) ;
2024-04-04 09:31:04 +02:00
var lnMethod = PaymentTypes . LN . GetPaymentMethodId ( "BTC" ) . ToString ( ) ;
var btcMethod = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) . ToString ( ) ;
2022-01-14 09:50:29 +01:00
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
2024-05-09 02:18:02 +02:00
var vm = await user . GetController < UIStoresController > ( ) . CheckoutAppearance ( ) . AssertViewModelAsync < CheckoutAppearanceViewModel > ( ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( 2 , vm . PaymentMethodCriteria . Count ) ;
var criteria = Assert . Single ( vm . PaymentMethodCriteria . Where ( m = > m . PaymentMethod = = btcMethod . ToString ( ) ) ) ;
2024-10-04 15:24:44 +02:00
Assert . Equal ( btcMethod . ToString ( ) , criteria . PaymentMethod ) ;
2022-01-14 09:50:29 +01:00
criteria . Value = "5 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . GreaterThan ;
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( ) . CheckoutAppearance ( vm )
. Result ) ;
var invoice = user . BitPay . CreateInvoice (
new Invoice
{
Price = 4.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2024-03-13 14:29:25 +01:00
// LN and LNURL
Assert . Equal ( 2 , invoice . CryptoInfo . Length ) ;
2024-04-04 09:31:04 +02:00
Assert . Contains ( invoice . CryptoInfo , c = > c . PaymentType = = "BTC-LNURL" ) ;
Assert . Contains ( invoice . CryptoInfo , c = > c . PaymentType = = "BTC-LN" ) ;
2022-01-14 09:50:29 +01:00
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
// We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default
// payment method should be LN.
2024-05-09 02:18:02 +02:00
vm = await user . GetController < UIStoresController > ( ) . CheckoutAppearance ( ) . AssertViewModelAsync < CheckoutAppearanceViewModel > ( ) ;
2022-01-14 09:50:29 +01:00
vm . DefaultPaymentMethod = lnMethod ;
criteria = vm . PaymentMethodCriteria . First ( ) ;
criteria . Value = "150 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . LessThan ;
criteria = vm . PaymentMethodCriteria . Skip ( 1 ) . First ( ) ;
criteria . Value = "5 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . GreaterThan ;
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( ) . CheckoutAppearance ( vm )
. Result ) ;
invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 50 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2024-10-07 12:58:08 +02:00
var checkout = ( await user . GetController < UIInvoiceController > ( ) . Checkout ( invoice . Id ) ) . AssertViewModel < CheckoutModel > ( ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( lnMethod , checkout . PaymentMethodId ) ;
// If we change store's default, it should change the checkout's default
vm . DefaultPaymentMethod = btcMethod ;
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( ) . CheckoutAppearance ( vm )
. Result ) ;
2024-10-07 12:58:08 +02:00
checkout = ( await user . GetController < UIInvoiceController > ( ) . Checkout ( invoice . Id ) ) . AssertViewModel < CheckoutModel > ( ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( btcMethod , checkout . PaymentMethodId ) ;
2019-12-24 10:11:21 +01:00
}
2018-04-03 10:39:28 +02:00
2020-11-10 06:41:29 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanSetUnifiedQrCode ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
var cryptoCode = "BTC" ;
await user . GrantAccessAsync ( true ) ;
user . RegisterDerivationScheme ( cryptoCode , ScriptPubKeyType . Segwit ) ;
user . RegisterLightningNode ( cryptoCode , LightningConnectionType . CLightning ) ;
2020-11-10 06:41:29 +01:00
2022-01-14 09:50:29 +01:00
var invoice = user . BitPay . CreateInvoice (
new Invoice
{
Price = 5.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2020-11-10 06:41:29 +01:00
2022-01-14 09:50:29 +01:00
// validate that invoice data model doesn't have lightning string initially
var res = await user . GetController < UIInvoiceController > ( ) . Checkout ( invoice . Id ) ;
2024-10-07 12:58:08 +02:00
var paymentMethodFirst = Assert . IsType < CheckoutModel > (
2022-01-14 09:50:29 +01:00
Assert . IsType < ViewResult > ( res ) . Model
) ;
Assert . DoesNotContain ( "&lightning=" , paymentMethodFirst . InvoiceBitcoinUrlQR ) ;
// enable unified QR code in settings
var vm = Assert . IsType < LightningSettingsViewModel > ( Assert
2022-05-24 06:18:16 +02:00
. IsType < ViewResult > ( user . GetController < UIStoresController > ( ) . LightningSettings ( user . StoreId , cryptoCode ) ) . Model
2022-01-14 09:50:29 +01:00
) ;
vm . OnChainWithLnInvoiceFallback = true ;
Assert . IsType < RedirectToActionResult > (
user . GetController < UIStoresController > ( ) . LightningSettings ( vm ) . Result
) ;
// validate that QR code now has both onchain and offchain payment urls
res = await user . GetController < UIInvoiceController > ( ) . Checkout ( invoice . Id ) ;
2024-10-07 12:58:08 +02:00
var paymentMethodUnified = Assert . IsType < CheckoutModel > (
2022-01-14 09:50:29 +01:00
Assert . IsType < ViewResult > ( res ) . Model
) ;
2023-02-10 03:23:48 +01:00
Assert . StartsWith ( "bitcoin:bcrt" , paymentMethodUnified . InvoiceBitcoinUrl ) ;
Assert . StartsWith ( "bitcoin:BCRT" , paymentMethodUnified . InvoiceBitcoinUrlQR ) ;
Assert . Contains ( "&lightning=lnbcrt" , paymentMethodUnified . InvoiceBitcoinUrl ) ;
Assert . Contains ( "&lightning=LNBCRT" , paymentMethodUnified . InvoiceBitcoinUrlQR ) ;
2023-01-24 01:44:39 +01:00
// Check correct casing: Addresses in payment URI need to be …
// - lowercase in link version
// - uppercase in QR version
2023-04-10 04:07:03 +02:00
2022-01-14 09:50:29 +01:00
// Standard for all uppercase characters in QR codes is still not implemented in all wallets
// But we're proceeding with BECH32 being uppercase
2024-10-07 12:29:05 +02:00
Assert . Equal ( $"bitcoin:{paymentMethodUnified.Address}" , paymentMethodUnified . InvoiceBitcoinUrl . Split ( '?' ) [ 0 ] ) ;
Assert . Equal ( $"bitcoin:{paymentMethodUnified.Address.ToUpperInvariant()}" , paymentMethodUnified . InvoiceBitcoinUrlQR . Split ( '?' ) [ 0 ] ) ;
2023-01-24 01:44:39 +01:00
// Fallback lightning invoice should be uppercase inside the QR code, lowercase in payment URI
var lightningFallback = paymentMethodUnified . InvoiceBitcoinUrl . Split ( new [ ] { "&lightning=" } , StringSplitOptions . None ) [ 1 ] ;
Assert . NotNull ( lightningFallback ) ;
Assert . Contains ( $"&lightning={lightningFallback}" , paymentMethodUnified . InvoiceBitcoinUrl ) ;
Assert . Contains ( $"&lightning={lightningFallback.ToUpperInvariant()}" , paymentMethodUnified . InvoiceBitcoinUrlQR ) ;
2019-12-24 10:11:21 +01:00
}
2018-04-03 10:39:28 +02:00
2019-12-24 10:11:21 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanSetPaymentMethodLimitsLightning ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
var cryptoCode = "BTC" ;
user . GrantAccess ( true ) ;
2023-05-16 02:17:21 +02:00
user . RegisterLightningNode ( cryptoCode ) ;
2023-04-25 01:51:38 +02:00
user . SetLNUrl ( cryptoCode , false ) ;
2024-05-09 02:18:02 +02:00
var vm = await user . GetController < UIStoresController > ( ) . CheckoutAppearance ( ) . AssertViewModelAsync < CheckoutAppearanceViewModel > ( ) ;
2022-01-14 09:50:29 +01:00
var criteria = Assert . Single ( vm . PaymentMethodCriteria ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( PaymentTypes . LN . GetPaymentMethodId ( cryptoCode ) . ToString ( ) , criteria . PaymentMethod ) ;
2022-01-14 09:50:29 +01:00
criteria . Value = "2 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . LessThan ;
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( ) . CheckoutAppearance ( vm )
. Result ) ;
var invoice = user . BitPay . CreateInvoice (
new Invoice
{
Price = 1.5 m ,
Currency = "USD"
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( "BTC-LN" , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
2022-01-14 09:50:29 +01:00
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
2023-05-16 02:17:21 +02:00
user . RegisterLightningNode ( cryptoCode ) ;
2023-04-25 01:51:38 +02:00
user . SetLNUrl ( cryptoCode , true ) ;
2024-05-09 02:18:02 +02:00
vm = await user . GetController < UIStoresController > ( ) . CheckoutAppearance ( ) . AssertViewModelAsync < CheckoutAppearanceViewModel > ( ) ;
2022-01-14 09:50:29 +01:00
criteria = Assert . Single ( vm . PaymentMethodCriteria ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( PaymentTypes . LN . GetPaymentMethodId ( cryptoCode ) . ToString ( ) , criteria . PaymentMethod ) ;
2022-01-14 09:50:29 +01:00
Assert . IsType < RedirectToActionResult > ( user . GetController < UIStoresController > ( ) . CheckoutAppearance ( vm ) . Result ) ;
// However, creating an invoice should show LNURL
invoice = user . BitPay . CreateInvoice (
new Invoice
{
Price = 1.5 m ,
Currency = "USD"
} , Facade . Merchant ) ;
Assert . Equal ( 2 , invoice . CryptoInfo . Length ) ;
2021-10-25 08:18:02 +02:00
2022-01-14 09:50:29 +01:00
// Make sure this throw: Since BOLT11 and LN Url share the same criteria, there should be no payment method available
Assert . Throws < BitPayException > ( ( ) = > user . BitPay . CreateInvoice (
new Invoice
{
Price = 2.5 m ,
Currency = "USD"
} , Facade . Merchant ) ) ;
2018-04-03 10:39:28 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-11-27 07:13:09 +01:00
[Trait("Integration", "Integration")]
public async Task PosDataParser_ParsesCorrectly_Slower ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-11-30 09:34:43 +01:00
2022-01-14 09:50:29 +01:00
var controller = tester . PayTester . GetController < UIInvoiceController > ( null ) ;
2018-11-27 07:13:09 +01:00
2022-01-14 09:50:29 +01:00
var testCases =
new List < ( string input , Dictionary < string , object > expectedOutput ) > ( )
{
2020-04-05 13:48:00 +02:00
{ ( "{ \"key\": \"value\"}" , new Dictionary < string , object > ( ) { { "key" , "value" } } ) } ,
2023-02-25 15:34:49 +01:00
{ ( "{ \"key\": true}" , new Dictionary < string , object > ( ) { { "key" , "True" } } ) }
2022-01-14 09:50:29 +01:00
} ;
2018-11-27 07:13:09 +01:00
2022-01-14 09:50:29 +01:00
foreach ( var valueTuple in testCases )
{
2023-02-25 15:34:49 +01:00
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 1 , "BTC" ) { PosData = valueTuple . input } ) ;
var result = await controller . Invoice ( invoice . Id ) ;
var viewModel = result . AssertViewModel < InvoiceDetailsModel > ( ) ;
Assert . Equal ( valueTuple . expectedOutput , viewModel . AdditionalData [ "posData" ] ) ;
2018-11-27 07:13:09 +01:00
}
}
2022-01-14 09:50:29 +01:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanChangeNetworkFeeMode ( )
{
using var tester = CreateServerTester ( ) ;
2024-04-04 09:31:04 +02:00
var btc = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) ;
2022-01-14 09:50:29 +01:00
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
foreach ( var networkFeeMode in Enum . GetValues ( typeof ( NetworkFeeMode ) ) . Cast < NetworkFeeMode > ( ) )
2018-11-30 09:34:43 +01:00
{
2022-01-14 09:50:29 +01:00
TestLogs . LogInformation ( $"Trying with {nameof(networkFeeMode)}={networkFeeMode}" ) ;
await user . SetNetworkFeeMode ( networkFeeMode ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2021-10-29 08:25:43 +02:00
new Invoice
2020-04-05 13:48:00 +02:00
{
Price = 10 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2022-01-14 09:50:29 +01:00
var nextNetworkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
2024-04-04 09:31:04 +02:00
. GetPaymentPrompt ( btc ) . PaymentMethodFee ;
2022-01-14 09:50:29 +01:00
var firstPaymentFee = nextNetworkFee ;
switch ( networkFeeMode )
{
case NetworkFeeMode . Never :
case NetworkFeeMode . MultiplePaymentsOnly :
Assert . Equal ( 0.0 m , nextNetworkFee ) ;
break ;
case NetworkFeeMode . Always :
Assert . NotEqual ( 0.0 m , nextNetworkFee ) ;
break ;
}
2018-11-30 09:34:43 +01:00
2022-01-14 09:50:29 +01:00
var missingMoney = Money . Satoshis ( 5000 ) . ToDecimal ( MoneyUnit . BTC ) ;
2018-11-30 09:34:43 +01:00
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
2018-12-18 18:56:12 +01:00
2022-01-14 09:50:29 +01:00
var due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
var productPartDue = ( invoice . Price / invoice . Rate ) ;
TestLogs . LogInformation (
$"Product part due is {productPartDue} and due {due} with network fee {nextNetworkFee}" ) ;
Assert . Equal ( productPartDue + nextNetworkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
var firstPayment = productPartDue - missingMoney ;
cashCow . SendToAddress ( invoiceAddress , Money . Coins ( firstPayment ) ) ;
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
await TestUtils . EventuallyAsync ( async ( ) = >
2019-01-04 16:37:09 +01:00
{
2022-01-14 09:50:29 +01:00
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
TestLogs . LogInformation ( $"Remaining due after first payment: {due}" ) ;
Assert . Equal ( Money . Coins ( firstPayment ) , Money . Parse ( invoice . CryptoInfo [ 0 ] . Paid ) ) ;
nextNetworkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
2024-04-04 09:31:04 +02:00
. GetPaymentPrompt ( btc )
. PaymentMethodFee ;
2019-01-04 16:37:09 +01:00
switch ( networkFeeMode )
{
2020-04-05 13:48:00 +02:00
case NetworkFeeMode . Never :
2020-04-05 13:00:28 +02:00
Assert . Equal ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
2022-01-14 09:50:29 +01:00
case NetworkFeeMode . MultiplePaymentsOnly :
2019-01-04 16:37:09 +01:00
case NetworkFeeMode . Always :
2020-04-05 13:00:28 +02:00
Assert . NotEqual ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
}
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
Assert . Equal ( missingMoney + firstPaymentFee + nextNetworkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
Assert . Equal ( firstPayment + missingMoney + firstPaymentFee + nextNetworkFee ,
Money . Parse ( invoice . CryptoInfo [ 0 ] . TotalDue ) . ToDecimal ( MoneyUnit . BTC ) ) ;
} ) ;
cashCow . SendToAddress ( invoiceAddress , due ) ;
TestLogs . LogInformation ( $"After payment of {due}, the invoice should be paid" ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
} ) ;
2019-01-04 16:37:09 +01:00
}
}
2018-11-30 09:34:43 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanCreateAndDeleteApps ( )
2018-04-03 09:53:55 +02:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
var user2 = tester . NewAccount ( ) ;
await user2 . GrantAccessAsync ( ) ;
2024-09-27 08:28:55 +02:00
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
await user2 . RegisterDerivationSchemeAsync ( "BTC" ) ;
2022-07-12 08:18:08 +02:00
var stores = user . GetController < UIStoresController > ( ) ;
2022-01-14 09:50:29 +01:00
var apps = user . GetController < UIAppsController > ( ) ;
var apps2 = user2 . GetController < UIAppsController > ( ) ;
2022-07-18 20:51:53 +02:00
var pos = user . GetController < UIPointOfSaleController > ( ) ;
2023-03-20 02:39:26 +01:00
var appType = PointOfSaleAppType . AppType ;
2023-03-17 03:56:32 +01:00
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( user . StoreId , appType ) ) . Model ) ;
Assert . Equal ( appType , vm . SelectedAppType ) ;
2022-01-14 09:50:29 +01:00
Assert . Null ( vm . AppName ) ;
vm . AppName = "test" ;
2023-03-17 03:56:32 +01:00
var redirect = Assert . IsType < RedirectResult > ( apps . CreateApp ( user . StoreId , vm ) . Result ) ;
Assert . EndsWith ( "/settings/pos" , redirect . Url ) ;
2022-01-14 09:50:29 +01:00
var appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( user . StoreId ) . Result ) . Model ) ;
var appList2 =
Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps2 . ListApps ( user2 . StoreId ) . Result ) . Model ) ;
var app = appList . Apps [ 0 ] ;
2022-07-18 20:51:53 +02:00
var appData = new AppData { Id = app . Id , StoreDataId = app . StoreId , Name = app . AppName , AppType = appType } ;
apps . HttpContext . SetAppData ( appData ) ;
pos . HttpContext . SetAppData ( appData ) ;
2022-01-14 09:50:29 +01:00
Assert . Single ( appList . Apps ) ;
Assert . Empty ( appList2 . Apps ) ;
Assert . Equal ( "test" , appList . Apps [ 0 ] . AppName ) ;
Assert . Equal ( apps . CreatedAppId , appList . Apps [ 0 ] . Id ) ;
2023-12-20 10:41:28 +01:00
2023-05-26 16:49:32 +02:00
Assert . True ( app . Role . ToPermissionSet ( app . StoreId ) . Contains ( Policies . CanModifyStoreSettings , app . StoreId ) ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( user . StoreId , appList . Apps [ 0 ] . StoreId ) ;
Assert . IsType < NotFoundResult > ( apps2 . DeleteApp ( appList . Apps [ 0 ] . Id ) ) ;
Assert . IsType < ViewResult > ( apps . DeleteApp ( appList . Apps [ 0 ] . Id ) ) ;
2023-03-17 03:56:32 +01:00
var redirectToAction = Assert . IsType < RedirectToActionResult > ( apps . DeleteAppPost ( appList . Apps [ 0 ] . Id ) . Result ) ;
2022-07-12 08:18:08 +02:00
Assert . Equal ( nameof ( stores . Dashboard ) , redirectToAction . ActionName ) ;
2022-01-14 09:50:29 +01:00
appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( user . StoreId ) . Result ) . Model ) ;
Assert . Empty ( appList . Apps ) ;
2018-04-03 09:53:55 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-01-15 14:12:29 +01:00
[Trait("Integration", "Integration")]
2021-11-15 05:48:07 +01:00
[Trait("Lightning", "Lightning")]
2019-10-07 09:04:25 +02:00
public async Task CanCreateStrangeInvoice ( )
2019-01-15 14:12:29 +01:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( true ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2023-06-16 03:47:58 +02:00
var btcpayClient = await user . CreateClient ( ) ;
2021-07-30 11:46:49 +02:00
2022-01-14 09:50:29 +01:00
DateTimeOffset expiration = DateTimeOffset . UtcNow + TimeSpan . FromMinutes ( 21 ) ;
2021-07-30 11:46:49 +02:00
2022-01-14 09:50:29 +01:00
// This should fail, the amount is too low to be above the dust limit of bitcoin
var ex = Assert . Throws < BitPayException > ( ( ) = > user . BitPay . CreateInvoice (
new Invoice ( )
2021-07-30 11:46:49 +02:00
{
2022-01-14 09:50:29 +01:00
Price = 0.000000012 m ,
2021-07-30 11:46:49 +02:00
Currency = "USD" ,
2022-01-14 09:50:29 +01:00
FullNotifications = true ,
ExpirationTime = expiration
} , Facade . Merchant ) ) ;
Assert . Contains ( "dust threshold" , ex . Message ) ;
await user . RegisterLightningNodeAsync ( "BTC" ) ;
var invoice1 = user . BitPay . CreateInvoice (
new Invoice ( )
2021-09-06 17:23:41 +02:00
{
2022-01-14 09:50:29 +01:00
Price = 0.000000012 m ,
Currency = "USD" ,
FullNotifications = true ,
ExpirationTime = expiration
} , Facade . Merchant ) ;
2021-09-06 17:23:41 +02:00
2022-01-14 09:50:29 +01:00
Assert . Equal ( expiration . ToUnixTimeSeconds ( ) , invoice1 . ExpirationTime . ToUnixTimeSeconds ( ) ) ;
var invoice2 = user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 0.000000019 m , Currency = "USD" } ,
Facade . Merchant ) ;
Assert . Equal ( 0.000000012 m , invoice1 . Price ) ;
Assert . Equal ( 0.000000019 m , invoice2 . Price ) ;
// Should round up to 1 because 0.000000019 is unsignificant
var invoice3 = user . BitPay . CreateInvoice (
new Invoice ( ) { Price = 1.000000019 m , Currency = "USD" , FullNotifications = true } , Facade . Merchant ) ;
Assert . Equal ( 1 m , invoice3 . Price ) ;
// Should not round up at 8 digit because the 9th is insignificant
var invoice4 = user . BitPay . CreateInvoice (
new Invoice ( ) { Price = 1.000000019 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
Assert . Equal ( 1.00000002 m , invoice4 . Price ) ;
// But not if the 9th is insignificant
invoice4 = user . BitPay . CreateInvoice (
new Invoice ( ) { Price = 0.000000019 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
Assert . Equal ( 0.000000019 m , invoice4 . Price ) ;
var invoice = user . BitPay . CreateInvoice (
new Invoice ( ) { Price = - 0.1 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
Assert . Equal ( 0.0 m , invoice . Price ) ;
// Should round down to 50.51, taxIncluded should be also clipped to this value because taxIncluded can't be higher than the price.
var invoice5 = user . BitPay . CreateInvoice (
new Invoice ( ) { Price = 50.513 m , Currency = "USD" , FullNotifications = true , TaxIncluded = 50.516 m } , Facade . Merchant ) ;
Assert . Equal ( 50.51 m , invoice5 . Price ) ;
Assert . Equal ( 50.51 m , invoice5 . TaxIncluded ) ;
var greenfield = await user . CreateClient ( ) ;
var invoice5g = await greenfield . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 50.513 m ,
Currency = "USD" ,
Metadata = new JObject ( ) { new JProperty ( "taxIncluded" , 50.516 m ) , new JProperty ( "orderId" , "000000161" ) }
} ) ;
Assert . Equal ( 50.51 m , invoice5g . Amount ) ;
Assert . Equal ( 50.51 m , ( decimal ) invoice5g . Metadata [ "taxIncluded" ] ) ;
Assert . Equal ( "000000161" , ( string ) invoice5g . Metadata [ "orderId" ] ) ;
var zeroInvoice = await greenfield . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 0 m ,
Currency = "USD"
} ) ;
Assert . Equal ( InvoiceStatus . New , zeroInvoice . Status ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
zeroInvoice = await greenfield . GetInvoice ( user . StoreId , zeroInvoice . Id ) ;
Assert . Equal ( InvoiceStatus . Settled , zeroInvoice . Status ) ;
} ) ;
var zeroInvoicePM = await greenfield . GetInvoicePaymentMethods ( user . StoreId , zeroInvoice . Id ) ;
Assert . Empty ( zeroInvoicePM ) ;
2023-06-16 03:47:58 +02:00
var invoice6 = await btcpayClient . CreateInvoice ( user . StoreId ,
new CreateInvoiceRequest ( )
{
Amount = GreenfieldConstants . MaxAmount ,
Currency = "USD"
} ) ;
var repo = tester . PayTester . GetService < InvoiceRepository > ( ) ;
var entity = ( await repo . GetInvoice ( invoice6 . Id ) ) ;
Assert . Equal ( ( decimal ) ulong . MaxValue , entity . Price ) ;
2024-04-04 09:31:04 +02:00
entity . GetPaymentPrompts ( ) . First ( ) . Calculate ( ) ;
2023-06-16 03:47:58 +02:00
// Shouldn't be possible as we clamp the value, but existing invoice may have that
entity . Price = decimal . MaxValue ;
2024-04-04 09:31:04 +02:00
entity . GetPaymentPrompts ( ) . First ( ) . Calculate ( ) ;
2019-01-15 14:12:29 +01:00
}
2024-02-23 09:44:42 +01:00
[Fact()]
[Trait("Integration", "Integration")]
public async Task EnsureWebhooksTrigger ( )
{
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
await user . SetupWebhook ( ) ;
var client = await user . CreateClient ( ) ;
var invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 0.00 m ,
Currency = "BTC"
} ) ; ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceCreated , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
//invoice payment webhooks
invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 0.01 m ,
Currency = "BTC"
} ) ;
var invoicePaymentRequest = new BitcoinUrlBuilder ( ( await client . GetInvoicePaymentMethods ( user . StoreId , invoice . Id ) ) . Single ( model = >
2024-04-04 09:31:04 +02:00
PaymentMethodId . Parse ( model . PaymentMethodId ) = =
PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) )
2024-02-23 09:44:42 +01:00
. PaymentLink , tester . ExplorerNode . Network ) ;
var halfPaymentTx = await tester . ExplorerNode . SendToAddressAsync ( invoicePaymentRequest . Address , Money . Coins ( invoicePaymentRequest . Amount . ToDecimal ( MoneyUnit . BTC ) / 2 m ) ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceCreated , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceReceivedPayment ,
( WebhookInvoiceReceivedPaymentEvent x ) = >
{
Assert . Equal ( invoice . Id , x . InvoiceId ) ;
Assert . Contains ( halfPaymentTx . ToString ( ) , x . Payment . Id ) ;
} ) ;
2024-04-16 05:48:42 +02:00
invoicePaymentRequest = new BitcoinUrlBuilder ( ( await client . GetInvoicePaymentMethods ( user . StoreId , invoice . Id ) ) . Single ( model = >
PaymentMethodId . Parse ( model . PaymentMethodId ) = =
PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) )
. PaymentLink , tester . ExplorerNode . Network ) ;
var remainingPaymentTx = await tester . ExplorerNode . SendToAddressAsync ( invoicePaymentRequest . Address , Money . Coins ( invoicePaymentRequest . Amount . ToDecimal ( MoneyUnit . BTC ) ) ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceReceivedPayment ,
2024-02-23 09:44:42 +01:00
( WebhookInvoiceReceivedPaymentEvent x ) = >
{
Assert . Equal ( invoice . Id , x . InvoiceId ) ;
Assert . Contains ( remainingPaymentTx . ToString ( ) , x . Payment . Id ) ;
} ) ;
2024-04-16 05:48:42 +02:00
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceProcessing , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
2024-02-23 09:44:42 +01:00
await tester . ExplorerNode . GenerateAsync ( 1 ) ;
2024-04-16 05:48:42 +02:00
2024-02-23 09:44:42 +01:00
await user . AssertHasWebhookEvent ( WebhookEventType . InvoicePaymentSettled ,
( WebhookInvoiceReceivedPaymentEvent x ) = >
{
Assert . Equal ( invoice . Id , x . InvoiceId ) ;
Assert . Contains ( halfPaymentTx . ToString ( ) , x . Payment . Id ) ;
} ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoicePaymentSettled ,
( WebhookInvoiceReceivedPaymentEvent x ) = >
{
Assert . Equal ( invoice . Id , x . InvoiceId ) ;
Assert . Contains ( remainingPaymentTx . ToString ( ) , x . Payment . Id ) ;
} ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceSettled , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 0.01 m ,
Currency = "BTC" ,
} ) ;
invoicePaymentRequest = new BitcoinUrlBuilder ( ( await client . GetInvoicePaymentMethods ( user . StoreId , invoice . Id ) ) . Single ( model = >
2024-04-04 09:31:04 +02:00
PaymentMethodId . Parse ( model . PaymentMethodId ) = =
PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) )
2024-02-23 09:44:42 +01:00
. PaymentLink , tester . ExplorerNode . Network ) ;
halfPaymentTx = await tester . ExplorerNode . SendToAddressAsync ( invoicePaymentRequest . Address , Money . Coins ( invoicePaymentRequest . Amount . ToDecimal ( MoneyUnit . BTC ) / 2 m ) ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceCreated , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceReceivedPayment ,
( WebhookInvoiceReceivedPaymentEvent x ) = >
{
Assert . Equal ( invoice . Id , x . InvoiceId ) ;
Assert . Contains ( halfPaymentTx . ToString ( ) , x . Payment . Id ) ;
} ) ;
invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 0.01 m ,
Currency = "BTC"
} ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceCreated , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
await client . MarkInvoiceStatus ( user . StoreId , invoice . Id , new MarkInvoiceStatusRequest ( ) { Status = InvoiceStatus . Invalid } ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . InvoiceInvalid , ( WebhookInvoiceEvent x ) = > Assert . Equal ( invoice . Id , x . InvoiceId ) ) ;
//payment request webhook test
var pr = await client . CreatePaymentRequest ( user . StoreId , new CreatePaymentRequestRequest ( )
{
Amount = 100 m ,
Currency = "USD" ,
Title = "test pr" ,
//TODO: this is a bug, we should not have these props in create request
StoreId = user . StoreId ,
FormResponse = new JObject ( ) ,
//END todo
Description = "lala baba"
} ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PaymentRequestCreated , ( WebhookPaymentRequestEvent x ) = > Assert . Equal ( pr . Id , x . PaymentRequestId ) ) ;
pr = await client . UpdatePaymentRequest ( user . StoreId , pr . Id ,
new UpdatePaymentRequestRequest ( ) { Title = "test pr updated" , Amount = 100 m ,
Currency = "USD" ,
//TODO: this is a bug, we should not have these props in create request
StoreId = user . StoreId ,
FormResponse = new JObject ( ) ,
//END todo
Description = "lala baba" } ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PaymentRequestUpdated , ( WebhookPaymentRequestEvent x ) = > Assert . Equal ( pr . Id , x . PaymentRequestId ) ) ;
var inv = await client . PayPaymentRequest ( user . StoreId , pr . Id , new PayPaymentRequestRequest ( ) { } ) ;
await client . MarkInvoiceStatus ( user . StoreId , inv . Id , new MarkInvoiceStatusRequest ( ) { Status = InvoiceStatus . Settled } ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PaymentRequestStatusChanged , ( WebhookPaymentRequestEvent x ) = >
{
Assert . Equal ( PaymentRequestData . PaymentRequestStatus . Completed , x . Status ) ;
Assert . Equal ( pr . Id , x . PaymentRequestId ) ;
} ) ;
await client . ArchivePaymentRequest ( user . StoreId , pr . Id ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PaymentRequestArchived , ( WebhookPaymentRequestEvent x ) = > Assert . Equal ( pr . Id , x . PaymentRequestId ) ) ;
//payoyt webhooks test
var payout = await client . CreatePayout ( user . StoreId ,
new CreatePayoutThroughStoreRequest ( )
{
Amount = 0.0001 m ,
Destination = ( await tester . ExplorerNode . GetNewAddressAsync ( ) ) . ToString ( ) ,
Approved = true ,
2024-09-06 06:24:33 +02:00
PayoutMethodId = "BTC"
2024-02-23 09:44:42 +01:00
} ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PayoutCreated , ( WebhookPayoutEvent x ) = > Assert . Equal ( payout . Id , x . PayoutId ) ) ;
await client . MarkPayout ( user . StoreId , payout . Id , new MarkPayoutRequest ( ) { State = PayoutState . AwaitingApproval } ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PayoutUpdated , ( WebhookPayoutEvent x ) = >
{
Assert . Equal ( payout . Id , x . PayoutId ) ;
Assert . Equal ( PayoutState . AwaitingApproval , x . PayoutState ) ;
} ) ;
await client . ApprovePayout ( user . StoreId , payout . Id , new ApprovePayoutRequest ( ) { } ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PayoutApproved , ( WebhookPayoutEvent x ) = >
{
Assert . Equal ( payout . Id , x . PayoutId ) ;
Assert . Equal ( PayoutState . AwaitingPayment , x . PayoutState ) ;
} ) ;
await client . CancelPayout ( user . StoreId , payout . Id ) ;
await user . AssertHasWebhookEvent ( WebhookEventType . PayoutUpdated , ( WebhookPayoutEvent x ) = >
{
Assert . Equal ( payout . Id , x . PayoutId ) ;
Assert . Equal ( PayoutState . Cancelled , x . PayoutState ) ;
} ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task InvoiceFlowThroughDifferentStatesCorrectly ( )
2018-01-07 18:36:41 +01:00
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
2023-12-01 10:50:05 +01:00
await user . GrantAccessAsync ( ) ;
2022-01-14 09:50:29 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
await user . SetupWebhook ( ) ;
2023-12-01 10:50:05 +01:00
var invoice = await user . BitPay . CreateInvoiceAsync (
new Invoice
2022-01-14 09:50:29 +01:00
{
Price = 5000.0 m ,
TaxIncluded = 1000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
var repo = tester . PayTester . GetService < InvoiceRepository > ( ) ;
var ctx = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) . CreateContext ( ) ;
Assert . Equal ( 0 , invoice . CryptoInfo [ 0 ] . TxCount ) ;
Assert . True ( invoice . MinerFees . ContainsKey ( "BTC" ) ) ;
2024-04-04 09:31:04 +02:00
Assert . Contains ( Math . Round ( invoice . MinerFees [ "BTC" ] . SatoshiPerBytes ) , new [ ] { 100.0 m , 20.0 m } ) ;
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
2018-01-07 18:36:41 +01:00
{
2022-01-14 09:50:29 +01:00
var textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = invoice . OrderId
} ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Single ( textSearchResult ) ;
textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
{
StoreId = new [ ] { user . StoreId } ,
TextSearch = invoice . Id
} ) . GetAwaiter ( ) . GetResult ( ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
Assert . Single ( textSearchResult ) ;
} ) ;
2017-10-27 10:53:04 +02:00
2023-12-06 01:21:04 +01:00
invoice = await user . BitPay . GetInvoiceAsync ( invoice . Id , Facade . Merchant ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( 1000.0 m , invoice . TaxIncluded ) ;
Assert . Equal ( 5000.0 m , invoice . Price ) ;
Assert . Equal ( Money . Coins ( 0 ) , invoice . BtcPaid ) ;
Assert . Equal ( "new" , invoice . Status ) ;
Assert . False ( ( bool ) ( ( JValue ) invoice . ExceptionStatus ) . Value ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime ) ) ;
Assert . Empty ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime + TimeSpan . FromDays ( 2 ) ) ) ;
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ) ) ;
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ,
invoice . InvoiceTime . DateTime + TimeSpan . FromDays ( 1.0 ) ) ) ;
Assert . Empty ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ,
invoice . InvoiceTime . DateTime - TimeSpan . FromDays ( 1 ) ) ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
var firstPayment = Money . Coins ( 0.04 m ) ;
var txFee = Money . Zero ;
var cashCow = tester . ExplorerNode ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
var invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
Assert . True ( IsMapped ( invoice , ctx ) ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
var invoiceEntity = repo . GetInvoice ( invoice . Id , true ) . GetAwaiter ( ) . GetResult ( ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
Money secondPayment = Money . Zero ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "new" , localInvoice . Status ) ;
Assert . Equal ( firstPayment , localInvoice . BtcPaid ) ;
txFee = localInvoice . BtcDue - invoice . BtcDue ;
Assert . Equal ( "paidPartial" , localInvoice . ExceptionStatus . ToString ( ) ) ;
Assert . Equal ( 1 , localInvoice . CryptoInfo [ 0 ] . TxCount ) ;
2023-05-18 09:53:01 +02:00
Assert . Equal ( localInvoice . BitcoinAddress , invoice . BitcoinAddress ) ; //Same address
2023-01-06 14:18:07 +01:00
Assert . True ( IsMapped ( invoice , ctx ) ) ;
2022-01-14 09:50:29 +01:00
Assert . True ( IsMapped ( localInvoice , ctx ) ) ;
invoiceEntity = repo . GetInvoice ( invoice . Id , true ) . GetAwaiter ( ) . GetResult ( ) ;
invoiceAddress = BitcoinAddress . Create ( localInvoice . BitcoinAddress , cashCow . Network ) ;
secondPayment = localInvoice . BtcDue ;
} ) ;
2017-10-27 10:53:04 +02:00
2023-12-01 10:50:05 +01:00
await cashCow . SendToAddressAsync ( invoiceAddress , secondPayment ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
Assert . Equal ( 2 , localInvoice . CryptoInfo [ 0 ] . TxCount ) ;
Assert . Equal ( firstPayment + secondPayment , localInvoice . BtcPaid ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( localInvoice . BitcoinAddress , invoiceAddress . ToString ( ) ) ; //no new address generated
2023-01-06 14:18:07 +01:00
Assert . True ( IsMapped ( localInvoice , ctx ) ) ;
2022-01-14 09:50:29 +01:00
Assert . False ( ( bool ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
} ) ;
2017-10-27 10:53:04 +02:00
2023-12-01 10:50:05 +01:00
await cashCow . GenerateAsync ( 1 ) ; //The user has medium speed settings, so 1 conf is enough to be confirmed
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "complete" , localInvoice . Status ) ;
Assert . NotEqual ( 0.0 m , localInvoice . Rate ) ;
} ) ;
2017-10-27 10:53:04 +02:00
2023-12-01 10:50:05 +01:00
invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice
2022-01-14 09:50:29 +01:00
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
//RedirectURL = redirect + "redirect",
//NotificationURL = CallbackUri + "/notification",
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
2017-10-27 10:53:04 +02:00
2023-12-01 10:50:05 +01:00
var txId = await cashCow . SendToAddressAsync ( invoiceAddress , invoice . BtcDue + Money . Coins ( 1 ) ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( "paidOver" , ( string ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
var textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = txId . ToString ( )
} ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Single ( textSearchResult ) ;
} ) ;
2018-11-02 06:26:13 +01:00
2023-12-01 10:50:05 +01:00
await cashCow . GenerateAsync ( 2 ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
TestUtils . Eventually ( ( ) = >
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( "complete" , localInvoice . Status ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( "paidOver" , ( string ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
} ) ;
2017-10-27 10:53:04 +02:00
2022-01-14 09:50:29 +01:00
// Test on the webhooks
2024-03-14 11:11:54 +01:00
await user . AssertHasWebhookEvent < WebhookInvoiceSettledEvent > ( WebhookEventType . InvoiceSettled ,
2022-01-14 09:50:29 +01:00
c = >
{
Assert . False ( c . ManuallyMarked ) ;
2023-12-06 01:21:04 +01:00
Assert . True ( c . OverPaid ) ;
2022-01-14 09:50:29 +01:00
} ) ;
2024-03-14 11:11:54 +01:00
await user . AssertHasWebhookEvent < WebhookInvoiceProcessingEvent > ( WebhookEventType . InvoiceProcessing ,
2022-01-14 09:50:29 +01:00
c = >
2017-10-27 10:53:04 +02:00
{
2022-01-14 09:50:29 +01:00
Assert . True ( c . OverPaid ) ;
2017-10-27 10:53:04 +02:00
} ) ;
2024-03-14 11:11:54 +01:00
await user . AssertHasWebhookEvent < WebhookInvoiceReceivedPaymentEvent > ( WebhookEventType . InvoiceReceivedPayment ,
2022-01-14 09:50:29 +01:00
c = >
{
Assert . False ( c . AfterExpiration ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) . ToString ( ) , c . PaymentMethodId ) ;
2022-01-14 09:50:29 +01:00
Assert . NotNull ( c . Payment ) ;
Assert . Equal ( invoice . BitcoinAddress , c . Payment . Destination ) ;
Assert . StartsWith ( txId . ToString ( ) , c . Payment . Id ) ;
2020-11-13 08:28:15 +01:00
2022-01-14 09:50:29 +01:00
} ) ;
2024-03-14 11:11:54 +01:00
await user . AssertHasWebhookEvent < WebhookInvoicePaymentSettledEvent > ( WebhookEventType . InvoicePaymentSettled ,
2022-01-14 09:50:29 +01:00
c = >
{
Assert . False ( c . AfterExpiration ) ;
2024-04-04 09:31:04 +02:00
Assert . Equal ( PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) . ToString ( ) , c . PaymentMethodId ) ;
2022-01-14 09:50:29 +01:00
Assert . NotNull ( c . Payment ) ;
Assert . Equal ( invoice . BitcoinAddress , c . Payment . Destination ) ;
Assert . StartsWith ( txId . ToString ( ) , c . Payment . Id ) ;
} ) ;
2017-10-27 10:53:04 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-11-07 14:29:35 +01:00
[Trait("Integration", "Integration")]
public async Task CheckLogsRoute ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var serverController = user . GetController < UIServerController > ( ) ;
2024-01-18 01:47:39 +01:00
Assert . IsType < LogsViewModel > ( Assert . IsType < ViewResult > ( await serverController . LogsView ( ) ) . Model ) ;
2018-11-30 09:34:43 +01:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-05-02 14:01:08 +02:00
[Trait("Integration", "Integration")]
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2019-05-02 14:01:08 +02:00
2022-01-14 09:50:29 +01:00
var accountController = tester . PayTester . GetController < UIAccountController > ( ) ;
2019-05-02 14:01:08 +02:00
2022-01-14 09:50:29 +01:00
//no 2fa or fido2 enabled, login should work
Assert . Equal ( nameof ( UIHomeController . Index ) ,
Assert . IsType < RedirectToActionResult > ( await accountController . Login ( new LoginViewModel ( )
{
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
} ) ) . ActionName ) ;
2020-02-13 06:44:31 +01:00
2022-01-14 09:50:29 +01:00
var listController = user . GetController < UIManageController > ( ) ;
2022-01-14 10:34:30 +01:00
var manageController = user . GetController < UIFido2Controller > ( ) ;
2019-11-07 10:35:47 +01:00
2022-01-14 09:50:29 +01:00
//by default no fido2 devices available
Assert . Empty ( Assert
. IsType < TwoFactorAuthenticationViewModel > ( Assert
. IsType < ViewResult > ( await listController . TwoFactorAuthentication ( ) ) . Model ) . Credentials ) ;
Assert . IsType < CredentialCreateOptions > ( Assert
. IsType < ViewResult > ( await manageController . Create ( new AddFido2CredentialViewModel
{
Name = "label"
} ) ) . Model ) ;
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
//sending an invalid response model back to server, should error out
Assert . IsType < RedirectToActionResult > ( await manageController . CreateResponse ( "sdsdsa" , "sds" ) ) ;
var statusModel = manageController . TempData . GetStatusMessageModel ( ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Error , statusModel . Severity ) ;
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
var contextFactory = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) ;
2020-04-05 13:48:00 +02:00
2022-01-14 09:50:29 +01:00
//add a fake fido2 device in db directly since emulating a fido2 device is hard and annoying
using ( var context = contextFactory . CreateContext ( ) )
{
var newDevice = new Fido2Credential ( )
2020-04-05 13:48:00 +02:00
{
2022-01-14 09:50:29 +01:00
Id = Guid . NewGuid ( ) . ToString ( ) ,
Name = "fake" ,
Type = Fido2Credential . CredentialType . FIDO2 ,
ApplicationUserId = user . UserId
} ;
newDevice . SetBlob ( new Fido2CredentialBlob ( ) { } ) ;
await context . Fido2Credentials . AddAsync ( newDevice ) ;
await context . SaveChangesAsync ( ) ;
Assert . NotNull ( newDevice . Id ) ;
Assert . NotEmpty ( Assert
. IsType < TwoFactorAuthenticationViewModel > ( Assert
. IsType < ViewResult > ( await listController . TwoFactorAuthentication ( ) ) . Model ) . Credentials ) ;
2019-05-02 14:01:08 +02:00
}
2022-01-14 09:50:29 +01:00
//check if we are showing the fido2 login screen now
var secondLoginResult = Assert . IsType < ViewResult > ( await accountController . Login ( new LoginViewModel ( )
{
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
} ) ) ;
Assert . Equal ( "SecondaryLogin" , secondLoginResult . ViewName ) ;
var vm = Assert . IsType < SecondaryLoginViewModel > ( secondLoginResult . Model ) ;
//2fa was never enabled for user so this should be empty
Assert . Null ( vm . LoginWith2FaViewModel ) ;
Assert . NotNull ( vm . LoginWithFido2ViewModel ) ;
2019-05-02 14:01:08 +02:00
}
2020-02-13 06:44:31 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-06-04 08:53:55 +02:00
[Trait("Integration", "Integration")]
public async void CheckOnionlocationForNonOnionHtmlRequests ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var url = tester . PayTester . ServerUri . AbsoluteUri ;
2020-06-04 08:53:55 +02:00
2022-01-14 09:50:29 +01:00
// check onion location is present for HTML page request
using var htmlRequest = new HttpRequestMessage ( HttpMethod . Get , new Uri ( url ) ) ;
htmlRequest . Headers . TryAddWithoutValidation ( "Accept" , "text/html,*/*" ) ;
2020-06-04 08:53:55 +02:00
2022-01-14 09:50:29 +01:00
var htmlResponse = await tester . PayTester . HttpClient . SendAsync ( htmlRequest ) ;
htmlResponse . EnsureSuccessStatusCode ( ) ;
Assert . True ( htmlResponse . Headers . TryGetValues ( "Onion-Location" , out var onionLocation ) ) ;
Assert . StartsWith ( "http://wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion" , onionLocation . FirstOrDefault ( ) ? ? "no-onion-location-header" ) ;
2020-06-04 08:53:55 +02:00
2022-01-14 09:50:29 +01:00
// no onion location for other mime types
using var otherRequest = new HttpRequestMessage ( HttpMethod . Get , new Uri ( url ) ) ;
otherRequest . Headers . TryAddWithoutValidation ( "Accept" , "*/*" ) ;
2020-06-04 08:53:55 +02:00
2022-01-14 09:50:29 +01:00
var otherResponse = await tester . PayTester . HttpClient . SendAsync ( otherRequest ) ;
otherResponse . EnsureSuccessStatusCode ( ) ;
Assert . False ( otherResponse . Headers . Contains ( "Onion-Location" ) ) ;
2020-06-04 08:53:55 +02:00
}
2017-10-27 10:53:04 +02:00
private static bool IsMapped ( Invoice invoice , ApplicationDbContext ctx )
{
2018-04-10 12:07:57 +02:00
var h = BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) . ScriptPubKey . Hash . ToString ( ) ;
2024-09-19 15:15:02 +02:00
var pmi = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
return ( ctx . AddressInvoices . Where ( i = > i . InvoiceDataId = = invoice . Id ) . ToArrayAsync ( ) . GetAwaiter ( )
. GetResult ( ) )
2024-09-19 15:15:02 +02:00
. Where ( i = > i . Address = = h & & i . PaymentMethodId = = pmi . ToString ( ) ) . Any ( ) ;
2017-10-27 10:53:04 +02:00
}
2020-07-31 03:52:33 +02:00
2023-11-30 10:12:44 +01:00
class MockVersionFetcher : GithubVersionFetcher
2020-07-31 03:52:33 +02:00
{
public const string MOCK_NEW_VERSION = "9.9.9.9" ;
2023-11-30 10:12:44 +01:00
public override Task < string > Fetch ( CancellationToken cancellation )
2020-07-31 03:52:33 +02:00
{
return Task . FromResult ( MOCK_NEW_VERSION ) ;
}
2023-11-30 10:12:44 +01:00
public MockVersionFetcher ( IHttpClientFactory httpClientFactory , BTCPayServerOptions options , ILogger < GithubVersionFetcher > logger , SettingsRepository settingsRepository , BTCPayServerEnvironment environment , NotificationSender notificationSender ) : base ( httpClientFactory , options , logger , settingsRepository , environment , notificationSender )
{
}
2020-07-31 03:52:33 +02:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-07-31 03:52:33 +02:00
[Trait("Integration", "Integration")]
public async Task CanCheckForNewVersion ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( newDb : true ) ;
await tester . StartAsync ( ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( true ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
await settings . UpdateSetting < PoliciesSettings > ( new PoliciesSettings ( ) { CheckForNewVersions = true } ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
var mockEnv = tester . PayTester . GetService < BTCPayServerEnvironment > ( ) ;
var mockSender = tester . PayTester . GetService < Services . Notifications . NotificationSender > ( ) ;
2020-07-31 03:52:33 +02:00
2023-11-30 10:12:44 +01:00
var svc = new MockVersionFetcher ( tester . PayTester . GetService < IHttpClientFactory > ( ) ,
tester . PayTester . GetService < BTCPayServerOptions > ( ) ,
tester . PayTester . GetService < ILogger < GithubVersionFetcher > > ( ) ,
settings ,
mockEnv ,
mockSender ) ;
await svc . Do ( CancellationToken . None ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
// since last version present in database was null, it should've been updated with version mock returned
var lastVersion = await settings . GetSettingAsync < NewVersionCheckerDataHolder > ( ) ;
Assert . Equal ( MockVersionFetcher . MOCK_NEW_VERSION , lastVersion . LastVersion ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
// we should also have notification in UI
var ctrl = acc . GetController < UINotificationsController > ( ) ;
var newVersion = MockVersionFetcher . MOCK_NEW_VERSION ;
2020-07-31 03:52:33 +02:00
2024-07-02 10:55:54 +02:00
var vm = Assert . IsType < Models . NotificationViewModels . NotificationIndexViewModel > (
2022-01-14 09:50:29 +01:00
Assert . IsType < ViewResult > ( await ctrl . Index ( ) ) . Model ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
Assert . True ( vm . Skip = = 0 ) ;
Assert . True ( vm . Count = = 50 ) ;
2022-05-02 09:35:28 +02:00
Assert . Null ( vm . Total ) ;
2022-01-14 09:50:29 +01:00
Assert . True ( vm . Items . Count = = 1 ) ;
2020-07-31 03:52:33 +02:00
2022-01-14 09:50:29 +01:00
var fn = vm . Items . First ( ) ;
var now = DateTimeOffset . UtcNow ;
Assert . True ( fn . Created > = now . AddSeconds ( - 3 ) ) ;
Assert . True ( fn . Created < = now ) ;
Assert . Equal ( $"New version {newVersion} released!" , fn . Body ) ;
Assert . Equal ( $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}" , fn . ActionLink ) ;
Assert . False ( fn . Seen ) ;
2020-07-31 03:52:33 +02:00
}
2021-03-02 03:11:58 +01:00
2023-04-17 03:34:41 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanFixMappedDomainAppType ( )
{
using var tester = CreateServerTester ( newDb : true ) ;
await tester . StartAsync ( ) ;
var f = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) ;
2024-01-31 06:45:54 +01:00
const string id = "BTCPayServer.Services.PoliciesSettings" ;
2023-04-17 03:34:41 +02:00
using ( var ctx = f . CreateContext ( ) )
{
2024-01-31 06:45:54 +01:00
// remove existing policies setting
var setting = await ctx . Settings . FirstOrDefaultAsync ( c = > c . Id = = id ) ;
if ( setting ! = null ) ctx . Settings . Remove ( setting ) ;
// create legacy policies setting that needs migration
2024-02-21 14:43:44 +01:00
setting = new SettingData { Id = id , Value = JObject . Parse ( "{\"RootAppId\": null, \"RootAppType\": 1, \"Experimental\": false, \"PluginSource\": null, \"LockSubscription\": false, \"DisableSSHService\": false, \"PluginPreReleases\": false, \"BlockExplorerLinks\": [],\"DomainToAppMapping\": [{\"AppId\": \"87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe\", \"Domain\": \"donate.nicolas-dorier.com\", \"AppType\": 0}], \"CheckForNewVersions\": false, \"AllowHotWalletForAll\": false, \"RequiresConfirmedEmail\": false, \"DiscourageSearchEngines\": false, \"DisableNonAdminCreateUserApi\": false, \"AllowHotWalletRPCImportForAll\": false, \"AllowLightningInternalNodeForAll\": false, \"DisableStoresToUseServerEmailSettings\": false}" ) . ToString ( ) } ;
2023-04-17 03:34:41 +02:00
ctx . Settings . Add ( setting ) ;
await ctx . SaveChangesAsync ( ) ;
}
await RestartMigration ( tester ) ;
using ( var ctx = f . CreateContext ( ) )
{
2024-01-31 06:45:54 +01:00
var setting = await ctx . Settings . FirstOrDefaultAsync ( c = > c . Id = = id ) ;
2023-04-17 03:34:41 +02:00
var o = JObject . Parse ( setting . Value ) ;
Assert . Equal ( "Crowdfund" , o [ "RootAppType" ] . Value < string > ( ) ) ;
o = ( JObject ) ( ( JArray ) o [ "DomainToAppMapping" ] ) [ 0 ] ;
Assert . Equal ( "PointOfSale" , o [ "AppType" ] . Value < string > ( ) ) ;
}
}
2024-05-09 02:18:02 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanMigrateFileIds ( )
{
using var tester = CreateServerTester ( newDb : true ) ;
tester . DeleteStore = false ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
using ( var ctx = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) . CreateContext ( ) )
{
var storeConfig = "" "
{
"spread" : 0.0 ,
"cssFileId" : "2a51c49a-9d54-4013-80a2-3f6e69d08523" ,
"logoFileId" : "8f890691-87f9-4c65-80e5-3b7ffaa3551f" ,
"soundFileId" : "62bc4757-b92b-4a3b-a8ab-0e9b693d6a29" ,
"networkFeeMode" : "MultiplePaymentsOnly" ,
"defaultCurrency" : "USD" ,
"showStoreHeader" : true ,
"celebratePayment" : true ,
"paymentTolerance" : 0.0 ,
"invoiceExpiration" : 15 ,
"preferredExchange" : "kraken" ,
"showRecommendedFee" : true ,
"monitoringExpiration" : 1440 ,
"showPayInWalletButton" : true ,
"displayExpirationTimer" : 5 ,
"excludedPaymentMethods" : null ,
"recommendedFeeBlockTarget" : 1
}
"" ";
var serverConfig = "" "
{
"CssUri" : null ,
"FirstRun" : false ,
"LogoFileId" : "ce71d90a-dd90-40a3-b1f0-96d00c9abb52" ,
"CustomTheme" : true ,
"CustomThemeCssUri" : null ,
"CustomThemeFileId" : "9b00f4ed-914b-437b-abd2-9a90c1b22c34" ,
"CustomThemeExtension" : 0
}
"" ";
await ctx . Database . GetDbConnection ( ) . ExecuteAsync ( "" "
UPDATE "Stores" SET "StoreBlob" = @storeConfig : : JSONB WHERE "Id" = @storeId ;
"" ", new { storeId = user.StoreId, storeConfig });
await ctx . Database . GetDbConnection ( ) . ExecuteAsync ( "" "
UPDATE "Settings" SET "Value" = @serverConfig : : JSONB WHERE "Id" = ' BTCPayServer . Services . ThemeSettings ' ;
"" ", new { serverConfig });
await ctx . Database . GetDbConnection ( ) . ExecuteAsync ( "" "
INSERT INTO "Files" VALUES ( @id , @fileName , @id | | '-' | | @fileName , NOW ( ) , @userId ) ;
"" ",
new [ ]
{
new { id = "2a51c49a-9d54-4013-80a2-3f6e69d08523" , fileName = "store.css" , userId = user . UserId } ,
new { id = "8f890691-87f9-4c65-80e5-3b7ffaa3551f" , fileName = "store.png" , userId = user . UserId } ,
new { id = "ce71d90a-dd90-40a3-b1f0-96d00c9abb52" , fileName = "admin.png" , userId = user . UserId } ,
new { id = "9b00f4ed-914b-437b-abd2-9a90c1b22c34" , fileName = "admin.css" , userId = user . UserId } ,
new { id = "62bc4757-b92b-4a3b-a8ab-0e9b693d6a29" , fileName = "store.mp3" , userId = user . UserId } ,
} ) ;
await ctx . Database . GetDbConnection ( ) . ExecuteAsync ( "" "
DELETE FROM "__EFMigrationsHistory" WHERE "MigrationId" = ' 20240508015052_f ileid '
"" ");
await ctx . Database . MigrateAsync ( ) ;
( ( MemoryCache ) tester . PayTester . GetService < IMemoryCache > ( ) ) . Clear ( ) ;
}
var controller = tester . PayTester . GetController < UIStoresController > ( user . UserId , user . StoreId ) ;
2024-09-30 12:13:51 +02:00
var vm = await controller . GeneralSettings ( user . StoreId ) . AssertViewModelAsync < GeneralSettingsViewModel > ( ) ;
2024-05-09 02:18:02 +02:00
Assert . Equal ( tester . PayTester . ServerUriWithIP + "LocalStorage/8f890691-87f9-4c65-80e5-3b7ffaa3551f-store.png" , vm . LogoUrl ) ;
Assert . Equal ( tester . PayTester . ServerUriWithIP + "LocalStorage/2a51c49a-9d54-4013-80a2-3f6e69d08523-store.css" , vm . CssUrl ) ;
var vm2 = await controller . CheckoutAppearance ( ) . AssertViewModelAsync < CheckoutAppearanceViewModel > ( ) ;
Assert . Equal ( tester . PayTester . ServerUriWithIP + "LocalStorage/62bc4757-b92b-4a3b-a8ab-0e9b693d6a29-store.mp3" , vm2 . PaymentSoundUrl ) ;
var serverController = tester . PayTester . GetController < UIServerController > ( ) ;
var branding = await serverController . Branding ( ) . AssertViewModelAsync < BrandingViewModel > ( ) ;
Assert . Equal ( tester . PayTester . ServerUriWithIP + "LocalStorage/ce71d90a-dd90-40a3-b1f0-96d00c9abb52-admin.png" , branding . LogoUrl ) ;
Assert . Equal ( tester . PayTester . ServerUriWithIP + "LocalStorage/9b00f4ed-914b-437b-abd2-9a90c1b22c34-admin.css" , branding . CustomThemeCssUrl ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2021-03-02 03:11:58 +01:00
[Trait("Integration", "Integration")]
public async Task CanDoLightningInternalNodeMigration ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( newDb : true ) ;
tester . ActivateLightning ( LightningConnectionType . CLightning ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
await acc . GrantAccessAsync ( true ) ;
await acc . CreateStoreAsync ( ) ;
// Test if legacy DerivationStrategy column is converted to DerivationStrategies
var store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
var xpub = "tpubDDmH1briYfZcTDMEc7uMEA5hinzjUTzR9yMC1drxTMeiWyw1VyCqTuzBke6df2sqbfw9QG6wbgTLF5yLjcXsZNaXvJMZLwNEwyvmiFWcLav" ;
var derivation = $"{xpub}-[legacy]" ;
store . DerivationStrategy = derivation ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
Assert . True ( string . IsNullOrEmpty ( store . DerivationStrategy ) ) ;
2024-04-04 09:31:04 +02:00
var handlers = tester . PayTester . GetService < PaymentMethodHandlerDictionary > ( ) ;
var pmi = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) ;
var v = store . GetPaymentMethodConfig < DerivationSchemeSettings > ( pmi , handlers ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( derivation , v . AccountDerivation . ToString ( ) ) ;
Assert . Equal ( derivation , v . AccountOriginal . ToString ( ) ) ;
Assert . Equal ( xpub , v . SigningKey . ToString ( ) ) ;
Assert . Equal ( xpub , v . GetSigningAccountKeySettings ( ) . AccountKey . ToString ( ) ) ;
await acc . RegisterLightningNodeAsync ( "BTC" , LightningConnectionType . CLightning , true ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2024-04-04 09:31:04 +02:00
pmi = PaymentTypes . LN . GetPaymentMethodId ( "BTC" ) ;
var lnMethod = store . GetPaymentMethodConfig < LightningPaymentMethodConfig > ( pmi , handlers ) ;
2022-01-14 09:50:29 +01:00
Assert . NotNull ( lnMethod . GetExternalLightningUrl ( ) ) ;
2024-04-04 09:31:04 +02:00
var conf = store . GetPaymentMethodConfig ( pmi ) ;
conf [ "LightningConnectionString" ] = conf [ "connectionString" ] . Value < string > ( ) ;
conf [ "DisableBOLT11PaymentOption" ] = true ;
( ( JObject ) conf ) . Remove ( "connectionString" ) ;
store . SetPaymentMethodConfig ( pmi , conf ) ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
2022-01-14 09:50:29 +01:00
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2024-04-04 09:31:04 +02:00
lnMethod = store . GetPaymentMethodConfig < LightningPaymentMethodConfig > ( pmi , handlers ) ;
2022-01-14 09:50:29 +01:00
Assert . Null ( lnMethod . GetExternalLightningUrl ( ) ) ;
2024-04-04 09:31:04 +02:00
Assert . True ( lnMethod . IsInternalNode ) ;
conf = store . GetPaymentMethodConfig ( pmi ) ;
Assert . Null ( conf [ "CryptoCode" ] ) ; // Osolete
Assert . Null ( conf [ "connectionString" ] ) ; // Null, so should be stripped
Assert . Null ( conf [ "DisableBOLT11PaymentOption" ] ) ; // Old garbage cleaned
2022-01-14 09:50:29 +01:00
// Test if legacy lightning charge settings are converted to LightningConnectionString
store . DerivationStrategies = new JObject ( )
2021-03-02 03:11:58 +01:00
{
new JProperty ( "BTC_LightningLike" , new JObject ( )
{
new JProperty ( "LightningChargeUrl" , "http://mycharge.com/" ) ,
new JProperty ( "Username" , "usr" ) ,
new JProperty ( "Password" , "pass" ) ,
new JProperty ( "CryptoCode" , "BTC" ) ,
new JProperty ( "PaymentId" , "someshit" ) ,
} )
} . ToString ( ) ;
2022-01-14 09:50:29 +01:00
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2024-04-04 09:31:04 +02:00
lnMethod = store . GetPaymentMethodConfig < LightningPaymentMethodConfig > ( pmi , handlers ) ;
2022-01-14 09:50:29 +01:00
Assert . NotNull ( lnMethod . GetExternalLightningUrl ( ) ) ;
2021-03-02 03:11:58 +01:00
2022-01-14 09:50:29 +01:00
var url = lnMethod . GetExternalLightningUrl ( ) ;
2024-01-18 01:47:39 +01:00
LightningConnectionStringHelper . ExtractValues ( url , out var connType ) ;
2023-12-20 10:41:28 +01:00
Assert . Equal ( LightningConnectionType . Charge , connType ) ;
2023-11-21 10:55:02 +01:00
var client = Assert . IsType < ChargeClient > ( tester . PayTester . GetService < LightningClientFactoryService > ( )
. Create ( url , tester . NetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ) ;
var auth = Assert . IsType < ChargeAuthentication . UserPasswordAuthentication > ( client . ChargeAuthentication ) ;
2023-12-20 10:41:28 +01:00
2023-11-21 10:55:02 +01:00
Assert . Equal ( "pass" , auth . NetworkCredential . Password ) ;
Assert . Equal ( "usr" , auth . NetworkCredential . UserName ) ;
2021-03-02 03:11:58 +01:00
2022-01-14 09:50:29 +01:00
// Test if lightning connection strings get migrated to internal
store . DerivationStrategies = new JObject ( )
2021-03-02 03:11:58 +01:00
{
new JProperty ( "BTC_LightningLike" , new JObject ( )
{
new JProperty ( "CryptoCode" , "BTC" ) ,
new JProperty ( "LightningConnectionString" , tester . PayTester . IntegratedLightning ) ,
} )
} . ToString ( ) ;
2022-01-14 09:50:29 +01:00
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2024-04-04 09:31:04 +02:00
lnMethod = store . GetPaymentMethodConfig < LightningPaymentMethodConfig > ( pmi , handlers ) ;
2022-01-14 09:50:29 +01:00
Assert . True ( lnMethod . IsInternalNode ) ;
2024-04-04 09:31:04 +02:00
store . SetPaymentMethodConfig ( PaymentMethodId . Parse ( "BTC-LNURL" ) ,
new JObject ( )
{
["CryptoCode"] = "BTC" ,
["LUD12Enabled"] = true ,
["UseBech32Scheme"] = false ,
} ) ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
conf = store . GetPaymentMethodConfig ( PaymentMethodId . Parse ( "BTC-LNURL" ) ) ;
Assert . Null ( conf [ "CryptoCode" ] ) ;
Assert . True ( conf [ "lud12Enabled" ] . Value < bool > ( ) ) ;
Assert . Null ( conf [ "useBech32Scheme" ] ) ; // default stripped
2021-03-02 03:11:58 +01:00
}
2022-10-11 10:34:29 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
[Obsolete]
public async Task CanDoLabelMigrations ( )
{
using var tester = CreateServerTester ( newDb : true ) ;
await tester . StartAsync ( ) ;
var dbf = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) ;
int walletCount = 1000 ;
var wallet = "walletttttttttttttttttttttttttttt" ;
using ( var db = dbf . CreateContext ( ) )
{
for ( int i = 0 ; i < walletCount ; i + + )
{
var walletData = new WalletData ( ) { Id = $"S-{wallet}{i}-BTC" } ;
walletData . Blob = ZipUtils . Zip ( "{\"LabelColors\": { \"label1\" : \"black\", \"payout\":\"green\" }}" ) ;
db . Wallets . Add ( walletData ) ;
}
await db . SaveChangesAsync ( ) ;
}
uint256 firstTxId = null ;
using ( var db = dbf . CreateContext ( ) )
{
int transactionCount = 10_000 ;
for ( int i = 0 ; i < transactionCount ; i + + )
{
var txId = RandomUtils . GetUInt256 ( ) ;
var wt = new WalletTransactionData ( )
{
WalletDataId = $"S-{wallet}{i % walletCount}-BTC" ,
TransactionId = txId . ToString ( ) ,
} ;
firstTxId ? ? = txId ;
if ( i ! = 10 )
wt . Blob = ZipUtils . Zip ( "{\"Comment\":\"test\"}" ) ;
if ( i % 1240 ! = 0 )
{
wt . Labels = "[{\"type\":\"raw\", \"text\":\"label1\"}]" ;
}
else if ( i = = 0 )
{
wt . Labels = "[{\"type\":\"raw\", \"text\":\"label1\"},{\"type\":\"raw\", \"text\":\"labelo" + i + "\"}, " +
"{\"type\":\"payout\", \"text\":\"payout\", \"pullPaymentPayouts\":{\"pp1\":[\"p1\",\"p2\"],\"pp2\":[\"p3\"]}}]" ;
}
else
{
wt . Labels = "[{\"type\":\"raw\", \"text\":\"label1\"},{\"type\":\"raw\", \"text\":\"labelo" + i + "\"}]" ;
}
db . WalletTransactions . Add ( wt ) ;
}
await db . SaveChangesAsync ( ) ;
}
await RestartMigration ( tester ) ;
var migrator = tester . PayTester . GetService < IEnumerable < IHostedService > > ( ) . OfType < DbMigrationsHostedService > ( ) . First ( ) ;
await migrator . MigratedTransactionLabels ( 0 ) ;
var walletRepo = tester . PayTester . GetService < WalletRepository > ( ) ;
var wi1 = await walletRepo . GetWalletLabels ( new WalletId ( $"{wallet}0" , "BTC" ) ) ;
Assert . Equal ( 3 , wi1 . Length ) ;
Assert . Contains ( wi1 , o = > o . Label = = "label1" & & o . Color = = "black" ) ;
Assert . Contains ( wi1 , o = > o . Label = = "labelo0" & & o . Color = = "#000" ) ;
Assert . Contains ( wi1 , o = > o . Label = = "payout" & & o . Color = = "green" ) ;
var txInfo = await walletRepo . GetWalletTransactionsInfo ( new WalletId ( $"{wallet}0" , "BTC" ) , new [ ] { firstTxId . ToString ( ) } ) ;
Assert . Equal ( "test" , txInfo . Values . First ( ) . Comment ) ;
// Should have the 2 raw labels, and one legacy label for payouts
Assert . Equal ( 3 , txInfo . Values . First ( ) . LegacyLabels . Count ) ;
var payoutLabel = txInfo . Values . First ( ) . LegacyLabels . Select ( l = > l . Value ) . OfType < PayoutLabel > ( ) . First ( ) ;
Assert . Equal ( 2 , payoutLabel . PullPaymentPayouts . Count ) ;
Assert . Equal ( 2 , payoutLabel . PullPaymentPayouts [ "pp1" ] . Count ) ;
Assert . Single ( payoutLabel . PullPaymentPayouts [ "pp2" ] ) ;
}
2023-01-30 01:46:12 +01:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanDoRateSourceMigration ( )
{
using var tester = CreateServerTester ( newDb : true ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
await acc . CreateStoreAsync ( ) ;
var db = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) ;
using var ctx = db . CreateContext ( ) ;
var store = ( await ctx . Stores . AsNoTracking ( ) . ToListAsync ( ) ) [ 0 ] ;
var b = store . GetStoreBlob ( ) ;
b . PreferredExchange = "coinaverage" ;
store . SetStoreBlob ( b ) ;
await ctx . SaveChangesAsync ( ) ;
await ctx . Database . ExecuteSqlRawAsync ( "DELETE FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\"='20230123062447_migrateoldratesource'" ) ;
await ctx . Database . MigrateAsync ( ) ;
store = ( await ctx . Stores . AsNoTracking ( ) . ToListAsync ( ) ) [ 0 ] ;
b = store . GetStoreBlob ( ) ;
Assert . Equal ( "coingecko" , b . PreferredExchange ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-12-29 09:58:35 +01:00
[Trait("Integration", "Integration")]
public async Task CanDoInvoiceMigrations ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( newDb : true ) ;
await tester . StartAsync ( ) ;
2020-12-29 09:58:35 +01:00
2022-01-14 09:50:29 +01:00
var acc = tester . NewAccount ( ) ;
await acc . GrantAccessAsync ( true ) ;
await acc . CreateStoreAsync ( ) ;
await acc . RegisterDerivationSchemeAsync ( "BTC" ) ;
var store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2021-03-02 03:11:58 +01:00
2022-01-14 09:50:29 +01:00
var blob = store . GetStoreBlob ( ) ;
var serializer = new Serializer ( null ) ;
2020-12-29 09:58:35 +01:00
2022-01-14 09:50:29 +01:00
blob . AdditionalData = new Dictionary < string , JToken > ( ) ;
blob . AdditionalData . Add ( "rateRules" , JToken . Parse (
serializer . ToString ( new List < MigrationStartupTask . RateRule_Obsolete > ( )
{
2020-12-29 09:58:35 +01:00
new MigrationStartupTask . RateRule_Obsolete ( )
{
Multiplier = 2
}
2022-01-14 09:50:29 +01:00
} ) ) ) ;
2020-12-29 09:58:35 +01:00
2022-01-14 09:50:29 +01:00
blob . AdditionalData . Add ( "onChainMinValue" , JToken . Parse (
serializer . ToString ( new CurrencyValue ( )
{
Currency = "USD" ,
Value = 5 m
} . ToString ( ) ) ) ) ;
blob . AdditionalData . Add ( "lightningMaxValue" , JToken . Parse (
serializer . ToString ( new CurrencyValue ( )
{
Currency = "USD" ,
Value = 5 m
} . ToString ( ) ) ) ) ;
store . SetStoreBlob ( blob ) ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
blob = store . GetStoreBlob ( ) ;
Assert . Empty ( blob . AdditionalData ) ;
Assert . Single ( blob . PaymentMethodCriteria ) ;
Assert . Contains ( blob . PaymentMethodCriteria ,
2024-04-04 09:31:04 +02:00
criteria = > criteria . PaymentMethod = = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) & &
2022-01-14 09:50:29 +01:00
criteria . Above & & criteria . Value . Value = = 5 m & & criteria . Value . Currency = = "USD" ) ;
2024-04-04 09:31:04 +02:00
var handlers = tester . PayTester . GetService < PaymentMethodHandlerDictionary > ( ) ;
await acc . ImportOldInvoices ( ) ;
var dbContext = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) . CreateContext ( ) ;
var invoiceMigrator = tester . PayTester . GetService < InvoiceBlobMigratorHostedService > ( ) ;
invoiceMigrator . BatchSize = 2 ;
await invoiceMigrator . ResetMigration ( ) ;
await invoiceMigrator . StartAsync ( default ) ;
tester . DeleteStore = false ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
var invoices = await dbContext . Invoices . AsNoTracking ( ) . ToListAsync ( ) ;
foreach ( var invoice in invoices )
{
Assert . NotNull ( invoice . Currency ) ;
Assert . NotNull ( invoice . Amount ) ;
Assert . NotNull ( invoice . Blob2 ) ;
}
Assert . True ( await invoiceMigrator . IsComplete ( ) ) ;
} ) ;
2024-10-16 12:05:07 +02:00
var invoiceRepo = tester . PayTester . GetService < InvoiceRepository > ( ) ;
var invoice = await invoiceRepo . GetInvoice ( "Q7RqoHLngK9svM4MgRyi9y" ) ;
var p = invoice . Payments . First ( p = > p . Id = = "26c879f3d27a894a62f8730c84205ac9dec38b7bbc0a11ccc0c196d1259b25aa-1" ) ;
var details = p . GetDetails < BitcoinLikePaymentData > ( handlers . GetBitcoinHandler ( "BTC" ) ) ;
Assert . Equal ( "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" , details . AssetId . ToString ( ) ) ;
2020-12-29 09:58:35 +01:00
}
2021-03-02 03:11:58 +01:00
private static async Task RestartMigration ( ServerTester tester )
{
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
await settings . UpdateSetting < MigrationSettings > ( new MigrationSettings ( ) ) ;
2024-07-24 13:16:20 +02:00
await tester . PayTester . RestartStartupTask < MigrationStartupTask > ( ) ;
2021-03-02 03:11:58 +01:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-12-08 08:12:29 +01:00
[Trait("Integration", "Integration")]
public async Task EmailSenderTests ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( newDb : true ) ;
await tester . StartAsync ( ) ;
2020-12-08 08:12:29 +01:00
2022-01-14 09:50:29 +01:00
var acc = tester . NewAccount ( ) ;
2024-03-19 14:58:33 +01:00
await acc . GrantAccessAsync ( true ) ;
2021-12-31 08:59:02 +01:00
2022-01-14 09:50:29 +01:00
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
var emailSenderFactory = tester . PayTester . GetService < EmailSenderFactory > ( ) ;
2020-12-08 08:12:29 +01:00
2022-01-14 09:50:29 +01:00
Assert . Null ( await Assert . IsType < ServerEmailSender > ( await emailSenderFactory . GetEmailSender ( ) ) . GetEmailSettings ( ) ) ;
Assert . Null ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) ;
2021-12-31 08:59:02 +01:00
2020-12-08 08:12:29 +01:00
2022-01-14 09:50:29 +01:00
await settings . UpdateSetting ( new PoliciesSettings ( ) { DisableStoresToUseServerEmailSettings = false } ) ;
await settings . UpdateSetting ( new EmailSettings ( )
{
From = "admin@admin.com" ,
Login = "admin@admin.com" ,
Password = "admin@admin.com" ,
Port = 1234 ,
Server = "admin.com" ,
} ) ;
Assert . Equal ( "admin@admin.com" , ( await Assert . IsType < ServerEmailSender > ( await emailSenderFactory . GetEmailSender ( ) ) . GetEmailSettings ( ) ) . Login ) ;
Assert . Equal ( "admin@admin.com" , ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) . Login ) ;
2021-12-31 08:59:02 +01:00
2022-01-14 09:50:29 +01:00
await settings . UpdateSetting ( new PoliciesSettings ( ) { DisableStoresToUseServerEmailSettings = true } ) ;
Assert . Equal ( "admin@admin.com" , ( await Assert . IsType < ServerEmailSender > ( await emailSenderFactory . GetEmailSender ( ) ) . GetEmailSettings ( ) ) . Login ) ;
Assert . Null ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) ;
2020-12-08 08:12:29 +01:00
2024-03-19 14:58:33 +01:00
Assert . IsType < RedirectToActionResult > ( await acc . GetController < UIStoresController > ( ) . StoreEmailSettings ( acc . StoreId , new EmailsViewModel ( new EmailSettings
2022-01-14 09:50:29 +01:00
{
From = "store@store.com" ,
Login = "store@store.com" ,
Password = "store@store.com" ,
Port = 1234 ,
Server = "store.com"
2024-07-09 15:51:16 +02:00
} ) , "" ) ) ;
2022-01-14 09:50:29 +01:00
Assert . Equal ( "store@store.com" , ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) . Login ) ;
2020-12-08 08:12:29 +01:00
}
2021-11-23 04:53:05 +01:00
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanConfigureStorage ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
var controller = tester . PayTester . GetController < UIServerController > ( user . UserId , user . StoreId ) ;
2021-11-23 04:53:05 +01:00
2022-01-14 09:50:29 +01:00
//Once we select a provider, redirect to its view
var localResult = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . FileSystem
} ) ) ;
Assert . Equal ( nameof ( UIServerController . StorageProvider ) , localResult . ActionName ) ;
Assert . Equal ( StorageProvider . FileSystem . ToString ( ) , localResult . RouteValues [ "provider" ] ) ;
2021-11-23 04:53:05 +01:00
2022-01-14 09:50:29 +01:00
var AmazonS3result = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . AmazonS3
} ) ) ;
Assert . Equal ( nameof ( UIServerController . StorageProvider ) , AmazonS3result . ActionName ) ;
Assert . Equal ( StorageProvider . AmazonS3 . ToString ( ) , AmazonS3result . RouteValues [ "provider" ] ) ;
2021-11-23 04:53:05 +01:00
2022-01-14 09:50:29 +01:00
var GoogleResult = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . GoogleCloudStorage
} ) ) ;
Assert . Equal ( nameof ( UIServerController . StorageProvider ) , GoogleResult . ActionName ) ;
Assert . Equal ( StorageProvider . GoogleCloudStorage . ToString ( ) , GoogleResult . RouteValues [ "provider" ] ) ;
2021-11-23 04:53:05 +01:00
2022-01-14 09:50:29 +01:00
var AzureResult = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . AzureBlobStorage
} ) ) ;
Assert . Equal ( nameof ( UIServerController . StorageProvider ) , AzureResult . ActionName ) ;
Assert . Equal ( StorageProvider . AzureBlobStorage . ToString ( ) , AzureResult . RouteValues [ "provider" ] ) ;
//Cool, we get redirected to the config pages
//Let's configure this stuff
//Let's try and cheat and go to an invalid storage provider config
Assert . Equal ( nameof ( Storage ) , ( Assert
. IsType < RedirectToActionResult > ( await controller . StorageProvider ( "I am not a real provider" ) )
. ActionName ) ) ;
2023-01-21 20:51:36 +01:00
//ok no more messing around, let's configure this shit.
2022-01-14 09:50:29 +01:00
var fileSystemStorageConfiguration = Assert . IsType < FileSystemStorageConfiguration > ( Assert
. IsType < ViewResult > ( await controller . StorageProvider ( StorageProvider . FileSystem . ToString ( ) ) )
. Model ) ;
//local file system does not need config, easy days!
Assert . IsType < ViewResult > (
await controller . EditFileSystemStorageProvider ( fileSystemStorageConfiguration ) ) ;
//ok cool, let's see if this got set right
var shouldBeRedirectingToLocalStorageConfigPage =
Assert . IsType < RedirectToActionResult > ( await controller . Storage ( ) ) ;
Assert . Equal ( nameof ( StorageProvider ) , shouldBeRedirectingToLocalStorageConfigPage . ActionName ) ;
Assert . Equal ( StorageProvider . FileSystem ,
shouldBeRedirectingToLocalStorageConfigPage . RouteValues [ "provider" ] ) ;
//if we tell the settings page to force, it should allow us to select a new provider
Assert . IsType < ChooseStorageViewModel > ( Assert . IsType < ViewResult > ( await controller . Storage ( true ) ) . Model ) ;
//awesome, now let's see if the files result says we're all set up
var viewFilesViewModel =
Assert . IsType < ViewFilesViewModel > ( Assert . IsType < ViewResult > ( await controller . Files ( ) ) . Model ) ;
Assert . True ( viewFilesViewModel . StorageConfigured ) ;
Assert . Empty ( viewFilesViewModel . Files ) ;
2021-11-23 04:53:05 +01:00
}
[Fact]
[Trait("Integration", "Integration")]
public async void CanUseLocalProviderFiles ( )
{
2022-01-14 09:50:29 +01:00
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
2023-08-05 10:44:59 +02:00
await user . GrantAccessAsync ( ) ;
2022-01-14 09:50:29 +01:00
var controller = tester . PayTester . GetController < UIServerController > ( user . UserId , user . StoreId ) ;
2021-11-23 04:53:05 +01:00
2022-01-14 09:50:29 +01:00
var fileSystemStorageConfiguration = Assert . IsType < FileSystemStorageConfiguration > ( Assert
. IsType < ViewResult > ( await controller . StorageProvider ( StorageProvider . FileSystem . ToString ( ) ) )
. Model ) ;
Assert . IsType < ViewResult > (
await controller . EditFileSystemStorageProvider ( fileSystemStorageConfiguration ) ) ;
2021-11-23 04:53:05 +01:00
2022-01-14 09:50:29 +01:00
var shouldBeRedirectingToLocalStorageConfigPage =
Assert . IsType < RedirectToActionResult > ( await controller . Storage ( ) ) ;
Assert . Equal ( nameof ( StorageProvider ) , shouldBeRedirectingToLocalStorageConfigPage . ActionName ) ;
Assert . Equal ( StorageProvider . FileSystem ,
shouldBeRedirectingToLocalStorageConfigPage . RouteValues [ "provider" ] ) ;
2021-11-23 04:53:05 +01:00
2024-05-09 02:18:02 +02:00
var fileId = await CanUploadFile ( controller ) ;
await CanRemoveFile ( controller , fileId ) ;
2021-11-23 04:53:05 +01:00
}
2024-05-09 02:18:02 +02:00
internal static async Task < string > CanUploadFile ( UIServerController controller )
2021-11-23 04:53:05 +01:00
{
var fileContent = "content" ;
2024-05-09 02:18:02 +02:00
var fileList = new List < IFormFile > { TestUtils . GetFormFile ( "uploadtestfile1.txt" , fileContent ) } ;
2021-11-23 04:53:05 +01:00
var uploadFormFileResult = Assert . IsType < RedirectToActionResult > ( await controller . CreateFiles ( fileList ) ) ;
Assert . True ( uploadFormFileResult . RouteValues . ContainsKey ( "fileIds" ) ) ;
string [ ] uploadFileList = ( string [ ] ) uploadFormFileResult . RouteValues [ "fileIds" ] ;
var fileId = uploadFileList [ 0 ] ;
Assert . Equal ( "Files" , uploadFormFileResult . ActionName ) ;
//check if file was uploaded and saved in db
var viewFilesViewModel =
Assert . IsType < ViewFilesViewModel > ( Assert . IsType < ViewResult > ( await controller . Files ( new string [ ] { fileId } ) ) . Model ) ;
Assert . NotEmpty ( viewFilesViewModel . Files ) ;
Assert . True ( viewFilesViewModel . DirectUrlByFiles . ContainsKey ( fileId ) ) ;
Assert . NotEmpty ( viewFilesViewModel . DirectUrlByFiles [ fileId ] ) ;
//verify file is available and the same
2021-12-27 05:46:31 +01:00
using var net = new HttpClient ( ) ;
var data = await net . GetStringAsync ( new Uri ( viewFilesViewModel . DirectUrlByFiles [ fileId ] ) ) ;
2021-11-23 04:53:05 +01:00
Assert . Equal ( fileContent , data ) ;
//create a temporary link to file
2024-01-18 01:47:39 +01:00
Assert . IsType < RedirectToActionResult > ( await controller . CreateTemporaryFileUrl ( fileId ,
2023-08-05 10:44:59 +02:00
new UIServerController . CreateTemporaryFileUrlViewModel
2021-11-23 04:53:05 +01:00
{
IsDownload = true ,
TimeAmount = 1 ,
2022-01-07 04:32:00 +01:00
TimeType = UIServerController . CreateTemporaryFileUrlViewModel . TmpFileTimeType . Minutes
2021-11-23 04:53:05 +01:00
} ) ) ;
var statusMessageModel = controller . TempData . GetStatusMessageModel ( ) ;
Assert . NotNull ( statusMessageModel ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Success , statusMessageModel . Severity ) ;
var index = statusMessageModel . Html . IndexOf ( "target='_blank'>" ) ;
var url = statusMessageModel . Html . Substring ( index )
. Replace ( "</a>" , string . Empty )
. Replace ( "target='_blank'>" , string . Empty ) ;
//verify tmpfile is available and the same
2021-12-27 05:46:31 +01:00
data = await net . GetStringAsync ( new Uri ( url ) ) ;
2021-11-23 04:53:05 +01:00
Assert . Equal ( fileContent , data ) ;
2024-05-09 02:18:02 +02:00
return fileId ;
}
2021-11-23 04:53:05 +01:00
2024-05-09 02:18:02 +02:00
internal static async Task CanRemoveFile ( UIServerController controller , string fileId )
{
2021-11-23 04:53:05 +01:00
//delete file
Assert . IsType < RedirectToActionResult > ( await controller . DeleteFile ( fileId ) ) ;
2024-05-09 02:18:02 +02:00
var statusMessageModel = controller . TempData . GetStatusMessageModel ( ) ;
2021-11-23 04:53:05 +01:00
Assert . NotNull ( statusMessageModel ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Success , statusMessageModel . Severity ) ;
//attempt to fetch deleted file
2024-05-09 02:18:02 +02:00
var viewFilesViewModel =
Assert . IsType < ViewFilesViewModel > ( Assert . IsType < ViewResult > ( await controller . Files ( [ fileId ] ) ) . Model ) ;
2021-11-23 04:53:05 +01:00
Assert . Null ( viewFilesViewModel . DirectUrlByFiles ) ;
}
2023-07-24 02:24:32 +02:00
[Fact]
2024-05-06 11:44:16 +02:00
[Trait("Integration", "Integration")]
2023-07-24 02:24:32 +02:00
public async Task CanCreateReports ( )
{
2024-03-05 08:10:54 +01:00
using var tester = CreateServerTester ( newDb : true ) ;
2023-07-24 02:24:32 +02:00
tester . ActivateLightning ( ) ;
tester . DeleteStore = false ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var acc = tester . NewAccount ( ) ;
await acc . GrantAccessAsync ( ) ;
await acc . MakeAdmin ( ) ;
acc . RegisterDerivationScheme ( "BTC" , importKeysToNBX : true ) ;
acc . RegisterLightningNode ( "BTC" ) ;
await acc . ReceiveUTXO ( Money . Coins ( 1.0 m ) ) ;
var client = await acc . CreateClient ( ) ;
var posController = acc . GetController < UIPointOfSaleController > ( ) ;
2024-06-26 10:42:22 +02:00
var app = await client . CreatePointOfSaleApp ( acc . StoreId , new PointOfSaleAppRequest
2023-07-24 02:24:32 +02:00
{
AppName = "Static" ,
2024-06-26 10:42:22 +02:00
DefaultView = PosViewType . Static ,
2023-07-24 02:24:32 +02:00
Template = new PointOfSaleSettings ( ) . Template
} ) ;
var resp = await posController . ViewPointOfSale ( app . Id , choiceKey : "green-tea" ) ;
var invoiceId = GetInvoiceId ( resp ) ;
await acc . PayOnChain ( invoiceId ) ;
2024-09-20 11:54:36 +02:00
// Quick unrelated test on GetMonitoredInvoices
var invoiceRepo = tester . PayTester . GetService < InvoiceRepository > ( ) ;
var monitored = Assert . Single ( await invoiceRepo . GetMonitoredInvoices ( PaymentMethodId . Parse ( "BTC-CHAIN" ) ) , i = > i . Id = = invoiceId ) ;
Assert . Single ( monitored . Payments ) ;
2024-09-24 01:43:30 +02:00
monitored = Assert . Single ( await invoiceRepo . GetMonitoredInvoices ( PaymentMethodId . Parse ( "BTC-CHAIN" ) , true ) , i = > i . Id = = invoiceId ) ;
Assert . Single ( monitored . Payments ) ;
//
2024-09-20 11:54:36 +02:00
2024-09-24 01:43:30 +02:00
app = await client . CreatePointOfSaleApp ( acc . StoreId , new PointOfSaleAppRequest
2023-07-24 02:24:32 +02:00
{
AppName = "Cart" ,
2024-06-26 10:42:22 +02:00
DefaultView = PosViewType . Cart ,
2023-07-24 02:24:32 +02:00
Template = new PointOfSaleSettings ( ) . Template
} ) ;
resp = await posController . ViewPointOfSale ( app . Id , posData : new JObject ( )
{
["cart"] = new JArray ( )
{
new JObject ( )
{
["id"] = "green-tea" ,
["count"] = 2
} ,
new JObject ( )
{
["id"] = "black-tea" ,
["count"] = 1
} ,
}
} . ToString ( ) ) ;
invoiceId = GetInvoiceId ( resp ) ;
await acc . PayOnBOLT11 ( invoiceId ) ;
resp = await posController . ViewPointOfSale ( app . Id , posData : new JObject ( )
{
["cart"] = new JArray ( )
{
new JObject ( )
{
["id"] = "green-tea" ,
["count"] = 5
}
}
} . ToString ( ) ) ;
invoiceId = GetInvoiceId ( resp ) ;
await acc . PayOnLNUrl ( invoiceId ) ;
await acc . CreateLNAddress ( ) ;
await acc . PayOnLNAddress ( ) ;
var report = await GetReport ( acc , new ( ) { ViewName = "Payments" } ) ;
// 1 payment on LN Address
// 1 payment on LNURL
// 1 payment on BOLT11
// 1 payment on chain
Assert . Equal ( 4 , report . Data . Count ) ;
var lnAddressIndex = report . GetIndex ( "LightningAddress" ) ;
2024-04-04 09:31:04 +02:00
var paymentTypeIndex = report . GetIndex ( "Category" ) ;
2023-07-24 02:24:32 +02:00
Assert . Contains ( report . Data , d = > d [ lnAddressIndex ] ? . Value < string > ( ) ? . Contains ( acc . LNAddress ) is true ) ;
var paymentTypes = report . Data
. GroupBy ( d = > d [ paymentTypeIndex ] . Value < string > ( ) )
. ToDictionary ( d = > d . Key ) ;
Assert . Equal ( 3 , paymentTypes [ "Lightning" ] . Count ( ) ) ;
Assert . Single ( paymentTypes [ "On-Chain" ] ) ;
// 2 on-chain transactions: It received from the cashcow, then paid its own invoice
2023-11-09 10:26:00 +01:00
report = await GetReport ( acc , new ( ) { ViewName = "Wallets" } ) ;
2023-07-24 02:24:32 +02:00
var txIdIndex = report . GetIndex ( "TransactionId" ) ;
var balanceIndex = report . GetIndex ( "BalanceChange" ) ;
Assert . Equal ( 2 , report . Data . Count ) ;
Assert . Equal ( 64 , report . Data [ 0 ] [ txIdIndex ] . Value < string > ( ) . Length ) ;
2023-10-11 13:48:40 +02:00
Assert . Contains ( report . Data , d = > d [ balanceIndex ] [ "v" ] . Value < decimal > ( ) = = 1.0 m ) ;
2023-07-24 02:24:32 +02:00
// Items sold
2023-11-09 10:26:00 +01:00
report = await GetReport ( acc , new ( ) { ViewName = "Sales" } ) ;
2023-07-24 02:24:32 +02:00
var itemIndex = report . GetIndex ( "Product" ) ;
var countIndex = report . GetIndex ( "Quantity" ) ;
var itemsCount = report . Data . GroupBy ( d = > d [ itemIndex ] . Value < string > ( ) )
. ToDictionary ( d = > d . Key , r = > r . Sum ( d = > d [ countIndex ] . Value < int > ( ) ) ) ;
Assert . Equal ( 8 , itemsCount [ "green-tea" ] ) ;
Assert . Equal ( 1 , itemsCount [ "black-tea" ] ) ;
2024-03-05 08:10:54 +01:00
await acc . ImportOldInvoices ( ) ;
var date2018 = new DateTimeOffset ( 2018 , 1 , 1 , 0 , 0 , 0 , TimeSpan . Zero ) ;
report = await GetReport ( acc , new ( ) { ViewName = "Payments" , TimePeriod = new TimePeriod ( ) { From = date2018 , To = date2018 + TimeSpan . FromDays ( 365 ) } } ) ;
var invoiceIdIndex = report . GetIndex ( "InvoiceId" ) ;
var oldPaymentsCount = report . Data . Count ( d = > d [ invoiceIdIndex ] . Value < string > ( ) = = "Q7RqoHLngK9svM4MgRyi9y" ) ;
2024-10-16 12:05:07 +02:00
Assert . Equal ( 9 , oldPaymentsCount ) ; // 11 payments, but 2 unaccounted
2024-05-06 11:44:16 +02:00
var addr = await tester . ExplorerNode . GetNewAddressAsync ( ) ;
// Two invoices get refunded
for ( int i = 0 ; i < 2 ; i + + )
{
var inv = await client . CreateInvoice ( acc . StoreId , new CreateInvoiceRequest ( ) { Amount = 10 m , Currency = "USD" } ) ;
await acc . PayInvoice ( inv . Id ) ;
await client . MarkInvoiceStatus ( acc . StoreId , inv . Id , new MarkInvoiceStatusRequest ( ) { Status = InvoiceStatus . Settled } ) ;
2024-09-06 06:24:33 +02:00
var refund = await client . RefundInvoice ( acc . StoreId , inv . Id , new RefundInvoiceRequest ( ) { RefundVariant = RefundVariant . Fiat , PayoutMethodId = "BTC-CHAIN" } ) ;
2024-05-06 11:44:16 +02:00
async Task AssertData ( string currency , decimal awaiting , decimal limit , decimal completed , bool fullyPaid )
{
report = await GetReport ( acc , new ( ) { ViewName = "Refunds" } ) ;
var currencyIndex = report . GetIndex ( "Currency" ) ;
var awaitingIndex = report . GetIndex ( "Awaiting" ) ;
var fullyPaidIndex = report . GetIndex ( "FullyPaid" ) ;
var completedIndex = report . GetIndex ( "Completed" ) ;
var limitIndex = report . GetIndex ( "Limit" ) ;
var d = Assert . Single ( report . Data . Where ( d = > d [ report . GetIndex ( "InvoiceId" ) ] . Value < string > ( ) = = inv . Id ) ) ;
Assert . Equal ( fullyPaid , ( bool ) d [ fullyPaidIndex ] ) ;
Assert . Equal ( currency , d [ currencyIndex ] . Value < string > ( ) ) ;
Assert . Equal ( completed , ( ( ( JObject ) d [ completedIndex ] ) [ "v" ] ) . Value < decimal > ( ) ) ;
Assert . Equal ( awaiting , ( ( ( JObject ) d [ awaitingIndex ] ) [ "v" ] ) . Value < decimal > ( ) ) ;
Assert . Equal ( limit , ( ( ( JObject ) d [ limitIndex ] ) [ "v" ] ) . Value < decimal > ( ) ) ;
}
await AssertData ( "USD" , awaiting : 0.0 m , limit : 10.0 m , completed : 0.0 m , fullyPaid : false ) ;
2024-09-06 06:24:33 +02:00
var payout = await client . CreatePayout ( refund . Id , new CreatePayoutRequest ( ) { Destination = addr . ToString ( ) , PayoutMethodId = "BTC-CHAIN" } ) ;
2024-05-06 11:44:16 +02:00
await AssertData ( "USD" , awaiting : 10.0 m , limit : 10.0 m , completed : 0.0 m , fullyPaid : false ) ;
await client . ApprovePayout ( acc . StoreId , payout . Id , new ApprovePayoutRequest ( ) ) ;
await AssertData ( "USD" , awaiting : 10.0 m , limit : 10.0 m , completed : 0.0 m , fullyPaid : false ) ;
if ( i = = 0 )
{
await client . MarkPayoutPaid ( acc . StoreId , payout . Id ) ;
await AssertData ( "USD" , awaiting : 0.0 m , limit : 10.0 m , completed : 10.0 m , fullyPaid : true ) ;
}
if ( i = = 1 )
{
await client . CancelPayout ( acc . StoreId , payout . Id ) ;
await AssertData ( "USD" , awaiting : 0.0 m , limit : 10.0 m , completed : 0.0 m , fullyPaid : false ) ;
}
}
2023-07-24 02:24:32 +02:00
}
private async Task < StoreReportResponse > GetReport ( TestAccount acc , StoreReportRequest req )
{
var controller = acc . GetController < UIReportsController > ( ) ;
return ( await controller . StoreReportsJson ( acc . StoreId , req ) ) . AssertType < JsonResult > ( )
. Value
. AssertType < StoreReportResponse > ( ) ;
}
private static string GetInvoiceId ( IActionResult resp )
{
var redirect = resp . AssertType < RedirectToActionResult > ( ) ;
Assert . Equal ( "Checkout" , redirect . ActionName ) ;
return ( string ) redirect . RouteValues [ "invoiceId" ] ;
}
2017-10-27 10:53:04 +02:00
}
2017-09-13 08:47:34 +02:00
}