2020-07-29 19:11:54 +09:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
2024-12-13 04:09:55 +01:00
using System.Net.Http ;
2020-07-29 19:11:54 +09:00
using System.Threading.Tasks ;
2024-11-05 03:49:30 +01:00
using BTCPayServer.Client.Models ;
2020-07-29 19:11:54 +09:00
using BTCPayServer.Controllers ;
using BTCPayServer.Data ;
2023-05-23 02:18:57 +02:00
using BTCPayServer.Hosting ;
2020-07-29 19:11:54 +09:00
using BTCPayServer.Lightning ;
using BTCPayServer.Models.AppViewModels ;
using BTCPayServer.Models.StoreViewModels ;
using BTCPayServer.Models.WalletViewModels ;
using BTCPayServer.Payments ;
2023-03-17 03:56:32 +01:00
using BTCPayServer.Plugins.PointOfSale ;
2022-07-18 20:51:53 +02:00
using BTCPayServer.Plugins.PointOfSale.Controllers ;
using BTCPayServer.Plugins.PointOfSale.Models ;
2020-07-29 19:11:54 +09:00
using BTCPayServer.Services.Apps ;
2024-04-04 16:31:04 +09:00
using BTCPayServer.Services.Invoices ;
2020-07-29 19:11:54 +09:00
using Microsoft.AspNetCore.Mvc ;
using NBitcoin ;
using NBitpayClient ;
using Newtonsoft.Json.Linq ;
using OpenQA.Selenium ;
using Xunit ;
using Xunit.Abstractions ;
2024-11-05 03:49:30 +01:00
using PosViewType = BTCPayServer . Plugins . PointOfSale . PosViewType ;
2021-10-29 08:25:43 +02:00
using WalletSettingsViewModel = BTCPayServer . Models . StoreViewModels . WalletSettingsViewModel ;
2020-07-29 19:11:54 +09:00
namespace BTCPayServer.Tests
{
2021-11-23 13:57:45 +09:00
[Collection(nameof(NonParallelizableCollectionDefinition))]
2021-11-22 17:16:08 +09:00
public class AltcoinTests : UnitTestBase
2020-07-29 19:11:54 +09:00
{
public const int TestTimeout = 60_000 ;
2021-11-22 17:16:08 +09:00
public AltcoinTests ( ITestOutputHelper helper ) : base ( helper )
2020-07-29 20:18:01 +09:00
{
}
2020-07-29 19:11:54 +09:00
[Fact]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
2021-02-18 18:06:43 +01:00
public async Task CanSetupWallet ( )
2020-07-29 19:11:54 +09:00
{
2021-11-22 17:16:08 +09:00
using ( var tester = CreateServerTester ( ) )
2020-07-29 19:11:54 +09:00
{
tester . ActivateLTC ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
2021-04-16 15:31:09 +02:00
var cryptoCode = "BTC" ;
await user . GrantAccessAsync ( true ) ;
user . RegisterDerivationScheme ( cryptoCode ) ;
2020-07-29 19:11:54 +09:00
user . RegisterDerivationScheme ( "LTC" ) ;
2021-04-16 15:31:09 +02:00
user . RegisterLightningNode ( cryptoCode , LightningConnectionType . CLightning ) ;
2023-04-25 08:51:38 +09:00
user . SetLNUrl ( "BTC" , false ) ;
2021-04-16 15:31:09 +02:00
var btcNetwork = tester . PayTester . Networks . GetNetwork < BTCPayNetwork > ( cryptoCode ) ;
var invoice = await user . BitPay . CreateInvoiceAsync (
new Invoice
2020-07-29 19:11:54 +09:00
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Equal ( 3 , invoice . CryptoInfo . Length ) ;
2022-01-19 12:58:02 +01:00
// Setup Lightning
2022-01-07 12:32:00 +09:00
var controller = user . GetController < UIStoresController > ( ) ;
2022-05-24 13:18:16 +09:00
var lightningVm = ( LightningNodeViewModel ) Assert . IsType < ViewResult > ( controller . SetupLightningNode ( user . StoreId , cryptoCode ) ) . Model ;
2021-02-18 18:06:43 +01:00
Assert . True ( lightningVm . Enabled ) ;
2021-04-16 15:31:09 +02:00
2022-01-19 12:58:02 +01:00
// Get enabled state from settings
2024-06-19 15:23:10 +02:00
var response = controller . LightningSettings ( user . StoreId , cryptoCode ) ;
var lnSettingsModel = ( LightningSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . NotNull ( lnSettingsModel ? . ConnectionString ) ;
Assert . True ( lnSettingsModel . Enabled ) ;
lnSettingsModel . Enabled = false ;
response = await controller . LightningSettings ( lnSettingsModel ) ;
Assert . IsType < RedirectToActionResult > ( response ) ;
2022-05-24 13:18:16 +09:00
response = controller . LightningSettings ( user . StoreId , cryptoCode ) ;
2022-01-19 12:58:02 +01:00
lnSettingsModel = ( LightningSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . False ( lnSettingsModel . Enabled ) ;
2021-02-18 18:06:43 +01:00
2022-01-19 12:58:02 +01:00
// Setup wallet
2021-02-18 18:06:43 +01:00
WalletSetupViewModel setupVm ;
var storeId = user . StoreId ;
2021-06-18 03:25:17 +02:00
response = await controller . GenerateWallet ( storeId , cryptoCode , WalletSetupMethod . GenerateOptions , new WalletSetupRequest ( ) ) ;
2021-02-18 18:06:43 +01:00
Assert . IsType < ViewResult > ( response ) ;
2022-01-19 12:58:02 +01:00
// Get enabled state from settings
2024-12-02 09:11:20 +09:00
response = await controller . WalletSettings ( user . StoreId , cryptoCode ) ;
2022-01-19 12:58:02 +01:00
var onchainSettingsModel = ( WalletSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . NotNull ( onchainSettingsModel ? . DerivationScheme ) ;
Assert . True ( onchainSettingsModel . Enabled ) ;
2020-07-29 19:11:54 +09:00
2021-04-16 15:31:09 +02:00
// Disable wallet
2022-01-19 12:58:02 +01:00
onchainSettingsModel . Enabled = false ;
2024-12-02 09:11:20 +09:00
response = await controller . UpdateWalletSettings ( onchainSettingsModel ) ;
2021-02-18 18:06:43 +01:00
Assert . IsType < RedirectToActionResult > ( response ) ;
2024-12-02 09:11:20 +09:00
response = await controller . WalletSettings ( user . StoreId , cryptoCode ) ;
2022-01-19 12:58:02 +01:00
onchainSettingsModel = ( WalletSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . NotNull ( onchainSettingsModel ? . DerivationScheme ) ;
Assert . False ( onchainSettingsModel . Enabled ) ;
2020-07-29 19:11:54 +09:00
2022-01-19 12:58:02 +01:00
var oldScheme = onchainSettingsModel . DerivationScheme ;
2020-07-29 19:11:54 +09:00
2021-04-16 15:31:09 +02:00
invoice = await user . BitPay . CreateInvoiceAsync (
2021-02-18 18:06:43 +01:00
new Invoice
2020-07-29 19:11:54 +09:00
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( "LTC" , invoice . CryptoInfo [ 0 ] . CryptoCode ) ;
// Removing the derivation scheme, should redirect to store page
2024-12-02 09:11:20 +09:00
response = await controller . ConfirmDeleteWallet ( user . StoreId , cryptoCode ) ;
2021-02-18 18:06:43 +01:00
Assert . IsType < RedirectToActionResult > ( response ) ;
2020-07-29 19:11:54 +09:00
2021-02-18 18:06:43 +01:00
// Setting it again should show the confirmation page
2024-03-13 22:29:25 +09:00
response = await controller . UpdateWallet ( new WalletSetupViewModel { StoreId = storeId , CryptoCode = cryptoCode , DerivationScheme = oldScheme } ) ;
2021-02-18 18:06:43 +01:00
setupVm = ( WalletSetupViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . True ( setupVm . Confirmation ) ;
2020-07-29 19:11:54 +09:00
2021-02-18 18:06:43 +01:00
// The following part posts a wallet update, confirms it and checks the result
2020-07-29 19:11:54 +09:00
2021-02-18 18:06:43 +01:00
// cobo vault file
2020-07-29 19:11:54 +09:00
var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}" ;
2024-03-13 22:29:25 +09:00
response = await controller . UpdateWallet ( new WalletSetupViewModel { StoreId = storeId , CryptoCode = cryptoCode , WalletFile = TestUtils . GetFormFile ( "cobovault.json" , content ) } ) ;
2021-02-18 18:06:43 +01:00
setupVm = ( WalletSetupViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . True ( setupVm . Confirmation ) ;
response = await controller . UpdateWallet ( setupVm ) ;
Assert . IsType < RedirectToActionResult > ( response ) ;
2021-10-29 08:25:43 +02:00
response = await controller . WalletSettings ( storeId , cryptoCode ) ;
var settingsVm = ( WalletSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . Equal ( "CoboVault" , settingsVm . Source ) ;
2020-07-29 19:11:54 +09:00
2021-02-18 18:06:43 +01:00
// wasabi wallet file
content = "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}" ;
2024-03-13 22:29:25 +09:00
response = await controller . UpdateWallet ( new WalletSetupViewModel { StoreId = storeId , CryptoCode = cryptoCode , WalletFile = TestUtils . GetFormFile ( "wasabi.json" , content ) } ) ;
2021-02-18 18:06:43 +01:00
setupVm = ( WalletSetupViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . True ( setupVm . Confirmation ) ;
response = await controller . UpdateWallet ( setupVm ) ;
Assert . IsType < RedirectToActionResult > ( response ) ;
2021-10-29 08:25:43 +02:00
response = await controller . WalletSettings ( storeId , cryptoCode ) ;
settingsVm = ( WalletSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . Equal ( "WasabiFile" , settingsVm . Source ) ;
2020-07-29 19:11:54 +09:00
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
2021-02-18 18:06:43 +01:00
content = "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ;
2024-03-13 22:29:25 +09:00
response = await controller . UpdateWallet ( new WalletSetupViewModel { StoreId = storeId , CryptoCode = cryptoCode , WalletFile = TestUtils . GetFormFile ( "coldcard-ypub.json" , content ) } ) ;
2021-02-18 18:06:43 +01:00
setupVm = ( WalletSetupViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . False ( setupVm . Confirmation ) ; // Should fail, we are giving a mainnet file to a testnet network
2020-07-29 19:11:54 +09:00
// And with a good file? (upub)
2021-02-18 18:06:43 +01:00
content = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ;
2024-03-13 22:29:25 +09:00
response = await controller . UpdateWallet ( new WalletSetupViewModel { StoreId = storeId , CryptoCode = cryptoCode , WalletFile = TestUtils . GetFormFile ( "coldcard-upub.json" , content ) } ) ;
2021-02-18 18:06:43 +01:00
setupVm = ( WalletSetupViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . True ( setupVm . Confirmation ) ;
response = await controller . UpdateWallet ( setupVm ) ;
Assert . IsType < RedirectToActionResult > ( response ) ;
2021-10-29 08:25:43 +02:00
response = await controller . WalletSettings ( storeId , cryptoCode ) ;
settingsVm = ( WalletSettingsViewModel ) Assert . IsType < ViewResult > ( response ) . Model ;
Assert . Equal ( "ElectrumFile" , settingsVm . Source ) ;
2020-07-29 19:11:54 +09:00
// Now let's check that no data has been lost in the process
2024-12-02 09:11:20 +09:00
var store = await tester . PayTester . StoreRepository . FindStore ( storeId ) ;
2024-04-04 16:31:04 +09:00
var handlers = tester . PayTester . GetService < PaymentMethodHandlerDictionary > ( ) ;
var pmi = PaymentTypes . CHAIN . GetPaymentMethodId ( "BTC" ) ;
var onchainBTC = store . GetPaymentMethodConfig < DerivationSchemeSettings > ( pmi , handlers ) ;
var network = handlers . GetBitcoinHandler ( "BTC" ) . Network ;
FastTests . GetParsers ( ) . TryParseWalletFile ( content , network , out var expected , out var error ) ;
var handler = handlers [ pmi ] ;
Assert . Equal ( JToken . FromObject ( expected , handler . Serializer ) , JToken . FromObject ( onchainBTC , handler . Serializer ) ) ;
2022-12-05 09:06:05 +01:00
Assert . Null ( error ) ;
2020-07-29 19:11:54 +09:00
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
2021-04-16 15:31:09 +02:00
invoice = await user . BitPay . CreateInvoiceAsync (
new Invoice
2020-07-29 19:11:54 +09:00
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
tester . ExplorerNode . Generate ( 1 ) ;
2021-04-16 15:31:09 +02:00
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo . First ( c = > c . CryptoCode = = cryptoCode ) . Address ,
2020-07-29 19:11:54 +09:00
tester . ExplorerNode . Network ) ;
tester . ExplorerNode . SendToAddress ( invoiceAddress , Money . Coins ( 1 m ) ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
} ) ;
2022-01-07 12:32:00 +09:00
var wallet = tester . PayTester . GetController < UIWalletsController > ( ) ;
2024-12-02 09:11:20 +09:00
var psbt = await wallet . CreatePSBT ( btcNetwork , onchainBTC ,
2020-07-29 19:11:54 +09:00
new WalletSendModel ( )
{
2021-04-16 15:31:09 +02:00
Outputs = new List < WalletSendModel . TransactionOutput >
2020-07-29 19:11:54 +09:00
{
2021-04-16 15:31:09 +02:00
new WalletSendModel . TransactionOutput
2020-07-29 19:11:54 +09:00
{
Amount = 0.5 m ,
DestinationAddress = new Key ( ) . PubKey . GetAddress ( ScriptPubKeyType . Legacy , btcNetwork . NBitcoinNetwork )
. ToString ( ) ,
}
} ,
FeeSatoshiPerByte = 1
2024-12-02 09:11:20 +09:00
} , default ) ;
2020-07-29 19:11:54 +09:00
Assert . NotNull ( psbt ) ;
var root = new Mnemonic (
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage" )
. DeriveExtKey ( ) . AsHDKeyCache ( ) ;
var account = root . Derive ( new KeyPath ( "m/49'/0'/0'" ) ) ;
Assert . All ( psbt . PSBT . Inputs , input = >
{
var keyPath = input . HDKeyPaths . Single ( ) ;
Assert . False ( keyPath . Value . KeyPath . IsHardened ) ;
Assert . Equal ( account . Derive ( keyPath . Value . KeyPath ) . GetPublicKey ( ) , keyPath . Key ) ;
Assert . Equal ( keyPath . Value . MasterFingerprint ,
onchainBTC . AccountKeySettings [ 0 ] . AccountKey . GetPublicKey ( ) . GetHDFingerPrint ( ) ) ;
} ) ;
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanCreateInvoiceWithSpecificPaymentMethods ( )
{
2021-11-22 17:16:08 +09:00
using ( var tester = CreateServerTester ( ) )
2020-07-29 19:11:54 +09:00
{
tester . ActivateLightning ( ) ;
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
2021-03-02 11:11:58 +09:00
user . GrantAccess ( true ) ;
2023-05-16 09:17:21 +09:00
user . RegisterLightningNode ( "BTC" ) ;
2020-07-29 19:11:54 +09:00
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 100 , "BTC" ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ) ;
invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 100 , "BTC" )
{
SupportedTransactionCurrencies = new Dictionary < string , InvoiceSupportedTransactionCurrency > ( )
{
{ "BTC" , new InvoiceSupportedTransactionCurrency ( ) { Enabled = true } }
}
} ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
public async Task CanHaveLTCOnlyStore ( )
{
2021-11-22 17:16:08 +09:00
using ( var tester = CreateServerTester ( ) )
2020-07-29 19:11:54 +09:00
{
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
// First we try payment with a merchant having only BTC
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( "LTC" , invoice . CryptoInfo [ 0 ] . CryptoCode ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "LTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "LTC" ) ) ;
var cashCow = tester . LTCExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var firstPayment = Money . Coins ( 0.1 m ) ;
2024-04-04 16:31:04 +09:00
var firstDue = invoice . CryptoInfo [ 0 ] . Due ;
2020-07-29 19:11:54 +09:00
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( firstPayment , invoice . CryptoInfo [ 0 ] . Paid ) ;
2024-04-16 12:48:42 +09:00
Assert . Equal ( "paidPartial" , invoice . ExceptionStatus ? . ToString ( ) ) ;
2020-07-29 19:11:54 +09:00
} ) ;
Assert . Single ( invoice . CryptoInfo ) ; // Only BTC should be presented
2022-01-07 12:32:00 +09:00
var controller = tester . PayTester . GetController < UIInvoiceController > ( null ) ;
2020-07-29 19:11:54 +09:00
var checkout =
2024-10-07 19:58:08 +09:00
( Models . InvoicingModels . CheckoutModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id )
2020-07-29 19:11:54 +09:00
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
2024-10-07 21:14:37 +09:00
Assert . Single ( checkout . AvailablePaymentMethods ) ;
2024-10-07 19:29:05 +09:00
Assert . Equal ( "LTC" , checkout . PaymentMethodCurrency ) ;
2020-07-29 19:11:54 +09:00
//////////////////////
// Despite it is called BitcoinAddress it should be LTC because BTC is not available
Assert . Null ( invoice . BitcoinAddress ) ;
Assert . NotEqual ( 1.0 m , invoice . Rate ) ;
Assert . NotEqual ( invoice . BtcDue , invoice . CryptoInfo [ 0 ] . Due ) ; // Should be BTC rate
cashCow . SendToAddress ( invoiceAddress , invoice . CryptoInfo [ 0 ] . Due ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
2024-10-07 19:58:08 +09:00
checkout = ( Models . InvoicingModels . CheckoutModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id )
2020-07-29 19:11:54 +09:00
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
2024-05-15 07:49:53 +09:00
Assert . Equal ( "Processing" , checkout . Status ) ;
2020-07-29 19:11:54 +09:00
} ) ;
}
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Altcoins", "Altcoins")]
public async Task CanCreateRefunds ( )
{
2021-11-22 17:16:08 +09:00
using ( var s = CreateSeleniumTester ( ) )
2020-07-29 19:11:54 +09:00
{
s . Server . ActivateLTC ( ) ;
await s . StartAsync ( ) ;
var user = s . Server . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
s . GoToLogin ( ) ;
2022-02-07 21:18:22 +09:00
s . LogIn ( user . RegisterDetails . Email , user . RegisterDetails . Password ) ;
2020-07-29 19:11:54 +09:00
user . RegisterDerivationScheme ( "BTC" ) ;
await s . Server . ExplorerNode . GenerateAsync ( 1 ) ;
foreach ( var multiCurrency in new [ ] { false , true } )
{
if ( multiCurrency )
user . RegisterDerivationScheme ( "LTC" ) ;
2022-06-02 10:08:55 +02:00
foreach ( var rateSelection in new [ ] { "FiatOption" , "CurrentRateOption" , "RateThenOption" , "CustomOption" } )
2024-05-01 10:22:07 +09:00
{
TestLogs . LogInformation ( ( multiCurrency , rateSelection ) . ToString ( ) ) ;
2020-07-29 19:11:54 +09:00
await CanCreateRefundsCore ( s , user , multiCurrency , rateSelection ) ;
2024-05-01 10:22:07 +09:00
}
2020-07-29 19:11:54 +09:00
}
}
}
private static async Task CanCreateRefundsCore ( SeleniumTester s , TestAccount user , bool multiCurrency , string rateSelection )
{
s . GoToHome ( ) ;
s . Server . PayTester . ChangeRate ( "BTC_USD" , new Rating . BidAsk ( 5000.0 m , 5100.0 m ) ) ;
2022-06-02 10:08:55 +02:00
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice
2020-07-29 19:11:54 +09:00
{
Currency = "USD" ,
Price = 5000.0 m
} ) ;
var info = invoice . CryptoInfo . First ( o = > o . CryptoCode = = "BTC" ) ;
var totalDue = decimal . Parse ( info . TotalDue , CultureInfo . InvariantCulture ) ;
var paid = totalDue + 0.1 m ;
await s . Server . ExplorerNode . SendToAddressAsync ( BitcoinAddress . Create ( info . Address , Network . RegTest ) , Money . Coins ( paid ) ) ;
await s . Server . ExplorerNode . GenerateAsync ( 1 ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
invoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
2024-04-04 16:31:04 +09:00
Assert . Equal ( "complete" , invoice . Status ) ;
2020-07-29 19:11:54 +09:00
} ) ;
// BTC crash by 50%
s . Server . PayTester . ChangeRate ( "BTC_USD" , new Rating . BidAsk ( 5000.0 m / 2.0 m , 5100.0 m / 2.0 m ) ) ;
2022-01-20 12:52:31 +01:00
s . GoToStore ( ) ;
2022-01-24 20:17:09 +09:00
s . Driver . FindElement ( By . Id ( "BOLT11Expiration" ) ) . Clear ( ) ;
s . Driver . FindElement ( By . Id ( "BOLT11Expiration" ) ) . SendKeys ( "5" + Keys . Enter ) ;
2020-07-29 19:11:54 +09:00
s . GoToInvoice ( invoice . Id ) ;
2022-06-02 10:08:55 +02:00
s . Driver . FindElement ( By . Id ( "IssueRefund" ) ) . Click ( ) ;
2023-03-17 03:56:32 +01:00
2020-07-29 19:11:54 +09:00
if ( multiCurrency )
{
2022-06-02 10:08:55 +02:00
s . Driver . WaitUntilAvailable ( By . Id ( "RefundForm" ) , TimeSpan . FromSeconds ( 1 ) ) ;
2024-05-01 10:22:07 +09:00
s . Driver . WaitUntilAvailable ( By . Id ( "SelectedPayoutMethod" ) , TimeSpan . FromSeconds ( 1 ) ) ;
s . Driver . FindElement ( By . Id ( "SelectedPayoutMethod" ) ) . SendKeys ( "BTC" + Keys . Enter ) ;
2020-07-29 19:11:54 +09:00
s . Driver . FindElement ( By . Id ( "ok" ) ) . Click ( ) ;
}
2022-06-02 10:08:55 +02:00
s . Driver . WaitUntilAvailable ( By . Id ( "RefundForm" ) , TimeSpan . FromSeconds ( 1 ) ) ;
2023-03-13 02:12:58 +01:00
Assert . Contains ( "5,500.00 USD" , s . Driver . PageSource ) ; // Should propose reimburse in fiat
Assert . Contains ( "1.10000000 BTC" , s . Driver . PageSource ) ; // Should propose reimburse in BTC at the rate of before
Assert . Contains ( "2.20000000 BTC" , s . Driver . PageSource ) ; // Should propose reimburse in BTC at the current rate
2022-06-02 10:08:55 +02:00
s . Driver . WaitForAndClick ( By . Id ( rateSelection ) ) ;
2020-07-29 19:11:54 +09:00
s . Driver . FindElement ( By . Id ( "ok" ) ) . Click ( ) ;
2023-03-13 02:12:58 +01:00
2022-06-02 10:08:55 +02:00
s . Driver . WaitUntilAvailable ( By . Id ( "Destination" ) , TimeSpan . FromSeconds ( 1 ) ) ;
2020-07-29 19:11:54 +09:00
Assert . Contains ( "pull-payments" , s . Driver . Url ) ;
2022-06-02 10:08:55 +02:00
if ( rateSelection = = "FiatOption" )
2023-03-13 02:12:58 +01:00
Assert . Contains ( "5,500.00 USD" , s . Driver . PageSource ) ;
2022-06-02 10:08:55 +02:00
if ( rateSelection = = "CurrentOption" )
2023-03-13 02:12:58 +01:00
Assert . Contains ( "2.20000000 BTC" , s . Driver . PageSource ) ;
2022-06-02 10:08:55 +02:00
if ( rateSelection = = "RateThenOption" )
2023-03-13 02:12:58 +01:00
Assert . Contains ( "1.10000000 BTC" , s . Driver . PageSource ) ;
2020-07-29 19:11:54 +09:00
s . GoToInvoice ( invoice . Id ) ;
2022-06-02 10:08:55 +02:00
s . Driver . FindElement ( By . Id ( "IssueRefund" ) ) . Click ( ) ;
s . Driver . WaitUntilAvailable ( By . Id ( "Destination" ) , TimeSpan . FromSeconds ( 1 ) ) ;
2020-07-29 19:11:54 +09:00
Assert . Contains ( "pull-payments" , s . Driver . Url ) ;
2022-01-24 20:17:09 +09:00
var client = await user . CreateClient ( ) ;
var ppid = s . Driver . Url . Split ( '/' ) . Last ( ) ;
var pps = await client . GetPullPayments ( user . StoreId ) ;
var pp = Assert . Single ( pps , p = > p . Id = = ppid ) ;
Assert . Equal ( TimeSpan . FromDays ( 5.0 ) , pp . BOLT11Expiration ) ;
2020-07-29 19:11:54 +09:00
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
public async Task CanPayWithTwoCurrencies ( )
{
2024-11-29 20:53:29 -06:00
using var tester = CreateServerTester ( ) ;
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
var invoice = await user . BitPay . CreateInvoiceAsync (
new Invoice
2020-07-29 19:11:54 +09:00
{
2024-11-29 20:53:29 -06:00
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
var cashCow = tester . ExplorerNode ;
await cashCow . GenerateAsync ( 2 ) ; // get some money in case
var invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
var firstPayment = Money . Coins ( 0.04 m ) ;
await cashCow . SendToAddressAsync ( invoiceAddress , firstPayment ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . True ( invoice . BtcPaid = = firstPayment ) ;
} ) ;
2020-07-29 19:11:54 +09:00
2024-11-29 20:53:29 -06:00
Assert . Single ( invoice . CryptoInfo ) ; // Only BTC should be presented
2020-07-29 19:11:54 +09:00
2024-11-29 20:53:29 -06:00
var controller = tester . PayTester . GetController < UIInvoiceController > ( null ) ;
var checkout =
( Models . InvoicingModels . CheckoutModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null )
2020-07-29 19:11:54 +09:00
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
2024-11-29 20:53:29 -06:00
Assert . Single ( checkout . AvailablePaymentMethods ) ;
Assert . Equal ( "BTC" , checkout . PaymentMethodCurrency ) ;
Assert . Single ( invoice . PaymentCodes ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
Assert . Single ( invoice . PaymentSubtotals ) ;
Assert . Single ( invoice . PaymentTotals ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "BTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "BTC" ) ) ;
//////////////////////
// Retry now with LTC enabled
user . RegisterDerivationScheme ( "LTC" ) ;
invoice = await user . BitPay . CreateInvoiceAsync (
new Invoice
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
cashCow = tester . ExplorerNode ;
invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
firstPayment = Money . Coins ( 0.04 m ) ;
await cashCow . SendToAddressAsync ( invoiceAddress , firstPayment ) ;
TestLogs . LogInformation ( "First payment sent to " + invoiceAddress ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . True ( invoice . BtcPaid = = firstPayment ) ;
} ) ;
2020-07-29 19:11:54 +09:00
2024-11-29 20:53:29 -06:00
cashCow = tester . LTCExplorerNode ;
var ltcCryptoInfo = invoice . CryptoInfo . FirstOrDefault ( c = > c . CryptoCode = = "LTC" ) ;
Assert . NotNull ( ltcCryptoInfo ) ;
invoiceAddress = BitcoinAddress . Create ( ltcCryptoInfo . Address , cashCow . Network ) ;
var secondPayment = Money . Coins ( decimal . Parse ( ltcCryptoInfo . Due , CultureInfo . InvariantCulture ) ) ;
await cashCow . GenerateAsync ( 4 ) ; // LTC is not worth a lot, so just to make sure we have money...
await cashCow . SendToAddressAsync ( invoiceAddress , secondPayment ) ;
TestLogs . LogInformation ( "Second payment sent to " + invoiceAddress ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( Money . Zero , invoice . BtcDue ) ;
var ltcPaid = invoice . CryptoInfo . First ( c = > c . CryptoCode = = "LTC" ) ;
Assert . Equal ( Money . Zero , ltcPaid . Due ) ;
Assert . Equal ( secondPayment , ltcPaid . CryptoPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . False ( ( bool ) ( ( JValue ) invoice . ExceptionStatus ) . Value ) ;
} ) ;
2023-03-17 03:56:32 +01:00
2024-11-29 20:53:29 -06:00
controller = tester . PayTester . GetController < UIInvoiceController > ( null ) ;
checkout = ( Models . InvoicingModels . CheckoutModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , "LTC" )
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Equal ( 2 , checkout . AvailablePaymentMethods . Count ) ;
Assert . Equal ( "LTC" , checkout . PaymentMethodCurrency ) ;
Assert . Equal ( 2 , invoice . PaymentCodes . Count ( ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ( ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ( ) ) ;
Assert . Equal ( 2 , invoice . PaymentSubtotals . Count ( ) ) ;
Assert . Equal ( 2 , invoice . PaymentTotals . Count ( ) ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "LTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "LTC" ) ) ;
// Check if we can disable LTC
invoice = await user . BitPay . CreateInvoiceAsync (
new Invoice
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true ,
SupportedTransactionCurrencies = new Dictionary < string , InvoiceSupportedTransactionCurrency > ( )
2020-07-29 19:11:54 +09:00
{
2024-11-29 20:53:29 -06:00
{ "BTC" , new InvoiceSupportedTransactionCurrency ( ) { Enabled = true } }
}
} , Facade . Merchant ) ;
2020-07-29 19:11:54 +09:00
2024-11-29 20:53:29 -06:00
Assert . Single ( invoice . CryptoInfo , c = > c . CryptoCode = = "BTC" ) ;
Assert . DoesNotContain ( invoice . CryptoInfo , c = > c . CryptoCode = = "LTC" ) ;
2020-07-29 19:11:54 +09:00
}
[Fact]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
public async Task CanUsePoSApp ( )
{
2021-11-22 17:16:08 +09:00
using ( var tester = CreateServerTester ( ) )
2020-07-29 19:11:54 +09:00
{
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
2021-12-11 04:32:23 +01:00
await user . GrantAccessAsync ( ) ;
2020-07-29 19:11:54 +09:00
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
2022-01-07 12:32:00 +09:00
var apps = user . GetController < UIAppsController > ( ) ;
2022-07-18 20:51:53 +02:00
var pos = user . GetController < UIPointOfSaleController > ( ) ;
2021-12-11 04:32:23 +01:00
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( user . StoreId ) ) . Model ) ;
2023-03-20 10:39:26 +09:00
var appType = PointOfSaleAppType . AppType ;
2021-10-29 06:29:02 -04:00
vm . AppName = "test" ;
2022-06-28 07:05:02 +02:00
vm . SelectedAppType = appType ;
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 ) ;
2021-12-16 17:37:19 +01:00
var appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( user . 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 ) ;
var vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
vmpos . Title = "hello" ;
vmpos . Currency = "CAD" ;
vmpos . ButtonText = "{0} Purchase" ;
vmpos . CustomButtonText = "Nicolas Sexy Hair" ;
vmpos . CustomTipText = "Wanna tip?" ;
vmpos . CustomTipPercentages = "15,18,20" ;
vmpos . Template = @ "
apple :
price : 5.0
title : good apple
orange :
price : 10.0
donation :
price : 1.02
custom : true
";
2023-05-23 02:18:57 +02:00
vmpos . Template = AppService . SerializeTemplate ( MigrationStartupTask . ParsePOSYML ( vmpos . Template ) ) ;
2022-07-18 20:51:53 +02:00
Assert . IsType < RedirectToActionResult > ( pos . UpdatePointOfSale ( app . Id , vmpos ) . Result ) ;
vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
Assert . Equal ( "hello" , vmpos . Title ) ;
2022-07-22 15:41:14 +02:00
var publicApps = user . GetController < UIPointOfSaleController > ( ) ;
2022-06-20 13:34:25 +09:00
var vmview = await publicApps . ViewPointOfSale ( app . Id , PosViewType . Cart ) . AssertViewModelAsync < ViewPointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
Assert . Equal ( "hello" , vmview . Title ) ;
Assert . Equal ( 3 , vmview . Items . Length ) ;
Assert . Equal ( "good apple" , vmview . Items [ 0 ] . Title ) ;
Assert . Equal ( "orange" , vmview . Items [ 1 ] . Title ) ;
2023-08-09 10:31:19 +03:00
Assert . Equal ( 10.0 m , vmview . Items [ 1 ] . Price ) ;
2020-07-29 19:11:54 +09:00
Assert . Equal ( "{0} Purchase" , vmview . ButtonText ) ;
Assert . Equal ( "Nicolas Sexy Hair" , vmview . CustomButtonText ) ;
Assert . Equal ( "Wanna tip?" , vmview . CustomTipText ) ;
Assert . Equal ( "15,18,20" , string . Join ( ',' , vmview . CustomTipPercentages ) ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
2023-06-16 23:02:14 +09:00
. ViewPointOfSale ( app . Id , PosViewType . Cart , 0 , choiceKey : "orange" ) . Result ) ;
2020-07-29 19:11:54 +09:00
//
2021-12-11 04:32:23 +01:00
var invoices = await user . BitPay . GetInvoicesAsync ( ) ;
2020-07-29 19:11:54 +09:00
var orangeInvoice = invoices . First ( ) ;
Assert . Equal ( 10.00 m , orangeInvoice . Price ) ;
Assert . Equal ( "CAD" , orangeInvoice . Currency ) ;
Assert . Equal ( "orange" , orangeInvoice . ItemDesc ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
2023-06-16 23:02:14 +09:00
. ViewPointOfSale ( app . Id , PosViewType . Cart , 0 , choiceKey : "apple" ) . Result ) ;
2020-07-29 19:11:54 +09:00
2023-08-09 10:31:19 +03:00
invoices = await user . BitPay . GetInvoicesAsync ( ) ;
2020-07-29 19:11:54 +09:00
var appleInvoice = invoices . SingleOrDefault ( invoice = > invoice . ItemCode . Equals ( "apple" ) ) ;
Assert . NotNull ( appleInvoice ) ;
Assert . Equal ( "good apple" , appleInvoice . ItemDesc ) ;
2023-03-17 03:56:32 +01:00
2020-07-29 19:11:54 +09:00
// testing custom amount
var action = Assert . IsType < RedirectToActionResult > ( publicApps
2023-06-16 23:02:14 +09:00
. ViewPointOfSale ( app . Id , PosViewType . Cart , 6.6 m , choiceKey : "donation" ) . Result ) ;
2022-01-07 12:32:00 +09:00
Assert . Equal ( nameof ( UIInvoiceController . Checkout ) , action . ActionName ) ;
2023-08-09 10:31:19 +03:00
invoices = await user . BitPay . GetInvoicesAsync ( ) ;
2020-07-29 19:11:54 +09:00
var donationInvoice = invoices . Single ( i = > i . Price = = 6.6 m ) ;
Assert . NotNull ( donationInvoice ) ;
Assert . Equal ( "CAD" , donationInvoice . Currency ) ;
Assert . Equal ( "donation" , donationInvoice . ItemDesc ) ;
foreach ( var test in new [ ]
{
( Code : "EUR" , ExpectedSymbol : "€" , ExpectedDecimalSeparator : "," , ExpectedDivisibility : 2 ,
ExpectedThousandSeparator : "\xa0" , ExpectedPrefixed : false , ExpectedSymbolSpace : true ) ,
( Code : "INR" , ExpectedSymbol : "₹" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 2 ,
ExpectedThousandSeparator : "," , ExpectedPrefixed : true , ExpectedSymbolSpace : true ) ,
( Code : "JPY" , ExpectedSymbol : "Â¥" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 0 ,
ExpectedThousandSeparator : "," , ExpectedPrefixed : true , ExpectedSymbolSpace : false ) ,
( Code : "BTC" , ExpectedSymbol : "â‚¿" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 8 ,
ExpectedThousandSeparator : "," , ExpectedPrefixed : false , ExpectedSymbolSpace : true ) ,
} )
{
2021-11-22 17:16:08 +09:00
TestLogs . LogInformation ( $"Testing for {test.Code}" ) ;
2022-07-18 20:51:53 +02:00
vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
vmpos . Title = "hello" ;
vmpos . Currency = test . Item1 ;
vmpos . ButtonText = "{0} Purchase" ;
vmpos . CustomButtonText = "Nicolas Sexy Hair" ;
vmpos . CustomTipText = "Wanna tip?" ;
vmpos . Template = @ "
apple :
price : 1000.0
title : good apple
orange :
price : 10.0
donation :
price : 1.02
custom : true
";
2023-05-23 02:18:57 +02:00
vmpos . Template = AppService . SerializeTemplate ( MigrationStartupTask . ParsePOSYML ( vmpos . Template ) ) ;
2022-07-18 20:51:53 +02:00
Assert . IsType < RedirectToActionResult > ( pos . UpdatePointOfSale ( app . Id , vmpos ) . Result ) ;
2022-07-22 15:41:14 +02:00
publicApps = user . GetController < UIPointOfSaleController > ( ) ;
2022-06-20 13:34:25 +09:00
vmview = await publicApps . ViewPointOfSale ( app . Id , PosViewType . Cart ) . AssertViewModelAsync < ViewPointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
Assert . Equal ( test . Code , vmview . CurrencyCode ) ;
Assert . Equal ( test . ExpectedSymbol ,
vmview . CurrencySymbol . Replace ( "ï¿¥" , "Â¥" ) ) ; // Hack so JPY test pass on linux as well);
Assert . Equal ( test . ExpectedSymbol ,
vmview . CurrencyInfo . CurrencySymbol
. Replace ( "ï¿¥" , "Â¥" ) ) ; // Hack so JPY test pass on linux as well);
Assert . Equal ( test . ExpectedDecimalSeparator , vmview . CurrencyInfo . DecimalSeparator ) ;
Assert . Equal ( test . ExpectedThousandSeparator , vmview . CurrencyInfo . ThousandSeparator ) ;
Assert . Equal ( test . ExpectedPrefixed , vmview . CurrencyInfo . Prefixed ) ;
Assert . Equal ( test . ExpectedDivisibility , vmview . CurrencyInfo . Divisibility ) ;
Assert . Equal ( test . ExpectedSymbolSpace , vmview . CurrencyInfo . SymbolSpace ) ;
}
2023-03-17 03:56:32 +01:00
2020-07-29 19:11:54 +09:00
//test inventory related features
2022-07-18 20:51:53 +02:00
vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
vmpos . Title = "hello" ;
vmpos . Currency = "BTC" ;
vmpos . Template = @ "
inventoryitem :
price : 1.0
title : good apple
inventory : 1
noninventoryitem :
price : 10.0 ";
2024-03-13 22:29:25 +09:00
2023-05-23 02:18:57 +02:00
vmpos . Template = AppService . SerializeTemplate ( MigrationStartupTask . ParsePOSYML ( vmpos . Template ) ) ;
2022-07-18 20:51:53 +02:00
Assert . IsType < RedirectToActionResult > ( pos . UpdatePointOfSale ( app . Id , vmpos ) . Result ) ;
2020-07-29 19:11:54 +09:00
2023-12-19 20:53:11 +09:00
async Task AssertCanBuy ( string choiceKey , bool expected )
2020-09-24 09:43:39 +02:00
{
2023-12-19 20:53:11 +09:00
var redirect = Assert . IsType < RedirectToActionResult > ( await publicApps
. ViewPointOfSale ( app . Id , PosViewType . Cart , 1 , choiceKey : choiceKey ) ) ;
if ( expected )
Assert . Equal ( "UIInvoice" , redirect . ControllerName ) ;
else
Assert . NotEqual ( "UIInvoice" , redirect . ControllerName ) ;
}
//inventoryitem has 1 item available
await AssertCanBuy ( "inventoryitem" , true ) ;
2023-03-17 03:56:32 +01:00
2020-07-29 19:11:54 +09:00
//we already bought all available stock so this should fail
await Task . Delay ( 100 ) ;
2023-12-19 20:53:11 +09:00
await AssertCanBuy ( "inventoryitem" , false ) ;
2020-07-29 19:11:54 +09:00
//inventoryitem has unlimited items available
2023-12-19 20:53:11 +09:00
await AssertCanBuy ( "noninventoryitem" , true ) ;
await AssertCanBuy ( "noninventoryitem" , true ) ;
2020-07-29 19:11:54 +09:00
//verify invoices where created
invoices = user . BitPay . GetInvoices ( ) ;
Assert . Equal ( 2 , invoices . Count ( invoice = > invoice . ItemCode . Equals ( "noninventoryitem" ) ) ) ;
var inventoryItemInvoice =
2024-11-29 20:53:29 -06:00
Assert . Single ( invoices , invoice = > invoice . ItemCode . Equals ( "inventoryitem" ) ) ;
2020-07-29 19:11:54 +09:00
Assert . NotNull ( inventoryItemInvoice ) ;
2021-12-11 04:32:23 +01:00
//let's mark the inventoryitem invoice as invalid, this should return the item to back in stock
2022-01-07 12:32:00 +09:00
var controller = tester . PayTester . GetController < UIInvoiceController > ( user . UserId , user . StoreId ) ;
2020-07-29 19:11:54 +09:00
Assert . IsType < JsonResult > ( await controller . ChangeInvoiceState ( inventoryItemInvoice . Id , "invalid" ) ) ;
//check that item is back in stock
2022-06-20 13:34:25 +09:00
await TestUtils . EventuallyAsync ( async ( ) = >
2020-07-29 19:11:54 +09:00
{
2022-07-18 20:51:53 +02:00
vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
2020-07-29 19:11:54 +09:00
Assert . Equal ( 1 ,
2023-05-23 02:18:57 +02:00
AppService . Parse ( vmpos . Template ) . Single ( item = > item . Id = = "inventoryitem" ) . Inventory ) ;
2020-07-29 19:11:54 +09:00
} , 10000 ) ;
2021-10-11 13:54:56 +02:00
//test topup option
vmpos . Template = @ "
a :
price : 1000.0
title : good apple
2023-03-17 03:56:32 +01:00
2021-10-11 13:54:56 +02:00
b :
price : 10.0
custom : false
c :
price : 1.02
custom : true
d :
price : 1.02
price_type : fixed
e :
price : 1.02
price_type : minimum
f :
price_type : topup
g :
custom : topup
";
2023-03-17 03:56:32 +01:00
2023-05-23 02:18:57 +02:00
vmpos . Template = AppService . SerializeTemplate ( MigrationStartupTask . ParsePOSYML ( vmpos . Template ) ) ;
2022-07-18 20:51:53 +02:00
Assert . IsType < RedirectToActionResult > ( pos . UpdatePointOfSale ( app . Id , vmpos ) . Result ) ;
vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
2021-10-11 13:54:56 +02:00
Assert . DoesNotContain ( "custom" , vmpos . Template ) ;
2023-05-23 02:18:57 +02:00
var items = AppService . Parse ( vmpos . Template ) ;
2024-11-05 03:49:30 +01:00
Assert . Contains ( items , item = > item . Id = = "a" & & item . PriceType = = AppItemPriceType . Fixed ) ;
Assert . Contains ( items , item = > item . Id = = "b" & & item . PriceType = = AppItemPriceType . Fixed ) ;
Assert . Contains ( items , item = > item . Id = = "c" & & item . PriceType = = AppItemPriceType . Minimum ) ;
Assert . Contains ( items , item = > item . Id = = "d" & & item . PriceType = = AppItemPriceType . Fixed ) ;
Assert . Contains ( items , item = > item . Id = = "e" & & item . PriceType = = AppItemPriceType . Minimum ) ;
Assert . Contains ( items , item = > item . Id = = "f" & & item . PriceType = = AppItemPriceType . Topup ) ;
Assert . Contains ( items , item = > item . Id = = "g" & & item . PriceType = = AppItemPriceType . Topup ) ;
2024-03-13 22:29:25 +09:00
2021-10-11 13:54:56 +02:00
Assert . IsType < RedirectToActionResult > ( publicApps
2023-06-16 23:02:14 +09:00
. ViewPointOfSale ( app . Id , PosViewType . Static , choiceKey : "g" ) . Result ) ;
2021-10-11 13:54:56 +02:00
invoices = user . BitPay . GetInvoices ( ) ;
var topupInvoice = invoices . Single ( invoice = > invoice . ItemCode = = "g" ) ;
Assert . Equal ( 0 , topupInvoice . Price ) ;
Assert . Equal ( "new" , topupInvoice . Status ) ;
2020-07-29 19:11:54 +09:00
}
}
2024-12-13 04:09:55 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanUsePoSAppJsonEndpoint ( )
{
using var tester = CreateServerTester ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var apps = user . GetController < UIAppsController > ( ) ;
var pos = user . GetController < UIPointOfSaleController > ( ) ;
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( user . StoreId ) ) . Model ) ;
var appType = PointOfSaleAppType . AppType ;
vm . AppName = "test" ;
vm . SelectedAppType = appType ;
var redirect = Assert . IsType < RedirectResult > ( apps . CreateApp ( user . StoreId , vm ) . Result ) ;
Assert . EndsWith ( "/settings/pos" , redirect . Url ) ;
var appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( user . StoreId ) . Result ) . Model ) ;
var app = appList . Apps [ 0 ] ;
var appData = new AppData { Id = app . Id , StoreDataId = app . StoreId , Name = app . AppName , AppType = appType } ;
apps . HttpContext . SetAppData ( appData ) ;
pos . HttpContext . SetAppData ( appData ) ;
var vmpos = await pos . UpdatePointOfSale ( app . Id ) . AssertViewModelAsync < UpdatePointOfSaleViewModel > ( ) ;
vmpos . Title = "App POS" ;
vmpos . Currency = "EUR" ;
Assert . IsType < RedirectToActionResult > ( pos . UpdatePointOfSale ( app . Id , vmpos ) . Result ) ;
// Failing requests
var ( invoiceId1 , error1 ) = await PosJsonRequest ( tester , app . Id , "amount=-21&discount=10&tip=2" ) ;
Assert . Null ( invoiceId1 ) ;
Assert . Equal ( "Negative amount is not allowed" , error1 ) ;
var ( invoiceId2 , error2 ) = await PosJsonRequest ( tester , app . Id , "amount=21&discount=-10&tip=-2" ) ;
Assert . Null ( invoiceId2 ) ;
Assert . Equal ( "Negative tip or discount is not allowed" , error2 ) ;
// Successful request
var ( invoiceId3 , error3 ) = await PosJsonRequest ( tester , app . Id , "amount=21" ) ;
Assert . NotNull ( invoiceId3 ) ;
Assert . Null ( error3 ) ;
// Check generated invoice
var invoices = await user . BitPay . GetInvoicesAsync ( ) ;
var invoice = invoices . First ( ) ;
Assert . Equal ( invoiceId3 , invoice . Id ) ;
Assert . Equal ( 21.00 m , invoice . Price ) ;
Assert . Equal ( "EUR" , invoice . Currency ) ;
}
private async Task < ( string invoiceId , string error ) > PosJsonRequest ( ServerTester tester , string appId , string query )
{
var uriBuilder = new UriBuilder ( tester . PayTester . ServerUri ) { Path = $"/apps/{appId}/pos/light" , Query = query } ;
var request = new HttpRequestMessage ( HttpMethod . Post , uriBuilder . Uri ) ;
request . Headers . Add ( "Accept" , "application/json" ) ;
var response = await tester . PayTester . HttpClient . SendAsync ( request ) ;
var content = await response . Content . ReadAsStringAsync ( ) ;
var json = JObject . Parse ( content ) ;
return ( json [ "invoiceId" ] ? . Value < string > ( ) , json [ "error" ] ? . Value < string > ( ) ) ;
}
2020-07-29 19:11:54 +09:00
}
}