Compare commits
85 Commits
histograms
...
v2.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
4a31cf0a09 | |||
82620ee327 | |||
6d284b4124 | |||
83fa8cbf0f | |||
9ba4b030ed | |||
272cc3d3c9 | |||
b5590a38fe | |||
443a350bad | |||
7013e618de | |||
363b60385b | |||
90635ffc4e | |||
056f850268 | |||
336f2d88e9 | |||
e16b4062b5 | |||
747dacf3b1 | |||
f00a71922f | |||
c97c9d4ece | |||
9d3f8672d9 | |||
fe48cd4236 | |||
587d3aa612 | |||
8a951940fd | |||
b726ef8a2e | |||
25e360e175 | |||
1d9ec253fb | |||
3cf1aa00fa | |||
36a5d0ee3f | |||
f5e5174045 | |||
ba2301ebfe | |||
df651a2157 | |||
2d2c1d5f2d | |||
0f93581ff5 | |||
397452a7fe | |||
cd3157361a | |||
29a89f185a | |||
2f7a5c2967 | |||
f07ed53f7e | |||
7348a6a62f | |||
b7ba53eb60 | |||
e389d6a96b | |||
0238dffc7a | |||
666445e8f7 | |||
36bada8feb | |||
b4946f4db1 | |||
f3d485da53 | |||
3342122be2 | |||
f59751853a | |||
a60c55c6df | |||
3bad5883bb | |||
d4c30866b7 | |||
7c92ce771f | |||
4601359ebe | |||
04b1130837 | |||
c377617b5a | |||
222e8f66df | |||
87e2f5f414 | |||
7de05700e9 | |||
841f41da2f | |||
73dcde7780 | |||
377ff52222 | |||
e4b5609d78 | |||
c93497af10 | |||
99dda66bbc | |||
4ce68f817d | |||
1c027be106 | |||
4a94074595 | |||
b49f6c3f86 | |||
9c8fe9277d | |||
a7c70e6912 | |||
28156a5a6b | |||
a3cfb9e5e4 | |||
25ccc6a9f9 | |||
8d1904c185 | |||
85cc221d50 | |||
588b00de45 | |||
656a1f8294 | |||
1dd37c5020 | |||
3c40dc1f49 | |||
6d560caf06 | |||
07f3301e32 | |||
9410a293f7 | |||
a0704eddb0 | |||
672491c66c | |||
bd91c45d1f | |||
f77cdb7148 | |||
7878a4365c |
BTCPayApp.CommonServer
BTCPayApp.CommonServer.csprojIBTCPayAppHubClient.cs
Models
AcceptInviteRequest.csAcceptInviteResult.csAccessTokenResult.csAppInstanceInfo.csAppUserInfo.csCreateStoreData.csSignupRequest.csSignupResult.cs
PosDataParser.csRoles.csBTCPayServer.Abstractions
BTCPayServer.Client
BTCPayServer.Client.csprojBTCPayServerClient.Apps.csBTCPayServerClient.Invoices.csBTCPayServerClient.Lightning.Internal.csBTCPayServerClient.Lightning.Store.csBTCPayServerClient.OnChainPaymentMethods.csBTCPayServerClient.OnChainWallet.Objects.csBTCPayServerClient.OnChainWallet.cs
Models
AppItemStats.csAppSalesStats.csCreatePayoutRequest.csCreatePullPaymentRequest.csGenerateOnChainWalletRequest.csLightningAutomatedPayoutSettings.csOnChainAutomatedPayoutSettings.csPaymentMethodCriteriaData.csPayoutData.csPayoutProcessorData.csRefundInvoiceRequest.csStoreBaseData.csWalletHistogramData.cs
Permissions.csBTCPayServer.Common
BTCPayNetwork.csBTCPayNetworkProvider.csBTCPayServer.Common.csprojMultiProcessingQueue.csSelectedChains.cs
BTCPayServer.Data
App
ApplicationDbContext.csApplicationDbContextFactory.csBTCPayServer.Data.csprojDBScripts
001.InvoiceFunctions.sql002.RefactorPayouts.sql003.RefactorPendingInvoicesPayments.sql004.MonitoredInvoices.sql
Data
AddressInvoiceData.csApplicationUser.csInvoiceData.Migration.csInvoiceData.csMigrationExtensions.csMigrationInterceptor.csPaymentData.Migration.csPaymentData.csPaymentRequestData.Migration.csPaymentRequestData.csPayoutData.Migration.csPayoutData.csPayoutProcessorData.csPendingInvoiceData.csPullPaymentData.cs
Migrations
20240405052858_cleanup_address_invoices.cs20240520042729_payoutsmigration.cs20240726113051_AppStuff.Designer.cs20240726113051_AppStuff.cs20240826065950_removeinvoicecols.cs20240827034505_migratepayouts.cs20240904092905_UpdateStoreOwnerRole.cs20240913034505_refactorpendinginvoicespayments.cs20240919085726_refactorinvoiceaddress.cs20240923065254_refactorpayments.cs20240924065254_monitoredinvoices.csApplicationDbContextModelSnapshot.cs
BTCPayServer.Tests
BTCPayServer.Tests.csprojBTCPayServerTester.csCheckoutUITests.csCrowdfundTests.csDatabaseTester.csDatabaseTests.csFastTests.csGreenfieldAPITests.csLanguageServiceTests.csPOSTests.csPSBTTests.csSeleniumTester.csSeleniumTests.csServerTester.csTestAccount.cs
TestData
ThirdPartyTests.csUnitTest1.csUnitTestBase.csUtilitiesTests.csdocker-compose.altcoins.ymldocker-compose.ymlsetup-dev-basics.shBTCPayServer
App
API
AppApiController.Account.csAppApiController.Store.csAppApiController.csProtobufFormatterAttribute.csProtobufFormatterModelBinder.csProtobufInputFormatter.csProtobufOutputFormatter.csResultOverrideFilter.csVSSController.cs
BTCPayAppExtensions.csBTCPayAppHub.csBTCPayAppLightningClient.csBTCPayAppLightningConnectionStringHandler.csBTCPayAppPlugin.csBTCPayAppState.csExts.csBlazor
Components
AppSales
AppTopItems
MainNav
StoreLightningBalance
StoreLightningServices
StoreWalletBalance
TruncateCenter
Configuration
Controllers
BitpayAccessTokenController.csBitpayInvoiceController.csBitpayRateController.cs
GreenField
GreenfieldApiKeysController.csGreenfieldAppsController.csGreenfieldFilesController.csGreenfieldInvoiceController.csGreenfieldLightningNodeApiController.Internal.csGreenfieldLightningNodeApiController.Store.csGreenfieldLightningNodeApiController.csGreenfieldNotificationsController.csGreenfieldObsoleteController.csGreenfieldPaymentRequestsController.csGreenfieldPayoutProcessorsController.csGreenfieldPullPaymentController.csGreenfieldReportsController.csGreenfieldServerInfoController.csGreenfieldServerRolesController.csGreenfieldStoreAutomatedLightningPayoutProcessorsController.csGreenfieldStoreAutomatedOnChainPayoutProcessorsController.csGreenfieldStoreEmailController.csGreenfieldStoreLightningAddressesController.csGreenfieldStoreOnChainPaymentMethodsController.WalletGeneration.csGreenfieldStoreOnChainPaymentMethodsController.csGreenfieldStoreOnChainWalletsController.csGreenfieldStorePaymentMethodsController.csGreenfieldStorePayoutProcessorsController.csGreenfieldStoreRatesConfigurationController.csGreenfieldStoreRatesController.csGreenfieldStoreRolesController.csGreenfieldStoreUsersController.csGreenfieldStoreWebhooksController.csGreenfieldStoresController.csGreenfieldTestApiKeyController.csGreenfieldUsersController.csLocalBTCPayServerClient.cs
UIAccountController.csUIAppsController.Dashboard.csUIAppsController.csUIHomeController.csUIInvoiceController.UI.csUIInvoiceController.csUILNURLAuthController.csUILNURLController.csUIManageController.APIKeys.csUIManageController.LoginCodes.csUIManageController.csUINotificationsController.csUIPaymentRequestController.csUIPullPaymentController.Boltcard.csUIPullPaymentController.csUIReportsController.csUIServerController.Roles.csUIServerController.Translations.csUIServerController.Users.csUIServerController.csUIStorePullPaymentsController.PullPayments.csUIStoresController.Dashboard.csUIStoresController.Email.csUIStoresController.Integrations.csUIStoresController.LightningLike.csUIStoresController.Onchain.csUIStoresController.Rates.csUIStoresController.Roles.csUIStoresController.Settings.csUIStoresController.Tokens.csUIStoresController.Users.csUIStoresController.csUIUserStoresController.csUIVaultController.csUIWalletsController.csData
AddressInvoiceDataExtensions.csInvoiceDataExtensions.csPaymentDataExtensions.csPaymentRequestDataExtensions.cs
Payouts
PullPayments
StoreBlob.csEvents
InvoiceStopWatchedEvent.csNewBlockEvent.csStoreCreatedEvent.csStoreEvent.csStoreRemovedEvent.csStoreUpdatedEvent.csUserDeletedEvent.csUserEvent.csUserRegisteredEvent.csUserStoreAddedEvent.csUserStoreEvent.csUserStoreRemovedEvent.csUserStoreUpdatedEvent.csUserUpdatedEvent.cs
Extensions.csExtensions
AuthorizationExtensions.csEmailSenderExtensions.csMoneyExtensions.csStoreExtensions.csUrlHelperExtensions.cs
Fido2
Forms
HostedServices
BitpayIPNSender.csBlobMigratorHostedService.csInvoiceBlobMigratorHostedService.csInvoiceEventSaverService.csInvoiceWatcher.csPaymentRequestsMigratorHostedService.csPayoutBlobMigratorHostedService.csPullPaymentHostedService.csUserEventHostedService.cs
Webhooks
Hosting
Models
InvoicingModels
ServerViewModels
StoreBrandingViewModel.csStoreViewModels
ViewPullPaymentModel.csWalletViewModels
Payments
Bitcoin
BitcoinLikePaymentHandler.csBitcoinPaymentMethodBitpayAPIExtension.csBitcoinPaymentModelExtension.csBitcoinPaymentPromptDetails.csNBXplorerListener.cs
IPaymentMethodHandler.csLightning
PayJoin
PaymentTypes.csPayoutProcessors
BaseAutomatedPayoutProcessor.cs
Lightning
OnChain
PayoutProcessorService.csPayoutProcessorsExtensions.csUIPayoutProcessorsController.csPayouts
Plugins
Crowdfund
PayButton/Controllers
PointOfSale
Shopify
Properties
Roles.csSecurity
Services
Altcoins
Monero
Configuration
Payments
MoneroLikeOnChainPaymentMethodDetails.csMoneroLikePaymentData.csMoneroLikePaymentMethodHandler.csMoneroPaymentLinkExtension.csMoneroPaymentModelExtension.csMoneroPaymentPromptDetails.cs
RPC
Services
MoneroLikeSummaryUpdaterHostedService.csMoneroListener.csMoneroRPCProvider.csMoneroSyncSummaryProvider.cs
UI
Zcash
Configuration
Payments
ZcashLikePaymentData.csZcashLikePaymentMethodHandler.csZcashPaymentLinkExtension.csZcashPaymentMethodConfig.csZcashPaymentModelExtension.csZcashPaymentPromptDetails.cs
RPC
Services
ZcashLikeSummaryUpdaterHostedService.csZcashListener.csZcashRPCProvider.csZcashSyncSummaryProvider.cs
UI
Apps
BTCPayServerEnvironment.csCheater.csDisplayFormatter.csInvoices
LightningHistogramService.csMigrationSettings.csNotifications
Reporting
ServerSettings.csStores
Translations.Default.csTranslations.csUserService.csWallets
Views
Shared
Bitcoin
Crowdfund
LayoutHead.cshtmlLayoutHeadStoreBranding.cshtmlLightning
ListRoles.cshtmlMonero
NFC
PointOfSale
PosData.cshtmlPosDataEntry.cshtmlTemplateEditor.cshtmlZcash
_BTCPaySupporters.cshtmlUIAccount
UIForms
UIInvoice
UIManage
UIServer
CreateDictionary.cshtmlCreateUser.cshtmlEditDictionary.cshtmlListDictionaries.cshtmlListUsers.cshtmlPolicies.cshtmlResetUserPassword.cshtmlUser.cshtml
UIStores
UIUserStores
UIWallets
wwwroot
checkout
img/readme
old
supporter.svgsupporter_acinq.svgsupporter_esc.svgsupporter_kraken.svgsupporter_pnxbet.svgsupporter_strike.svg
supporter_unbank.svgjs
main
pos
swagger/v1
swagger.template.apps.jsonswagger.template.invoices.jsonswagger.template.jsonswagger.template.payout-processors.jsonswagger.template.pull-payments.jsonswagger.template.stores-payment-methods.jsonswagger.template.stores-payment-methods.on-chain.jsonswagger.template.stores-wallet.on-chain.jsonswagger.template.stores-wallet.on-chain.objects.jsonswagger.template.stores.json
vendor/chartist
Build
README.mdbtcpayserver.sln@ -1,16 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,122 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayApp.CommonServer;
|
||||
|
||||
//methods available on the hub in the client
|
||||
public interface IBTCPayAppHubClient
|
||||
{
|
||||
Task NotifyServerEvent(ServerEvent ev);
|
||||
Task NotifyNetwork(string network);
|
||||
Task NotifyServerNode(string nodeInfo);
|
||||
Task TransactionDetected(TransactionDetectedRequest request);
|
||||
Task NewBlock(string block);
|
||||
|
||||
Task<LightningInvoice> CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest);
|
||||
Task<LightningInvoice?> GetLightningInvoice(uint256 paymentHash);
|
||||
Task<LightningPayment?> GetLightningPayment(uint256 paymentHash);
|
||||
Task<List<LightningPayment>> GetLightningPayments(ListPaymentsParams request);
|
||||
Task<List<LightningInvoice>> GetLightningInvoices(ListInvoicesParams request);
|
||||
Task<PayResponse> PayInvoice(string bolt11, long? amountMilliSatoshi);
|
||||
Task MasterUpdated(long? deviceIdentifier);
|
||||
Task<LightningNodeInformation> GetLightningNodeInfo();
|
||||
Task<LightningNodeBalance> GetLightningBalance();
|
||||
}
|
||||
|
||||
//methods available on the hub in the server
|
||||
public interface IBTCPayAppHubServer
|
||||
{
|
||||
Task<bool> DeviceMasterSignal(long deviceIdentifier, bool active);
|
||||
|
||||
Task<Dictionary<string,string>> Pair(PairRequest request);
|
||||
Task<AppHandshakeResponse> Handshake(AppHandshake request);
|
||||
Task<bool> BroadcastTransaction(string tx);
|
||||
Task<decimal> GetFeeRate(int blockTarget);
|
||||
Task<BestBlockResponse> GetBestBlock();
|
||||
|
||||
Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string[] txIds);
|
||||
Task<string> DeriveScript(string identifier);
|
||||
Task TrackScripts(string identifier, string[] scripts);
|
||||
Task<string> UpdatePsbt(string[] identifiers, string psbt);
|
||||
Task<CoinResponse[]> GetUTXOs(string[] identifiers);
|
||||
Task<Dictionary<string, TxResp[]>> GetTransactions(string[] identifiers);
|
||||
|
||||
Task SendInvoiceUpdate(string identifier, LightningInvoice lightningInvoice);
|
||||
}
|
||||
|
||||
public class ServerEvent(string type)
|
||||
{
|
||||
public string Type { get; } = type;
|
||||
public string? StoreId { get; init; }
|
||||
public string? UserId { get; init; }
|
||||
public string? InvoiceId { get; init; }
|
||||
}
|
||||
|
||||
public record TxResp(long Confirmations, long? Height, decimal BalanceChange, DateTimeOffset Timestamp, string TransactionId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{ Confirmations = {Confirmations}, Height = {Height}, BalanceChange = {BalanceChange}, Timestamp = {Timestamp}, TransactionId = {TransactionId} }}";
|
||||
}
|
||||
}
|
||||
|
||||
public class TransactionDetectedRequest
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public string TxId { get; set; }
|
||||
public string[] SpentScripts { get; set; }
|
||||
public string[] ReceivedScripts { get; set; }
|
||||
public bool Confirmed { get; set; }
|
||||
}
|
||||
|
||||
public class CoinResponse
|
||||
{
|
||||
public string Identifier{ get; set; }
|
||||
public bool Confirmed { get; set; }
|
||||
public string Script { get; set; }
|
||||
public string Outpoint { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public class TxInfoResponse
|
||||
{
|
||||
public Dictionary<string,TransactionResponse> Txs { get; set; }
|
||||
public Dictionary<string,string> Blocks { get; set; }
|
||||
public Dictionary<string,int> BlockHeghts { get; set; }
|
||||
}
|
||||
|
||||
public class TransactionResponse
|
||||
{
|
||||
public string? BlockHash { get; set; }
|
||||
public int? BlockHeight { get; set; }
|
||||
public string Transaction { get; set; }
|
||||
}
|
||||
|
||||
public class BestBlockResponse
|
||||
{
|
||||
public required string BlockHash { get; set; }
|
||||
public required int BlockHeight { get; set; }
|
||||
public string BlockHeader { get; set; }
|
||||
}
|
||||
|
||||
public class AppHandshake
|
||||
{
|
||||
public string[] Identifiers { get; set; }
|
||||
}
|
||||
|
||||
public class AppHandshakeResponse
|
||||
{
|
||||
//response about identifiers being tracked successfully
|
||||
public string[] IdentifiersAcknowledged { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class PairRequest
|
||||
{
|
||||
public Dictionary<string, string?> Derivations { get; set; } = new();
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class AcceptInviteRequest
|
||||
{
|
||||
[Required]
|
||||
public string? UserId { get; init; }
|
||||
|
||||
[Required]
|
||||
public string? Code { get; init; }
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class AcceptInviteResult(string email)
|
||||
{
|
||||
public string Email { get; init; } = email;
|
||||
public bool? RequiresUserApproval { get; set; }
|
||||
public bool? EmailHasBeenConfirmed { get; set; }
|
||||
public string? PasswordSetCode { get; set; }
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class AccessTokenResult(string accessToken, string refreshToken, DateTimeOffset expiry)
|
||||
{
|
||||
public string AccessToken { get; init; } = accessToken;
|
||||
public string RefreshToken { get; init; } = refreshToken;
|
||||
public DateTimeOffset Expiry { get; init; } = expiry;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class AppInstanceInfo
|
||||
{
|
||||
public string BaseUrl { get; set; }
|
||||
public string ServerName { get; set; }
|
||||
public string? ContactUrl { get; set; }
|
||||
public string? LogoUrl { get; set; }
|
||||
public string? CustomThemeCssUrl { get; set; }
|
||||
public string? CustomThemeExtension { get; set; }
|
||||
public bool RegistrationEnabled { get; set; }
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
// public partial class LightningPayment
|
||||
// {
|
||||
// public string PaymentHash { get; set; }
|
||||
// public string? PaymentId { get; set; }
|
||||
// public string? Preimage { get; set; }
|
||||
// public string? Secret { get; set; }
|
||||
// public bool Inbound { get; set; }
|
||||
// public DateTimeOffset Timestamp { get; set; }
|
||||
// public long Value { get; set; }
|
||||
// public LightningPaymentStatus Status { get; set; }
|
||||
//
|
||||
// //you can have multiple requests generated for the same payment hash, but once you reveal the preimage, you should reject any attempt to pay the same payment hash
|
||||
// public List<string> PaymentRequests { get; set; }
|
||||
// [JsonIgnore]
|
||||
// public Dictionary<string, JsonDocument> AdditionalData { get; set; }
|
||||
//
|
||||
// }
|
||||
|
||||
public class AppUserInfo
|
||||
{
|
||||
public string? UserId { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
public IEnumerable<string>? Roles { get; set; }
|
||||
public IEnumerable<AppUserStoreInfo>? Stores { get; set; }
|
||||
|
||||
public void SetInfo(string email, string? name, string? imageUrl)
|
||||
{
|
||||
Email = email;
|
||||
Name = name;
|
||||
ImageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public static bool Equals(AppUserInfo? x, AppUserInfo? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return true;
|
||||
if (ReferenceEquals(x, null)) return false;
|
||||
if (ReferenceEquals(y, null)) return false;
|
||||
if (x.GetType() != y.GetType()) return false;
|
||||
return x.UserId == y.UserId && x.Email == y.Email &&
|
||||
x.Name == y.Name && x.ImageUrl == y.ImageUrl &&
|
||||
Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores);
|
||||
}
|
||||
}
|
||||
|
||||
public class AppUserStoreInfo
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? LogoUrl { get; set; }
|
||||
public string? RoleId { get; set; }
|
||||
public string? PosAppId { get; set; }
|
||||
public string? DefaultCurrency { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public IEnumerable<string>? Permissions { get; set; }
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class CreateStoreData
|
||||
{
|
||||
public string? DefaultCurrency { get; set; }
|
||||
public string? RecommendedExchangeId { get; set; }
|
||||
public Dictionary<string, string>? Exchanges { get; set; }
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class SignupRequest
|
||||
{
|
||||
[Required]
|
||||
public string? Email { get; init; }
|
||||
|
||||
[Required]
|
||||
public string? Password { get; init; }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace BTCPayApp.CommonServer.Models;
|
||||
|
||||
public class SignupResult
|
||||
{
|
||||
public string? Email { get; set; }
|
||||
public bool RequiresConfirmedEmail { get; set; }
|
||||
public bool RequiresUserApproval { get; set; }
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayApp.CommonServer;
|
||||
|
||||
public static class PosDataParser
|
||||
{
|
||||
public static Dictionary<string, object> ParsePosData(JToken? posData)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
if (posData is JObject jobj)
|
||||
{
|
||||
foreach (var item in jobj)
|
||||
{
|
||||
ParsePosDataItem(item, ref result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ParsePosDataItem(KeyValuePair<string, JToken?> item, ref Dictionary<string, object> result)
|
||||
{
|
||||
switch (item.Value?.Type)
|
||||
{
|
||||
case JTokenType.Array:
|
||||
var items = item.Value.AsEnumerable().ToList();
|
||||
var arrayResult = new List<object>();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
arrayResult.Add(items[i] is JObject
|
||||
? ParsePosData(items[i])
|
||||
: items[i].ToString());
|
||||
}
|
||||
|
||||
result.TryAdd(item.Key, arrayResult);
|
||||
|
||||
break;
|
||||
case JTokenType.Object:
|
||||
result.TryAdd(item.Key, ParsePosData(item.Value));
|
||||
break;
|
||||
case null:
|
||||
break;
|
||||
default:
|
||||
result.TryAdd(item.Key, item.Value.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayApp.CommonServer;
|
||||
|
||||
public class Roles
|
||||
{
|
||||
public const string ServerAdmin = "ServerAdmin";
|
||||
public static bool HasServerAdmin(IList<string> roles)
|
||||
{
|
||||
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
@ -36,7 +36,6 @@
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayApp.CommonServer\BTCPayApp.CommonServer.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,12 +1,11 @@
|
||||
namespace BTCPayApp.CommonServer
|
||||
namespace BTCPayServer.Abstractions.Constants
|
||||
{
|
||||
public class AuthenticationSchemes
|
||||
{
|
||||
public const string Cookie = "Identity.Application";
|
||||
public const string Bitpay = "Bitpay";
|
||||
public const string Greenfield = "Greenfield.APIKeys,Greenfield.Basic,Greenfield.Bearer";
|
||||
public const string Greenfield = "Greenfield.APIKeys,Greenfield.Basic";
|
||||
public const string GreenfieldAPIKeys = "Greenfield.APIKeys";
|
||||
public const string GreenfieldBasic = "Greenfield.Basic";
|
||||
public const string GreenfieldBearer = "Greenfield.Bearer";
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
using BTCPayApp.CommonServer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace BTCPayApp.CommonServer
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class PolicyRequirement : IAuthorizationRequirement
|
||||
{
|
@ -12,7 +12,7 @@
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Configurations>Debug;Release;Altcoins-Debug;Altcoins-Release</Configurations>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -58,6 +59,20 @@ public partial class BTCPayServerClient
|
||||
return await SendHttpRequest<CrowdfundAppData>($"api/v1/apps/crowdfund/{appId}", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<AppSalesStats> GetAppSales(string appId, int numberOfDays = 7, CancellationToken token = default)
|
||||
{
|
||||
if (appId == null) throw new ArgumentNullException(nameof(appId));
|
||||
var queryPayload = new Dictionary<string, object> { { nameof(numberOfDays), numberOfDays } };
|
||||
return await SendHttpRequest<AppSalesStats>($"api/v1/apps/{appId}/sales", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<List<AppItemStats>> GetAppTopItems(string appId, int offset = 0, int count = 10, CancellationToken token = default)
|
||||
{
|
||||
if (appId == null) throw new ArgumentNullException(nameof(appId));
|
||||
var queryPayload = new Dictionary<string, object> { { nameof(offset), offset }, { nameof(count), count } };
|
||||
return await SendHttpRequest<List<AppItemStats>>($"api/v1/apps/{appId}/top-items", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteApp(string appId, CancellationToken token = default)
|
||||
{
|
||||
if (appId == null) throw new ArgumentNullException(nameof(appId));
|
||||
|
@ -46,9 +46,15 @@ public partial class BTCPayServerClient
|
||||
return await SendHttpRequest<InvoiceData>($"api/v1/stores/{storeId}/invoices/{invoiceId}", null, HttpMethod.Get, token);
|
||||
}
|
||||
public virtual async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId, string invoiceId,
|
||||
bool onlyAccountedPayments = true, bool includeSensitive = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<InvoicePaymentMethodDataModel[]>($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods", null, HttpMethod.Get, token);
|
||||
var queryPayload = new Dictionary<string, object>
|
||||
{
|
||||
{ nameof(onlyAccountedPayments), onlyAccountedPayments },
|
||||
{ nameof(includeSensitive), includeSensitive }
|
||||
};
|
||||
return await SendHttpRequest<InvoicePaymentMethodDataModel[]>($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task ArchiveInvoice(string storeId, string invoiceId,
|
||||
|
@ -21,13 +21,6 @@ public partial class BTCPayServerClient
|
||||
return await SendHttpRequest<LightningNodeBalanceData>($"api/v1/server/lightning/{cryptoCode}/balance", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<HistogramData> GetLightningNodeHistogram(string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = type == null ? null : new Dictionary<string, string> { ["type"] = type.ToString() };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/server/lightning/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -21,13 +21,6 @@ public partial class BTCPayServerClient
|
||||
return await SendHttpRequest<LightningNodeBalanceData>($"api/v1/stores/{storeId}/lightning/{cryptoCode}/balance", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<HistogramData> GetLightningNodeHistogram(string storeId, string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = type == null ? null : new Dictionary<string, string> { ["type"] = type.ToString() };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/lightning/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ public partial class BTCPayServerClient
|
||||
int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<UpdatePaymentMethodRequest, OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview",
|
||||
return await SendHttpRequest<UpdatePaymentMethodRequest, OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview",
|
||||
new Dictionary<string, object> { { "offset", offset }, { "amount", amount } },
|
||||
new UpdatePaymentMethodRequest { Config = JValue.CreateString(derivationScheme) },
|
||||
HttpMethod.Post, token);
|
||||
@ -25,7 +25,7 @@ public partial class BTCPayServerClient
|
||||
string storeId, string paymentMethodId, int offset = 0, int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview",
|
||||
return await SendHttpRequest<OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview",
|
||||
new Dictionary<string, object> { { "offset", offset }, { "amount", amount } }, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public partial class BTCPayServerClient
|
||||
string paymentMethodId, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<GenerateOnChainWalletResponse>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate", request, HttpMethod.Post, token);
|
||||
return await SendHttpRequest<GenerateOnChainWalletResponse>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/generate", request, HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ public partial class BTCPayServerClient
|
||||
parameters.Add("includeNeighbourData", v);
|
||||
try
|
||||
{
|
||||
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, HttpMethod.Get, token);
|
||||
}
|
||||
catch (GreenfieldAPIException err) when (err.APIError.Code == "wallet-object-not-found")
|
||||
{
|
||||
@ -31,17 +31,17 @@ public partial class BTCPayServerClient
|
||||
parameters.Add("ids", ids);
|
||||
if (query?.IncludeNeighbourData is bool v)
|
||||
parameters.Add("includeNeighbourData", v);
|
||||
return await SendHttpRequest<OnChainWalletObjectData[]>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", parameters, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<OnChainWalletObjectData[]>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects", parameters, HttpMethod.Get, token);
|
||||
}
|
||||
public virtual async Task RemoveOnChainWalletObject(string storeId, string cryptoCode, OnChainWalletObjectId objectId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", null, HttpMethod.Delete, token);
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
public virtual async Task<OnChainWalletObjectData> AddOrUpdateOnChainWalletObject(string storeId, string cryptoCode, AddOnChainWalletObjectRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", request, HttpMethod.Post, token);
|
||||
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects", request, HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task AddOrUpdateOnChainWalletLink(string storeId, string cryptoCode,
|
||||
@ -49,7 +49,7 @@ public partial class BTCPayServerClient
|
||||
AddOnChainWalletObjectLinkRequest request = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links", request, HttpMethod.Post, token);
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}/links", request, HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveOnChainWalletLinks(string storeId, string cryptoCode,
|
||||
@ -57,6 +57,6 @@ public partial class BTCPayServerClient
|
||||
OnChainWalletObjectId link,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", null, HttpMethod.Delete, token);
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
}
|
||||
|
@ -14,16 +14,8 @@ public partial class BTCPayServerClient
|
||||
public virtual async Task<OnChainWalletOverviewData> ShowOnChainWalletOverview(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<OnChainWalletOverviewData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet", null, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<OnChainWalletOverviewData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<HistogramData> GetOnChainWalletHistogram(string storeId, string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = type == null ? null : new Dictionary<string, string> { ["type"] = type.ToString() };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
@ -32,13 +24,13 @@ public partial class BTCPayServerClient
|
||||
{
|
||||
queryParams.Add("blockTarget", blockTarget);
|
||||
}
|
||||
return await SendHttpRequest<OnChainWalletFeeRateData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feeRate", queryParams, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<OnChainWalletFeeRateData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/feerate", queryParams, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletAddressData> GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, bool forceGenerate = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<OnChainWalletAddressData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>
|
||||
return await SendHttpRequest<OnChainWalletAddressData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/address", new Dictionary<string, object>
|
||||
{
|
||||
{"forceGenerate", forceGenerate}
|
||||
}, HttpMethod.Get, token);
|
||||
@ -47,7 +39,7 @@ public partial class BTCPayServerClient
|
||||
public virtual async Task UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", null, HttpMethod.Delete, token);
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/address", null, HttpMethod.Delete, token);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
|
||||
@ -67,14 +59,14 @@ public partial class BTCPayServerClient
|
||||
{
|
||||
query.Add(nameof(skip), skip);
|
||||
}
|
||||
return await SendHttpRequest<IEnumerable<OnChainWalletTransactionData>>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<IEnumerable<OnChainWalletTransactionData>>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", query, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> GetOnChainWalletTransaction(
|
||||
string storeId, string cryptoCode, string transactionId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}", null, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions/{transactionId}", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> PatchOnChainWalletTransaction(
|
||||
@ -82,7 +74,7 @@ public partial class BTCPayServerClient
|
||||
PatchOnChainTransactionRequest request,
|
||||
bool force = false, CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<PatchOnChainTransactionRequest, OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}",
|
||||
return await SendHttpRequest<PatchOnChainTransactionRequest, OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions/{transactionId}",
|
||||
new Dictionary<string, object> { {"force", force} }, request, HttpMethod.Patch, token);
|
||||
}
|
||||
|
||||
@ -90,7 +82,7 @@ public partial class BTCPayServerClient
|
||||
string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<IEnumerable<OnChainWalletUTXOData>>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos", null, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<IEnumerable<OnChainWalletUTXOData>>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/utxos", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> CreateOnChainTransaction(string storeId,
|
||||
@ -102,7 +94,7 @@ public partial class BTCPayServerClient
|
||||
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
|
||||
"Please use CreateOnChainTransactionButDoNotBroadcast when wanting to only create the transaction");
|
||||
}
|
||||
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", request, HttpMethod.Post, token);
|
||||
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", request, HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task<Transaction> CreateOnChainTransactionButDoNotBroadcast(string storeId,
|
||||
@ -114,6 +106,6 @@ public partial class BTCPayServerClient
|
||||
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
|
||||
"Please use CreateOnChainTransaction when wanting to also broadcast the transaction");
|
||||
}
|
||||
return Transaction.Parse(await SendHttpRequest<string>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", request, HttpMethod.Post, token), network);
|
||||
return Transaction.Parse(await SendHttpRequest<string>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", request, HttpMethod.Post, token), network);
|
||||
}
|
||||
}
|
||||
|
15
BTCPayServer.Client/Models/AppItemStats.cs
Normal file
15
BTCPayServer.Client/Models/AppItemStats.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class AppItemStats
|
||||
{
|
||||
public string ItemCode { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int SalesCount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Total { get; set; }
|
||||
public string TotalFormatted { get; set; }
|
||||
}
|
19
BTCPayServer.Client/Models/AppSalesStats.cs
Normal file
19
BTCPayServer.Client/Models/AppSalesStats.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class AppSalesStats
|
||||
{
|
||||
public int SalesCount { get; set; }
|
||||
public IEnumerable<AppSalesStatsItem> Series { get; set; }
|
||||
}
|
||||
|
||||
public class AppSalesStatsItem
|
||||
{
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTime Date { get; set; }
|
||||
public string Label { get; set; }
|
||||
public int SalesCount { get; set; }
|
||||
}
|
@ -8,6 +8,6 @@ namespace BTCPayServer.Client.Models
|
||||
public string Destination { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Amount { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string PayoutMethodId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace BTCPayServer.Client.Models
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? StartsAt { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
public string[] PayoutMethods { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
public class GenerateOnChainWalletRequest
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public int AccountNumber { get; set; } = 0;
|
||||
[JsonConverter(typeof(MnemonicJsonConverter))]
|
||||
public Mnemonic ExistingMnemonic { get; set; }
|
||||
@ -29,6 +30,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
public class ConfigData
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public string AccountDerivation { get; set; }
|
||||
[JsonExtensionData]
|
||||
IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
|
@ -6,7 +6,7 @@ namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class LightningAutomatedPayoutSettings
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
public string PayoutMethodId { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
|
@ -6,7 +6,7 @@ namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class OnChainAutomatedPayoutSettings
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
public string PayoutMethodId { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
|
@ -5,7 +5,7 @@ namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class PaymentMethodCriteriaData
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
public string CurrencyCode { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
|
@ -21,12 +21,13 @@ namespace BTCPayServer.Client.Models
|
||||
public string Id { get; set; }
|
||||
public string PullPaymentId { get; set; }
|
||||
public string Destination { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string PayoutMethodId { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
public decimal OriginalAmount { get; set; }
|
||||
public string OriginalCurrency { get; set; }
|
||||
public string PayoutCurrency { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? PaymentMethodAmount { get; set; }
|
||||
public decimal? PayoutAmount { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PayoutState State { get; set; }
|
||||
public int Revision { get; set; }
|
||||
|
@ -4,6 +4,6 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FriendlyName { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
public string[] PayoutMethods { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace BTCPayServer.Client.Models
|
||||
public class RefundInvoiceRequest
|
||||
{
|
||||
public string? Name { get; set; } = null;
|
||||
public string? PaymentMethod { get; set; }
|
||||
public string? PayoutMethodId { get; set; }
|
||||
public string? Description { get; set; } = null;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string Website { get; set; }
|
||||
|
||||
public string BrandColor { get; set; }
|
||||
public bool ApplyBrandColorToBackend { get; set; }
|
||||
public string LogoUrl { get; set; }
|
||||
public string CssUrl { get; set; }
|
||||
public string PaymentSoundUrl { get; set; }
|
||||
|
@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public enum HistogramType
|
||||
{
|
||||
Week,
|
||||
Month,
|
||||
Year
|
||||
}
|
||||
|
||||
public class HistogramData
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public HistogramType Type { get; set; }
|
||||
public List<decimal> Series { get; set; }
|
||||
public List<DateTimeOffset> Labels { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Balance { get; set; }
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
@ -16,7 +18,7 @@ namespace BTCPayServer.Client
|
||||
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
|
||||
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
|
||||
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
|
||||
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
|
||||
public const string CanModifyWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
|
||||
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
|
||||
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
|
||||
public const string CanViewReports = "btcpay.store.canviewreports";
|
||||
@ -48,7 +50,7 @@ namespace BTCPayServer.Client
|
||||
yield return CanViewInvoices;
|
||||
yield return CanCreateInvoice;
|
||||
yield return CanModifyInvoices;
|
||||
yield return CanModifyStoreWebhooks;
|
||||
yield return CanModifyWebhooks;
|
||||
yield return CanModifyServerSettings;
|
||||
yield return CanModifyStoreSettings;
|
||||
yield return CanViewStoreSettings;
|
||||
@ -104,6 +106,16 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
return policy.StartsWith("btcpay.user", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static readonly CultureInfo _culture = new (CultureInfo.InvariantCulture.Name);
|
||||
public static string DisplayName(string policy)
|
||||
{
|
||||
var p = policy.Split(".");
|
||||
if (p.Length < 3 || p[0] != "btcpay") return policy;
|
||||
var constName = typeof(Policies).GetFields().Select(f => f.Name).FirstOrDefault(f => f.Equals(p[^1], StringComparison.OrdinalIgnoreCase));
|
||||
var perm = string.IsNullOrEmpty(constName) ? string.Join(' ', p[2..]) : Regex.Replace(constName, "([A-Z])", " $1", RegexOptions.Compiled).Trim();
|
||||
return $"{_culture.TextInfo.ToTitleCase(p[1])}: {_culture.TextInfo.ToTitleCase(perm)}";
|
||||
}
|
||||
}
|
||||
|
||||
public class PermissionSet
|
||||
@ -247,7 +259,7 @@ namespace BTCPayServer.Client
|
||||
Policies.CanManagePullPayments,
|
||||
Policies.CanModifyInvoices,
|
||||
Policies.CanViewStoreSettings,
|
||||
Policies.CanModifyStoreWebhooks,
|
||||
Policies.CanModifyWebhooks,
|
||||
Policies.CanModifyPaymentRequests,
|
||||
Policies.CanManagePayouts,
|
||||
Policies.CanUseLightningNodeInStore);
|
||||
|
@ -4,6 +4,8 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Common;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
@ -131,7 +133,7 @@ namespace BTCPayServer
|
||||
|
||||
public string GetTrackedDestination(Script scriptPubKey)
|
||||
{
|
||||
return scriptPubKey.Hash.ToString() + "#" + CryptoCode.ToUpperInvariant();
|
||||
return scriptPubKey.Hash.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using StandardConfiguration;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -30,11 +34,6 @@ namespace BTCPayServer
|
||||
Logs logs)
|
||||
{
|
||||
var networksList = networks.ToList();
|
||||
#if !ALTCOINS
|
||||
var onlyBTC = networksList.Count == 1 && networksList.First().IsBTC;
|
||||
if (!onlyBTC)
|
||||
throw new ConfigException($"This build of BTCPay Server does not support altcoins. Configured networks: {string.Join(',', networksList.Select(n => n.CryptoCode).ToArray())}");
|
||||
#endif
|
||||
_NBXplorerNetworkProvider = nbxplorerNetworkProvider;
|
||||
NetworkType = nbxplorerNetworkProvider.NetworkType;
|
||||
foreach (var network in networksList)
|
||||
|
@ -1,13 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.3.1" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Altcoins\" />
|
||||
</ItemGroup>
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Bson;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -11,17 +12,12 @@ namespace BTCPayServer
|
||||
{
|
||||
HashSet<string> chains = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
bool all = false;
|
||||
public SelectedChains(IConfiguration configuration, Logs logs)
|
||||
public SelectedChains(IConfiguration configuration)
|
||||
{
|
||||
foreach (var chain in (configuration["chains"] ?? "btc")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.ToUpperInvariant()))
|
||||
{
|
||||
if (new[] { "ETH", "USDT20", "FAU" }.Contains(chain, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
logs.Configuration.LogWarning($"'{chain}' is not anymore supported, please remove it from 'chains'");
|
||||
continue;
|
||||
}
|
||||
if (chain == "*")
|
||||
{
|
||||
all = true;
|
||||
|
@ -1,32 +0,0 @@
|
||||
using BTCPayServer.Data;
|
||||
using Laraue.EfCoreTriggers.Common.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.App.BackupStorage;
|
||||
|
||||
public class AppStorageItemData
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public long Version { get; set; }
|
||||
public byte[] Value { get; set; }
|
||||
public string UserId { get; set; }
|
||||
|
||||
public static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<AppStorageItemData>()
|
||||
.HasKey(o => new {o.Key, o.Version, o.UserId});
|
||||
builder.Entity<AppStorageItemData>()
|
||||
.HasIndex(o => new {o.Key, o.UserId}).IsUnique();
|
||||
|
||||
builder.Entity<ApplicationUser>().HasMany(user => user.AppStorageItems).WithOne(data => data.User)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<AppStorageItemData>()
|
||||
.BeforeInsert(trigger => trigger
|
||||
.Action(group => group
|
||||
.Delete<AppStorageItemData>((@ref, entity) => @ref.New.UserId == entity.UserId && @ref.New.Key == entity.Key &&
|
||||
@ref.New.Version > entity.Version)));
|
||||
}
|
||||
|
||||
public ApplicationUser User { get; set; }
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.App.BackupStorage;
|
||||
using Laraue.EfCoreTriggers.PostgreSql.Extensions;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
@ -17,7 +15,6 @@ namespace BTCPayServer.Data
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
// Same as launchsettings.json, it's connecting to the docker's postgres.
|
||||
builder.UseNpgsql("User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=btcpayserver");
|
||||
builder.UsePostgreSqlTriggers();
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
}
|
||||
@ -28,8 +25,6 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
}
|
||||
public DbSet<AddressInvoiceData> AddressInvoices { get; set; }
|
||||
public DbSet<AppStorageItemData> AppStorageItems { get; set; }
|
||||
|
||||
public DbSet<APIKeyData> ApiKeys { get; set; }
|
||||
public DbSet<AppData> Apps { get; set; }
|
||||
public DbSet<StoredFile> Files { get; set; }
|
||||
@ -44,7 +39,6 @@ namespace BTCPayServer.Data
|
||||
public DbSet<PaymentRequestData> PaymentRequests { get; set; }
|
||||
public DbSet<PaymentData> Payments { get; set; }
|
||||
public DbSet<PayoutData> Payouts { get; set; }
|
||||
public DbSet<PendingInvoiceData> PendingInvoices { get; set; }
|
||||
public DbSet<PlannedTransaction> PlannedTransactions { get; set; }
|
||||
public DbSet<PullPaymentData> PullPayments { get; set; }
|
||||
public DbSet<RefundData> Refunds { get; set; }
|
||||
@ -73,7 +67,7 @@ namespace BTCPayServer.Data
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
// some of the data models don't have OnModelCreating for now, commenting them
|
||||
AppStorageItemData.OnModelCreating(builder);
|
||||
|
||||
ApplicationUser.OnModelCreating(builder, Database);
|
||||
AddressInvoiceData.OnModelCreating(builder);
|
||||
APIKeyData.OnModelCreating(builder, Database);
|
||||
@ -88,9 +82,8 @@ namespace BTCPayServer.Data
|
||||
PairingCodeData.OnModelCreating(builder);
|
||||
//PayjoinLock.OnModelCreating(builder);
|
||||
PaymentRequestData.OnModelCreating(builder, Database);
|
||||
PaymentData.OnModelCreating(builder, Database);
|
||||
PaymentData.OnModelCreating(builder);
|
||||
PayoutData.OnModelCreating(builder, Database);
|
||||
PendingInvoiceData.OnModelCreating(builder);
|
||||
//PlannedTransaction.OnModelCreating(builder);
|
||||
PullPaymentData.OnModelCreating(builder, Database);
|
||||
RefundData.OnModelCreating(builder);
|
||||
|
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Laraue.EfCoreTriggers.PostgreSql.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
|
||||
|
||||
@ -12,20 +10,20 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public class ApplicationDbContextFactory : BaseDbContextFactory<ApplicationDbContext>
|
||||
{
|
||||
public ApplicationDbContextFactory(IOptions<DatabaseOptions> options) : base(options, "")
|
||||
public ApplicationDbContextFactory(IOptions<DatabaseOptions> options, ILoggerFactory loggerFactory) : base(options, "")
|
||||
{
|
||||
LoggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public ILoggerFactory LoggerFactory { get; }
|
||||
|
||||
public override ApplicationDbContext CreateContext(Action<NpgsqlDbContextOptionsBuilder> npgsqlOptionsAction = null)
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
|
||||
builder.UseLoggerFactory(LoggerFactory);
|
||||
builder.AddInterceptors(MigrationInterceptor.Instance);
|
||||
ConfigureBuilder(builder, npgsqlOptionsAction);
|
||||
|
||||
builder.UsePostgreSqlTriggers();
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Laraue.EfCoreTriggers.PostgreSql" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@ -18,4 +17,10 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="DBScripts\*.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="DBScripts\001.InvoiceFunctions.sql" />
|
||||
<None Remove="DBScripts\002.RefactorPayouts.sql" />
|
||||
<None Remove="DBScripts\003.RefactorPendingInvoicesPayments.sql" />
|
||||
<None Remove="DBScripts\004.MonitoredInvoices.sql" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
12
BTCPayServer.Data/DBScripts/001.InvoiceFunctions.sql
Normal file
12
BTCPayServer.Data/DBScripts/001.InvoiceFunctions.sql
Normal file
@ -0,0 +1,12 @@
|
||||
CREATE OR REPLACE FUNCTION get_orderid(invoice_blob jsonb)
|
||||
RETURNS text AS $$
|
||||
SELECT invoice_blob->'metadata'->>'orderId';
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_itemcode(invoice_blob jsonb)
|
||||
RETURNS text AS $$
|
||||
SELECT invoice_blob->'metadata'->>'itemCode';
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Invoices_Metadata_OrderId" ON "Invoices" (get_orderid("Blob2")) WHERE get_orderid("Blob2") IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IX_Invoices_Metadata_ItemCode" ON "Invoices" (get_itemcode("Blob2")) WHERE get_itemcode("Blob2") IS NOT NULL;
|
62
BTCPayServer.Data/DBScripts/002.RefactorPayouts.sql
Normal file
62
BTCPayServer.Data/DBScripts/002.RefactorPayouts.sql
Normal file
@ -0,0 +1,62 @@
|
||||
-- Rename column
|
||||
ALTER TABLE "Payouts" RENAME COLUMN "PaymentMethodId" TO "PayoutMethodId";
|
||||
|
||||
-- Add Currency column, guessed from the PaymentMethodId
|
||||
ALTER TABLE "Payouts" ADD COLUMN "Currency" TEXT;
|
||||
UPDATE "Payouts" SET
|
||||
"Currency" = split_part("PayoutMethodId", '_', 1),
|
||||
"PayoutMethodId"=
|
||||
CASE
|
||||
WHEN ("Blob"->>'Amount')::NUMERIC < 0 THEN 'TOPUP'
|
||||
WHEN split_part("PayoutMethodId", '_', 2) = 'LightningLike' THEN split_part("PayoutMethodId", '_', 1) || '-LN'
|
||||
ELSE split_part("PayoutMethodId", '_', 1) || '-CHAIN'
|
||||
END;
|
||||
ALTER TABLE "Payouts" ALTER COLUMN "Currency" SET NOT NULL;
|
||||
|
||||
-- Remove Currency and Limit from PullPayment Blob, and put it into the columns in the table
|
||||
ALTER TABLE "PullPayments" ADD COLUMN "Currency" TEXT;
|
||||
UPDATE "PullPayments" SET "Currency" = "Blob"->>'Currency';
|
||||
ALTER TABLE "PullPayments" ALTER COLUMN "Currency" SET NOT NULL;
|
||||
ALTER TABLE "PullPayments" ADD COLUMN "Limit" NUMERIC;
|
||||
UPDATE "PullPayments" SET "Limit" = ("Blob"->>'Limit')::NUMERIC;
|
||||
ALTER TABLE "PullPayments" ALTER COLUMN "Limit" SET NOT NULL;
|
||||
|
||||
-- Remove unused properties, rename SupportedPaymentMethods, and fix legacy payment methods IDs
|
||||
UPDATE "PullPayments" SET
|
||||
"Blob" = jsonb_set(
|
||||
"Blob" - 'SupportedPaymentMethods' - 'Limit' - 'Currency' - 'Period',
|
||||
'{SupportedPayoutMethods}',
|
||||
(SELECT jsonb_agg(to_jsonb(
|
||||
CASE
|
||||
WHEN split_part(value::TEXT, '_', 2) = 'LightningLike' THEN split_part(value::TEXT, '_', 1) || '-LN'
|
||||
ELSE split_part(value::TEXT, '_', 1) || '-CHAIN'
|
||||
END))
|
||||
FROM jsonb_array_elements_text("Blob"->'SupportedPaymentMethods') AS value
|
||||
));
|
||||
|
||||
--Remove "Amount" and "CryptoAmount" from Payout Blob, and put it into the columns in the table
|
||||
-- Respectively "OriginalAmount" and "Amount"
|
||||
|
||||
ALTER TABLE "Payouts" ADD COLUMN "Amount" NUMERIC;
|
||||
UPDATE "Payouts" SET "Amount" = ("Blob"->>'CryptoAmount')::NUMERIC;
|
||||
|
||||
ALTER TABLE "Payouts" ADD COLUMN "OriginalAmount" NUMERIC;
|
||||
UPDATE "Payouts" SET "OriginalAmount" = ("Blob"->>'Amount')::NUMERIC;
|
||||
ALTER TABLE "Payouts" ALTER COLUMN "OriginalAmount" SET NOT NULL;
|
||||
|
||||
ALTER TABLE "Payouts" ADD COLUMN "OriginalCurrency" TEXT;
|
||||
|
||||
|
||||
UPDATE "Payouts" p
|
||||
SET
|
||||
"OriginalCurrency" = "Currency",
|
||||
"Blob" = "Blob" - 'Amount' - 'CryptoAmount'
|
||||
WHERE "PullPaymentDataId" IS NULL AND "OriginalCurrency" IS NULL;
|
||||
|
||||
UPDATE "Payouts" p
|
||||
SET
|
||||
"OriginalCurrency" = pp."Currency"
|
||||
FROM "PullPayments" pp
|
||||
WHERE "OriginalCurrency" IS NULL AND pp."Id"=p."PullPaymentDataId";
|
||||
|
||||
ALTER TABLE "Payouts" ALTER COLUMN "OriginalCurrency" SET NOT NULL;
|
@ -0,0 +1,9 @@
|
||||
CREATE OR REPLACE FUNCTION is_pending(status TEXT)
|
||||
RETURNS BOOLEAN AS $$
|
||||
SELECT status = 'Processing' OR status = 'New';
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
|
||||
CREATE INDEX "IX_Invoices_Pending" ON "Invoices"((1)) WHERE is_pending("Status");
|
||||
CREATE INDEX "IX_Payments_Pending" ON "Payments"((1)) WHERE is_pending("Status");
|
||||
DROP TABLE "PendingInvoices";
|
||||
ANALYZE "Invoices";
|
23
BTCPayServer.Data/DBScripts/004.MonitoredInvoices.sql
Normal file
23
BTCPayServer.Data/DBScripts/004.MonitoredInvoices.sql
Normal file
@ -0,0 +1,23 @@
|
||||
CREATE OR REPLACE FUNCTION get_prompt(invoice_blob JSONB, payment_method_id TEXT)
|
||||
RETURNS JSONB AS $$
|
||||
SELECT invoice_blob->'prompts'->payment_method_id
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_monitored_invoices(arg_payment_method_id TEXT, include_non_activated BOOLEAN)
|
||||
RETURNS TABLE (invoice_id TEXT, payment_id TEXT, payment_method_id TEXT) AS $$
|
||||
WITH cte AS (
|
||||
-- Get all the invoices which are pending. Even if no payments.
|
||||
SELECT i."Id" invoice_id, p."Id" payment_id, p."PaymentMethodId" payment_method_id FROM "Invoices" i LEFT JOIN "Payments" p ON i."Id" = p."InvoiceDataId"
|
||||
WHERE is_pending(i."Status")
|
||||
UNION ALL
|
||||
-- For invoices not pending, take all of those which have pending payments
|
||||
SELECT i."Id" invoice_id, p."Id" payment_id, p."PaymentMethodId" payment_method_id FROM "Invoices" i INNER JOIN "Payments" p ON i."Id" = p."InvoiceDataId"
|
||||
WHERE is_pending(p."Status") AND NOT is_pending(i."Status"))
|
||||
SELECT cte.* FROM cte
|
||||
JOIN "Invoices" i ON cte.invoice_id=i."Id"
|
||||
LEFT JOIN "Payments" p ON cte.payment_id=p."Id" AND cte.payment_method_id=p."PaymentMethodId"
|
||||
WHERE (p."PaymentMethodId" IS NOT NULL AND p."PaymentMethodId" = arg_payment_method_id) OR
|
||||
(p."PaymentMethodId" IS NULL AND get_prompt(i."Blob2", arg_payment_method_id) IS NOT NULL AND
|
||||
(include_non_activated IS TRUE OR (get_prompt(i."Blob2", arg_payment_method_id)->'inactive')::BOOLEAN IS NOT TRUE));
|
||||
$$ LANGUAGE SQL STABLE;
|
@ -9,6 +9,7 @@ namespace BTCPayServer.Data
|
||||
public string Address { get; set; }
|
||||
public InvoiceData InvoiceData { get; set; }
|
||||
public string InvoiceDataId { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
@ -18,7 +19,7 @@ namespace BTCPayServer.Data
|
||||
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<AddressInvoiceData>()
|
||||
#pragma warning disable CS0618
|
||||
.HasKey(o => o.Address);
|
||||
.HasKey(o => new { o.Address, o.PaymentMethodId });
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.App.BackupStorage;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
@ -30,9 +29,6 @@ namespace BTCPayServer.Data
|
||||
|
||||
public List<IdentityUserRole<string>> UserRoles { get; set; }
|
||||
|
||||
public List<AppStorageItemData> AppStorageItems { get; set; }
|
||||
|
||||
|
||||
public static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<ApplicationUser>()
|
||||
@ -50,5 +46,6 @@ namespace BTCPayServer.Data
|
||||
public bool ShowInvoiceStatusChangeHint { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string InvitationToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -15,36 +15,13 @@ using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using BTCPayServer.Migrations;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public partial class InvoiceData
|
||||
public partial class InvoiceData : MigrationInterceptor.IHasMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// We have a migration running in the background that will migrate the data from the old blob to the new blob
|
||||
/// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly.
|
||||
/// </summary>
|
||||
public class MigrationInterceptor : IMaterializationInterceptor
|
||||
{
|
||||
public static readonly MigrationInterceptor Instance = new MigrationInterceptor();
|
||||
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
|
||||
{
|
||||
if (entity is InvoiceData invoiceData && invoiceData.Currency is null)
|
||||
{
|
||||
invoiceData.Migrate();
|
||||
}
|
||||
else if (entity is PaymentData paymentData && paymentData.Currency is null)
|
||||
{
|
||||
paymentData.Migrate();
|
||||
}
|
||||
else if (entity is PayoutData payoutData && payoutData.Currency is null)
|
||||
{
|
||||
payoutData.Migrate();
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
static HashSet<string> superflousProperties = new HashSet<string>()
|
||||
{
|
||||
"availableAddressHashes",
|
||||
@ -78,13 +55,14 @@ namespace BTCPayServer.Data
|
||||
};
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public void Migrate()
|
||||
public bool TryMigrate()
|
||||
{
|
||||
if (Currency is not null)
|
||||
return;
|
||||
return false;
|
||||
if (Blob is not (null or { Length: 0 }))
|
||||
{
|
||||
Blob2 = MigrationExtensions.Unzip(Blob);
|
||||
Blob2 = MigrationExtensions.SanitizeJSON(Blob2);
|
||||
Blob = null;
|
||||
}
|
||||
var blob = JObject.Parse(Blob2);
|
||||
@ -232,11 +210,10 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
|
||||
blob.ConvertNumberToString("price");
|
||||
Currency = blob["currency"].Value<string>();
|
||||
Currency = blob["currency"].Value<string>().ToUpperInvariant();
|
||||
var isTopup = blob["type"]?.Value<string>() is "TopUp";
|
||||
var amount = decimal.Parse(blob["price"].Value<string>(), CultureInfo.InvariantCulture);
|
||||
Amount = isTopup && amount == 0 ? null : decimal.Parse(blob["price"].Value<string>(), CultureInfo.InvariantCulture);
|
||||
CustomerEmail = null;
|
||||
foreach (var prop in superflousProperties)
|
||||
blob.Property(prop)?.Remove();
|
||||
if (blob["speedPolicy"] is JValue { Type: JTokenType.Integer, Value: 0 or 0L })
|
||||
@ -373,7 +350,11 @@ namespace BTCPayServer.Data
|
||||
};
|
||||
blob["version"] = 3;
|
||||
Blob2 = blob.ToString(Formatting.None);
|
||||
return true;
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public bool Migrated { get; set; }
|
||||
static string[] detailsRemoveDefault =
|
||||
[
|
||||
"paymentMethodFeeRate",
|
||||
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -20,18 +22,17 @@ namespace BTCPayServer.Data
|
||||
[Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
public string Blob2 { get; set; }
|
||||
public string ItemCode { get; set; }
|
||||
public string OrderId { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string ExceptionStatus { get; set; }
|
||||
[Obsolete("Unused")]
|
||||
public string CustomerEmail { get; set; }
|
||||
public List<AddressInvoiceData> AddressInvoices { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public List<PendingInvoiceData> PendingInvoices { get; set; }
|
||||
public List<InvoiceSearchData> InvoiceSearchData { get; set; }
|
||||
public List<RefundData> Refunds { get; set; }
|
||||
|
||||
public static string GetOrderId(string blob) => throw new NotSupportedException();
|
||||
public static string GetItemCode(string blob) => throw new NotSupportedException();
|
||||
public static bool IsPending(string status) => throw new NotSupportedException();
|
||||
|
||||
[Timestamp]
|
||||
// With this, update of InvoiceData will fail if the row was modified by another process
|
||||
public uint XMin { get; set; }
|
||||
@ -41,7 +42,6 @@ namespace BTCPayServer.Data
|
||||
.HasOne(o => o.StoreData)
|
||||
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
|
||||
builder.Entity<InvoiceData>()
|
||||
.Property(o => o.Blob2)
|
||||
@ -49,6 +49,9 @@ namespace BTCPayServer.Data
|
||||
builder.Entity<InvoiceData>()
|
||||
.Property(o => o.Amount)
|
||||
.HasColumnType("NUMERIC");
|
||||
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(GetOrderId), new[] { typeof(string) }), b => b.HasName("get_orderid"));
|
||||
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(GetItemCode), new[] { typeof(string) }), b => b.HasName("get_itemcode"));
|
||||
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(IsPending), new[] { typeof(string) }), b => b.HasName("is_pending"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,5 +147,8 @@ namespace BTCPayServer.Data
|
||||
return $"{splitted[0]}-CHAIN";
|
||||
throw new NotSupportedException("Unknown payment id " + paymentMethodId);
|
||||
}
|
||||
|
||||
// Make postgres happy
|
||||
public static string SanitizeJSON(string json) => json.Replace("\\u0000", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
45
BTCPayServer.Data/Data/MigrationInterceptor.cs
Normal file
45
BTCPayServer.Data/Data/MigrationInterceptor.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// We have a migration running in the background that will migrate the data from the old blob to the new blob
|
||||
/// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly.
|
||||
/// </summary>
|
||||
public class MigrationInterceptor : IMaterializationInterceptor, ISaveChangesInterceptor
|
||||
{
|
||||
public interface IHasMigration
|
||||
{
|
||||
bool TryMigrate();
|
||||
bool Migrated { get; set; }
|
||||
}
|
||||
public static readonly MigrationInterceptor Instance = new MigrationInterceptor();
|
||||
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
|
||||
{
|
||||
if (entity is IHasMigration hasMigration && hasMigration.TryMigrate())
|
||||
{
|
||||
hasMigration.Migrated = true;
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
public ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
|
||||
foreach (var entry in eventData.Context.ChangeTracker.Entries())
|
||||
{
|
||||
if (entry is { Entity: IHasMigration { Migrated: true }, State: EntityState.Modified })
|
||||
// It seems doing nothing, but this actually set all properties as modified
|
||||
entry.State = EntityState.Modified;
|
||||
}
|
||||
return new ValueTask<InterceptionResult<int>>(result);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Altcoins;
|
||||
using NBitcoin.DataEncoders;
|
||||
@ -13,16 +16,17 @@ using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public partial class PaymentData
|
||||
public partial class PaymentData : MigrationInterceptor.IHasMigration
|
||||
{
|
||||
public void Migrate()
|
||||
public bool TryMigrate()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (Currency is not null)
|
||||
return;
|
||||
return false;
|
||||
if (Blob is not (null or { Length: 0 }))
|
||||
{
|
||||
Blob2 = MigrationExtensions.Unzip(Blob);
|
||||
Blob2 = MigrationExtensions.SanitizeJSON(Blob2);
|
||||
Blob = null;
|
||||
}
|
||||
var blob = JObject.Parse(Blob2);
|
||||
@ -42,9 +46,10 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
|
||||
var cryptoCode = blob["cryptoCode"].Value<string>();
|
||||
Type = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
|
||||
Type = MigrationExtensions.MigratePaymentMethodId(Type);
|
||||
var divisibility = MigrationExtensions.GetDivisibility(Type);
|
||||
MigratedPaymentMethodId = PaymentMethodId;
|
||||
PaymentMethodId = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
|
||||
PaymentMethodId = MigrationExtensions.MigratePaymentMethodId(PaymentMethodId);
|
||||
var divisibility = MigrationExtensions.GetDivisibility(PaymentMethodId);
|
||||
Currency = blob["cryptoCode"].Value<string>();
|
||||
blob.Remove("cryptoCode");
|
||||
blob.Remove("cryptoPaymentDataType");
|
||||
@ -157,7 +162,13 @@ namespace BTCPayServer.Data
|
||||
Blob2 = blob.ToString(Formatting.None);
|
||||
Accounted = null;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return true;
|
||||
}
|
||||
[NotMapped]
|
||||
public bool Migrated { get; set; }
|
||||
[NotMapped]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public string MigratedPaymentMethodId { get; set; }
|
||||
|
||||
static readonly DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
public static long DateTimeToMilliUnixTime(in DateTime time)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
@ -26,13 +27,15 @@ namespace BTCPayServer.Data
|
||||
[Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
public string Blob2 { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
[Obsolete("Use Status instead")]
|
||||
public bool? Accounted { get; set; }
|
||||
public PaymentStatus? Status { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
public static bool IsPending(PaymentStatus? status) => throw new NotSupportedException();
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<PaymentData>()
|
||||
.HasKey(o => new { o.Id, o.PaymentMethodId });
|
||||
builder.Entity<PaymentData>()
|
||||
.HasOne(o => o.InvoiceData)
|
||||
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
|
||||
@ -47,6 +50,7 @@ namespace BTCPayServer.Data
|
||||
builder.Entity<PaymentData>()
|
||||
.Property(o => o.Amount)
|
||||
.HasColumnType("NUMERIC");
|
||||
builder.HasDbFunction(typeof(PaymentData).GetMethod(nameof(IsPending), new[] { typeof(PaymentStatus?) }), b => b.HasName("is_pending"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
BTCPayServer.Data/Data/PaymentRequestData.Migration.cs
Normal file
39
BTCPayServer.Data/Data/PaymentRequestData.Migration.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public partial class PaymentRequestData : MigrationInterceptor.IHasMigration
|
||||
{
|
||||
[NotMapped]
|
||||
public bool Migrated { get; set; }
|
||||
|
||||
public bool TryMigrate()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (Blob is null && Blob2 is not null)
|
||||
return false;
|
||||
if (Blob2 is null)
|
||||
{
|
||||
Blob2 = Blob is not (null or { Length: 0 }) ? MigrationExtensions.Unzip(Blob) : "{}";
|
||||
Blob2 = MigrationExtensions.SanitizeJSON(Blob2);
|
||||
}
|
||||
Blob = null;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
var jobj = JObject.Parse(Blob2);
|
||||
// Fixup some legacy payment requests
|
||||
if (jobj["expiryDate"].Type == JTokenType.Date)
|
||||
{
|
||||
jobj["expiryDate"] = new JValue(NBitcoin.Utils.DateTimeToUnixTime(jobj["expiryDate"].Value<DateTime>()));
|
||||
Blob2 = jobj.ToString(Newtonsoft.Json.Formatting.None);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PaymentRequestData : IHasBlobUntyped
|
||||
public partial class PaymentRequestData : IHasBlobUntyped
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public partial class PayoutData
|
||||
{
|
||||
public void Migrate()
|
||||
{
|
||||
PayoutMethodId = MigrationExtensions.MigratePaymentMethodId(PayoutMethodId);
|
||||
// Could only be BTC-LN or BTC-CHAIN, so we extract the crypto currency
|
||||
Currency = PayoutMethodId.Split('-')[0];
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,23 @@ namespace BTCPayServer.Data
|
||||
public DateTimeOffset Date { get; set; }
|
||||
public string PullPaymentDataId { get; set; }
|
||||
public string StoreDataId { get; set; }
|
||||
/// <summary>
|
||||
/// The currency of the payout (eg. BTC)
|
||||
/// </summary>
|
||||
public string Currency { get; set; }
|
||||
/// <summary>
|
||||
/// The amount of the payout in Currency.
|
||||
/// The Amount only get set when the payout is actually approved.
|
||||
/// </summary>
|
||||
public decimal? Amount { get; set; }
|
||||
/// <summary>
|
||||
/// The original currency of the payout (eg. USD)
|
||||
/// </summary>
|
||||
public string OriginalCurrency { get; set; }
|
||||
/// <summary>
|
||||
/// The amount of the payout in OriginalCurrency
|
||||
/// </summary>
|
||||
public decimal OriginalAmount { get; set; }
|
||||
public PullPaymentData PullPaymentData { get; set; }
|
||||
[MaxLength(20)]
|
||||
public PayoutState State { get; set; }
|
||||
@ -28,7 +44,14 @@ namespace BTCPayServer.Data
|
||||
public string Blob { get; set; }
|
||||
public string Proof { get; set; }
|
||||
#nullable enable
|
||||
public string? Destination { get; set; }
|
||||
/// <summary>
|
||||
/// For example, BTC-CHAIN needs to ensure that only a single address is tied to an active payout.
|
||||
/// If `PayoutBlob.Destination` is `bitcoin://1BvBMSeYstWetqTFn5Au4m4GFg7xJaNVN2?amount=0.1`
|
||||
/// Then `DedupId` is `1BvBMSeYstWetqTFn5Au4m4GFg7xJaNVN2`
|
||||
/// For Lightning, Destination could be the lightning address, BOLT11 or LNURL
|
||||
/// But the `DedupId` would be the `PaymentHash`.
|
||||
/// </summary>
|
||||
public string? DedupId { get; set; }
|
||||
#nullable restore
|
||||
public StoreData StoreData { get; set; }
|
||||
|
||||
@ -46,7 +69,7 @@ namespace BTCPayServer.Data
|
||||
builder.Entity<PayoutData>()
|
||||
.HasIndex(o => o.State);
|
||||
builder.Entity<PayoutData>()
|
||||
.HasIndex(x => new { DestinationId = x.Destination, x.State });
|
||||
.HasIndex(x => new { DestinationId = x.DedupId, x.State });
|
||||
|
||||
builder.Entity<PayoutData>()
|
||||
.Property(o => o.Blob)
|
||||
|
@ -16,7 +16,7 @@ public class PayoutProcessorData : IHasBlobUntyped
|
||||
public string Id { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string PayoutMethodId { get; set; }
|
||||
public string Processor { get; set; }
|
||||
|
||||
[Obsolete("Use Blob2 instead")]
|
||||
@ -36,6 +36,6 @@ public class PayoutProcessorData : IHasBlobUntyped
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Processor} {PaymentMethod} {StoreId}";
|
||||
return $"{Processor} {PayoutMethodId} {StoreId}";
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PendingInvoiceData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public InvoiceData InvoiceData { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<PendingInvoiceData>()
|
||||
.HasOne(o => o.InvoiceData)
|
||||
.WithMany(o => o.PendingInvoices)
|
||||
.HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ namespace BTCPayServer.Data
|
||||
public StoreData StoreData { get; set; }
|
||||
[MaxLength(50)]
|
||||
public string StoreId { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public decimal Limit { get; set; }
|
||||
public DateTimeOffset StartDate { get; set; }
|
||||
public DateTimeOffset? EndDate { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240405052858_cleanup_address_invoices")]
|
||||
public partial class cleanup_address_invoices : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"
|
||||
DELETE FROM ""AddressInvoices"" WHERE ""Address"" LIKE '%_LightningLike';
|
||||
ALTER TABLE ""AddressInvoices"" DROP COLUMN IF EXISTS ""CreatedTime"";
|
||||
");
|
||||
migrationBuilder.Sql(@"VACUUM (FULL, ANALYZE) ""AddressInvoices"";", true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240520042729_payoutsmigration")]
|
||||
public partial class payoutsmigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Currency",
|
||||
table: "Payouts",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
migrationBuilder.RenameColumn("PaymentMethodId", "Payouts", "PayoutMethodId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,56 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AppStuff : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AppStorageItems",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
Version = table.Column<long>(type: "bigint", nullable: false),
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
Value = table.Column<byte[]>(type: "bytea", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AppStorageItems", x => new { x.Key, x.Version, x.UserId });
|
||||
table.ForeignKey(
|
||||
name: "FK_AppStorageItems_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AppStorageItems_Key_UserId",
|
||||
table: "AppStorageItems",
|
||||
columns: new[] { "Key", "UserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AppStorageItems_UserId",
|
||||
table: "AppStorageItems",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.Sql("CREATE FUNCTION \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"() RETURNS trigger as $LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA$\r\nBEGIN\r\n DELETE FROM \"AppStorageItems\"\r\n WHERE NEW.\"UserId\" = \"AppStorageItems\".\"UserId\" AND NEW.\"Key\" = \"AppStorageItems\".\"Key\" AND NEW.\"Version\" > \"AppStorageItems\".\"Version\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA BEFORE INSERT\r\nON \"AppStorageItems\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"();");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("DROP FUNCTION \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"() CASCADE;");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AppStorageItems");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240826065950_removeinvoicecols")]
|
||||
[DBScript("001.InvoiceFunctions.sql")]
|
||||
public partial class removeinvoicecols : DBScriptsMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Invoices_OrderId",
|
||||
table: "Invoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ItemCode",
|
||||
table: "Invoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OrderId",
|
||||
table: "Invoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CustomerEmail",
|
||||
table: "Invoices");
|
||||
base.Up(migrationBuilder);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240827034505_migratepayouts")]
|
||||
[DBScript("002.RefactorPayouts.sql")]
|
||||
public partial class migratepayouts : DBScriptsMigration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
base.Up(migrationBuilder);
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Destination",
|
||||
table: "Payouts",
|
||||
newName: "DedupId");
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Payouts_Destination_State",
|
||||
table: "Payouts",
|
||||
newName: "IX_Payouts_DedupId_State");
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PaymentMethod",
|
||||
table: "PayoutProcessors",
|
||||
newName: "PayoutMethodId");
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
UPDATE "PayoutProcessors"
|
||||
SET
|
||||
"PayoutMethodId" = CASE WHEN STRPOS("PayoutMethodId", '_') = 0 THEN "PayoutMethodId" || '-CHAIN'
|
||||
WHEN STRPOS("PayoutMethodId", '_LightningLike') > 0 THEN split_part("PayoutMethodId", '_LightningLike', 1) || '-LN'
|
||||
WHEN STRPOS("PayoutMethodId", '_LNURLPAY') > 0 THEN split_part("PayoutMethodId",'_LNURLPAY', 1) || '-LN'
|
||||
ELSE "PayoutMethodId" END
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240904092905_UpdateStoreOwnerRole")]
|
||||
public partial class UpdateStoreOwnerRole : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
"StoreRoles",
|
||||
keyColumns: new[] { "Id" },
|
||||
keyColumnTypes: new[] { "TEXT" },
|
||||
keyValues: new[] { "Owner" },
|
||||
columns: new[] { "Permissions" },
|
||||
columnTypes: new[] { "TEXT[]" },
|
||||
values: new object[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
"StoreRoles",
|
||||
keyColumns: new[] { "Id" },
|
||||
keyColumnTypes: new[] { "TEXT" },
|
||||
keyValues: new[] { "Owner" },
|
||||
columns: new[] { "Permissions" },
|
||||
columnTypes: new[] { "TEXT[]" },
|
||||
values: new object[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
"btcpay.store.canmodifystoresettings",
|
||||
"btcpay.store.cantradecustodianaccount",
|
||||
"btcpay.store.canwithdrawfromcustodianaccount",
|
||||
"btcpay.store.candeposittocustodianaccount"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240913034505_refactorpendinginvoicespayments")]
|
||||
[DBScript("003.RefactorPendingInvoicesPayments.sql")]
|
||||
public partial class refactorpendinginvoicespayments : DBScriptsMigration
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240919085726_refactorinvoiceaddress")]
|
||||
public partial class refactorinvoiceaddress : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PaymentMethodId",
|
||||
table: "AddressInvoices",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
UPDATE "AddressInvoices"
|
||||
SET
|
||||
"Address" = (string_to_array("Address", '#'))[1],
|
||||
"PaymentMethodId" = CASE WHEN (string_to_array("Address", '#'))[2] IS NULL THEN 'BTC-CHAIN'
|
||||
WHEN STRPOS((string_to_array("Address", '#'))[2], '_') = 0 THEN (string_to_array("Address", '#'))[2] || '-CHAIN'
|
||||
WHEN STRPOS((string_to_array("Address", '#'))[2], '_MoneroLike') > 0 THEN replace((string_to_array("Address", '#'))[2],'_MoneroLike','-CHAIN')
|
||||
WHEN STRPOS((string_to_array("Address", '#'))[2], '_ZcashLike') > 0 THEN replace((string_to_array("Address", '#'))[2],'_ZcashLike','-CHAIN')
|
||||
ELSE '' END;
|
||||
ALTER TABLE "AddressInvoices" DROP COLUMN IF EXISTS "CreatedTime";
|
||||
DELETE FROM "AddressInvoices" WHERE "PaymentMethodId" = '';
|
||||
""");
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices",
|
||||
columns: new[] { "Address", "PaymentMethodId" });
|
||||
migrationBuilder.Sql("VACUUM (ANALYZE) \"AddressInvoices\";", true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PaymentMethodId",
|
||||
table: "AddressInvoices");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices",
|
||||
column: "Address");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PendingInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PendingInvoices", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PendingInvoices_Invoices_Id",
|
||||
column: x => x.Id,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240923065254_refactorpayments")]
|
||||
public partial class refactorpayments : DBScriptsMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Payments",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Type",
|
||||
table: "Payments",
|
||||
newName: "PaymentMethodId");
|
||||
migrationBuilder.Sql("UPDATE \"Payments\" SET \"PaymentMethodId\"='' WHERE \"PaymentMethodId\" IS NULL;");
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Payments",
|
||||
table: "Payments",
|
||||
columns: new[] { "Id", "PaymentMethodId" });
|
||||
base.Up(migrationBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240924065254_monitoredinvoices")]
|
||||
[DBScript("004.MonitoredInvoices.sql")]
|
||||
public partial class monitoredinvoices : DBScriptsMigration
|
||||
{
|
||||
}
|
||||
}
|
@ -23,35 +23,6 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.App.BackupStorage.AppStorageItemData", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<byte[]>("Value")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.HasKey("Key", "Version", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("Key", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppStorageItems", t =>
|
||||
{
|
||||
t.HasTrigger("LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA");
|
||||
});
|
||||
|
||||
b.HasAnnotation("LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA", "CREATE FUNCTION \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"() RETURNS trigger as $LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA$\r\nBEGIN\r\n DELETE FROM \"AppStorageItems\"\r\n WHERE NEW.\"UserId\" = \"AppStorageItems\".\"UserId\" AND NEW.\"Key\" = \"AppStorageItems\".\"Key\" AND NEW.\"Version\" > \"AppStorageItems\".\"Version\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA BEFORE INSERT\r\nON \"AppStorageItems\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"();");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -92,10 +63,13 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PaymentMethodId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InvoiceDataId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Address");
|
||||
b.HasKey("Address", "PaymentMethodId");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
@ -296,18 +270,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Currency")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CustomerEmail")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExceptionStatus")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ItemCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OrderId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.HasColumnType("text");
|
||||
|
||||
@ -324,8 +289,6 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("Created");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
@ -519,6 +482,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PaymentMethodId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool?>("Accounted")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
@ -543,10 +509,7 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Status")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
b.HasKey("Id", "PaymentMethodId");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
@ -593,6 +556,9 @@ namespace BTCPayServer.Migrations
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<decimal?>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Blob")
|
||||
.HasColumnType("JSONB");
|
||||
|
||||
@ -602,7 +568,13 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<DateTimeOffset>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Destination")
|
||||
b.Property<string>("DedupId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("OriginalAmount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("OriginalCurrency")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PayoutMethodId")
|
||||
@ -632,7 +604,7 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.HasIndex("Destination", "State");
|
||||
b.HasIndex("DedupId", "State");
|
||||
|
||||
b.ToTable("Payouts");
|
||||
});
|
||||
@ -649,7 +621,7 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Blob2")
|
||||
.HasColumnType("JSONB");
|
||||
|
||||
b.Property<string>("PaymentMethod")
|
||||
b.Property<string>("PayoutMethodId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Processor")
|
||||
@ -665,16 +637,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("PayoutProcessors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -704,9 +666,15 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Blob")
|
||||
.HasColumnType("JSONB");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal>("Limit")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
@ -1181,17 +1149,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.App.BackupStorage.AppStorageItemData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "User")
|
||||
.WithMany("AppStorageItems")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1367,17 +1324,6 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("PendingInvoices")
|
||||
.HasForeignKey("Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1587,8 +1533,6 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
b.Navigation("APIKeys");
|
||||
|
||||
b.Navigation("AppStorageItems");
|
||||
|
||||
b.Navigation("Fido2Credentials");
|
||||
|
||||
b.Navigation("Notifications");
|
||||
@ -1610,8 +1554,6 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Navigation("Payments");
|
||||
|
||||
b.Navigation("PendingInvoices");
|
||||
|
||||
b.Navigation("Refunds");
|
||||
});
|
||||
|
||||
|
@ -34,16 +34,13 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.22.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="125.0.6422.14100" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="128.0.6613.11900" />
|
||||
<PackageReference Include="xunit" Version="2.6.6" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="AltcoinTests\**\*.cs"></Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update=".dockerignore">
|
||||
<DependentUpon>Dockerfile</DependentUpon>
|
||||
|
@ -31,12 +31,6 @@ using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.Authentication
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public enum TestDatabases
|
||||
{
|
||||
Postgres,
|
||||
MySQL,
|
||||
}
|
||||
|
||||
public class BTCPayServerTester : IDisposable
|
||||
{
|
||||
internal readonly string _Directory;
|
||||
@ -69,11 +63,6 @@ namespace BTCPayServer.Tests
|
||||
set;
|
||||
}
|
||||
|
||||
public string MySQL
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Postgres
|
||||
{
|
||||
get; set;
|
||||
@ -89,11 +78,6 @@ namespace BTCPayServer.Tests
|
||||
get; set;
|
||||
}
|
||||
|
||||
public TestDatabases TestDatabase
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public async Task RestartStartupTask<T>()
|
||||
{
|
||||
var startupTask = GetService<IServiceProvider>().GetServices<Abstractions.Contracts.IStartupTask>()
|
||||
@ -164,9 +148,7 @@ namespace BTCPayServer.Tests
|
||||
if (!string.IsNullOrEmpty(SSHConnection))
|
||||
config.AppendLine($"sshconnection={SSHConnection}");
|
||||
|
||||
if (TestDatabase == TestDatabases.MySQL && !String.IsNullOrEmpty(MySQL))
|
||||
config.AppendLine($"mysql=" + MySQL);
|
||||
else if (!String.IsNullOrEmpty(Postgres))
|
||||
if (!String.IsNullOrEmpty(Postgres))
|
||||
config.AppendLine($"postgres=" + Postgres);
|
||||
|
||||
if (!string.IsNullOrEmpty(ExplorerPostgres))
|
||||
@ -195,7 +177,7 @@ namespace BTCPayServer.Tests
|
||||
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
|
||||
l.SetMinimumLevel(LogLevel.Information)
|
||||
.AddFilter("Microsoft", LogLevel.Error)
|
||||
.AddFilter("Hangfire", LogLevel.Error)
|
||||
.AddFilter("Microsoft.EntityFrameworkCore.Migrations", LogLevel.Information)
|
||||
.AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error)
|
||||
.AddProvider(LoggerProvider);
|
||||
});
|
||||
|
@ -58,8 +58,8 @@ namespace BTCPayServer.Tests
|
||||
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
var clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text);
|
||||
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
|
||||
Assert.StartsWith("bcrt", address);
|
||||
Assert.DoesNotContain("lightning=", payUrl);
|
||||
Assert.Equal($"bitcoin:{address}", payUrl);
|
||||
Assert.Equal($"bitcoin:{address}", clipboard);
|
||||
@ -80,7 +80,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("lightning:lnurl", payUrl);
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text);
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center")).GetAttribute("data-text"));
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC-CHAIN"));
|
||||
});
|
||||
|
||||
@ -94,7 +94,7 @@ namespace BTCPayServer.Tests
|
||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center-start")).Text;
|
||||
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center")).GetAttribute("data-text");
|
||||
Assert.Equal($"lightning:{address}", payUrl);
|
||||
Assert.Equal($"lightning:{address}", clipboard);
|
||||
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
|
||||
@ -147,7 +147,7 @@ namespace BTCPayServer.Tests
|
||||
invoiceId = s.CreateInvoice(2100, "EUR");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
await Task.Delay(200);
|
||||
address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
|
||||
var amountFraction = "0.00001";
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||
Money.Parse(amountFraction));
|
||||
@ -261,8 +261,8 @@ namespace BTCPayServer.Tests
|
||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
|
||||
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
|
||||
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
|
||||
Assert.StartsWith($"bitcoin:{copyAddressOnchain}?amount=", payUrl);
|
||||
Assert.Contains("?amount=", payUrl);
|
||||
Assert.Contains("&lightning=", payUrl);
|
||||
@ -327,8 +327,8 @@ namespace BTCPayServer.Tests
|
||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
|
||||
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
|
||||
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
|
||||
Assert.StartsWith($"bitcoin:{copyAddressOnchain}", payUrl);
|
||||
Assert.Contains("?lightning=lnurl", payUrl);
|
||||
Assert.DoesNotContain("amount=", payUrl);
|
||||
|
@ -39,6 +39,8 @@ namespace BTCPayServer.Tests
|
||||
await user.GrantAccessAsync();
|
||||
var user2 = tester.NewAccount();
|
||||
await user2.GrantAccessAsync();
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user2.RegisterDerivationSchemeAsync("BTC");
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var apps2 = user2.GetController<UIAppsController>();
|
||||
var crowdfund = user.GetController<UICrowdfundController>();
|
||||
|
101
BTCPayServer.Tests/DatabaseTester.cs
Normal file
101
BTCPayServer.Tests/DatabaseTester.cs
Normal file
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Dapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using Npgsql;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class DatabaseTester
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly string dbname;
|
||||
private string[] notAppliedMigrations;
|
||||
|
||||
public DatabaseTester(ILog log, ILoggerFactory loggerFactory)
|
||||
{
|
||||
var connStr = Environment.GetEnvironmentVariable("TESTS_POSTGRES");
|
||||
if (string.IsNullOrEmpty(connStr))
|
||||
connStr = ServerTester.DefaultConnectionString;
|
||||
var r = RandomUtils.GetUInt32();
|
||||
dbname = $"btcpayserver{r}";
|
||||
connStr = connStr.Replace("btcpayserver", dbname);
|
||||
log.LogInformation("DB: " + dbname);
|
||||
ConnectionString = connStr;
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public ApplicationDbContextFactory CreateContextFactory()
|
||||
{
|
||||
return new ApplicationDbContextFactory(new OptionsWrapper<DatabaseOptions>(new DatabaseOptions()
|
||||
{
|
||||
ConnectionString = ConnectionString
|
||||
}), _loggerFactory);
|
||||
}
|
||||
|
||||
public InvoiceRepository GetInvoiceRepository()
|
||||
{
|
||||
var logs = new BTCPayServer.Logging.Logs();
|
||||
logs.Configure(_loggerFactory);
|
||||
return new InvoiceRepository(CreateContextFactory(), new EventAggregator(logs));
|
||||
}
|
||||
|
||||
public ApplicationDbContext CreateContext() => CreateContextFactory().CreateContext();
|
||||
|
||||
public async Task MigrateAsync()
|
||||
{
|
||||
using var ctx = CreateContext();
|
||||
await EnsureCreatedAsync();
|
||||
await ctx.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
private async Task EnsureCreatedAsync()
|
||||
{
|
||||
var builder = new Npgsql.NpgsqlConnectionStringBuilder(ConnectionString);
|
||||
builder.Database = null;
|
||||
NpgsqlConnection conn = new NpgsqlConnection(builder.ToString());
|
||||
await conn.ExecuteAsync($"CREATE DATABASE \"{dbname}\";");
|
||||
}
|
||||
|
||||
public async Task MigrateUntil(string migration = null)
|
||||
{
|
||||
using var ctx = CreateContext();
|
||||
var db = ctx.Database.GetDbConnection();
|
||||
await EnsureCreatedAsync();
|
||||
var migrations = ctx.Database.GetMigrations().ToArray();
|
||||
if (migration is not null)
|
||||
{
|
||||
var untilMigrationIdx = Array.IndexOf(migrations, migration);
|
||||
if (untilMigrationIdx == -1)
|
||||
throw new InvalidOperationException($"Migration {migration} not found");
|
||||
notAppliedMigrations = migrations[untilMigrationIdx..];
|
||||
await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS \"__EFMigrationsHistory\" (\"MigrationId\" TEXT, \"ProductVersion\" TEXT)");
|
||||
await db.ExecuteAsync("INSERT INTO \"__EFMigrationsHistory\" VALUES (@migration, '8.0.0')", notAppliedMigrations.Select(m => new { migration = m }).ToArray());
|
||||
}
|
||||
await ctx.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
public async Task ContinueMigration()
|
||||
{
|
||||
if (notAppliedMigrations is null)
|
||||
throw new InvalidOperationException("Call MigrateUpTo first");
|
||||
using var ctx = CreateContext();
|
||||
var db = ctx.Database.GetDbConnection();
|
||||
await db.ExecuteAsync("DELETE FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = ANY (@migrations)", new { migrations = notAppliedMigrations });
|
||||
await ctx.Database.MigrateAsync();
|
||||
notAppliedMigrations = null;
|
||||
}
|
||||
|
||||
public string ConnectionString { get; }
|
||||
}
|
||||
}
|
217
BTCPayServer.Tests/DatabaseTests.cs
Normal file
217
BTCPayServer.Tests/DatabaseTests.cs
Normal file
@ -0,0 +1,217 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using Dapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Altcoins;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
[Trait("Integration", "Integration")]
|
||||
public class DatabaseTests : UnitTestBase
|
||||
{
|
||||
|
||||
public DatabaseTests(ITestOutputHelper helper):base(helper)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanQueryMonitoredInvoices()
|
||||
{
|
||||
var tester = CreateDBTester();
|
||||
await tester.MigrateUntil();
|
||||
var invoiceRepository = tester.GetInvoiceRepository();
|
||||
using var ctx = tester.CreateContext();
|
||||
var conn = ctx.Database.GetDbConnection();
|
||||
|
||||
async Task AddPrompt(string invoiceId, string paymentMethodId, bool activated = true)
|
||||
{
|
||||
JObject prompt = new JObject();
|
||||
if (!activated)
|
||||
prompt["inactive"] = true;
|
||||
prompt["currency"] = "USD";
|
||||
var query = """
|
||||
UPDATE "Invoices" SET "Blob2" = jsonb_set('{"prompts": {}}'::JSONB || COALESCE("Blob2",'{}'), ARRAY['prompts','@paymentMethodId'], '@prompt'::JSONB)
|
||||
WHERE "Id" = '@invoiceId'
|
||||
""";
|
||||
query = query.Replace("@paymentMethodId", paymentMethodId);
|
||||
query = query.Replace("@prompt", prompt.ToString());
|
||||
query = query.Replace("@invoiceId", invoiceId);
|
||||
Assert.Equal(1, await conn.ExecuteAsync(query));
|
||||
}
|
||||
|
||||
await conn.ExecuteAsync("""
|
||||
INSERT INTO "Invoices" ("Id", "Created", "Status","Currency") VALUES
|
||||
('BTCOnly', NOW(), 'New', 'USD'),
|
||||
('LTCOnly', NOW(), 'New', 'USD'),
|
||||
('LTCAndBTC', NOW(), 'New', 'USD'),
|
||||
('LTCAndBTCLazy', NOW(), 'New', 'USD')
|
||||
""");
|
||||
foreach (var invoiceId in new string[] { "LTCOnly", "LTCAndBTCLazy", "LTCAndBTC" })
|
||||
{
|
||||
await AddPrompt(invoiceId, "LTC-CHAIN", true);
|
||||
}
|
||||
foreach (var invoiceId in new string[] { "BTCOnly", "LTCAndBTC" })
|
||||
{
|
||||
await AddPrompt(invoiceId, "BTC-CHAIN", true);
|
||||
}
|
||||
await AddPrompt("LTCAndBTCLazy", "BTC-CHAIN", false);
|
||||
|
||||
var btc = PaymentMethodId.Parse("BTC-CHAIN");
|
||||
var ltc = PaymentMethodId.Parse("LTC-CHAIN");
|
||||
var invoices = await invoiceRepository.GetMonitoredInvoices(btc);
|
||||
Assert.Equal(2, invoices.Length);
|
||||
foreach (var invoiceId in new[] { "BTCOnly", "LTCAndBTC" })
|
||||
{
|
||||
Assert.Contains(invoices, i => i.Id == invoiceId);
|
||||
}
|
||||
invoices = await invoiceRepository.GetMonitoredInvoices(btc, true);
|
||||
Assert.Equal(3, invoices.Length);
|
||||
foreach (var invoiceId in new[] { "BTCOnly", "LTCAndBTC", "LTCAndBTCLazy" })
|
||||
{
|
||||
Assert.Contains(invoices, i => i.Id == invoiceId);
|
||||
}
|
||||
|
||||
invoices = await invoiceRepository.GetMonitoredInvoices(ltc);
|
||||
Assert.Equal(3, invoices.Length);
|
||||
foreach (var invoiceId in new[] { "LTCAndBTC", "LTCAndBTC", "LTCAndBTCLazy" })
|
||||
{
|
||||
Assert.Contains(invoices, i => i.Id == invoiceId);
|
||||
}
|
||||
|
||||
await conn.ExecuteAsync("""
|
||||
INSERT INTO "Payments" ("Id", "InvoiceDataId", "PaymentMethodId", "Status", "Blob2", "Created", "Amount", "Currency") VALUES
|
||||
('1','LTCAndBTC', 'LTC-CHAIN', 'Processing', '{}'::JSONB, NOW(), 123, 'USD'),
|
||||
('2','LTCAndBTC', 'BTC-CHAIN', 'Processing', '{}'::JSONB, NOW(), 123, 'USD'),
|
||||
('3','LTCAndBTC', 'BTC-CHAIN', 'Processing', '{}'::JSONB, NOW(), 123, 'USD'),
|
||||
('4','LTCAndBTC', 'BTC-CHAIN', 'Settled', '{}'::JSONB, NOW(), 123, 'USD');
|
||||
|
||||
INSERT INTO "AddressInvoices" ("InvoiceDataId", "Address", "PaymentMethodId") VALUES
|
||||
('LTCAndBTC', 'BTC1', 'BTC-CHAIN'),
|
||||
('LTCAndBTC', 'BTC2', 'BTC-CHAIN'),
|
||||
('LTCAndBTC', 'LTC1', 'LTC-CHAIN');
|
||||
""");
|
||||
|
||||
var invoice = Assert.Single(await invoiceRepository.GetMonitoredInvoices(ltc), i => i.Id == "LTCAndBTC");
|
||||
var payment = Assert.Single(invoice.GetPayments(false));
|
||||
Assert.Equal("1", payment.Id);
|
||||
|
||||
foreach (var includeNonActivated in new[] { true, false })
|
||||
{
|
||||
invoices = await invoiceRepository.GetMonitoredInvoices(btc, includeNonActivated);
|
||||
invoice = Assert.Single(invoices, i => i.Id == "LTCAndBTC");
|
||||
var payments = invoice.GetPayments(false);
|
||||
Assert.Equal(3, payments.Count);
|
||||
|
||||
foreach (var paymentId in new[] { "2", "3", "4" })
|
||||
{
|
||||
Assert.Contains(payments, p => p.Id == paymentId);
|
||||
}
|
||||
Assert.Equal(2, invoice.Addresses.Count);
|
||||
foreach (var addr in new[] { "BTC1", "BTC2" })
|
||||
{
|
||||
Assert.Contains(invoice.Addresses, p => p.Address == addr);
|
||||
}
|
||||
if (!includeNonActivated)
|
||||
Assert.DoesNotContain(invoices, i => i.Id == "LTCAndBTCLazy");
|
||||
else
|
||||
Assert.Contains(invoices, i => i.Id == "LTCAndBTCLazy");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanMigrateInvoiceAddresses()
|
||||
{
|
||||
var tester = CreateDBTester();
|
||||
await tester.MigrateUntil("20240919085726_refactorinvoiceaddress");
|
||||
using var ctx = tester.CreateContext();
|
||||
var conn = ctx.Database.GetDbConnection();
|
||||
await conn.ExecuteAsync("INSERT INTO \"Invoices\" (\"Id\", \"Created\") VALUES ('i', NOW())");
|
||||
await conn.ExecuteAsync(
|
||||
"INSERT INTO \"AddressInvoices\" VALUES ('aaa#BTC', 'i'),('bbb','i'),('ccc#BTC_LNU', 'i'),('ddd#XMR_MoneroLike', 'i'),('eee#ZEC_ZcashLike', 'i')");
|
||||
await tester.ContinueMigration();
|
||||
foreach (var v in new[] { ("aaa", "BTC-CHAIN"), ("bbb", "BTC-CHAIN"), ("ddd", "XMR-CHAIN") , ("eee", "ZEC-CHAIN") })
|
||||
{
|
||||
var ok = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"AddressInvoices\" WHERE \"Address\"=@a AND \"PaymentMethodId\"=@b", new { a = v.Item1, b = v.Item2 });
|
||||
Assert.True(ok);
|
||||
}
|
||||
var notok = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"AddressInvoices\" WHERE \"Address\"='ccc'");
|
||||
Assert.False(notok);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanMigratePayoutsAndPullPayments()
|
||||
{
|
||||
var tester = CreateDBTester();
|
||||
await tester.MigrateUntil("20240827034505_migratepayouts");
|
||||
|
||||
using var ctx = tester.CreateContext();
|
||||
var conn = ctx.Database.GetDbConnection();
|
||||
await conn.ExecuteAsync("INSERT INTO \"Stores\"(\"Id\", \"SpeedPolicy\") VALUES (@store, 0)", new { store = "store1" });
|
||||
var param = new
|
||||
{
|
||||
Id = "pp1",
|
||||
StoreId = "store1",
|
||||
Blob = "{\"Name\": \"CoinLottery\", \"View\": {\"Email\": null, \"Title\": \"\", \"Description\": \"\", \"EmbeddedCSS\": null, \"CustomCSSLink\": null}, \"Limit\": \"10.00\", \"Period\": null, \"Currency\": \"GBP\", \"Description\": \"\", \"Divisibility\": 0, \"MinimumClaim\": \"0\", \"AutoApproveClaims\": false, \"SupportedPaymentMethods\": [\"BTC\", \"BTC_LightningLike\"]}"
|
||||
};
|
||||
await conn.ExecuteAsync("INSERT INTO \"PullPayments\"(\"Id\", \"StoreId\", \"Blob\", \"StartDate\", \"Archived\") VALUES (@Id, @StoreId, @Blob::JSONB, NOW(), 'f')", param);
|
||||
var parameters = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
Id = "p1",
|
||||
StoreId = "store1",
|
||||
PullPaymentDataId = "pp1",
|
||||
PaymentMethodId = "BTC",
|
||||
Blob = "{\"Amount\": \"10.0\", \"Revision\": 0, \"Destination\": \"address\", \"CryptoAmount\": \"0.00012225\", \"MinimumConfirmation\": 1}"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = "p2",
|
||||
StoreId = "store1",
|
||||
PullPaymentDataId = "pp1",
|
||||
PaymentMethodId = "BTC_LightningLike",
|
||||
Blob = "{\"Amount\": \"10.0\", \"Revision\": 0, \"Destination\": \"address\", \"CryptoAmount\": null, \"MinimumConfirmation\": 1}"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = "p3",
|
||||
StoreId = "store1",
|
||||
PullPaymentDataId = null as string,
|
||||
PaymentMethodId = "BTC_LightningLike",
|
||||
Blob = "{\"Amount\": \"10.0\", \"Revision\": 0, \"Destination\": \"address\", \"CryptoAmount\": null, \"MinimumConfirmation\": 1}"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = "p4",
|
||||
StoreId = "store1",
|
||||
PullPaymentDataId = null as string,
|
||||
PaymentMethodId = "BTC_LightningLike",
|
||||
Blob = "{\"Amount\": \"-10.0\", \"Revision\": 0, \"Destination\": \"address\", \"CryptoAmount\": null, \"MinimumConfirmation\": 1}"
|
||||
}
|
||||
};
|
||||
await conn.ExecuteAsync("INSERT INTO \"Payouts\"(\"Id\", \"StoreDataId\", \"PullPaymentDataId\", \"PaymentMethodId\", \"Blob\", \"State\", \"Date\") VALUES (@Id, @StoreId, @PullPaymentDataId, @PaymentMethodId, @Blob::JSONB, 'state', NOW())", parameters);
|
||||
await tester.ContinueMigration();
|
||||
|
||||
var migrated = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"PullPayments\" WHERE \"Id\"='pp1' AND \"Limit\"=10.0 AND \"Currency\"='GBP' AND \"Blob\"->>'SupportedPayoutMethods'='[\"BTC-CHAIN\", \"BTC-LN\"]'");
|
||||
Assert.True(migrated);
|
||||
|
||||
migrated = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"Payouts\" WHERE \"Id\"='p1' AND \"Amount\"= 0.00012225 AND \"OriginalAmount\"=10.0 AND \"OriginalCurrency\"='GBP' AND \"PayoutMethodId\"='BTC-CHAIN'");
|
||||
Assert.True(migrated);
|
||||
|
||||
migrated = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"Payouts\" WHERE \"Id\"='p2' AND \"Amount\" IS NULL AND \"OriginalAmount\"=10.0 AND \"OriginalCurrency\"='GBP' AND \"PayoutMethodId\"='BTC-LN'");
|
||||
Assert.True(migrated);
|
||||
|
||||
migrated = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"Payouts\" WHERE \"Id\"='p3' AND \"Amount\" IS NULL AND \"OriginalAmount\"=10.0 AND \"OriginalCurrency\"='BTC'");
|
||||
Assert.True(migrated);
|
||||
|
||||
migrated = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"Payouts\" WHERE \"Id\"='p4' AND \"Amount\" IS NULL AND \"OriginalAmount\"=-10.0 AND \"OriginalCurrency\"='BTC' AND \"PayoutMethodId\"='TOPUP'");
|
||||
Assert.True(migrated);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,14 +3,15 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Converters;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -19,21 +20,31 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Plugins.Bitcoin;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Fees;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Scripting.Parser;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
@ -41,6 +52,7 @@ using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -239,12 +251,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(id, id1);
|
||||
Assert.Equal(id, id2);
|
||||
Assert.Equal("LTC-LN", id.ToString());
|
||||
#if ALTCOINS
|
||||
id = PaymentMethodId.Parse("XMR");
|
||||
id1 = PaymentMethodId.Parse("XMR-MoneroLike");
|
||||
Assert.Equal(id, id1);
|
||||
Assert.Equal("XMR-CHAIN", id.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -476,7 +486,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
#if ALTCOINS
|
||||
|
||||
[Fact]
|
||||
public void CanCalculateCryptoDue()
|
||||
{
|
||||
@ -496,7 +506,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var paymentMethod = entity.GetPaymentPrompts().TryGet(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
var accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(1.0m, accounting.ToSmallestUnit(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC)));
|
||||
Assert.Equal(1.1m, accounting.Due);
|
||||
Assert.Equal(1.1m, accounting.TotalDue);
|
||||
|
||||
@ -647,7 +656,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void DeterministicUTXOSorter()
|
||||
@ -1617,7 +1625,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
{
|
||||
Assert.Equal(tuple.expectedOutput, PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||
Assert.Equal(tuple.expectedOutput, UIInvoiceController.PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||
});
|
||||
}
|
||||
[Fact]
|
||||
@ -2238,7 +2246,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
Data.InvoiceData data = new Data.InvoiceData();
|
||||
obj = data;
|
||||
data.Blob2 = v.Input.ToString();
|
||||
data.Migrate();
|
||||
data.TryMigrate();
|
||||
var actual = JObject.Parse(data.Blob2);
|
||||
AssertSameJson(v.Expected, actual);
|
||||
if (!v.SkipRountripTest)
|
||||
@ -2258,7 +2266,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
//data.
|
||||
obj = data;
|
||||
data.Blob2 = v.Input.ToString();
|
||||
data.Migrate();
|
||||
data.TryMigrate();
|
||||
var actual = JObject.Parse(data.Blob2);
|
||||
AssertSameJson(v.Expected, actual);
|
||||
if (!v.SkipRountripTest)
|
||||
@ -2373,7 +2381,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
});
|
||||
var data = new Data.InvoiceData();
|
||||
data.Blob2 = o.ToString();
|
||||
data.Migrate();
|
||||
data.TryMigrate();
|
||||
var migrated = JObject.Parse(data.Blob2);
|
||||
return migrated["prompts"]["BTC-CHAIN"]["details"]["accountDerivation"].Value<string>();
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -13,9 +14,14 @@ using BTCPayServer.Events;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@ -30,6 +36,7 @@ using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -90,6 +97,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(e.APIError.Message);
|
||||
GreenfieldPermissionAPIError permissionError = Assert.IsType<GreenfieldPermissionAPIError>(e.APIError);
|
||||
Assert.Equal(Policies.CanModifyStoreSettings, permissionError.MissingPermission);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanViewStoreSettings);
|
||||
await AssertAPIError("unsupported-in-v2", () => client.SendHttpRequest<object>($"api/v1/stores/{user.StoreId}/payment-methods/LightningNetwork"));
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -361,6 +371,27 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
)
|
||||
);
|
||||
var template = @"[
|
||||
{
|
||||
""description"": ""Lovely, fresh and tender, Meng Ding Gan Lu ('sweet dew') is grown in the lush Meng Ding Mountains of the southwestern province of Sichuan where it has been cultivated for over a thousand years."",
|
||||
""id"": ""green-tea"",
|
||||
""image"": ""~/img/pos-sample/green-tea.jpg"",
|
||||
""priceType"": ""Fixed"",
|
||||
""price"": ""1"",
|
||||
""title"": ""Green Tea"",
|
||||
""disabled"": false
|
||||
}
|
||||
]";
|
||||
await AssertValidationError(new[] { "Template" },
|
||||
async () => await client.CreatePointOfSaleApp(
|
||||
user.StoreId,
|
||||
new PointOfSaleAppRequest
|
||||
{
|
||||
AppName = "good name",
|
||||
Template = template.Replace(@"""id"": ""green-tea"",", "")
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Test creating a POS app successfully
|
||||
var app = await client.CreatePointOfSaleApp(
|
||||
@ -369,7 +400,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
AppName = "test app from API",
|
||||
Currency = "JPY",
|
||||
Title = "test app title"
|
||||
Title = "test app title",
|
||||
Template = template
|
||||
}
|
||||
);
|
||||
Assert.Equal("test app from API", app.AppName);
|
||||
@ -552,6 +584,27 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
)
|
||||
);
|
||||
var template = @"[
|
||||
{
|
||||
""description"": ""Lovely, fresh and tender, Meng Ding Gan Lu ('sweet dew') is grown in the lush Meng Ding Mountains of the southwestern province of Sichuan where it has been cultivated for over a thousand years."",
|
||||
""id"": ""green-tea"",
|
||||
""image"": ""~/img/pos-sample/green-tea.jpg"",
|
||||
""priceType"": ""Fixed"",
|
||||
""price"": ""1"",
|
||||
""title"": ""Green Tea"",
|
||||
""disabled"": false
|
||||
}
|
||||
]";
|
||||
await AssertValidationError(new[] { "PerksTemplate" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CrowdfundAppRequest
|
||||
{
|
||||
AppName = "good name",
|
||||
PerksTemplate = template.Replace(@"""id"": ""green-tea"",", "")
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Test creating a crowdfund app
|
||||
var app = await client.CreateCrowdfundApp(
|
||||
@ -559,7 +612,8 @@ namespace BTCPayServer.Tests
|
||||
new CrowdfundAppRequest
|
||||
{
|
||||
AppName = "test app from API",
|
||||
Title = "test app title"
|
||||
Title = "test app title",
|
||||
PerksTemplate = template
|
||||
}
|
||||
);
|
||||
Assert.Equal("test app from API", app.AppName);
|
||||
@ -678,6 +732,93 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(apps[2].Archived);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanGetAppStats()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
await user.MakeAdmin();
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var client = await user.CreateClient();
|
||||
|
||||
var item1 = new ViewPointOfSaleViewModel.Item { Id = "item1", Title = "Item 1", Price = 1, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed };
|
||||
var item2 = new ViewPointOfSaleViewModel.Item { Id = "item2", Title = "Item 2", Price = 2, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed };
|
||||
var item3 = new ViewPointOfSaleViewModel.Item { Id = "item3", Title = "Item 3", Price = 3, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed };
|
||||
var posItems = AppService.SerializeTemplate([item1, item2, item3]);
|
||||
var posApp = await client.CreatePointOfSaleApp(user.StoreId, new PointOfSaleAppRequest { AppName = "test pos", Template = posItems, });
|
||||
var crowdfundApp = await client.CreateCrowdfundApp(user.StoreId, new CrowdfundAppRequest { AppName = "test crowdfund" });
|
||||
|
||||
// empty states
|
||||
var posSales = await client.GetAppSales(posApp.Id);
|
||||
Assert.NotNull(posSales);
|
||||
Assert.Equal(0, posSales.SalesCount);
|
||||
Assert.Equal(7, posSales.Series.Count());
|
||||
|
||||
var crowdfundSales = await client.GetAppSales(crowdfundApp.Id);
|
||||
Assert.NotNull(crowdfundSales);
|
||||
Assert.Equal(0, crowdfundSales.SalesCount);
|
||||
Assert.Equal(7, crowdfundSales.Series.Count());
|
||||
|
||||
var posTopItems = await client.GetAppTopItems(posApp.Id);
|
||||
Assert.Empty(posTopItems);
|
||||
|
||||
var crowdfundItems = await client.GetAppTopItems(crowdfundApp.Id);
|
||||
Assert.Empty(crowdfundItems);
|
||||
|
||||
// with sales - fiddle invoices via the UI controller
|
||||
var uiPosController = tester.PayTester.GetController<UIPointOfSaleController>();
|
||||
|
||||
var action = Assert.IsType<RedirectToActionResult>(uiPosController.ViewPointOfSale(posApp.Id, PosViewType.Static, 1, choiceKey: item1.Id).GetAwaiter().GetResult());
|
||||
Assert.Equal(nameof(UIInvoiceController.Checkout), action.ActionName);
|
||||
Assert.True(action.RouteValues!.TryGetValue("invoiceId", out var i1Id));
|
||||
|
||||
var cart = new JObject {
|
||||
["cart"] = new JArray
|
||||
{
|
||||
new JObject { ["id"] = item2.Id, ["count"] = 4 },
|
||||
new JObject { ["id"] = item3.Id, ["count"] = 2 }
|
||||
},
|
||||
["subTotal"] = 14,
|
||||
["total"] = 14,
|
||||
["amounts"] = new JArray()
|
||||
}.ToString();
|
||||
action = Assert.IsType<RedirectToActionResult>(uiPosController.ViewPointOfSale(posApp.Id, PosViewType.Cart, 7, posData: cart).GetAwaiter().GetResult());
|
||||
Assert.Equal(nameof(UIInvoiceController.Checkout), action.ActionName);
|
||||
Assert.True(action.RouteValues!.TryGetValue("invoiceId", out var i2Id));
|
||||
|
||||
await user.PayInvoice(i1Id!.ToString());
|
||||
await user.PayInvoice(i2Id!.ToString());
|
||||
|
||||
posSales = await client.GetAppSales(posApp.Id);
|
||||
Assert.Equal(7, posSales.SalesCount);
|
||||
Assert.Equal(7, posSales.Series.Count());
|
||||
Assert.Equal(0, posSales.Series.First().SalesCount);
|
||||
Assert.Equal(7, posSales.Series.Last().SalesCount);
|
||||
|
||||
posTopItems = await client.GetAppTopItems(posApp.Id);
|
||||
Assert.Equal(3, posTopItems.Count);
|
||||
Assert.Equal(item2.Id, posTopItems[0].ItemCode);
|
||||
Assert.Equal(4, posTopItems[0].SalesCount);
|
||||
|
||||
Assert.Equal(item3.Id, posTopItems[1].ItemCode);
|
||||
Assert.Equal(2, posTopItems[1].SalesCount);
|
||||
|
||||
Assert.Equal(item1.Id, posTopItems[2].ItemCode);
|
||||
Assert.Equal(1, posTopItems[2].SalesCount);
|
||||
|
||||
// with count and offset
|
||||
posTopItems = await client.GetAppTopItems(posApp.Id,1, 5);
|
||||
Assert.Equal(2, posTopItems.Count);
|
||||
Assert.Equal(item3.Id, posTopItems[0].ItemCode);
|
||||
Assert.Equal(2, posTopItems[0].SalesCount);
|
||||
|
||||
Assert.Equal(item1.Id, posTopItems[1].ItemCode);
|
||||
Assert.Equal(1, posTopItems[1].SalesCount);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDeleteUsersViaApi()
|
||||
@ -1010,7 +1151,7 @@ namespace BTCPayServer.Tests
|
||||
Description = "Test description",
|
||||
Amount = 12.3m,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PayoutMethods = new[] { "BTC" }
|
||||
});
|
||||
|
||||
void VerifyResult()
|
||||
@ -1041,7 +1182,7 @@ namespace BTCPayServer.Tests
|
||||
Name = "Test 2",
|
||||
Amount = 12.3m,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
PayoutMethods = new[] { "BTC" },
|
||||
BOLT11Expiration = TimeSpan.FromDays(31.0)
|
||||
});
|
||||
Assert.Equal(TimeSpan.FromDays(31.0), test2.BOLT11Expiration);
|
||||
@ -1071,30 +1212,30 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Destination = destination,
|
||||
Amount = 1_000_000m,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
}));
|
||||
|
||||
await this.AssertAPIError("archived", async () => await unauthenticated.CreatePayout(pps[1].Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
}));
|
||||
|
||||
var payout = await unauthenticated.CreatePayout(pps[0].Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
});
|
||||
|
||||
payouts = await unauthenticated.GetPayouts(pps[0].Id);
|
||||
var payout2 = Assert.Single(payouts);
|
||||
Assert.Equal(payout.Amount, payout2.Amount);
|
||||
Assert.Equal(payout.OriginalAmount, payout2.OriginalAmount);
|
||||
Assert.Equal(payout.Id, payout2.Id);
|
||||
Assert.Equal(destination, payout2.Destination);
|
||||
Assert.Equal(PayoutState.AwaitingApproval, payout.State);
|
||||
Assert.Equal("BTC-CHAIN", payout2.PaymentMethod);
|
||||
Assert.Equal("BTC", payout2.CryptoCode);
|
||||
Assert.Null(payout.PaymentMethodAmount);
|
||||
Assert.Equal("BTC-CHAIN", payout2.PayoutMethodId);
|
||||
Assert.Equal("BTC", payout2.PayoutCurrency);
|
||||
Assert.Null(payout.PayoutAmount);
|
||||
|
||||
TestLogs.LogInformation("Can't overdraft");
|
||||
|
||||
@ -1103,14 +1244,14 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Destination = destination2,
|
||||
Amount = 0.00001m,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
}));
|
||||
|
||||
TestLogs.LogInformation("Can't create too low payout");
|
||||
await this.AssertAPIError("amount-too-low", async () => await unauthenticated.CreatePayout(pps[0].Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination2,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
}));
|
||||
|
||||
TestLogs.LogInformation("Can archive payout");
|
||||
@ -1126,7 +1267,7 @@ namespace BTCPayServer.Tests
|
||||
payout = await unauthenticated.CreatePayout(pps[0].Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
});
|
||||
|
||||
var start = RoundSeconds(DateTimeOffset.Now + TimeSpan.FromDays(7.0));
|
||||
@ -1136,7 +1277,7 @@ namespace BTCPayServer.Tests
|
||||
Amount = 12.3m,
|
||||
StartsAt = start,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PayoutMethods = new[] { "BTC" }
|
||||
});
|
||||
Assert.Equal(start, inFuture.StartsAt);
|
||||
Assert.Null(inFuture.ExpiresAt);
|
||||
@ -1144,7 +1285,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 1.0m,
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
}));
|
||||
|
||||
var expires = RoundSeconds(DateTimeOffset.Now - TimeSpan.FromDays(7.0));
|
||||
@ -1154,13 +1295,13 @@ namespace BTCPayServer.Tests
|
||||
Amount = 12.3m,
|
||||
ExpiresAt = expires,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PayoutMethods = new[] { "BTC" }
|
||||
});
|
||||
await this.AssertAPIError("expired", async () => await unauthenticated.CreatePayout(inPast.Id, new CreatePayoutRequest()
|
||||
{
|
||||
Amount = 1.0m,
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
}));
|
||||
|
||||
await this.AssertValidationError(new[] { "ExpiresAt" }, async () => await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
||||
@ -1178,7 +1319,7 @@ namespace BTCPayServer.Tests
|
||||
Name = "Test USD",
|
||||
Amount = 5000m,
|
||||
Currency = "USD",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PayoutMethods = new[] { "BTC" }
|
||||
});
|
||||
|
||||
await this.AssertAPIError("lnurl-not-supported", async () => await unauthenticated.GetPullPaymentLNURL(pp.Id));
|
||||
@ -1188,7 +1329,7 @@ namespace BTCPayServer.Tests
|
||||
payout = await unauthenticated.CreatePayout(pp.Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
});
|
||||
await this.AssertAPIError("old-revision", async () => await client.ApprovePayout(storeId, payout.Id, new ApprovePayoutRequest()
|
||||
{
|
||||
@ -1203,8 +1344,8 @@ namespace BTCPayServer.Tests
|
||||
Revision = payout.Revision
|
||||
});
|
||||
Assert.Equal(PayoutState.AwaitingPayment, payout.State);
|
||||
Assert.NotNull(payout.PaymentMethodAmount);
|
||||
Assert.Equal(1.0m, payout.PaymentMethodAmount); // 1 BTC == 5000 USD in tests
|
||||
Assert.NotNull(payout.PayoutAmount);
|
||||
Assert.Equal(1.0m, payout.PayoutAmount); // 1 BTC == 5000 USD in tests
|
||||
await this.AssertAPIError("invalid-state", async () => await client.ApprovePayout(storeId, payout.Id, new ApprovePayoutRequest()
|
||||
{
|
||||
Revision = payout.Revision
|
||||
@ -1216,18 +1357,18 @@ namespace BTCPayServer.Tests
|
||||
Name = "Test 2",
|
||||
Amount = 12.303228134m,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PayoutMethods = new[] { "BTC" }
|
||||
});
|
||||
destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString();
|
||||
payout = await unauthenticated.CreatePayout(test3.Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
});
|
||||
payout = await client.ApprovePayout(storeId, payout.Id, new ApprovePayoutRequest());
|
||||
// The payout should round the value of the payment down to the network of the payment method
|
||||
Assert.Equal(12.30322814m, payout.PaymentMethodAmount);
|
||||
Assert.Equal(12.303228134m, payout.Amount);
|
||||
Assert.Equal(12.30322814m, payout.PayoutAmount);
|
||||
Assert.Equal(12.303228134m, payout.OriginalAmount);
|
||||
|
||||
await client.MarkPayoutPaid(storeId, payout.Id);
|
||||
payout = (await client.GetPayouts(payout.PullPaymentId)).First(data => data.Id == payout.Id);
|
||||
@ -1240,7 +1381,7 @@ namespace BTCPayServer.Tests
|
||||
Name = "Test 3",
|
||||
Amount = 12.303228134m,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
PayoutMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
});
|
||||
var lnrURLs = await unauthenticated.GetPullPaymentLNURL(test4.Id);
|
||||
Assert.IsType<string>(lnrURLs.LNURLBech32);
|
||||
@ -1315,7 +1456,7 @@ namespace BTCPayServer.Tests
|
||||
Name = "Test SATS",
|
||||
Amount = 21000,
|
||||
Currency = "SATS",
|
||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
PayoutMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
});
|
||||
lnrURLs = await unauthenticated.GetPullPaymentLNURL(testSats.Id);
|
||||
Assert.IsType<string>(lnrURLs.LNURLBech32);
|
||||
@ -1333,7 +1474,7 @@ namespace BTCPayServer.Tests
|
||||
Amount = 100,
|
||||
Currency = "USD",
|
||||
Name = "pull payment",
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
PayoutMethods = new[] { "BTC" },
|
||||
AutoApproveClaims = true
|
||||
});
|
||||
});
|
||||
@ -1342,7 +1483,7 @@ namespace BTCPayServer.Tests
|
||||
await nonApproved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Approved = true,
|
||||
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
|
||||
});
|
||||
@ -1353,14 +1494,14 @@ namespace BTCPayServer.Tests
|
||||
Amount = 100,
|
||||
Currency = "USD",
|
||||
Name = "pull payment",
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
PayoutMethods = new[] { "BTC" },
|
||||
AutoApproveClaims = true
|
||||
});
|
||||
|
||||
await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Approved = true,
|
||||
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
|
||||
});
|
||||
@ -1381,7 +1522,7 @@ namespace BTCPayServer.Tests
|
||||
var payout = await client.CreatePayout(storeId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Destination = address.ToString(),
|
||||
|
||||
@ -1408,7 +1549,7 @@ namespace BTCPayServer.Tests
|
||||
payout = await client.CreatePayout(storeId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Destination = address.ToString()
|
||||
});
|
||||
@ -1529,13 +1670,14 @@ namespace BTCPayServer.Tests
|
||||
CssUrl = "https://example.org/style.css",
|
||||
LogoUrl = "https://example.org/logo.svg",
|
||||
BrandColor = "#003366",
|
||||
ApplyBrandColorToBackend = true,
|
||||
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Amount = 10,
|
||||
Above = true,
|
||||
PaymentMethod = "BTC",
|
||||
PaymentMethodId = "BTC",
|
||||
CurrencyCode = "USD"
|
||||
}
|
||||
}
|
||||
@ -1544,13 +1686,14 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("https://example.org/style.css", updatedStore.CssUrl);
|
||||
Assert.Equal("https://example.org/logo.svg", updatedStore.LogoUrl);
|
||||
Assert.Equal("#003366", updatedStore.BrandColor);
|
||||
Assert.True(updatedStore.ApplyBrandColorToBackend);
|
||||
var s = (await client.GetStore(newStore.Id));
|
||||
Assert.Equal("B", s.Name);
|
||||
var pmc = Assert.Single(s.PaymentMethodCriteria);
|
||||
//check that pmc equals the one we set
|
||||
Assert.Equal(10, pmc.Amount);
|
||||
Assert.True(pmc.Above);
|
||||
Assert.Equal("BTC-CHAIN", pmc.PaymentMethod);
|
||||
Assert.Equal("BTC-CHAIN", pmc.PaymentMethodId);
|
||||
Assert.Equal("USD", pmc.CurrencyCode);
|
||||
updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" });
|
||||
Assert.Empty(newStore.PaymentMethodCriteria);
|
||||
@ -1711,7 +1854,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var clientProfile = await user.CreateClient(Policies.CanModifyStoreWebhooks, Policies.CanCreateInvoice);
|
||||
var clientProfile = await user.CreateClient(Policies.CanModifyWebhooks, Policies.CanCreateInvoice);
|
||||
var hook = await clientProfile.CreateWebhook(user.StoreId, new CreateStoreWebhookRequest()
|
||||
{
|
||||
Url = fakeServer.ServerUri.AbsoluteUri,
|
||||
@ -2138,7 +2281,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, "lol fake invoice id", new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
});
|
||||
});
|
||||
@ -2146,7 +2289,7 @@ namespace BTCPayServer.Tests
|
||||
// test validation error for when invoice is not yet in the state in which it can be refunded
|
||||
var apiError = await AssertAPIError("non-refundable", () => client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
}));
|
||||
Assert.Equal("Cannot refund this invoice", apiError.Message);
|
||||
@ -2164,20 +2307,20 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
|
||||
// test validation for the payment method
|
||||
var validationError = await AssertValidationError(new[] { "PaymentMethod" }, async () =>
|
||||
var validationError = await AssertValidationError(new[] { "PayoutMethodId" }, async () =>
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = "fake payment method",
|
||||
PayoutMethodId = "fake payment method",
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
});
|
||||
});
|
||||
Assert.Contains("PaymentMethod: Please select one of the payment methods which were available for the original invoice", validationError.Message);
|
||||
Assert.Contains("PayoutMethodId: Please select one of the payment methods which were available for the original invoice", validationError.Message);
|
||||
|
||||
// test RefundVariant.RateThen
|
||||
var pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
@ -2188,7 +2331,7 @@ namespace BTCPayServer.Tests
|
||||
// test RefundVariant.CurrentRate
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.CurrentRate
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
@ -2198,7 +2341,7 @@ namespace BTCPayServer.Tests
|
||||
// test RefundVariant.Fiat
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Fiat,
|
||||
Name = "my test name"
|
||||
});
|
||||
@ -2212,7 +2355,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Custom,
|
||||
});
|
||||
});
|
||||
@ -2221,7 +2364,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Custom,
|
||||
CustomAmount = 69420,
|
||||
CustomCurrency = "JPY"
|
||||
@ -2233,7 +2376,7 @@ namespace BTCPayServer.Tests
|
||||
// should auto-approve if currencies match
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Custom,
|
||||
CustomAmount = 0.00069420m,
|
||||
CustomCurrency = "BTC"
|
||||
@ -2245,7 +2388,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen,
|
||||
SubtractPercentage = 101
|
||||
});
|
||||
@ -2255,7 +2398,7 @@ namespace BTCPayServer.Tests
|
||||
// should auto-approve
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen,
|
||||
SubtractPercentage = 6.15m
|
||||
});
|
||||
@ -2268,7 +2411,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.OverpaidAmount
|
||||
});
|
||||
});
|
||||
@ -2278,6 +2421,14 @@ namespace BTCPayServer.Tests
|
||||
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = 5000.0m, Currency = "USD" });
|
||||
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
||||
method = methods.First();
|
||||
Assert.Equal(JTokenType.Null, method.AdditionalData["accountDerivation"].Type);
|
||||
Assert.NotNull(method.AdditionalData["keyPath"]);
|
||||
|
||||
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id, includeSensitive: true);
|
||||
method = methods.First();
|
||||
Assert.Equal(JTokenType.String, method.AdditionalData["accountDerivation"].Type);
|
||||
var clientViewOnly = await user.CreateClient(Policies.CanViewInvoices);
|
||||
await AssertApiError(403, "missing-permission", () => clientViewOnly.GetInvoicePaymentMethods(user.StoreId, invoice.Id, includeSensitive: true));
|
||||
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
@ -2298,7 +2449,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.OverpaidAmount
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
@ -2308,7 +2459,7 @@ namespace BTCPayServer.Tests
|
||||
// once more with subtract percentage
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.OverpaidAmount,
|
||||
SubtractPercentage = 21m
|
||||
});
|
||||
@ -2321,7 +2472,7 @@ namespace BTCPayServer.Tests
|
||||
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest { Status = InvoiceStatus.Settled });
|
||||
var refund = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
PayoutMethodId = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.CurrentRate
|
||||
});
|
||||
Assert.Equal(1.0m, refund.Amount);
|
||||
@ -2616,6 +2767,14 @@ namespace BTCPayServer.Tests
|
||||
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
|
||||
Assert.DoesNotContain(invoiceObject.Links.Select(l => l.Type), t => t == "address");
|
||||
|
||||
// Check if we can get the monitored invoice
|
||||
var invoiceRepo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var includeNonActivated = true;
|
||||
Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN"), includeNonActivated), i => i.Id == invoice.Id);
|
||||
includeNonActivated = false;
|
||||
Assert.DoesNotContain(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN"), includeNonActivated), i => i.Id == invoice.Id);
|
||||
Assert.DoesNotContain(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN")), i => i.Id == invoice.Id);
|
||||
//
|
||||
|
||||
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
@ -3105,6 +3264,8 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC", xpub)).Addresses.First().Address);
|
||||
// Testing if the rewrite rule to old API path is working
|
||||
await viewOnlyClient.SendHttpRequest($"api/v1/stores/{store.Id}/payment-methods/onchain/BTC/preview", new JObject() { ["config"] = xpub.ToString() }, HttpMethod.Post);
|
||||
|
||||
var method = await client.UpdateStorePaymentMethod(store.Id, "BTC-CHAIN", new UpdatePaymentMethodRequest() { Enabled = true, Config = JValue.CreateString(xpub.ToString())});
|
||||
|
||||
@ -3158,8 +3319,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1 });
|
||||
|
||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1, Label = "test" });
|
||||
Assert.Equal("test", generateResponse.Config.Label);
|
||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
|
||||
Assert.Equal(new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
||||
@ -3384,6 +3545,12 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
|
||||
});
|
||||
|
||||
// Testing if the rewrite rule to old API path is working
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.SendHttpRequest($"api/v1/stores/{walletId.StoreId}/payment-methods/onchain/{walletId.CryptoCode}/wallet/address", null as object);
|
||||
});
|
||||
var address = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
|
||||
var address2 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
|
||||
var address3 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true);
|
||||
@ -4010,7 +4177,12 @@ namespace BTCPayServer.Tests
|
||||
var resp = await tester.CustomerLightningD.Pay(inv.BOLT11);
|
||||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
|
||||
|
||||
var store = tester.PayTester.GetService<StoreRepository>();
|
||||
Assert.True(await store.InternalNodePayoutAuthorized(admin.StoreId));
|
||||
Assert.False(await store.InternalNodePayoutAuthorized("blah"));
|
||||
await admin.MakeAdmin(false);
|
||||
Assert.False(await store.InternalNodePayoutAuthorized(admin.StoreId));
|
||||
await admin.MakeAdmin(true);
|
||||
|
||||
var customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
|
||||
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
|
||||
@ -4018,7 +4190,7 @@ namespace BTCPayServer.Tests
|
||||
new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC_LightningNetwork",
|
||||
PayoutMethodId = "BTC_LightningNetwork",
|
||||
Destination = customerInvoice.BOLT11
|
||||
});
|
||||
Assert.Equal(payout.Metadata.ToString(), new JObject().ToString()); //empty
|
||||
@ -4037,7 +4209,7 @@ namespace BTCPayServer.Tests
|
||||
new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(),
|
||||
Amount = 0.0001m,
|
||||
Metadata = JObject.FromObject(new
|
||||
@ -4068,10 +4240,10 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Approved = true,
|
||||
Amount = new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC),
|
||||
PaymentMethod = "BTC_LightningNetwork",
|
||||
PayoutMethodId = "BTC_LightningNetwork",
|
||||
Destination = customerInvoice.BOLT11
|
||||
});
|
||||
Assert.Equal(payout2.Amount, new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC));
|
||||
Assert.Equal(payout2.OriginalAmount, new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC));
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -4098,7 +4270,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 0.0001m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
@ -4106,7 +4278,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 0.00001m,
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
@ -4115,7 +4287,7 @@ namespace BTCPayServer.Tests
|
||||
Amount = 100,
|
||||
Currency = "USD",
|
||||
Name = "pull payment",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
PayoutMethods = new[] { "BTC" }
|
||||
});
|
||||
|
||||
var notapprovedPayoutWithPullPayment = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
@ -4123,7 +4295,7 @@ namespace BTCPayServer.Tests
|
||||
PullPaymentId = pullPayment.Id,
|
||||
Amount = 10,
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
await adminClient.ApprovePayout(admin.StoreId, notapprovedPayoutWithPullPayment.Id,
|
||||
@ -4141,7 +4313,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Equal(3, payouts.Length);
|
||||
Assert.Empty(payouts.Where(data => data.State == PayoutState.AwaitingApproval));
|
||||
Assert.Empty(payouts.Where(data => data.PaymentMethodAmount is null));
|
||||
Assert.Empty(payouts.Where(data => data.PayoutAmount is null));
|
||||
|
||||
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
@ -4154,12 +4326,12 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
Assert.Equal("BTC-CHAIN", Assert.Single(tpGen.PaymentMethods));
|
||||
Assert.Equal("BTC-CHAIN", Assert.Single(tpGen.PayoutMethods));
|
||||
//still too poor to process any payouts
|
||||
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
|
||||
await adminClient.RemovePayoutProcessor(admin.StoreId, tpGen.Name, tpGen.PaymentMethods.First());
|
||||
await adminClient.RemovePayoutProcessor(admin.StoreId, tpGen.Name, tpGen.PayoutMethods.First());
|
||||
|
||||
Assert.Empty(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
@ -4254,7 +4426,7 @@ namespace BTCPayServer.Tests
|
||||
PullPaymentId = pullPayment.Id,
|
||||
Amount = 0.5m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
TestLogs.LogInformation("Waiting before hook...");
|
||||
@ -4300,7 +4472,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 0.1m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
@ -4316,7 +4488,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 0.3m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
@ -4333,7 +4505,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 0.3m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
PayoutMethodId = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
|
@ -50,10 +50,8 @@ namespace BTCPayServer.Tests
|
||||
tester.Driver.FindElement(By.Name("Name")).SendKeys("English (Custom)");
|
||||
tester.ClickPagePrimary();
|
||||
var translations = tester.Driver.FindElement(By.Name("Translations"));
|
||||
var text = translations.Text;
|
||||
text = text.Replace("Password => Password", "Password => Mot de passe");
|
||||
translations.Clear();
|
||||
translations.SendKeys("Password => Mot de passe");
|
||||
translations.SendKeys("{ \"Password\": \"Mot de passe\" }");
|
||||
tester.ClickPagePrimary();
|
||||
|
||||
// Check English (Custom) can be selected
|
||||
@ -64,7 +62,7 @@ namespace BTCPayServer.Tests
|
||||
// Check if we can remove English (Custom)
|
||||
tester.LogIn();
|
||||
tester.GoToServer(Views.Server.ServerNavPages.Translations);
|
||||
text = tester.Driver.PageSource;
|
||||
var text = tester.Driver.PageSource;
|
||||
Assert.Contains("Select-Cypherpunk", text);
|
||||
Assert.DoesNotContain("Select-English (Custom)", text);
|
||||
// Cypherpunk is loaded from file, can't edit
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Controllers;
|
||||
@ -90,6 +91,54 @@ fruit tea:
|
||||
Assert.Null( parsedDefault[4].AdditionalData);
|
||||
Assert.Null( parsedDefault[4].PaymentMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanParseAppTemplate()
|
||||
{
|
||||
var template = @"[
|
||||
{
|
||||
""description"": ""Lovely, fresh and tender, Meng Ding Gan Lu ('sweet dew') is grown in the lush Meng Ding Mountains of the southwestern province of Sichuan where it has been cultivated for over a thousand years."",
|
||||
""id"": ""green-tea"",
|
||||
""image"": ""~/img/pos-sample/green-tea.jpg"",
|
||||
""priceType"": ""Fixed"",
|
||||
""price"": ""1"",
|
||||
""title"": ""Green Tea"",
|
||||
""disabled"": false
|
||||
},
|
||||
{
|
||||
""description"": ""Tian Jian Tian Jian means 'heavenly tippy tea' in Chinese, and it describes the finest grade of dark tea. Our Tian Jian dark tea is from Hunan province which is famous for making some of the best dark teas available."",
|
||||
""id"": ""black-tea"",
|
||||
""image"": ""~/img/pos-sample/black-tea.jpg"",
|
||||
""priceType"": ""Fixed"",
|
||||
""price"": ""1"",
|
||||
""title"": ""Black Tea"",
|
||||
""disabled"": false
|
||||
}
|
||||
]";
|
||||
|
||||
var items = AppService.Parse(template);
|
||||
Assert.Equal(2, items.Length);
|
||||
Assert.Equal("green-tea", items[0].Id);
|
||||
Assert.Equal("black-tea", items[1].Id);
|
||||
|
||||
// Fails gracefully for missing ID
|
||||
var missingId = template.Replace(@"""id"": ""green-tea"",", "");
|
||||
items = AppService.Parse(missingId);
|
||||
Assert.Single(items);
|
||||
Assert.Equal("black-tea", items[0].Id);
|
||||
|
||||
// Throws for missing ID
|
||||
Assert.Throws<ArgumentException>(() => AppService.Parse(missingId, true, true));
|
||||
|
||||
// Fails gracefully for duplicate IDs
|
||||
var duplicateId = template.Replace(@"""id"": ""green-tea"",", @"""id"": ""black-tea"",");
|
||||
items = AppService.Parse(duplicateId);
|
||||
Assert.Empty(items);
|
||||
|
||||
// Throws for duplicate IDs
|
||||
Assert.Throws<ArgumentException>(() => AppService.Parse(duplicateId, true, true));
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@ -121,13 +122,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("SignTransaction")).Click();
|
||||
}
|
||||
|
||||
private static string ExtractPSBT(SeleniumTester s)
|
||||
{
|
||||
var pageSource = s.Driver.PageSource;
|
||||
var start = pageSource.IndexOf("id=\"psbt-base64\">");
|
||||
start += "id=\"psbt-base64\">".Length;
|
||||
var end = pageSource.IndexOf("<", start);
|
||||
return pageSource[start..end];
|
||||
}
|
||||
private string ExtractPSBT(SeleniumTester s) => s.Driver.FindElement(By.Id("psbt-base64")).GetAttribute("innerText");
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,8 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
using BTCPayServer.Views.Stores;
|
||||
@ -18,7 +16,6 @@ using NBitcoin;
|
||||
using NBitcoin.RPC;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
|
||||
@ -58,6 +55,7 @@ namespace BTCPayServer.Tests
|
||||
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
|
||||
options.AddArgument("shm-size=2g");
|
||||
options.AddArgument("start-maximized");
|
||||
options.AddArgument("disable-search-engine-choice-screen");
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
// Shot in the dark to fix https://stackoverflow.com/questions/53902507/unknown-error-session-deleted-because-of-page-crash-from-unknown-error-cannot
|
||||
@ -93,7 +91,15 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
if (amount is not null)
|
||||
{
|
||||
Driver.FindElement(By.Id("test-payment-amount")).Clear();
|
||||
try
|
||||
{
|
||||
Driver.FindElement(By.Id("test-payment-amount")).Clear();
|
||||
}
|
||||
// Sometimes the element is not available after a window switch... retry
|
||||
catch (StaleElementReferenceException)
|
||||
{
|
||||
Driver.FindElement(By.Id("test-payment-amount")).Clear();
|
||||
}
|
||||
Driver.FindElement(By.Id("test-payment-amount")).SendKeys(amount.ToString());
|
||||
}
|
||||
Driver.WaitUntilAvailable(By.Id("FakePayment"));
|
||||
@ -541,7 +547,6 @@ retry:
|
||||
{
|
||||
walletId ??= WalletId;
|
||||
GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = Driver.FindElement(By.Id("Address")).GetAttribute("data-text");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
|
||||
for (var i = 0; i < coins; i++)
|
||||
|
@ -6,7 +6,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -20,6 +19,7 @@ using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
@ -28,7 +28,6 @@ using BTCPayServer.Views.Wallets;
|
||||
using ExchangeSharp;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
@ -373,7 +372,7 @@ namespace BTCPayServer.Tests
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
s.ClickPagePrimary();
|
||||
var url = s.FindAlertMessage().FindElement(By.TagName("a")).Text;
|
||||
var url = s.Driver.FindElement(By.Id("InvitationUrl")).GetAttribute("data-text");
|
||||
|
||||
s.Logout();
|
||||
s.Driver.Navigate().GoToUrl(url);
|
||||
@ -404,6 +403,84 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("/login", s.Driver.Url);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageUsers()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
//Create Users
|
||||
s.RegisterNewUser();
|
||||
var user = s.AsTestAccount();
|
||||
s.Logout();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
var admin = s.AsTestAccount();
|
||||
s.GoToHome();
|
||||
s.GoToServer(ServerNavPages.Users);
|
||||
|
||||
// Manage user password reset
|
||||
var rows = s.Driver.FindElements(By.CssSelector("#UsersList tr.user-overview-row"));
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).Clear();
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(user.RegisterDetails.Email);
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(Keys.Enter);
|
||||
rows = s.Driver.FindElements(By.CssSelector("#UsersList tr.user-overview-row"));
|
||||
Assert.Single(rows);
|
||||
Assert.Contains(user.RegisterDetails.Email, rows.First().Text);
|
||||
s.Driver.FindElement(By.CssSelector("#UsersList tr.user-overview-row:first-child .reset-password")).Click();
|
||||
s.Driver.WaitForElement(By.Id("Password")).SendKeys("Password@1!");
|
||||
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("Password@1!");
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("Password successfully set", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success).Text);
|
||||
|
||||
// Manage user status (disable and enable)
|
||||
// Disable user
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).Clear();
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(user.RegisterDetails.Email);
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(Keys.Enter);
|
||||
rows = s.Driver.FindElements(By.CssSelector("#UsersList tr.user-overview-row"));
|
||||
Assert.Single(rows);
|
||||
Assert.Contains(user.RegisterDetails.Email, rows.First().Text);
|
||||
s.Driver.FindElement(By.CssSelector("#UsersList tr.user-overview-row:first-child .disable-user")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmContinue")).Click();
|
||||
Assert.Contains("User disabled", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success).Text);
|
||||
//Enable user
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).Clear();
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(user.RegisterDetails.Email);
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(Keys.Enter);
|
||||
rows = s.Driver.FindElements(By.CssSelector("#UsersList tr.user-overview-row"));
|
||||
Assert.Single(rows);
|
||||
Assert.Contains(user.RegisterDetails.Email, rows.First().Text);
|
||||
s.Driver.FindElement(By.CssSelector("#UsersList tr.user-overview-row:first-child .enable-user")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmContinue")).Click();
|
||||
Assert.Contains("User enabled", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success).Text);
|
||||
|
||||
// Manage user details (edit)
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).Clear();
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(user.RegisterDetails.Email);
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(Keys.Enter);
|
||||
rows = s.Driver.FindElements(By.CssSelector("#UsersList tr.user-overview-row"));
|
||||
Assert.Single(rows);
|
||||
Assert.Contains(user.RegisterDetails.Email, rows.First().Text);
|
||||
s.Driver.FindElement(By.CssSelector("#UsersList tr.user-overview-row:first-child .user-edit")).Click();
|
||||
s.Driver.WaitForElement(By.Id("Name")).SendKeys("Test User");
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("User successfully updated", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success).Text);
|
||||
|
||||
// Manage user deletion
|
||||
s.GoToServer(ServerNavPages.Users);
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).Clear();
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(user.RegisterDetails.Email);
|
||||
s.Driver.FindElement(By.Id("SearchTerm")).SendKeys(Keys.Enter);
|
||||
rows = s.Driver.FindElements(By.CssSelector("#UsersList tr.user-overview-row"));
|
||||
Assert.Single(rows);
|
||||
Assert.Contains(user.RegisterDetails.Email, rows.First().Text);
|
||||
s.Driver.FindElement(By.CssSelector("#UsersList tr.user-overview-row:first-child .delete-user")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmContinue")).Click();
|
||||
Assert.Contains("User deleted", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success).Text);
|
||||
|
||||
s.Driver.AssertNoError();
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanRequireApprovalForNewAccounts()
|
||||
{
|
||||
@ -747,6 +824,14 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("[data-invoice-state-badge] .dropdown-toggle")).Click();
|
||||
s.Driver.FindElements(By.CssSelector("[data-invoice-state-badge] .dropdown-menu button"))[0].Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("Settled (marked)", s.Driver.PageSource));
|
||||
|
||||
// zero amount invoice should redirect to receipt
|
||||
var zeroAmountId = s.CreateInvoice(0);
|
||||
s.GoToUrl($"/i/{zeroAmountId}");
|
||||
Assert.EndsWith("/receipt", s.Driver.Url);
|
||||
Assert.Contains("$0.00", s.Driver.PageSource);
|
||||
s.GoToInvoice(zeroAmountId);
|
||||
Assert.Equal("Settled", s.Driver.FindElement(By.CssSelector("[data-invoice-state-badge]")).Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -1153,6 +1238,7 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
var userId = s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GenerateWallet();
|
||||
(_, string appId) = s.CreateApp("PointOfSale");
|
||||
s.Driver.FindElement(By.Id("Title")).Clear();
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Tea shop");
|
||||
@ -1164,10 +1250,20 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");
|
||||
Assert.Contains("\"buyButtonText\": \"Take my money\"", template);
|
||||
Assert.Matches("\"categories\": \\[\n\\s+\"Drinks\"\n\\s+\\]", template);
|
||||
Assert.Matches("\"categories\": \\[\r?\n\\s*\"Drinks\"\\s*\\]", template);
|
||||
|
||||
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.ScrollTo(By.Id("CodeTabButton"));
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");
|
||||
s.Driver.FindElement(By.Id("TemplateConfig")).Clear();
|
||||
s.Driver.FindElement(By.Id("TemplateConfig")).SendKeys(template.Replace(@"""id"": ""green-tea"",", ""));
|
||||
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("Invalid template: Missing ID for item \"Green Tea\".", s.Driver.FindElement(By.CssSelector(".validation-summary-errors")).Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
@ -1546,7 +1642,6 @@ namespace BTCPayServer.Tests
|
||||
s.GenerateWallet("BTC", "", false, true);
|
||||
var walletId = new WalletId(storeId, "BTC");
|
||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
s.Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = s.Driver.FindElement(By.Id("Address")).GetAttribute("data-text");
|
||||
var address = BitcoinAddress.Create(addressStr,
|
||||
((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
||||
@ -1751,7 +1846,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("WalletNav-Receive")).Click();
|
||||
|
||||
//generate a receiving address
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.CssSelector("#address-tab .qr-container")).Displayed);
|
||||
// no previous page in the wizard, hence no back button
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("GoBack")));
|
||||
@ -1773,10 +1867,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(s.Driver.FindElement(By.CssSelector("[data-value='test-label']")));
|
||||
});
|
||||
|
||||
//unreserve
|
||||
s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
|
||||
//generate it again, should be the same one as before as nothing got used in the meantime
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.CssSelector("#address-tab .qr-container")).Displayed);
|
||||
Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("Address")).GetAttribute("data-text"));
|
||||
TestUtils.Eventually(() =>
|
||||
@ -1866,7 +1956,7 @@ namespace BTCPayServer.Tests
|
||||
// Check the tx sent earlier arrived
|
||||
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
|
||||
s.Driver.WaitWalletTransactionsLoaded();
|
||||
s.Driver.FindElement(By.PartialLinkText(tx.ToString()));
|
||||
s.Driver.FindElement(By.CssSelector($"[data-text='{tx}']"));
|
||||
|
||||
var walletTransactionUri = new Uri(s.Driver.Url);
|
||||
|
||||
@ -1894,13 +1984,13 @@ namespace BTCPayServer.Tests
|
||||
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(s, 0, jack, 0.01m);
|
||||
s.Driver.FindElement(By.Id("SignTransaction")).Click();
|
||||
|
||||
s.Driver.WaitForElement(By.CssSelector("button[value=broadcast]"));
|
||||
Assert.Contains(jack.ToString(), s.Driver.PageSource);
|
||||
Assert.Contains("0.01000000", s.Driver.PageSource);
|
||||
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
Assert.Equal(walletTransactionUri.ToString(), s.Driver.Url);
|
||||
var bip21 = invoice.EntityToDTO(s.Server.PayTester.GetService<Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension>>()).CryptoInfo.First().PaymentUrls.BIP21;
|
||||
var bip21 = invoice.EntityToDTO(s.Server.PayTester.GetService<Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension>>(), s.Server.PayTester.GetService<CurrencyNameTable>()).CryptoInfo.First().PaymentUrls.BIP21;
|
||||
//let's make bip21 more interesting
|
||||
bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
|
||||
var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
|
||||
@ -2684,9 +2774,9 @@ namespace BTCPayServer.Tests
|
||||
var sums = cartData.FindElements(By.CssSelector("tfoot tr"));
|
||||
Assert.Equal(2, items.Count);
|
||||
Assert.Equal(4, sums.Count);
|
||||
Assert.Contains("Manual entry 1", items[0].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("Custom Amount 1", items[0].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("1 234,00 €", items[0].FindElement(By.CssSelector("td")).Text);
|
||||
Assert.Contains("Manual entry 2", items[1].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("Custom Amount 2", items[1].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("0,56 €", items[1].FindElement(By.CssSelector("td")).Text);
|
||||
Assert.Contains("Subtotal", sums[0].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("1 234,56 €", sums[0].FindElement(By.CssSelector("td")).Text);
|
||||
@ -2707,9 +2797,9 @@ namespace BTCPayServer.Tests
|
||||
sums = paymentDetails.FindElements(By.CssSelector("tr.sums-data"));
|
||||
Assert.Equal(2, items.Count);
|
||||
Assert.Equal(4, sums.Count);
|
||||
Assert.Contains("Manual entry 1", items[0].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("Custom Amount 1", items[0].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("1 234,00 €", items[0].FindElement(By.CssSelector(".val")).Text);
|
||||
Assert.Contains("Manual entry 2", items[1].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("Custom Amount 2", items[1].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("0,56 €", items[1].FindElement(By.CssSelector(".val")).Text);
|
||||
Assert.Contains("Subtotal", sums[0].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("1 234,56 €", sums[0].FindElement(By.CssSelector(".val")).Text);
|
||||
@ -2770,7 +2860,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("1 x 1,00 € = 1,00 €", items[0].FindElement(By.CssSelector("td")).Text);
|
||||
Assert.Contains("Green Tea", items[1].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("2 x 1,00 € = 2,00 €", items[1].FindElement(By.CssSelector("td")).Text);
|
||||
Assert.Contains("Manual entry 1", items[2].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("Custom Amount 1", items[2].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("1,23 €", items[2].FindElement(By.CssSelector("td")).Text);
|
||||
Assert.Contains("Total", sums[0].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("4,23 €", sums[0].FindElement(By.CssSelector("td")).Text);
|
||||
@ -2789,7 +2879,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("1 x 1,00 € = 1,00 €", items[0].FindElement(By.CssSelector(".val")).Text);
|
||||
Assert.Contains("Green Tea", items[1].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("2 x 1,00 € = 2,00 €", items[1].FindElement(By.CssSelector(".val")).Text);
|
||||
Assert.Contains("Manual entry 1", items[2].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("Custom Amount 1", items[2].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("1,23 €", items[2].FindElement(By.CssSelector(".val")).Text);
|
||||
Assert.Contains("Total", sums[0].FindElement(By.CssSelector(".key")).Text);
|
||||
Assert.Contains("4,23 €", sums[0].FindElement(By.CssSelector(".val")).Text);
|
||||
@ -3014,7 +3104,7 @@ namespace BTCPayServer.Tests
|
||||
// Topup Invoice test
|
||||
var i = s.CreateInvoice(storeId, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
var lnurl = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LNURL .truncate-center-start")).Text;
|
||||
var lnurl = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LNURL .truncate-center")).GetAttribute("data-text");
|
||||
var parsed = LNURL.LNURL.Parse(lnurl, out var tag);
|
||||
var fetchedReuqest =
|
||||
Assert.IsType<LNURL.LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||
@ -3051,7 +3141,7 @@ namespace BTCPayServer.Tests
|
||||
i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
// BOLT11 is also displayed for standard invoice (not LNURL, even if it is available)
|
||||
var bolt11 = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center-start")).Text;
|
||||
var bolt11 = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center")).GetAttribute("data-text");
|
||||
BOLT11PaymentRequest.Parse(bolt11, s.Server.ExplorerNode.Network);
|
||||
var invoiceId = s.Driver.Url.Split('/').Last();
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync("BTC/lnurl/pay/i/" + invoiceId))
|
||||
@ -3104,7 +3194,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
i = s.CreateInvoice(storeId, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LNURL .truncate-center-start")).Text;
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LNURL .truncate-center")).GetAttribute("data-text");
|
||||
Assert.StartsWith("lnurlp", lnurl);
|
||||
LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
@ -3117,7 +3207,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
var invForPP = s.CreateInvoice(null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(invForPP);
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LNURL .truncate-center-start")).Text;
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LNURL .truncate-center")).GetAttribute("data-text");
|
||||
LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
// Check that pull payment has lightning option
|
||||
@ -3338,11 +3428,14 @@ namespace BTCPayServer.Tests
|
||||
var user = s.RegisterNewUser();
|
||||
s.GoToHome();
|
||||
s.GoToProfile(ManageNavPages.LoginCodes);
|
||||
var code = s.Driver.FindElement(By.Id("logincode")).GetAttribute("value");
|
||||
s.ClickPagePrimary();
|
||||
Assert.NotEqual(code, s.Driver.FindElement(By.Id("logincode")).GetAttribute("value"));
|
||||
|
||||
code = s.Driver.FindElement(By.Id("logincode")).GetAttribute("value");
|
||||
string code = null;
|
||||
TestUtils.Eventually(() => { code = s.Driver.FindElement(By.CssSelector("#LoginCode .qr-code")).GetAttribute("alt"); });
|
||||
string prevCode = code;
|
||||
await s.Driver.Navigate().RefreshAsync();
|
||||
TestUtils.Eventually(() => { code = s.Driver.FindElement(By.CssSelector("#LoginCode .qr-code")).GetAttribute("alt"); });
|
||||
Assert.NotEqual(prevCode, code);
|
||||
TestUtils.Eventually(() => { code = s.Driver.FindElement(By.CssSelector("#LoginCode .qr-code")).GetAttribute("alt"); });
|
||||
s.Logout();
|
||||
s.GoToLogin();
|
||||
s.Driver.SetAttribute("LoginCode", "value", "bad code");
|
||||
|
@ -25,6 +25,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public class ServerTester : IDisposable
|
||||
{
|
||||
public const string DefaultConnectionString = "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=btcpayserver";
|
||||
public List<IDisposable> Resources = new List<IDisposable>();
|
||||
readonly string _Directory;
|
||||
|
||||
@ -50,19 +51,16 @@ namespace BTCPayServer.Tests
|
||||
PayTester = new BTCPayServerTester(TestLogs, LoggerProvider, Path.Combine(_Directory, "pay"))
|
||||
{
|
||||
NBXplorerUri = ExplorerClient.Address,
|
||||
TestDatabase = Enum.Parse<TestDatabases>(GetEnvironment("TESTS_DB", TestDatabases.Postgres.ToString()), true),
|
||||
// TODO: The fact that we use same conn string as development database can cause huge problems with tests
|
||||
// since in dev we already can have some users / stores registered, while on CI database is being initalized
|
||||
// for the first time and first registered user gets admin status by default
|
||||
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=btcpayserver"),
|
||||
Postgres = GetEnvironment("TESTS_POSTGRES", DefaultConnectionString),
|
||||
ExplorerPostgres = GetEnvironment("TESTS_EXPLORER_POSTGRES", "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer"),
|
||||
MySQL = GetEnvironment("TESTS_MYSQL", "User ID=root;Host=127.0.0.1;Port=33036;Database=btcpayserver")
|
||||
};
|
||||
if (newDb)
|
||||
{
|
||||
var r = RandomUtils.GetUInt32();
|
||||
PayTester.Postgres = PayTester.Postgres.Replace("btcpayserver", $"btcpayserver{r}");
|
||||
PayTester.MySQL = PayTester.MySQL.Replace("btcpayserver", $"btcpayserver{r}");
|
||||
TestLogs.LogInformation($"Database used: btcpayserver{r}");
|
||||
}
|
||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
||||
@ -85,7 +83,7 @@ namespace BTCPayServer.Tests
|
||||
File.Copy(file, Path.Combine(langdir, Path.GetFileName(file)));
|
||||
}
|
||||
|
||||
#if ALTCOINS
|
||||
|
||||
public void ActivateLTC()
|
||||
{
|
||||
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBitcoinNetwork);
|
||||
@ -100,12 +98,7 @@ namespace BTCPayServer.Tests
|
||||
PayTester.Chains.Add("LBTC");
|
||||
PayTester.LBTCNBXplorerUri = LBTCExplorerClient.Address;
|
||||
}
|
||||
public void ActivateETH()
|
||||
{
|
||||
PayTester.Chains.Add("ETH");
|
||||
}
|
||||
|
||||
#endif
|
||||
public void ActivateLightning()
|
||||
{
|
||||
ActivateLightning(LightningConnectionType.CLightning);
|
||||
@ -239,7 +232,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
#if ALTCOINS
|
||||
|
||||
public RPCClient LTCExplorerNode
|
||||
{
|
||||
get; set;
|
||||
@ -248,15 +241,12 @@ namespace BTCPayServer.Tests
|
||||
public RPCClient LBTCExplorerNode { get; set; }
|
||||
public ExplorerClient LTCExplorerClient { get; set; }
|
||||
public ExplorerClient LBTCExplorerClient { get; set; }
|
||||
#endif
|
||||
|
||||
public ExplorerClient ExplorerClient
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
readonly HttpClient _Http = new HttpClient();
|
||||
|
||||
public BTCPayServerTester PayTester
|
||||
{
|
||||
get; set;
|
||||
|
@ -146,19 +146,27 @@ namespace BTCPayServer.Tests
|
||||
public async Task ModifyPayment(Action<GeneralSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<UIStoresController>();
|
||||
var response = await storeController.GeneralSettings();
|
||||
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model;
|
||||
var response = await storeController.GeneralSettings(StoreId);
|
||||
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model!;
|
||||
modify(settings);
|
||||
await storeController.GeneralSettings(settings);
|
||||
}
|
||||
|
||||
public async Task ModifyGeneralSettings(Action<GeneralSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<UIStoresController>();
|
||||
var response = await storeController.GeneralSettings(StoreId);
|
||||
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model!;
|
||||
modify(settings);
|
||||
storeController.GeneralSettings(settings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task ModifyOnchainPaymentSettings(Action<WalletSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<UIStoresController>();
|
||||
var response = await storeController.WalletSettings(StoreId, "BTC");
|
||||
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
||||
modify(walletSettings);
|
||||
storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult();
|
||||
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
@ -527,7 +535,7 @@ retry:
|
||||
{
|
||||
var server = new FakeServer();
|
||||
await server.Start();
|
||||
var client = await CreateClient(Policies.CanModifyStoreWebhooks);
|
||||
var client = await CreateClient(Policies.CanModifyWebhooks);
|
||||
var wh = await client.CreateWebhook(StoreId, new CreateStoreWebhookRequest()
|
||||
{
|
||||
AutomaticRedelivery = false,
|
||||
@ -669,7 +677,7 @@ retry:
|
||||
var db = (NpgsqlConnection)dbContext.Database.GetDbConnection();
|
||||
await db.OpenAsync();
|
||||
bool isHeader = true;
|
||||
using (var writer = db.BeginTextImport("COPY \"Invoices\" (\"Id\",\"Blob\",\"Created\",\"CustomerEmail\",\"ExceptionStatus\",\"ItemCode\",\"OrderId\",\"Status\",\"StoreDataId\",\"Archived\",\"Blob2\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
using (var writer = db.BeginTextImport("COPY \"Invoices\" (\"Id\",\"Blob\",\"Created\",\"ExceptionStatus\",\"Status\",\"StoreDataId\",\"Archived\",\"Blob2\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
{
|
||||
foreach (var invoice in oldInvoices)
|
||||
{
|
||||
@ -698,11 +706,13 @@ retry:
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
isHeader = true;
|
||||
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"Type\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"PaymentMethodId\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
{
|
||||
foreach (var invoice in oldPayments)
|
||||
{
|
||||
var localPayment = invoice.Replace("3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd", storeId);
|
||||
// Old data could have Type to null.
|
||||
localPayment += "UNKNOWN";
|
||||
await writer.WriteLineAsync(localPayment);
|
||||
}
|
||||
await writer.FlushAsync();
|
||||
|
@ -604,7 +604,7 @@
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Created": "04/23/2019 18:27:56 +00:00",
|
||||
"Type": "BTC-CHAIN",
|
||||
"PaymentMethodId": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Amount": "0.07299962",
|
||||
@ -634,7 +634,7 @@
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Created": "10/01/2018 14:13:22 +00:00",
|
||||
"Type": "BTC-CHAIN",
|
||||
"PaymentMethodId": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Amount": "0.00017863",
|
||||
@ -666,7 +666,7 @@
|
||||
"Created": "03/21/2024 07:24:35 +00:00",
|
||||
"CreatedInMs": "1711005875969",
|
||||
"Amount": "0.00000001",
|
||||
"Type": "BTC-LNURL",
|
||||
"PaymentMethodId": "BTC-LNURL",
|
||||
"Currency": "BTC"
|
||||
}
|
||||
},
|
||||
@ -697,7 +697,7 @@
|
||||
"Created": "03/20/2024 22:39:08 +00:00",
|
||||
"CreatedInMs": "1710974348741",
|
||||
"Amount": "0.00197864",
|
||||
"Type": "BTC-CHAIN",
|
||||
"PaymentMethodId": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Accounted": null
|
||||
|
@ -1,11 +1,11 @@
|
||||
Id,Blob,Created,CustomerEmail,ExceptionStatus,ItemCode,OrderId,Status,StoreDataId,Archived,Blob2
|
||||
Q7RqoHLngK9svM4MgRyi9y,\x1f8b0800000000000003c454cb76a24010dde72b7258cf180d08929d0a89899ae323ea4962163c0ae808ddd8344426c72f9bc57cd2fcc2340d31ea38b398cd2cfb5657d5add7fdf9fdc7fbd9f9b9845ce9ea5c1a6b9335e90db0dfd7936ca80cfd498ef45cfa52fc4818a1702bbec9893feb76d9ace3abd3d6e06e456dd76b2fece46eb8eee4d7032f9bae5f6fd44dbfb330ddd2995017a870c6691896f16200774442e4e41cae0b8c5a0cf8436dea6a4d56344d5135596e2ac286704690030f282abe349a724bd6e5a67c298cb089117746041fd815a5b2bb109304b1b6eb524812514347ef1bb2a6cd0686663e7a8b4779dcf3bd45eb5ae9bcb181e10f50c93ca6c44d1d768b3d422391817b172d2b2831880c489c229e0d4085478577890b7be51691823c418e1572d4b3c2043e60caabe29856ab578893520a58b4459a4d0d89a35bc1c54e73dec5534c84e5de8a8e520ad88c2c149ec0bb24c58ce6272c4f283e814e59399ddfe220762a48d5ebcb3f9b1a274ca380e08f24bbbaf9ec0c8b59fbdbc3d70965a20953566c8d2fbab589535b35dab61f78e9a4bb50c76b2af7bda9f18a56caca9ca1acbdf202e7a6375ccd4d50eca775539e8fbf69fa4a514c326e0e8d7870d7efb747fd66369faba38d366b4c641dc6c8c67660240b3d35c78dbe1be85dc38efcc9d7e7f832095ea4d38c1088457b5f4a9d87ee52ba5afe277a4b69fb71c1164b05270c6f5275370ec425e7cab6eb706ce511605660cf2fe575829762d7b243d85fe10a1e1e2e19475d44c161b3c9a0c818301627571717919530a0981f0767b342d8af39242ab9b0cd3588d3ad9762e0f150f784218f1f4d41b160c2685a26c57b8632c5a7b000cd80ce68789817610cace642446a36737875e5bf1aa17e99dfa179cc48b568d55df1c9ed1e7fd7a7f296cb9e0d8105a410bbf7edcee4014c4aefc60e3baa5860ffa454c279bbbb978860c4d59a77d7dce9e24e135bf54a130354487aa14855323898bf95f18916c3aeac3d2b090e7fc0860176c13d1ed2e76a40566dd0f1563d9010a88585f0356af5b3ed2f000000ffff030035140a5d88060000,2018-10-01 11:32:12+00,,,,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
Ka6GHBrFJPRwFRga1RD6Yz,\x1f8b0800000000000003c454cd769a4014dee7297258b74682a066a7427e24e6284a729a9fc5001798083364188826c727eba28fd457e830101353db45375dcef7cdbdf7bbbf3fbfff783d383c5470a09c1c2a3632cece87ec743c759e4f9d08a98e697c7b51be543f724e195cc86f5a1eb9a31177879131ef5d8e97cc0bc2c18d978f274fc3f5e96558ce9f1ecf8c953dbcb182da98b2009834264592d4fe3280604a13ecaf05dc9618431cc44337ba46abad6aed76db50b5635d72989414fbb0c069f545d5b59ed6eff4f4da10561916c698921d5eef367c0019cd311f0401833c973998b7a5e742ef7110a7fd65e9f62889c7b6f5726b777d3fc25c9fd5ca334683c2e71724a42c9511847555b24a1287d484dcaffc9d310072b80024cd1a724403f89073e52e5ee7d84789404394e4f00633915a558656fbb881fc823120b2388ae53a8a4037529157ac452df7e991cc154a3fc594b095229cecc147b4209cadf730b738db83ce79dda3dffc60becf4953f1e33f53ea1e6a1a53f216649bb7e8a08938fa384362a870298b30e7d5ec44b25aabacf00c73e045715838a31b63f6c4343b9c9b8f78d9595a2e2e07cb30f6cfce27cb6b0b3adeed93ae5dcf5ebafd65a763d1993e31b3cbb16d0fa6b65e5e5f1bd355d7551dad0f33ec112f36f39b7e61cd543b88fb23d34b23e7eb5d769cc70fca7e4518e4b8bdde2bc3c5e85e39b9ff4ff2ee95cddb1e235e484d049e95667b7cc86acd0db7ad7086d629105e61770ff58e4258900079097c9ce1069eec0e994003ccc0e7ae7359458c39cff293a3a314e51c1811db21d42c31895a3e4d6b2d7c750a7281dbf5e686c2d515e538145b5349ac947056d441c907a20ef17e5e8095c05c96ecc6c584006f0590d296c77d915dfdaf455954c7f7d93ae3b419b466af44e7b68fbf5fa97a99eb9a4d80c7b43a79af9b2d150238b5b5bac53e652cb17fba57d278b3dd9794122c6eb6a8aeb5bd8edbcbd8d79acb18e3eab05727a909063bfd47a5e868d5ec863d4779bcfb03561c4800c1e726bd8f0694cd047d9eaa054d8021222f9fda6a1f6c7e010000ffff0300ddecc9e08e060000,2018-10-01 11:54:10+00,,,,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
Q3kZ3F8cUD57WUqcc8QLs2,\x1f8b0800000000000003c4544972da4014ddfb1494d6099110a377806463a662b0c085f142c3176a23758b564b86b83859163952ae90564b96812259649365bff77ffff9fdfaf1f3fda654929023dd96a4a9ba5da9774ddbd06a8da5b1b3ede6741855a42fa945c408850761a6461ba3db654667539f3787fd2db51cb7bdb4a2fe68d739dc0ddd64be7bbdafef079da5ee64ce843a4085338e7d3ffb2f047026c447f681c3b2c0a8c9803f6af546bd2c2baa2ccb7545add404877042900d8f28484d949ada545bd566a32248d887883b2382cff85a23e71d08498458db71284491a861921876af3f1ec361bc5bb4d427dadb57bcfba145f4fd66fe36795a6599879438b1cd1eb04b68202270efb465694a0c020d223bfd6f64460c28260e94e6ccdc22bc11feb95597e327c5a7ff7a8708d9a6cf51d7f423f88029af916395b29c23764c2960d12449376612478f22332b3ef09e5ecb4b306333b80829603d30917f05ef9218337ab8c2ac507805e545b26bff7711bbf649def9ca9f29e50a35f108fe0852d4cd27a999cc3cdd25be5c28114d98b3748736a25bfb30b6ea5adbda786e3ceb2eebd31d5507ee5c7b45dbea563750d2deba9e7ddf1b6d173a54add5aea62ea6df1bad6db5aa93696da485c3fe60d09e0c6ac962519fec1b8632535b304516b63c2d5ab6627daa0c1cafd5d5ac6033fbfa1c5622ef45ba9e1102b176ef6ba9f3d85d4bb7ebff94de5a3a7edcb3c9629113863729bf221bc22ce79c2b3a1c9a8700304bb1e797ec56c18db1635a3e9cae700e8fce978ca30ea2603363364c237a8c85d1edb76f417134517633659b04592e6c7f07e290e54c1a5cfed59830e4f2a349534c336134ce82e213220bf129334013a006f5cfe3228c81951d0848d96236af2eb32b139addad64d343c848be68f95df1c9158fbfab5576cb59cf46c03c924adffbb1a05c8059e6ad14d845c502fb27dd12cec7e25e028211d76ede5dbd50c942215b6aae901e4a053e55a43c189ccddf4cf844d361e76ccf8cbc730bd833c00e389743fa5c0d48f20dbadcaa47e20335b1103ea52cdf1c7f030000ffff03003e6b8efb96060000,2018-10-01 11:54:32+00,,,,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
FSktP1Nrxu7arh7TUFAgTZ,\x1f8b0800000000000003c4554b72e33610ddcf295c5c27b23e16297a1599923c63591efd55f1781620d1143124011a0035545c3a59163952ae10008415d9a54caab2c992af5f773ff48f7ffefec7cb878b0b8760e7fac2192d52396d3df0aaf4104fbce56ad4df2e1f9d9f344348c6e193a175c47615047275b37517bdfbbb948738ee6f42713779bed98feee3dde2f9dbad5b8d6f36435c3b338e81d7ce41a922e59f2d50872e00f0946524da2b46d3601c49501f5dd76b377a6dbfedf7bc96e7f9c646e88e91089624d79456b7d3ebf8dd66a76b8c501544391346dfda7d6bc7503041641f630e4298e70c969bd093de7886d63de1a2ea7b67ddc28fb76595dcfdba1db9eeb296597086cb487ea231e3b9c9a0bc75f5b42409f90044a4e34d9090c029c370b1902825746bfc2d2b50b862d132cb2c5a247b41229429344699805798ab376afdcd66a369b1a8e41ca82993335ccd1d851e8cb6b0dcab7a9e53662c0f287f97d4c0c31c119d56c5d54d01fe0b54282f3268442c774e99012ba9e4fb33311e497106550f97e73206449e0b62bbd1fe6753eb8c699a30fa9ae45809d5dd0192e884ae5acec9ce946521f55c6d4dfdaaa20cdd413fdc2671390f36eeec9977c6f162f08da457e9704576fd344ea2db8f93743d84abf0f1b9db59cf7ef3fcf4ea6ac866ddc9a0b8bf1b8ffbd37177b75ebbd3ca5bb5e61d1f6624a46132101bbf1cce5a639cf8c120ccb7f39fbf146d917c75ce2b226046f1e5c9b959064fcef5d3ff24efc939bcae3b92a5d144e1bb63372b82a2d66c6dc70a17689f03951afbf2b5de5f884b8a5198c1e9585b78f26f63a778987088e46a7eaf89899485b8bebc3ce15dca04d154ec59597bc86a04765dcc77acb43d304962b55a5ab4d6267959cba027861fa4504b9985284a85ad09f01df015cf4ef96a852805d9c090b3462823558a9ad760bc5e7c27e2fb42323b95762d559b8f1f3f3e77f531a80b3c0199307d465f0e47530c30afbd5b47ec5d310cf69f0e9f713e1c972b6794a8ff803a69c3e3993d9e58bf6b4f6c42f4cf429f349b0cde0c0bdaa9f6ebc9b0d68f48246f195049a018f0fbfefd3d47b0b3e3f67e04972c038e687d391bcd0f87bf000000ffff030075db901fe2060000,2018-10-01 11:57:15+00,customer@example.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
HuzCsv9hghew2FD6zVqUyY,\x1f8b0800000000000003c455cb76d33010ddf3153d5e439aa75377456aa794268126a913286521dbe358b52d3992ecc6edc997b1e093f80564590d694f8073d8b0f49d3b3357f3f28f6fdf1f5f1d1d1938304e8f8c8bfcc1e68515ad22b86f9f3be6c362ed969f8dd715830bcae0bda275f8cab56de19eadccf9c9f832665e100e961ebf9caccfcaf37158ccd777efcccde86c390c6a67ca0260b5b39dcb48e9470dd4a13380e08a26d82f25a3a9308604c88f9ed96f374eda56db3ae9b7fa7d4bd9302928f6e11aa715a5d5eb9c74ac5edfea28236c322c9d3125cfeca6a9ed01649463310802069cabe758e9f2be701d7ad3b3379f9069a72be406a865ad3fdf59e36ceab46a9919a341ee8bf724a42c5519a47755bd4a9280d401ee57f126880b60840670341728c664a5fc35cb96b864913c49349a4525c73e4a241aa284c313cce41b2bfdcd66a3a9313f670c882a933174678644b74a9b9797b29e879429cb0794be48aae0618a709556c6ad9a02ec2d6c509a25d0f0696aec336d9a13c1ca03316e707600950f178732da581c0aa2bbd1febda975c0741551f294645709d95d0709b447972d67b85065998b6aae56aa7e9b2cf74c67e0ada2309fd94b73ba669d513877ee70dc8d872e2e067118f9ef2e26f162085def66ddeb2ca60f7d2bee768774da9b38d9f872341a5c8d7ac562615e6dfa6e6bd6b1608a3de2450e5f5af970da1a0591653b5eba9abdf992b579f4d538ac08831ac5c75be3ecdabe354e6fff93bc5b63fbb4ee48e44a13817b436f960f59ad59db7615ce5099021115f6e56bbdbf10e624405e02fb63ade1c9dfc64ef202ccc017ee6c5c112321327e7a7cbcc73b161122312f695e7b88cd39e87551dfa1d4f6810a1ccad5aa4457da04cb6b1964cff087147229130ff931d7350156007359b2cf972b4408884600296d78c297a5a8790dcaeac5377c566682eaa9d46b29dbbcfbf8f3b9ab8f415de00988885667f471bb338500b3dabbb5c35e144361ff74f894f376b75c292558fe07e4491beeceac3eb1dd66b3ad4f6c84ab9f4575d2743278362ca890edaf26435b2f108f9e336023800410bcecdfaf3982428fdbcb11bca6093044eacbd968bedafe040000ffff030069b828dae2060000,2018-10-01 12:09:53+00,customer@example.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
YZV1PQasUKEXdSkSV69XDL,\x1f8b0800000000000003c455cb76db3610dde72b7cb86e65911429cbabca1463c792523d2c39759c05480c4584244003202bd5475fd6453e29bf50108455d9474dcfe9a64bdeb93373312f7efff3dbf3bbb3338b60ebf2ccfaed616dcfe648acc6e127bccc966b7ff06934b17e6a1842320e1f34cd159b5510c8d5d5c65f5e4c6e331ee164781f89dbe9d3d5eefd24a9974f5faffdedf8ea3ec4ad33e31878eb1c542a52f1ab01dad025009eb19cc43bc5e86a8c2309eac3f37da7e3f56cd7f55dcf751c6d23b46624863b523414db732fdc81ef38b636c2b624ca9930facadeb78d1d43c90491438c3908a19f33b85927f2b62f2807a7866c7ce1f5c49dbc76275e9a08e7a2ba6e65969ce12a961f68c278a13328efa67a8d2409c50844dcc49b2221815386e16c295146e846fb1b56a070c5a2559e1bb44c7782c42857688272012f30576f6cf477bb9daec1e28a73a0ba4c56b85a580add6b6d51b553f53ca54c5b3ea2e24d520d8705224d5a15b7690af05f608b8a32874ecc0aeb9819b08a4abe3b11e381942750f570792a6340e4a920a61bce3f9bec13a659cae84b92432554774748a223ba6a3927b52ecb523673b5d1f5db9655e48f86d1264daa4570efcf9fb83b4e96a3af24eb65e18ad4c32c49e3eb9b69b60ea1173d3c79ee7afe477f90f57a219b7bd35139b91d8f87b3b157afd7fe6cdb5fd90b77007312d1281d89fb4115ceed314e07c1282a368b9f3f978e48bf58a71511d0a3f8fc685ddd058fd6e5e3ff24efd1dabfac3b9295d644e177cb6c560c65abd9d80e152ed1ae002a1becf397767f21a92846510ec7636de0e9bf8d9de261c22196abc5a421a65296e2f2fcfc88772e534433b16355eb21b7efc1ac8bfe4e94b68f4c9244ad5623bad12679d5caa047861fa4504b994728ce84a909f01af88ae7c77cb5429482ec6028582792b12a45cbeb30de2ebe15f35d2999994ab396aacd878f1f9fbbf618b4059e824c5973469ff7075302b068bded03f6a6181afb4f874f3bef0fcb55304ad47f409db4f07066cd89ed75bb7d736253d2fc2c9a936692c1ab6141b56a7f3319c67a8344fa9a015b0914037edbbfbfe7086a336e6f47f08ee5c0116d2f67a7fb6eff17000000ffff03003a3b8e5ae2060000,2018-10-01 12:17:01+00,customer@example.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
3qtdXmycQwhWEPs91MExB2,\x1f8b0800000000000003c455cb769b4810dde72b7c5827b21002455e4502f2b0a48c1e96e538cea2a10bd106ba5177238bf8e8cb66319f34bf90a6c18aeca324e7cc6696dcba5575bb5efcfbf73f8fafcece0c828d8b33c3da487c9395e1ec215ef953d13727fe6ed8315e570c2119874f354dac97ae2b97c3b5b3783bbe4c7880a3c12a109793cdb07c3f8eb68bcdfd0767371aae7c5c3b338e81d7ce6ea122657f35401d3a07c0539692b0548cb6c63892a03e6cc7e9b4ecae69598e655b9d8eb611ba6524842b925514d3b6de5a7dc7b11d6d845d4e943361f499bd6737760c3913440e30e620847e8ef9b0bcf70a6bf6ddc3e3e48bdfe38e7f39ef4d2837931b424641af969973868b507ea211e399cea0bcabea559224641e88b08a37414202a70cc3d942a284d0b5f66f58aec2158b1669daa0795c0a12a254a1114a053cc15cbd51eb373bad76038605e740759d0c7f393714bad7e282a254053d254d5b3ea3ec45560dfb1922555e15b7ea0af077b043599e422b649971cc745941252f4fc4b825f90954bd5c9ecae812792a48d38eceaf4de609d33466f429c9a112aabd1e92e888ae7acec956976521abc15aebfaedf22270bc41b08ea362eeae9cd9865ba368e1dd93a49bf84bb21d24511c7ef83849ae7de806b71bdbba9e7deff5936ed767337be2e5e3cbd168301dd9dbeb6b67baeb2dcdb9d587190968107b62d52ffc9939c271dff5826c3d7ff335ef88f89b715a11013d8b8f77c6f0cabd332eeefe277977c6fe69df912cb4260a0f46b35a21e4b5e6c676a8708eca0ca8acb0afdfea0586a8a01805291ccf75034ffe34768a870987502ee7e38a184b998b8bf3f323deb98c114d44c98ada43eede83de97767d4122a5ed33932452bb5589aeb4495ed432e891e13729d456a6010a13d1d404f816f892a7c77cb54294826c61c8582b90a12a45cd6b315e6fbe11f23297ac99ca662d559b0f1fbfbf77f535a80b3c0119b3ea8e3eee0fa608605e7b9b07ec453134f69f2e9f76de1f962b6394a81f81ba69fee1ce3637b6db36cde6c6c6a4fa5b5437ad4906cf86056d55fbabc968ac1f91889f3360278162c02ffbf7738e60db8cdbcb11bc62297044ebd3d96abfdaff000000ffff0300fd61e9cae3060000,2018-10-01 12:24:16+00,customer@example.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
BtJGr7kt52QkZe8RdhKz42,\x1f8b0800000000000003c455c172da4810bde72b5c3a6f301208249f168462078cd780c115e21c469a161a4b9a9167465a888b2fcb219f945fc86824b3d8c566abf692a3de7b3dfda6a7bbf5e3dbf7e777676706c1c6c5993194e34bde4fa46dcd923538731c4fbe762de38f4a2124e3f051cb3a62b3f43cb91c6e7a0be77a9cf0004783fb408ca74fc3dd87eba85c3c3d5ef6b693e1bd8feb60c631f03ad82bd449d95f0d501f9d03e05b969270a7146d8d7124417dd83dc76a5996d9711dc771db3dcd115a3212c21dc92a8969779c8edb6ff72d4dc236272a9830fa9a771b1e43ce0491038c3908a1af73152d3ee55bfea9fb784978de21ebf286925537598ef12258737f52dbcc39c345283fd288f14c6750d155f52a4b12b21188b03a6f8a84044e1986b3854409a11b1ddfa83c852b152dd2b441f3782748885285462815f002737547eddf6cb79c7ebf6d9b96dd7061c139505d2ec35fce0d85eeb5c7a0d8a9ba9e72a8991b94bd49ae613f43a44a6fa4880b4168028efbe7a6025b21cb8c63a9c70a2af9eec4216b929f405505e4a9941e91a70e699ec5fa77ca3c41ddc68cbe243994423df308497424576fcf49a9ebb29055836d7401b77911f44683601347c5dcbbefcd9e7867122d468f24e926fe929483248ac3cbab69b2f2a11bac9fecce6af6b5ef26ddaecf66f674945f8f2793c1edc42e57abdeedb6bf34e71d176624a0413c12f76ee1cfcc098e5d6f14649bf9fbcfb925e22fc6694704744f3e3f18c33befc1b878f84df61e8cfdcbdc2359684f14fe369a110b21af3d37dca1c239da654065857dfe520f324405c52848e1b8bf1b78fa9f7da784987008e5727e5d2963297371717e0e5b94e52954ba7319239a881d2bea08b9fd007a70daf52a8994b91b2649a486ac725d9993bca87dd023e21729d478a6010a13d1140578097cc9d363bdf24d29c816868cb50219aa5ad4ba16e3f50a3042becb256bdab2194cf5ce878f5f2fbe7a2dd4159e828c59b5509ff7072a0298d7d1e6017b530c8dfdaf15a883f787e9ca1825ea8fa0969b7f58b8cdb2edb64dbb59b631a97e1bd5726b92c1ab6e41a57aeaaa351af60a89f8b502b6122806fcf6fdfe6924289b7e7bdb83772c058e68bd435bed77fb9f000000ffff030034f07389ec060000,2018-10-01 12:31:12+00,customer@gmail.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
VWB3riH9WuCv9NtSedaGE6,\x1f8b0800000000000003c455c1729b4810bde72b5c9cb3b21012029f222162c792bc9664a494631f0668c404984133032bc5a52fcb219f945fc83060457629deaabdec91f75e4fbfe9e96e7e7efff1f4eeec4cc3a17671a62d574383e12b7b5538a57d231610a24bd7d4de570a2e28834f4a66f0b5e738c21baecd8535b94e981f468395cfafa79be1eee3242a179baf97e6763c5cb9611d4c5908ac0e760a7952f67703d447e700e12d4d71b0938ab6c21812203f7aa6d569753aba615b9665b74dc56152521cc01dce2a89de332cc3eeebb6ae48d8e65806634a5ef056bbe143c829c76210860c3857d7e96fd6b6037d4f1457c10adb9fbd9bfb8531b059d17326cbcbd5106a9b39a36111884f24a22c5319647455bdca92806c043ca8ce9b222e80111ac2d942a00493b58a6f548ec4a58a1469daa079bce33840a944239472788699bca3f2afb75b56bfdfeee99d5ec30505634054b934d79b6b12dd2b8f7eb193753de5503137287b955cc16e8670955e4b11e31c93042cfbc3ba025b01cdb463a9430b22d8eec421f7383f81ca0a8853291d2c4e1dd23c4be7cf947e82ba8d29794e7228857ce61112e8482edf9ee152d56521aa065bab026ef3c23747037f1d47c5dc5999b30d33c6d162f41527ddc4f5703948a238b8bc9a264b17bafefda6672c67dffa76d2edba74d69b8ef2c9f5783cb81df7cae5d2bcddf63d7d6ed830c33ef1e3115fd9853bd3c7616c3b233f5bcffffa927778fca89d768441f5e4d38336bc731eb48b87ffc9de83b67f9e7b240ae589c03f5a336201e4b5e7863b543847bb0c88a8b02f8ff52043549010f9291cf777034fffb5efa430c40c02e1cd2795321622e717e7e7b045599e42a53b17312209dfd1a28e10db8fa006a75daf92489abba1024772c82ad79539c18ada073922de4821c733f55190f0a628c04a601e4b8ff5d23721205a2164b4e58b40d6a2d6b528ab578016b05d2e68d396cd60ca773e7cbcbdf8eab55057780a22a6d5427dda1fa808605e47eb07ec553114f69f56a00ade1fa62ba304cb3f825c6eee61e136cbb6dbd6cd66d9c6b8fa6d54cbad49062fba0595f2a9abd668d82bc4e3970ad80a202184afdfef772341d9f4dbeb1ebca3293044ea1dda6abfdbff020000ffff0300e1a26ae9ec060000,2018-10-01 12:33:11+00,customer@gmail.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
At4CDM5vfewV2WDHEPKRbt,\x1f8b0800000000000003c4554d73a33810bdcfaf4871de758c0db6c9696d20c9c471c61fb15d35933908688c06908824183329ffb23dec4fdabfb092205e27e59daddacb1e79efb5faa9d5ddfcf9fb1f2f1f2e2e0c1c195717c65858ae37b3ab18be6f7a5befd69f4f9781307e510a2e28838f5ad6e7bbb5eb8af56437588deeef521644f1781bf0bbd9f3a4bebe8fabd5f3b79bc17e3ad9fa51134c5904ac09764b7952fea9059aa30b80684e331cd652d1d5184302e4873d18f53b3db33f1c398edd1d3a9ac3a4a23884479c2b8969f74756d71c0d4c4dc2bec0321853f286ef0d5b3e8282722cc651c480737d9db1fde361b901320bf8c4ab83bb38c2dc22d73dd7de06f5eac6228dcd82d1a80cc547125396eb0c325a554f5912907bc04375de0c71018cd0082e5602a598ec747cab72252e55a4ccb2162d929ae31065128d51c6e11566f28ecabf351a76468ee90c7bc3960a4bc680e86a19fe7a6948f4a02d06652dcb7acea0661e50fe2eb786fd1c6195ddc810e31c931446ce6f3b0576429a1ba752979644b0facc219f717106950510e752ba589c3ba47d95de3f53e6196a9e50f29ae4580af9ca1e12e8442e9f9ee14ad76525547fed7401f745190cbc71b04be272e96e078b67d69fc62bef1b4eadd45fe36a9cc64978733b4b373e58c1e767bbbf59fc183aa965f97461cfbce2fe6e3a1dcfa776b5d90ce6fbe1da5cf61d58e0800489c7b74ee92fcc699438ae17e4bbe5af5f8a1e4fbe1ae71d61d02df9f2644c1edd27e3eae97fb2f7641c5ec71e89527b22f0dd68272c84a2f1dc72c70a17a8ce8108857df9dacc31c425895090c1697bb7f0ec5ffb4e0a23cc2014ebe5bd52264214fceaf212f6282f3250ba4b912092f29a964d84d85f839e9b6eb3496269ee810a1ccb1953ae9539c1cac60739217e92424e6716a030e56d518055c0d62c3bd54bdf8480e84490d34e2042598b46d7a1acd90046c8ea42d0b62ddbc194ef7cfcf8f9de6bb64253e1198884aa7dfa72385231c0b289368fd8bb6268ec3f6d401d7c384e574e09963f04b9dbfce3be3deeda41bfddb509567f0db5dbda64f0a65b50259f5ab546cbde229ebc55c05e0089207aff7e7f3712546dbfbdefc1479a0143a459a19dee87c35f000000ffff0300011a2ad8eb060000,2018-10-01 13:51:01+00,customer@gmail.com,,,CustomOrderId,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
Id,Blob,Created,ExceptionStatus,Status,StoreDataId,Archived,Blob2
|
||||
Q7RqoHLngK9svM4MgRyi9y,\x1f8b0800000000000003c454cb76a24010dde72b7258cf180d08929d0a89899ae323ea4962163c0ae808ddd8344426c72f9bc57cd2fcc2340d31ea38b398cd2cfb5657d5add7fdf9fdc7fbd9f9b9845ce9ea5c1a6b9335e90db0dfd7936ca80cfd498ef45cfa52fc4818a1702bbec9893feb76d9ace3abd3d6e06e456dd76b2fece46eb8eee4d7032f9bae5f6fd44dbfb330ddd2995017a870c6691896f16200774442e4e41cae0b8c5a0cf8436dea6a4d56344d5135596e2ac286704690030f282abe349a724bd6e5a67c298cb089117746041fd815a5b2bb109304b1b6eb524812514347ef1bb2a6cd0686663e7a8b4779dcf3bd45eb5ae9bcb181e10f50c93ca6c44d1d768b3d422391817b172d2b2831880c489c229e0d4085478577890b7be51691823c418e1572d4b3c2043e60caabe29856ab578893520a58b4459a4d0d89a35bc1c54e73dec5534c84e5de8a8e520ad88c2c149ec0bb24c58ce6272c4f283e814e59399ddfe220762a48d5ebcb3f9b1a274ca380e08f24bbbaf9ec0c8b59fbdbc3d70965a20953566c8d2fbab589535b35dab61f78e9a4bb50c76b2af7bda9f18a56caca9ca1acbdf202e7a6375ccd4d50eca775539e8fbf69fa4a514c326e0e8d7870d7efb747fd66369faba38d366b4c641dc6c8c67660240b3d35c78dbe1be85dc38efcc9d7e7f832095ea4d38c1088457b5f4a9d87ee52ba5afe277a4b69fb71c1164b05270c6f5275370ec425e7cab6eb706ce511605660cf2fe575829762d7b243d85fe10a1e1e2e19475d44c161b3c9a0c818301627571717919530a0981f0767b342d8af39242ab9b0cd3588d3ad9762e0f150f784218f1f4d41b160c2685a26c57b8632c5a7b000cd80ce68789817610cace642446a36737875e5bf1aa17e99dfa179cc48b568d55df1c9ed1e7fd7a7f296cb9e0d8105a410bbf7edcee4014c4aefc60e3baa5860ffa454c279bbbb978860c4d59a77d7dce9e24e135bf54a130354487aa14855323898bf95f18916c3aeac3d2b090e7fc0860176c13d1ed2e76a40566dd0f1563d9010a88585f0356af5b3ed2f000000ffff030035140a5d88060000,2018-10-01 11:32:12+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
Ka6GHBrFJPRwFRga1RD6Yz,\x1f8b0800000000000003c454cd769a4014dee7297258b74682a066a7427e24e6284a729a9fc5001798083364188826c727eba28fd457e830101353db45375dcef7cdbdf7bbbf3fbfff783d383c5470a09c1c2a3632cece87ec743c759e4f9d08a98e697c7b51be543f724e195cc86f5a1eb9a31177879131ef5d8e97cc0bc2c18d978f274fc3f5e96558ce9f1ecf8c953dbcb182da98b2009834264592d4fe3280604a13ecaf05dc9618431cc44337ba46abad6aed76db50b5635d72989414fbb0c069f545d5b59ed6eff4f4da10561916c698921d5eef367c0019cd311f0401833c973998b7a5e742ef7110a7fd65e9f62889c7b6f5726b777d3fc25c9fd5ca334683c2e71724a42c9511847555b24a1287d484dcaffc9d310072b80024cd1a724403f89073e52e5ee7d84789404394e4f00633915a558656fbb881fc823120b2388ae53a8a4037529157ac452df7e991cc154a3fc594b095229cecc147b4209cadf730b738db83ce79dda3dffc60becf4953f1e33f53ea1e6a1a53f216649bb7e8a08938fa384362a870298b30e7d5ec44b25aabacf00c73e045715838a31b63f6c4343b9c9b8f78d9595a2e2e07cb30f6cfce27cb6b0b3adeed93ae5dcf5ebafd65a763d1993e31b3cbb16d0fa6b65e5e5f1bd355d7551dad0f33ec112f36f39b7e61cd543b88fb23d34b23e7eb5d769cc70fca7e4518e4b8bdde2bc3c5e85e39b9ff4ff2ee95cddb1e235e484d049e95667b7cc86acd0db7ad7086d629105e61770ff58e4258900079097c9ce1069eec0e994003ccc0e7ae7359458c39cff293a3a314e51c1811db21d42c31895a3e4d6b2d7c750a7281dbf5e686c2d515e538145b5349ac947056d441c907a20ef17e5e8095c05c96ecc6c584006f0590d296c77d915dfdaf455954c7f7d93ae3b419b466af44e7b68fbf5fa97a99eb9a4d80c7b43a79af9b2d150238b5b5bac53e652cb17fba57d278b3dd9794122c6eb6a8aeb5bd8edbcbd8d79acb18e3eab05727a909063bfd47a5e868d5ec863d4779bcfb03561c4800c1e726bd8f0694cd047d9eaa054d8021222f9fda6a1f6c7e010000ffff0300ddecc9e08e060000,2018-10-01 11:54:10+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
Q3kZ3F8cUD57WUqcc8QLs2,\x1f8b0800000000000003c4544972da4014ddfb1494d6099110a377806463a662b0c085f142c3176a23758b564b86b83859163952ae90564b96812259649365bff77ffff9fdfaf1f3fda654929023dd96a4a9ba5da9774ddbd06a8da5b1b3ede6741855a42fa945c408850761a6461ba3db654667539f3787fd2db51cb7bdb4a2fe68d739dc0ddd64be7bbdafef079da5ee64ce843a4085338e7d3ffb2f047026c447f681c3b2c0a8c9803f6af546bd2c2baa2ccb7545add404877042900d8f28484d949ada545bd566a32248d887883b2382cff85a23e71d08498458db71284491a861921876af3f1ec361bc5bb4d427dadb57bcfba145f4fd66fe36795a6599879438b1cd1eb04b68202270efb465694a0c020d223bfd6f64460c28260e94e6ccdc22bc11feb95597e327c5a7ff7a8708d9a6cf51d7f423f88029af916395b29c23764c2960d12449376612478f22332b3ef09e5ecb4b306333b80829603d30917f05ef9218337ab8c2ac507805e545b26bff7711bbf649def9ca9f29e50a35f108fe0852d4cd27a999cc3cdd25be5c28114d98b3748736a25bfb30b6ea5adbda786e3ceb2eebd31d5507ee5c7b45dbea563750d2deba9e7ddf1b6d173a54add5aea62ea6df1bad6db5aa93696da485c3fe60d09e0c6ac962519fec1b8632535b304516b63c2d5ab6627daa0c1cafd5d5ac6033fbfa1c5622ef45ba9e1102b176ef6ba9f3d85d4bb7ebff94de5a3a7edcb3c9629113863729bf221bc22ce79c2b3a1c9a8700304bb1e797ec56c18db1635a3e9cae700e8fce978ca30ea2603363364c237a8c85d1edb76f417134517633659b04592e6c7f07e290e54c1a5cfed59830e4f2a349534c336134ce82e213220bf129334013a006f5cfe3228c81951d0848d96236af2eb32b139addad64d343c848be68f95df1c9158fbfab5576cb59cf46c03c924adffbb1a05c8059e6ad14d845c502fb27dd12cec7e25e028211d76ede5dbd50c942215b6aae901e4a053e55a43c189ccddf4cf844d361e76ccf8cbc730bd833c00e389743fa5c0d48f20dbadcaa47e20335b1103ea52cdf1c7f030000ffff03003e6b8efb96060000,2018-10-01 11:54:32+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
FSktP1Nrxu7arh7TUFAgTZ,\x1f8b0800000000000003c4554b72e33610ddcf295c5c27b23e16297a1599923c63591efd55f1781620d1143124011a0035545c3a59163952ae10008415d9a54caab2c992af5f773ff48f7ffefec7cb878b0b8760e7fac2192d52396d3df0aaf4104fbce56ad4df2e1f9d9f344348c6e193a175c47615047275b37517bdfbbb948738ee6f42713779bed98feee3dde2f9dbad5b8d6f36435c3b338e81d7ce41a922e59f2d50872e00f0946524da2b46d3601c49501f5dd76b377a6dbfedf7bc96e7f9c646e88e91089624d79456b7d3ebf8dd66a76b8c501544391346dfda7d6bc7503041641f630e4298e70c969bd093de7886d63de1a2ea7b67ddc28fb76595dcfdba1db9eeb296597086cb487ea231e3b9c9a0bc75f5b42409f90044a4e34d9090c029c370b1902825746bfc2d2b50b862d132cb2c5a247b41229429344699805798ab376afdcd66a369b1a8e41ca82993335ccd1d851e8cb6b0dcab7a9e53662c0f287f97d4c0c31c119d56c5d54d01fe0b54282f3268442c774e99012ba9e4fb33311e497106550f97e73206449e0b62bbd1fe6753eb8c699a30fa9ae45809d5dd0192e884ae5acec9ce946521f55c6d4dfdaaa20cdd413fdc2671390f36eeec9977c6f162f08da457e9704576fd344ea2db8f93743d84abf0f1b9db59cf7ef3fcf4ea6ac866ddc9a0b8bf1b8ffbd37177b75ebbd3ca5bb5e61d1f6624a46132101bbf1cce5a639cf8c120ccb7f39fbf146d917c75ce2b226046f1e5c9b959064fcef5d3ff24efc939bcae3b92a5d144e1bb63372b82a2d66c6dc70a17689f03951afbf2b5de5f884b8a5198c1e9585b78f26f63a778987088e46a7eaf89899485b8bebc3ce15dca04d154ec59597bc86a04765dcc77acb43d304962b55a5ab4d6267959cba027861fa4504b9985284a85ad09f01df015cf4ef96a852805d9c090b3462823558a9ad760bc5e7c27e2fb42323b95762d559b8f1f3f3e77f531a80b3c0199307d465f0e47530c30afbd5b47ec5d310cf69f0e9f713e1c972b6794a8ff803a69c3e3993d9e58bf6b4f6c42f4cf429f349b0cde0c0bdaa9f6ebc9b0d68f48246f195049a018f0fbfefd3d47b0b3e3f67e04972c038e687d391bcd0f87bf000000ffff030075db901fe2060000,2018-10-01 11:57:15+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
HuzCsv9hghew2FD6zVqUyY,\x1f8b0800000000000003c455cb76d33010ddf3153d5e439aa75377456aa794268126a913286521dbe358b52d3992ecc6edc997b1e093f80564590d694f8073d8b0f49d3b3357f3f28f6fdf1f5f1d1d1938304e8f8c8bfcc1e68515ad22b86f9f3be6c362ed969f8dd715830bcae0bda275f8cab56de19eadccf9c9f832665e100e961ebf9caccfcaf37158ccd777efcccde86c390c6a67ca0260b5b39dcb48e9470dd4a13380e08a26d82f25a3a9308604c88f9ed96f374eda56db3ae9b7fa7d4bd9302928f6e11aa715a5d5eb9c74ac5edfea28236c322c9d3125cfeca6a9ed01649463310802069cabe758e9f2be701d7ad3b3379f9069a72be406a865ad3fdf59e36ceab46a9919a341ee8bf724a42c5519a47755bd4a9280d401ee57f126880b60840670341728c664a5fc35cb96b864913c49349a4525c73e4a241aa284c313cce41b2bfdcd66a3a9313f670c882a933174678644b74a9b9797b29e879429cb0794be48aae0618a709556c6ad9a02ec2d6c509a25d0f0696aec336d9a13c1ca03316e707600950f178732da581c0aa2bbd1febda975c0741551f294645709d95d0709b447972d67b85065998b6aae56aa7e9b2cf74c67e0ada2309fd94b73ba669d513877ee70dc8d872e2e067118f9ef2e26f162085def66ddeb2ca60f7d2bee768774da9b38d9f872341a5c8d7ac562615e6dfa6e6bd6b1608a3de2450e5f5af970da1a0591653b5eba9abdf992b579f4d538ac08831ac5c75be3ecdabe354e6fff93bc5b63fbb4ee48e44a13817b436f960f59ad59db7615ce5099021115f6e56bbdbf10e624405e02fb63ade1c9dfc64ef202ccc017ee6c5c112321327e7a7cbcc73b161122312f695e7b88cd39e87551dfa1d4f6810a1ccad5aa4457da04cb6b1964cff087147229130ff931d7350156007359b2cf972b4408884600296d78c297a5a8790dcaeac5377c566682eaa9d46b29dbbcfbf8f3b9ab8f415de00988885667f471bb338500b3dabbb5c35e144361ff74f894f376b75c292558fe07e4491beeceac3eb1dd66b3ad4f6c84ab9f4575d2743278362ca890edaf26435b2f108f9e336023800410bcecdfaf3982428fdbcb11bca6093044eacbd968bedafe040000ffff030069b828dae2060000,2018-10-01 12:09:53+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
YZV1PQasUKEXdSkSV69XDL,\x1f8b0800000000000003c455cb76db3610dde72b7cb86e65911429cbabca1463c792523d2c39759c05480c4584244003202bd5475fd6453e29bf50108455d9474dcfe9a64bdeb93373312f7efff3dbf3bbb3338b60ebf2ccfaed616dcfe648acc6e127bccc966b7ff06934b17e6a1842320e1f34cd159b5510c8d5d5c65f5e4c6e331ee164781f89dbe9d3d5eefd24a9974f5faffdedf8ea3ec4ad33e31878eb1c542a52f1ab01dad025009eb19cc43bc5e86a8c2309eac3f37da7e3f56cd7f55dcf751c6d23b46624863b523414db732fdc81ef38b636c2b624ca9930facadeb78d1d43c90491438c3908a19f33b85927f2b62f2807a7866c7ce1f5c49dbc76275e9a08e7a2ba6e65969ce12a961f68c278a13328efa67a8d2409c50844dcc49b2221815386e16c295146e846fb1b56a070c5a2559e1bb44c7782c42857688272012f30576f6cf477bb9daec1e28a73a0ba4c56b85a580add6b6d51b553f53ca54c5b3ea2e24d520d8705224d5a15b7690af05f608b8a32874ecc0aeb9819b08a4abe3b11e381942750f570792a6340e4a920a61bce3f9bec13a659cae84b92432554774748a223ba6a3927b52ecb523673b5d1f5db9655e48f86d1264daa4570efcf9fb83b4e96a3af24eb65e18ad4c32c49e3eb9b69b60ea1173d3c79ee7afe477f90f57a219b7bd35139b91d8f87b3b157afd7fe6cdb5fd90b77007312d1281d89fb4115ceed314e07c1282a368b9f3f978e48bf58a71511d0a3f8fc685ddd058fd6e5e3ff24efd1dabfac3b9295d644e177cb6c560c65abd9d80e152ed1ae002a1becf397767f21a92846510ec7636de0e9bf8d9de261c22196abc5a421a65296e2f2fcfc88772e534433b16355eb21b7efc1ac8bfe4e94b68f4c9244ad5623bad12679d5caa047861fa4504b994728ce84a909f01af88ae7c77cb5429482ec6028582792b12a45cbeb30de2ebe15f35d2999994ab396aacd878f1f9fbbf618b4059e824c5973469ff7075302b068bded03f6a6181afb4f874f3bef0fcb55304ad47f409db4f07066cd89ed75bb7d736253d2fc2c9a936692c1ab6141b56a7f3319c67a8344fa9a015b0914037edbbfbfe7086a336e6f47f08ee5c0116d2f67a7fb6eff17000000ffff03003a3b8e5ae2060000,2018-10-01 12:17:01+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
3qtdXmycQwhWEPs91MExB2,\x1f8b0800000000000003c455cb769b4810dde72b7c5827b21002455e4502f2b0a48c1e96e538cea2a10bd106ba5177238bf8e8cb66319f34bf90a6c18aeca324e7cc6696dcba5575bb5efcfbf73f8fafcece0c828d8b33c3da487c9395e1ec215ef953d13727fe6ed8315e570c2119874f354dac97ae2b97c3b5b3783bbe4c7880a3c12a109793cdb07c3f8eb68bcdfd0767371aae7c5c3b338e81d7ce6ea122657f35401d3a07c0539692b0548cb6c63892a03e6cc7e9b4ecae69598e655b9d8eb611ba6524842b925514d3b6de5a7dc7b11d6d845d4e943361f499bd6737760c3913440e30e620847e8ef9b0bcf70a6bf6ddc3e3e48bdfe38e7f39ef4d2837931b424641af969973868b507ea211e399cea0bcabea559224641e88b08a37414202a70cc3d942a284d0b5f66f58aec2158b1669daa0795c0a12a254a1114a053cc15cbd51eb373bad76038605e740759d0c7f393714bad7e282a254053d254d5b3ea3ec45560dfb1922555e15b7ea0af077b043599e422b649971cc745941252f4fc4b825f90954bd5c9ecae812792a48d38eceaf4de609d33466f429c9a112aabd1e92e888ae7acec956976521abc15aebfaedf22270bc41b08ea362eeae9cd9865ba368e1dd93a49bf84bb21d24511c7ef83849ae7de806b71bdbba9e7deff5936ed767337be2e5e3cbd168301dd9dbeb6b67baeb2dcdb9d587190968107b62d52ffc9939c271dff5826c3d7ff335ef88f89b715a11013d8b8f77c6f0cabd332eeefe277977c6fe69df912cb4260a0f46b35a21e4b5e6c676a8708eca0ca8acb0afdfea0586a8a01805291ccf75034ffe34768a870987502ee7e38a184b998b8bf3f323deb98c114d44c98ada43eede83de97767d4122a5ed33932452bb5589aeb4495ed432e891e13729d456a6010a13d1d404f816f892a7c77cb54294826c61c8582b90a12a45cd6b315e6fbe11f23297ac99ca662d559b0f1fbfbf77f535a80b3c0119b3ea8e3eee0fa608605e7b9b07ec453134f69f2e9f76de1f962b6394a81f81ba69fee1ce3637b6db36cde6c6c6a4fa5b5437ad4906cf86056d55fbabc968ac1f91889f3360278162c02ffbf7738e60db8cdbcb11bc62297044ebd3d96abfdaff000000ffff0300fd61e9cae3060000,2018-10-01 12:24:16+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
BtJGr7kt52QkZe8RdhKz42,\x1f8b0800000000000003c455c172da4810bde72b5c3a6f301208249f168462078cd780c115e21c469a161a4b9a9167465a888b2fcb219f945fc86824b3d8c566abf692a3de7b3dfda6a7bbf5e3dbf7e777676706c1c6c5993194e34bde4fa46dcd923538731c4fbe762de38f4a2124e3f051cb3a62b3f43cb91c6e7a0be77a9cf0004783fb408ca74fc3dd87eba85c3c3d5ef6b693e1bd8feb60c631f03ad82bd449d95f0d501f9d03e05b969270a7146d8d7124417dd83dc76a5996d9711dc771db3dcd115a3212c21dc92a8969779c8edb6ff72d4dc236272a9830fa9a771b1e43ce0491038c3908a1af73152d3ee55bfea9fb784978de21ebf286925537598ef12258737f52dbcc39c345283fd288f14c6750d155f52a4b12b21188b03a6f8a84044e1986b3854409a11b1ddfa83c852b152dd2b441f3782748885285462815f002737547eddf6cb79c7ebf6d9b96dd7061c139505d2ec35fce0d85eeb5c7a0d8a9ba9e72a8991b94bd49ae613f43a44a6fa4880b4168028efbe7a6025b21cb8c63a9c70a2af9eec4216b929f405505e4a9941e91a70e699ec5fa77ca3c41ddc68cbe243994423df308497424576fcf49a9ebb29055836d7401b77911f44683601347c5dcbbefcd9e7867122d468f24e926fe929483248ac3cbab69b2f2a11bac9fecce6af6b5ef26ddaecf66f674945f8f2793c1edc42e57abdeedb6bf34e71d176624a0413c12f76ee1cfcc098e5d6f14649bf9fbcfb925e22fc6694704744f3e3f18c33befc1b878f84df61e8cfdcbdc2359684f14fe369a110b21af3d37dca1c239da654065857dfe520f324405c52848e1b8bf1b78fa9f7da784987008e5727e5d2963297371717e0e5b94e52954ba7319239a881d2bea08b9fd007a70daf52a8994b91b2649a486ac725d9993bca87dd023e21729d478a6010a13d1140578097cc9d363bdf24d29c816868cb50219aa5ad4ba16e3f50a3042becb256bdab2194cf5ce878f5f2fbe7a2dd4159e828c59b5509ff7072a0298d7d1e6017b530c8dfdaf15a883f787e9ca1825ea8fa0969b7f58b8cdb2edb64dbb59b631a97e1bd5726b92c1ab6e41a57aeaaa351af60a89f8b502b6122806fcf6fdfe6924289b7e7bdb83772c058e68bd435bed77fb9f000000ffff030034f07389ec060000,2018-10-01 12:31:12+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
VWB3riH9WuCv9NtSedaGE6,\x1f8b0800000000000003c455c1729b4810bde72b5c9cb3b21012029f222162c792bc9664a494631f0668c404984133032bc5a52fcb219f945fc83060457629deaabdec91f75e4fbfe9e96e7e7efff1f4eeec4cc3a17671a62d574383e12b7b5538a57d231610a24bd7d4de570a2e28834f4a66f0b5e738c21baecd8535b94e981f468395cfafa79be1eee3242a179baf97e6763c5cb9611d4c5908ac0e760a7952f67703d447e700e12d4d71b0938ab6c21812203f7aa6d569753aba615b9665b74dc56152521cc01dce2a89de332cc3eeebb6ae48d8e65806634a5ef056bbe143c829c76210860c3857d7e96fd6b6037d4f1457c10adb9fbd9bfb8531b059d17326cbcbd5106a9b39a36111884f24a22c5319647455bdca92806c043ca8ce9b222e80111ac2d942a00493b58a6f548ec4a58a1469daa079bce33840a944239472788699bca3f2afb75b56bfdfeee99d5ec30505634054b934d79b6b12dd2b8f7eb193753de5503137287b955cc16e8670955e4b11e31c93042cfbc3ba025b01cdb463a9430b22d8eec421f7383f81ca0a8853291d2c4e1dd23c4be7cf947e82ba8d29794e7228857ce61112e8482edf9ee152d56521aa065bab026ef3c23747037f1d47c5dc5999b30d33c6d162f41527ddc4f5703948a238b8bc9a264b17bafefda6672c67dffa76d2edba74d69b8ef2c9f5783cb81df7cae5d2bcddf63d7d6ed830c33ef1e3115fd9853bd3c7616c3b233f5bcffffa927778fca89d768441f5e4d38336bc731eb48b87ffc9de83b67f9e7b240ae589c03f5a336201e4b5e7863b543847bb0c88a8b02f8ff52043549010f9291cf777034fffb5efa430c40c02e1cd2795321622e717e7e7b045599e42a53b17312209dfd1a28e10db8fa006a75daf92489abba1024772c82ad79539c18ada073922de4821c733f55190f0a628c04a601e4b8ff5d23721205a2164b4e58b40d6a2d6b528ab578016b05d2e68d396cd60ca773e7cbcbdf8eab55057780a22a6d5427dda1fa808605e47eb07ec553114f69f56a00ade1fa62ba304cb3f825c6eee61e136cbb6dbd6cd66d9c6b8fa6d54cbad49062fba0595f2a9abd668d82bc4e3970ad80a202184afdfef772341d9f4dbeb1ebca3293044ea1dda6abfdbff020000ffff0300e1a26ae9ec060000,2018-10-01 12:33:11+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
At4CDM5vfewV2WDHEPKRbt,\x1f8b0800000000000003c4554d73a33810bdcfaf4871de758c0db6c9696d20c9c471c61fb15d35933908688c06908824183329ffb23dec4fdabfb092205e27e59daddacb1e79efb5faa9d5ddfcf9fb1f2f1f2e2e0c1c195717c65858ae37b3ab18be6f7a5befd69f4f9781307e510a2e28838f5ad6e7bbb5eb8af56437588deeef521644f1781bf0bbd9f3a4bebe8fabd5f3b79bc17e3ad9fa51134c5904ac09764b7952fea9059aa30b80684e331cd652d1d5184302e4873d18f53b3db33f1c398edd1d3a9ac3a4a23884479c2b8969f74756d71c0d4c4dc2bec0321853f286ef0d5b3e8282722cc651c480737d9db1fde361b901320bf8c4ab83bb38c2dc22d73dd7de06f5eac6228dcd82d1a80cc547125396eb0c325a554f5912907bc04375de0c71018cd0082e5602a598ec747cab72252e55a4ccb2162d929ae31065128d51c6e11566f28ecabf351a76468ee90c7bc3960a4bc680e86a19fe7a6948f4a02d06652dcb7acea0661e50fe2eb786fd1c6195ddc810e31c931446ce6f3b0576429a1ba752979644b0facc219f717106950510e752ba589c3ba47d95de3f53e6196a9e50f29ae4580af9ca1e12e8442e9f9ee14ad76525547fed7401f745190cbc71b04be272e96e078b67d69fc62bef1b4eadd45fe36a9cc64978733b4b373e58c1e767bbbf59fc183aa965f97461cfbce2fe6e3a1dcfa776b5d90ce6fbe1da5cf61d58e0800489c7b74ee92fcc699438ae17e4bbe5af5f8a1e4fbe1ae71d61d02df9f2644c1edd27e3eae97fb2f7641c5ec71e89527b22f0dd68272c84a2f1dc72c70a17a8ce8108857df9dacc31c425895090c1697bb7f0ec5ffb4e0a23cc2014ebe5bd52264214fceaf212f6282f3250ba4b912092f29a964d84d85f839e9b6eb3496269ee810a1ccb1953ae9539c1cac60739217e92424e6716a030e56d518055c0d62c3bd54bdf8480e84490d34e2042598b46d7a1acd90046c8ea42d0b62ddbc194ef7cfcf8f9de6bb64253e1198884aa7dfa72385231c0b289368fd8bb6268ec3f6d401d7c384e574e09963f04b9dbfce3be3deeda41bfddb509567f0db5dbda64f0a65b50259f5ab546cbde229ebc55c05e0089207aff7e7f3712546dbfbdefc1479a0143a459a19dee87c35f000000ffff0300011a2ad8eb060000,2018-10-01 13:51:01+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
|
|
@ -133,7 +133,7 @@ namespace BTCPayServer.Tests
|
||||
public async Task CanQueryDirectProviders()
|
||||
{
|
||||
// TODO: Check once in a while whether or not they are working again
|
||||
string[] brokenShitcoinCasinos = { };
|
||||
string[] brokenShitcoinCasinos = { "binance", "coinbasepro" };
|
||||
var skipped = 0;
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var directlySupported = factory.AvailableRateProviders.Where(s => s.Source == RateSource.Direct)
|
||||
@ -272,7 +272,8 @@ namespace BTCPayServer.Tests
|
||||
"https://www.bitpay.com", // not allowing to be hit from circleci
|
||||
"https://support.bitpay.com",
|
||||
"https://www.coingecko.com", // unhappy service
|
||||
"https://www.wasabiwallet.io" // Banning US, CI unhappy
|
||||
"https://www.wasabiwallet.io", // Banning US, CI unhappy
|
||||
"https://fullynoded.app" // Sometimes DNS doesn't work
|
||||
};
|
||||
|
||||
foreach (var match in regex.Matches(text).OfType<Match>())
|
||||
|
@ -303,7 +303,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Set tolerance to 50%
|
||||
var stores = user.GetController<UIStoresController>();
|
||||
var response = await stores.GeneralSettings();
|
||||
var response = await stores.GeneralSettings(user.StoreId);
|
||||
var vm = Assert.IsType<GeneralSettingsViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
Assert.Equal(0.0, vm.PaymentTolerance);
|
||||
vm.PaymentTolerance = 50.0;
|
||||
@ -385,7 +385,7 @@ namespace BTCPayServer.Tests
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
await user.ModifyOnchainPaymentSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
await user.ModifyGeneralSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
|
||||
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
||||
{
|
||||
@ -445,7 +445,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
var storeController = user.GetController<UIStoresController>();
|
||||
var storeResponse = await storeController.GeneralSettings();
|
||||
var storeResponse = await storeController.GeneralSettings(user.StoreId);
|
||||
Assert.IsType<ViewResult>(storeResponse);
|
||||
Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
@ -568,10 +568,10 @@ namespace BTCPayServer.Tests
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
await acc.GrantAccessAsync();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
await acc.ModifyOnchainPaymentSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice
|
||||
await acc.ModifyGeneralSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
var invoice = await acc.BitPay.CreateInvoiceAsync(new Invoice
|
||||
{
|
||||
Price = 5.0m,
|
||||
Currency = "USD",
|
||||
@ -1448,6 +1448,41 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
|
||||
[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.0m,
|
||||
PayoutMethods = [ "BTC-CHAIN" ]
|
||||
});
|
||||
var controller = user.GetController<UIInvoiceController>();
|
||||
var invoice = await controller.CreateInvoiceCoreRaw(new()
|
||||
{
|
||||
Amount = 0.5m,
|
||||
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);
|
||||
Assert.Equal("TOPUP", payout.PayoutMethodId);
|
||||
Assert.Equal(invoice.Id, payout.Destination);
|
||||
Assert.Equal(-0.5m, payout.OriginalAmount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseDefaultCurrency()
|
||||
@ -1809,6 +1844,8 @@ namespace BTCPayServer.Tests
|
||||
await user.GrantAccessAsync();
|
||||
var user2 = tester.NewAccount();
|
||||
await user2.GrantAccessAsync();
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user2.RegisterDerivationSchemeAsync("BTC");
|
||||
var stores = user.GetController<UIStoresController>();
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var apps2 = user2.GetController<UIAppsController>();
|
||||
@ -2091,7 +2128,7 @@ namespace BTCPayServer.Tests
|
||||
Amount = 0.0001m,
|
||||
Destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(),
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC"
|
||||
PayoutMethodId = "BTC"
|
||||
});
|
||||
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});
|
||||
@ -2414,9 +2451,10 @@ namespace BTCPayServer.Tests
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
{
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
return (ctx.AddressInvoices.Where(i => i.InvoiceDataId == invoice.Id).ToArrayAsync().GetAwaiter()
|
||||
.GetResult())
|
||||
.Where(i => i.GetAddress() == h).Any();
|
||||
.Where(i => i.Address == h && i.PaymentMethodId == pmi.ToString()).Any();
|
||||
}
|
||||
|
||||
|
||||
@ -2581,7 +2619,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
var controller = tester.PayTester.GetController<UIStoresController>(user.UserId, user.StoreId);
|
||||
var vm = await controller.GeneralSettings().AssertViewModelAsync<GeneralSettingsViewModel>();
|
||||
var vm = await controller.GeneralSettings(user.StoreId).AssertViewModelAsync<GeneralSettingsViewModel>();
|
||||
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);
|
||||
|
||||
@ -2800,7 +2838,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("coingecko", b.PreferredExchange);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDoInvoiceMigrations()
|
||||
@ -3122,7 +3159,15 @@ namespace BTCPayServer.Tests
|
||||
var invoiceId = GetInvoiceId(resp);
|
||||
await acc.PayOnChain(invoiceId);
|
||||
|
||||
app = await client.CreatePointOfSaleApp(acc.StoreId, new PointOfSaleAppRequest
|
||||
// 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);
|
||||
monitored = Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN"), true), i => i.Id == invoiceId);
|
||||
Assert.Single(monitored.Payments);
|
||||
//
|
||||
|
||||
app = await client.CreatePointOfSaleApp(acc.StoreId, new PointOfSaleAppRequest
|
||||
{
|
||||
AppName = "Cart",
|
||||
DefaultView = PosViewType.Cart,
|
||||
@ -3210,7 +3255,7 @@ namespace BTCPayServer.Tests
|
||||
var inv = await client.CreateInvoice(acc.StoreId, new CreateInvoiceRequest() { Amount = 10m, Currency = "USD" });
|
||||
await acc.PayInvoice(inv.Id);
|
||||
await client.MarkInvoiceStatus(acc.StoreId, inv.Id, new MarkInvoiceStatusRequest() { Status = InvoiceStatus.Settled });
|
||||
var refund = await client.RefundInvoice(acc.StoreId, inv.Id, new RefundInvoiceRequest() { RefundVariant = RefundVariant.Fiat, PaymentMethod = "BTC-CHAIN" });
|
||||
var refund = await client.RefundInvoice(acc.StoreId, inv.Id, new RefundInvoiceRequest() { RefundVariant = RefundVariant.Fiat, PayoutMethodId = "BTC-CHAIN" });
|
||||
|
||||
async Task AssertData(string currency, decimal awaiting, decimal limit, decimal completed, bool fullyPaid)
|
||||
{
|
||||
@ -3229,7 +3274,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
await AssertData("USD", awaiting: 0.0m, limit: 10.0m, completed: 0.0m, fullyPaid: false);
|
||||
var payout = await client.CreatePayout(refund.Id, new CreatePayoutRequest() { Destination = addr.ToString(), PaymentMethod = "BTC-CHAIN" });
|
||||
var payout = await client.CreatePayout(refund.Id, new CreatePayoutRequest() { Destination = addr.ToString(), PayoutMethodId = "BTC-CHAIN" });
|
||||
await AssertData("USD", awaiting: 10.0m, limit: 10.0m, completed: 0.0m, fullyPaid: false);
|
||||
await client.ApprovePayout(acc.StoreId, payout.Id, new ApprovePayoutRequest());
|
||||
await AssertData("USD", awaiting: 10.0m, limit: 10.0m, completed: 0.0m, fullyPaid: false);
|
||||
|
@ -29,6 +29,11 @@ namespace BTCPayServer.Tests
|
||||
BTCPayLogs.Configure(LoggerFactory);
|
||||
}
|
||||
|
||||
public DatabaseTester CreateDBTester()
|
||||
{
|
||||
return new DatabaseTester(TestLogs, LoggerFactory);
|
||||
}
|
||||
|
||||
public BTCPayNetworkProvider CreateNetworkProvider(ChainName chainName)
|
||||
{
|
||||
var conf = new ConfigurationRoot(new List<IConfigurationProvider>()
|
||||
@ -58,9 +63,9 @@ namespace BTCPayServer.Tests
|
||||
var bootstrap = Startup.CreateBootstrap(conf);
|
||||
var services = new PluginServiceCollection(new ServiceCollection(), bootstrap);
|
||||
var plugins = new List<BaseBTCPayServerPlugin>() { new BitcoinPlugin() };
|
||||
#if ALTCOINS
|
||||
|
||||
plugins.Add(new BTCPayServer.Plugins.Altcoins.AltcoinsPlugin());
|
||||
#endif
|
||||
|
||||
foreach (var p in plugins)
|
||||
{
|
||||
p.Execute(services);
|
||||
|
@ -321,7 +321,7 @@ retry:
|
||||
base.VisitTagHelper(node);
|
||||
}
|
||||
|
||||
private string ToString(IntermediateNode? node)
|
||||
private string ToString(IntermediateNode node)
|
||||
{
|
||||
return _txt.Substring(node.Source.Value.AbsoluteIndex, node.Source.Value.Length);
|
||||
}
|
||||
|
@ -10,14 +10,12 @@ services:
|
||||
context: ..
|
||||
dockerfile: BTCPayServer.Tests/Dockerfile
|
||||
args:
|
||||
CONFIGURATION_NAME: Altcoins-Release
|
||||
CONFIGURATION_NAME: Release
|
||||
environment:
|
||||
TESTS_EXPERIMENTALV2_CONFIRM: "true"
|
||||
TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||
TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||
TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/
|
||||
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
|
||||
TESTS_DB: "Postgres"
|
||||
TESTS_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=btcpayserver
|
||||
TESTS_EXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer
|
||||
TESTS_HOSTNAME: tests
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user