Compare commits
309 Commits
v2.0.0-bet
...
v2.0.6
Author | SHA1 | Date | |
---|---|---|---|
ba8feeddd9 | |||
4e57d27cc8 | |||
580518e5aa | |||
81cd51cc66 | |||
499ddbde65 | |||
20e8db7307 | |||
8e927eee73 | |||
cdf7cbfeb7 | |||
97173c9811 | |||
bbe95d780f | |||
5534ddeb28 | |||
1e93dccea2 | |||
4e2684b917 | |||
2725bbfe2a | |||
01250f6aee | |||
6180ee9b80 | |||
d6b547d8bf | |||
d3d6b2d15c | |||
1830d398a4 | |||
846fa30be1 | |||
b13d0a4300 | |||
ef8071ba93 | |||
2e458af4fb | |||
1c25d793c7 | |||
92a92a7fff | |||
2255da4a56 | |||
955091c793 | |||
e479522d9f | |||
2250853b3e | |||
ae1eb67677 | |||
c430d3b4ed | |||
e1f47b2738 | |||
9d8a231274 | |||
5a8c959b6b | |||
e0a51537a1 | |||
d2c52c10bd | |||
5536935ff8 | |||
0754a809e7 | |||
286bf7d822 | |||
a6f67e82b6 | |||
408bba6225 | |||
ce55666ba3 | |||
c766047f63 | |||
07b9eb079d | |||
b59182f169 | |||
6ae36825d5 | |||
f1719ed3d2 | |||
79c5ff9ed0 | |||
76a880a30e | |||
08835895e9 | |||
4ee12b41b1 | |||
4fb43cbbad | |||
adce1dffb1 | |||
0f049eee1b | |||
cfc2b9c236 | |||
637c06fc01 | |||
1ef177ba0f | |||
8acf1c2d62 | |||
44dc6499cd | |||
f5a420a272 | |||
d24e0cd1a2 | |||
b3bc11c19d | |||
fe3bccf3ce | |||
7829a93251 | |||
d4b76823a2 | |||
00cc16455c | |||
6e222c573b | |||
6f9e0dca51 | |||
d2c3c37f4d | |||
b797cc9af8 | |||
cc915df10e | |||
fe53424710 | |||
6cfd4cb633 | |||
4d38f91bd5 | |||
4f63f08aeb | |||
e7e7fab1a9 | |||
1214367503 | |||
d24adda700 | |||
eb89f4636b | |||
7da247ffd6 | |||
cadcf27ebc | |||
61b882d426 | |||
e4d780417d | |||
4d01e3a16a | |||
52a1627d81 | |||
d5b9d91a75 | |||
cd507b10e0 | |||
f7a96272c1 | |||
a8d1f55544 | |||
fb8ca19327 | |||
341bea48f8 | |||
aa336e7d8a | |||
77f9b6a88c | |||
3b94c2798c | |||
70f97382a4 | |||
e7b98fbae3 | |||
ad3a635f59 | |||
eea9ba10fd | |||
f2dc033067 | |||
bf37ee9cf6 | |||
9b4fe5930e | |||
a4e950343a | |||
af54e7ebc2 | |||
8edb4eae3b | |||
694b8e111c | |||
cccaf0e72c | |||
7d65817acd | |||
43f159cd81 | |||
6e2f355aa6 | |||
e2f868df52 | |||
898f0f4481 | |||
615b7842db | |||
2bc6499f1b | |||
9175af4abe | |||
3046fe0f0b | |||
6822608c14 | |||
177ddb4117 | |||
3b876413c1 | |||
6397c8e07c | |||
9fce9fcb7b | |||
0eec3eefd6 | |||
83ce5a2107 | |||
4ea6a03b4b | |||
bdf12aab0f | |||
299527fe16 | |||
e14c086fed | |||
fbf707cde2 | |||
45a6bc8ae2 | |||
71c578f866 | |||
5cf3819743 | |||
d3315c2fa6 | |||
f7f3244b3d | |||
d6211327db | |||
76f87011a2 | |||
c6fc0302d2 | |||
843c813f86 | |||
57742ad871 | |||
7db510b5ca | |||
1490fbb721 | |||
dd4400f8dd | |||
4279ee1962 | |||
558f45a48c | |||
6a76167637 | |||
f56ad3408c | |||
8e688b7f28 | |||
6d737ce119 | |||
2075e16767 | |||
2c4b1798ad | |||
b040f78f70 | |||
c221cb7ccb | |||
8a51e21310 | |||
31b73f0d82 | |||
777748d79e | |||
d0f97e85d2 | |||
2eff7523c3 | |||
12d4803c5c | |||
fdbee350b8 | |||
78f47516b8 | |||
088c0c7f85 | |||
809e475e29 | |||
8b776ed9e5 | |||
984475b9d0 | |||
894f68f2ae | |||
796d8d4406 | |||
7ac6cad18c | |||
b3abc8f161 | |||
e80296fa85 | |||
d0779b88e0 | |||
acd7765159 | |||
0a51031334 | |||
0f7c0341c5 | |||
d40669c7bd | |||
fe8360e870 | |||
72fd2ec424 | |||
7e7d4086cd | |||
f5d26a1555 | |||
540fb6c9f6 | |||
12681eda36 | |||
a05dbef337 | |||
a8581510e5 | |||
bbba7551b7 | |||
a129114603 | |||
392ec623c0 | |||
641bdcff31 | |||
b3945d758a | |||
4272ea97db | |||
5e438b84e1 | |||
ff79a31066 | |||
225264a283 | |||
8a5a160645 | |||
5cbadc09f9 | |||
7aa87d397e | |||
693eceb80f | |||
7d8fc14159 | |||
4687bb95cb | |||
e3ec07da76 | |||
910801d305 | |||
5ad0b128aa | |||
5cbeea4fb3 | |||
a6e18736d6 | |||
373b90e3b5 | |||
92f9b226fe | |||
0ac6553840 | |||
41a2241ae1 | |||
9bb1a5b80a | |||
0e59107eee | |||
c9fe68b812 | |||
e7b9688602 | |||
a962e60de9 | |||
e5611f9165 | |||
540ad13265 | |||
2849426092 | |||
c4a2b4e975 | |||
d508f5dc09 | |||
81ce8b0469 | |||
5a3a661e91 | |||
bb5c6bd68d | |||
9dfabeab52 | |||
ad07330bf1 | |||
74011e50e3 | |||
3dfdbf544a | |||
4bf0b79c2a | |||
cc0ea0b3f8 | |||
62d765125d | |||
b5b45d9a27 | |||
8e098710c1 | |||
6dfb369b55 | |||
817522ff97 | |||
8b5b90d247 | |||
b670097592 | |||
7b6a115adc | |||
77fba4aee3 | |||
7e1712c8cd | |||
b7affb1d34 | |||
d7fd90c4c3 | |||
1d94782463 | |||
c7a05c3f09 | |||
2dc58a82b7 | |||
755dbbab00 | |||
b470fe22f1 | |||
5b2560ddf7 | |||
be429c527c | |||
65fd537200 | |||
6e43c7f06f | |||
5867b5c000 | |||
05887cf8b0 | |||
c43721d489 | |||
0bf75d52d7 | |||
c35af2dc69 | |||
73a9835a27 | |||
87ab15f754 | |||
6bc608c081 | |||
cbea1d8691 | |||
58f21a69aa | |||
426c5b9a24 | |||
511e90efd1 | |||
bc7b856654 | |||
d50d2f9ca0 | |||
1b53defab3 | |||
3e612921f3 | |||
ec51d43490 | |||
80dc5028f7 | |||
2329c4a75f | |||
ae76cc1ca2 | |||
e4f79f046a | |||
622d837ea1 | |||
c0aa9a8bd4 | |||
9b1052f023 | |||
c77c2f8bd6 | |||
402eaa8f12 | |||
212e8c3654 | |||
7c77b16517 | |||
e5bb0bcba3 | |||
ca4a7d8771 | |||
663f97265a | |||
b91f3048ef | |||
4fe0bf1236 | |||
dd35af3c55 | |||
68f24e47cd | |||
968223a953 | |||
2f287874e3 | |||
c35e7406cd | |||
34b2cca492 | |||
e1bfc04451 | |||
ef0ba7b0c4 | |||
0a2d8880ba | |||
8dcd7e6966 | |||
b744fd6167 | |||
5bcc5c919a | |||
01e12329e9 | |||
471bf57835 | |||
b246beab3e | |||
abc8161a08 | |||
64ba8248d2 | |||
206d222455 | |||
2e114d7c29 | |||
5190c25be0 | |||
5704919b3a | |||
c3e51f51b6 | |||
8c35edb6e8 | |||
2f2b4094f6 | |||
413a9b4269 | |||
a698aa8a5b | |||
0f79526566 | |||
1ffbab7338 | |||
3a71c45a89 | |||
8f062f918b | |||
2f05d00219 | |||
b48ca92675 |
.circleci
.github/ISSUE_TEMPLATE
.gitignoreBTCPayServer.Abstractions
BTCPayServer.Client
BTCPayServer.Client.csprojBTCPayServerClient.Apps.csBTCPayServerClient.Lightning.Internal.csBTCPayServerClient.Lightning.Store.csBTCPayServerClient.OnChainWallet.csBTCPayServerClient.StorePayoutProcessors.csBTCPayServerClient.StoreUsers.csBTCPayServerClient.cs
Models
AppCartItem.csAppItem.csCreatePosInvoiceRequest.csCrowdfundAppData.csHistogramData.csLightningAutomatedPayoutSettings.csNotificationData.csPermissionMetadata.csPointOfSaleAppData.csServerInfoData.csStoreData.cs
Permissions.csPosDataParser.csBTCPayServer.Common
BTCPayNetwork.csBTCPayNetworkProvider.csBTCPayServer.Common.csprojCustomThreadPool.csMultiProcessingQueue.csSelectedChains.cs
BTCPayServer.Data
BTCPayServer.Rating
BTCPayServer.Tests
AltcoinTests
BTCPayServer.Tests.csprojBTCPayServerTester.csCheckoutUITests.csCrowdfundTests.csDockerfileFastTests.csGreenfieldAPITests.csLanguageServiceTests.csMocks
OutputPathAttribute.csPOSTests.csPayJoinTests.csSeleniumTester.csSeleniumTests.csTestAccount.csTestData
TestUtils.csThirdPartyTests.csUnitTest1.csUtilitiesTests.csdocker-compose.altcoins.ymldocker-compose.mutinynet.ymldocker-compose.testnet.ymldocker-compose.ymlxunit.runner.jsonBTCPayServer
BTCPayServer.csprojProgram.csShowQR.cshtmlTemplateEditor.cshtmlVaultElements.cshtmlWalletId.cs_ViewImports.cshtml
Blazor
ColorPalette.csComponents
AppSales
AppTopItems
InvoiceStatus
LabelManager
MainNav
Pager
QRCode
StoreLightningBalance
StoreLightningServices
StoreNumbers
StoreRecentInvoices
StoreRecentTransactions
StoreSelector
StoreWalletBalance
ThemeSwitch
TruncateCenter
WalletNav
Configuration
Controllers
BitpayRateController.cs
CurrencyValue.csGreenField
GreenfieldApiKeysController.csGreenfieldAppsController.csGreenfieldInvoiceController.csGreenfieldLightningNodeApiController.Internal.csGreenfieldLightningNodeApiController.Store.csGreenfieldLightningNodeApiController.csGreenfieldNotificationsController.csGreenfieldPaymentRequestsController.csGreenfieldPayoutProcessorsController.csGreenfieldPullPaymentController.csGreenfieldReportsController.csGreenfieldServerInfoController.csGreenfieldStoreAutomatedLightningPayoutProcessorsController.csGreenfieldStoreAutomatedOnChainPayoutProcessorsController.csGreenfieldStoreEmailController.csGreenfieldStoreOnChainPaymentMethodsController.WalletGeneration.csGreenfieldStoreOnChainPaymentMethodsController.csGreenfieldStoreOnChainWalletsController.csGreenfieldStorePaymentMethodsController.csGreenfieldStorePayoutProcessorsController.csGreenfieldStoreRatesConfigurationController.csGreenfieldStoreRatesController.csGreenfieldStoreUsersController.csGreenfieldStoreWebhooksController.csGreenfieldStoresController.csGreenfieldUsersController.csLocalBTCPayServerClient.cs
UIAccountController.csUIAppsController.Dashboard.csUIAppsController.csUIBoltcardController.csUIInvoiceController.Testing.csUIInvoiceController.UI.csUIInvoiceController.csUILNURLAuthController.csUILNURLController.csUIManageController.APIKeys.csUIManageController.Notifications.csUIManageController.csUINotificationsController.csUIPaymentRequestController.csUIPublicController.csUIPublicLightningNodeInfoController.csUIPullPaymentController.Boltcard.csUIPullPaymentController.csUIReportsController.CheatMode.csUIReportsController.csUIServerController.Plugins.csUIServerController.Roles.csUIServerController.Storage.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.PSBT.csUIWalletsController.csData
DerivationSchemeParser.csDerivationSchemeSettings.csEvents
AppEvent.csNewBlockEvent.csNewOnChainTransactionEvent.csStoreEvent.csStoreRemovedEvent.csStoreRoleEvent.csStoreUserEvent.csUserApprovedEvent.csUserConfirmedEmailEvent.csUserEvent.csUserInviteAcceptedEvent.csUserPasswordResetRequestedEvent.csUserRegisteredEvent.cs
Extensions.csExtensions
AuthorizationExtensions.csColorExtensions.csEmailSenderExtensions.csMoneyExtensions.csSettingsRepositoryExtensions.csStoreExtensions.csUrlHelperExtensions.cs
Fido2
Filters
Forms
HostedServices
AppInventoryUpdaterHostedService.csBlobMigratorHostedService.csEventHostedServiceBase.csInvoiceBlobMigratorHostedService.csPendingTransactionService.csPluginUpdateFetcher.csPullPaymentHostedService.csTransactionLabelMarkerHostedService.csUserEventHostedService.cs
Hosting
BTCPayServerServices.csLoadCurrencyNameTableStartupTask.csLoadTranslationsStartupTask.csMigrationStartupTask.csStartup.cs
Models
InvoicingModels
ManageViewModels
NotificationViewModels
PaymentRequestViewModels
ServerViewModels
StoreReportsViewModels
StoreViewModels
CheckoutAppearanceViewModel.csCreateStoreViewModel.csLightningNodeViewModel.csStoreDerivationScheme.csTokensViewModel.csWalletSettingsViewModel.cs
ViewPullPaymentModel.csWalletViewModels
PaymentRequest
Payments
Bitcoin
BitcoinCheckoutCheatModeExtension.csBitcoinCheckoutModelExtension.csBitcoinLikePaymentData.csBitcoinLikePaymentHandler.csBitcoinPaymentMethodViewExtension.csBitcoinPaymentPromptDetails.csNBXplorerListener.cs
ICheckoutCheatModeExtension.csICheckoutModelExtension.csIGlobalCheckoutModelExtension.csIPaymentMethodHandler.csIPaymentMethodViewExtension.csLNURLPay
LNURLCheckoutModelExtension.csLNURLPayPaymentHandler.csLNURLPaymentMethodViewExtension.csStoreLNURLPayRequest.cs
Lightning
IExtendedLightningClient.csLNCheckoutModelExtension.csLightningCheckoutCheatModeExtension.csLightningLikePaymentData.csLightningLikePaymentHandler.csLightningListener.csLightningPaymentMethodViewExtension.csLightningPendingPayoutListener.cs
PayJoin
PaymentMethodId.csPayoutProcessors
BaseAutomatedPayoutProcessor.cs
Lightning
LightningAutomatedPayoutBlob.csLightningAutomatedPayoutProcessor.csLightningAutomatedPayoutSenderFactory.csUILightningAutomatedPayoutProcessorsController.cs
OnChain
OnChainAutomatedPayoutProcessor.csOnChainAutomatedPayoutSenderFactory.csUIOnChainAutomatedPayoutProcessorsController.cs
UIPayoutProcessorsController.csPlugins
Altcoins
AltcoinsPlugin.BGold.csAltcoinsPlugin.Dash.csAltcoinsPlugin.Dogecoin.csAltcoinsPlugin.Groestlcoin.csAltcoinsPlugin.Litecoin.csAltcoinsPlugin.Monacoin.csAltcoinsPlugin.cs
Liquid
Monero
Zcash
Bitcoin
Crowdfund
NFC
PayButton
PluginBuilderClient.csPluginExceptionHandler.csPluginManager.csPluginService.csPointOfSale
Shopify
Properties
ResourceTracker.csRoles.csSecurity
Services
Altcoins
Monero
Payments
MoneroCheckoutModelExtension.csMoneroLikePaymentData.csMoneroLikePaymentMethodHandler.csMoneroPaymentModelExtension.cs
RPC
Services
UI
Zcash
Apps
CallbackGenerator.csCheater.csDelayedTransactionBroadcaster.csInvoices
Labels
LightningHistogramService.csLocalizerService.csMails
MigrationSettings.csNBXSyncSummaryProvider.csNotifications/Blobs
ExternalPayoutTransactionNotification.csInviteAcceptedNotification.csInvoiceEventNotification.csNewUserRequiresApprovalNotification.csNewVersionNotification.csPayoutNotification.cs
PoliciesSettings.csPrettyNameProvider.csServerSettings.csStores
TransactionLinkProviders.csTranslations.Default.csTranslations.csUserService.csWallets
Storage/Services
UserManagerExtensions.csViews
Shared
Bitcoin
BitcoinLikeMethodCheckout.cshtmlBitcoinLikeMethodCheckoutNoScript.cshtmlNBXSyncSummary.cshtmlViewBitcoinLikePaymentData.cshtml
CameraScanner.cshtmlConfirmModal.cshtmlCrowdfund
EmailsBody.cshtmlEmailsTest.cshtmlError.cshtmlLNURL
LayoutHeadStoreBranding.cshtmlLayoutHeadTheme.cshtmlLightning
LightningLikeMethodCheckout.cshtmlLightningLikeMethodCheckoutNoScript.cshtmlViewLightningLikePaymentData.cshtml
ListRoles.cshtmlLocalhostBrowserSupport.cshtmlMonero
NFC
NotificationEmailWarning.cshtmlPayButton
PointOfSale
Public
Cart.cshtmlLight.cshtmlMinimalLight.cshtmlPrint.cshtmlRecentTransactions.cshtmlStatic.cshtmlVueLight.cshtml_Layout.cshtml
UpdatePointOfSale.cshtmlZcash
_BTCPaySupporters.cshtml_Footer.cshtml_Layout.cshtml_LayoutSignedOut.cshtml_StatusMessage.cshtmlUIAccount
CheatPermissions.cshtmlForgotPassword.cshtmlForgotPasswordConfirmation.cshtmlLockout.cshtmlLogin.cshtmlLoginWith2fa.cshtmlLoginWithFido2.cshtmlLoginWithLNURLAuth.cshtmlLoginWithRecoveryCode.cshtmlRegister.cshtmlSecondaryLogin.cshtmlSignedOut.cshtml
UIApps
UIError
UIFido2
UIForms
UIHome
UIInvoice
Checkout-Cheating.cshtmlCheckout-Testing.cshtmlCheckout.cshtmlCheckoutNoScript.cshtmlCreateInvoice.cshtmlInvoice.cshtmlInvoiceReceipt.cshtmlInvoiceReceiptPrint.cshtmlInvoiceStatusChangePartial.cshtmlListInvoices.cshtmlListInvoicesPaymentsPartial.cshtml_RefundModal.cshtml
UILNURL
UILNURLAuth
UILightningAutomatedPayoutProcessors
UILightningLikePayout
UIManage
APIKeys.cshtmlAddApiKey.cshtmlAuthorizeAPIKey.cshtmlChangePassword.cshtmlConfirmAPIKey.cshtmlEnableAuthenticator.cshtmlGenerateRecoveryCodes.cshtmlIndex.cshtmlLoginCodes.cshtmlNotificationSettings.cshtmlSetPassword.cshtmlTwoFactorAuthentication.cshtml
UIMoneroLikeStore
UINotifications
UIOnChainAutomatedPayoutProcessors
UIPaymentRequest
UIPayoutProcessors
UIPublic
UIPublicLightningNodeInfo
UIPullPayment
UIReports
UIServer
Branding.cshtmlCLightningRestServices.cshtmlConfiguratorService.cshtmlCreateTemporaryFileUrl.cshtmlCreateUser.cshtmlDynamicDnsService.cshtmlDynamicDnsServices.cshtmlEditAmazonS3StorageProvider.cshtmlEditAzureBlobStorageStorageProvider.cshtmlEditDictionary.cshtmlEditFileSystemStorageProvider.cshtmlEditGoogleCloudStorageStorageProvider.cshtmlEmails.cshtmlFiles.cshtmlLightningChargeServices.cshtmlLightningWalletServices.cshtmlListDictionaries.cshtmlListPlugins.cshtmlListStores.cshtmlListUsers.cshtmlLndSeedBackup.cshtmlLndServices.cshtmlLogs.cshtmlMaintenance.cshtmlP2PService.cshtmlPolicies.cshtmlRPCService.cshtmlResetUserPassword.cshtmlSSHService.cshtmlServices.cshtmlStorage.cshtmlUser.cshtml
UIShopify
UIStorePullPayments
UIStores
CheckoutAppearance.cshtmlCreateToken.cshtmlDashboard.cshtmlGeneralSettings.cshtmlGenerateWallet.cshtmlGenerateWalletOptions.cshtml
ImportWallet
ImportWalletOptions.cshtmlLightning.cshtmlLightningSettings.cshtmlListTokens.cshtmlModifyWebhook.cshtmlRates.cshtmlRequestPairing.cshtmlSetupLightningNode.cshtmlSetupWallet.cshtmlShowToken.cshtmlStoreEmailSettings.cshtmlStoreEmails.cshtmlStoreUsers.cshtmlTestWebhook.cshtmlWalletSettings.cshtmlWebhooks.cshtml_GenerateWalletForm.cshtml_LayoutWalletSetup.cshtmlUIUserStores
UIWallets
CoinSelection.cshtmlListWallets.cshtmlSignWithSeed.cshtmlSigningContext.cshtmlWalletLabels.cshtmlWalletPSBT.cshtmlWalletPSBTCombine.cshtmlWalletPSBTDecoded.cshtmlWalletReceive.cshtmlWalletRescan.cshtmlWalletSend.cshtmlWalletSendVault.cshtmlWalletSigningOptions.cshtmlWalletTransactions.cshtml_PSBTInfo.cshtml_WalletTransactionsList.cshtml
UIZcashLikeStore
wwwroot
checkout
crowdfund
img
js
locales
am-ET.jsonar.jsonaz.jsonbg-BG.jsonbs-BA.jsonca-ES.json
checkout
bg-BG.jsoncs-CZ.jsonde-DE.jsonel-GR.jsonfa.jsonfi-FI.jsonhu-HU.jsonis-IS.jsonja-JP.jsonko.jsonnl-NL.jsonno.jsonpl.jsonpt-BR.jsonro.jsonsk-SK.jsonzh-SP.json
cs-CZ.jsonda-DK.jsonde-DE.jsonel-GR.jsonen.jsones-ES.jsonfa.jsonfi-FI.jsonfr-FR.jsonhe.jsonhi.jsonhr-HR.jsonhu-HU.jsonhy.jsonid.jsonis-IS.jsonit-IT.jsonja-JP.jsonka.jsonkk-KZ.jsonko.jsonlv.jsonnl-NL.jsonno.jsonnp-NP.jsonpl.jsonpt-BR.jsonpt-PT.jsonro.jsonru-RU.jsonsk-SK.jsonsl-SI.jsonsr.jsonsv.jsonth-TH.jsontr.jsonuk-UA.jsonvi-VN.jsonzh-SG.jsonzh-SP.jsonzh-TW.jsonzu.jsonmain
pos
swagger/v1
swagger.template.api-keys.jsonswagger.template.apps.jsonswagger.template.files.jsonswagger.template.health.jsonswagger.template.invoices.jsonswagger.template.jsonswagger.template.lightning.common.jsonswagger.template.lightning.internal.jsonswagger.template.lightning.store.jsonswagger.template.misc.jsonswagger.template.notifications.jsonswagger.template.payment-requests.jsonswagger.template.payout-processors.jsonswagger.template.pull-payments.jsonswagger.template.serverinfo.jsonswagger.template.stores-email.jsonswagger.template.stores-lightning-addresses.jsonswagger.template.stores-payment-methods.jsonswagger.template.stores-rates-config.jsonswagger.template.stores-rates.jsonswagger.template.stores-users.jsonswagger.template.stores-wallet.on-chain.jsonswagger.template.stores-wallet.on-chain.objects.jsonswagger.template.stores.jsonswagger.template.users.jsonswagger.template.webhooks.json
vendor/font-awesome
Build
Changelog.mdDockerfileREADME.md@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
fast_tests:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -10,7 +10,7 @@ jobs:
|
||||
cd .circleci && ./run-tests.sh "Fast=Fast|ThirdParty=ThirdParty" && ./can-build.sh
|
||||
selenium_tests:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -18,7 +18,7 @@ jobs:
|
||||
cd .circleci && ./run-tests.sh "Selenium=Selenium"
|
||||
integration_tests:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
cd .circleci && ./run-tests.sh "Integration=Integration"
|
||||
trigger_docs_build:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
@ -47,7 +47,6 @@ jobs:
|
||||
docker buildx create --use
|
||||
DOCKER_BUILDX_OPTS="--platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg GIT_COMMIT=${GIT_COMMIT} --push"
|
||||
docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG .
|
||||
docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins --build-arg CONFIGURATION_NAME=Altcoins-Release .
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
|
@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
cd ../BTCPayServer.Tests
|
||||
docker-compose -v
|
||||
docker-compose -f "docker-compose.altcoins.yml" down --v
|
||||
docker-compose --version
|
||||
docker-compose -f "docker-compose.altcoins.yml" down -v
|
||||
|
||||
# For some reason, docker-compose pull fails time to time, so we try several times
|
||||
n=0
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -62,7 +62,7 @@ body:
|
||||
id: terms
|
||||
attributes:
|
||||
label: Are you sure this is a bug report?
|
||||
description: By submitting this report, you agree that this is not a support or a feature request. For general questions please read our [documentation](https://docs.btcpayserver.org). You can ask questions in [discussions](https://github.com/btcpayserver/btcpayserver/discussions) and [on our community chat](https://chat.btcpayserver.org)
|
||||
description: By submitting this report, you agree that this is not a support or a feature request. For general questions please read our [documentation](https://docs.btcpayserver.org). You can ask questions in [discussions](https://github.com/btcpayserver/btcpayserver/discussions) and [on our community chat](https://chat.btcpayserver.org) Beware of scammers we will never direct you to third-party sites for support or ask for sensitive information your private key especially. Ignore any bots and scammers replies to GitHub issues claiming to be support agents
|
||||
options:
|
||||
- label: I confirm this is a bug report
|
||||
required: true
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -266,6 +266,7 @@ paket-files/
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
.run
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
@ -32,8 +32,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlSanitizer" Version="8.0.838" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
@ -14,4 +15,5 @@ public interface IFileService
|
||||
Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
|
||||
bool isDownload);
|
||||
Task RemoveFile(string fileId, string userId);
|
||||
Task<UploadImageResultModel> UploadImage(IFormFile file, string userId, long maxFileSizeInBytes = 1_000_000);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer.Abstractions.Contracts
|
||||
|
||||
public interface ISyncStatus
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
public bool Available { get; }
|
||||
}
|
||||
}
|
||||
|
@ -106,8 +106,8 @@ public static class HttpRequestExtensions
|
||||
|
||||
/// <summary>
|
||||
/// Will return an absolute URL.
|
||||
/// If `relativeOrAsbolute` is absolute, returns it.
|
||||
/// If `relativeOrAsbolute` is relative, send absolute url based on the HOST of this request (without PathBase)
|
||||
/// If `relativeOrAbsolute` is absolute, returns it.
|
||||
/// If `relativeOrAbsolute` is relative, send absolute url based on the HOST of this request (without PathBase)
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="relativeOrAbsolte"></param>
|
||||
|
@ -8,6 +8,14 @@ namespace BTCPayServer.Abstractions.Extensions;
|
||||
|
||||
public static class SetStatusMessageModelExtensions
|
||||
{
|
||||
public static void SetStatusSuccess(this ITempDataDictionary tempData, string statusMessage)
|
||||
{
|
||||
tempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = statusMessage
|
||||
});
|
||||
}
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
@ -26,19 +34,14 @@ public static class SetStatusMessageModelExtensions
|
||||
tempData.TryGetValue("StatusMessageModel", out var model);
|
||||
if (successMessage != null || errorMessage != null)
|
||||
{
|
||||
var parsedModel = new StatusMessageModel();
|
||||
parsedModel.Message = (string)successMessage ?? (string)errorMessage;
|
||||
if (successMessage != null)
|
||||
var parsedModel = new StatusMessageModel
|
||||
{
|
||||
parsedModel.Severity = StatusMessageModel.StatusSeverity.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedModel.Severity = StatusMessageModel.StatusSeverity.Error;
|
||||
}
|
||||
Message = (string)successMessage ?? (string)errorMessage,
|
||||
Severity = successMessage != null ? StatusMessageModel.StatusSeverity.Success : StatusMessageModel.StatusSeverity.Error
|
||||
};
|
||||
return parsedModel;
|
||||
}
|
||||
else if (model != null && model is string str)
|
||||
if (model is string str)
|
||||
{
|
||||
return JObject.Parse(str).ToObject<StatusMessageModel>();
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
viewData[ACTIVE_CATEGORY_KEY] = activeCategory;
|
||||
}
|
||||
|
||||
public static bool IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null)
|
||||
public static bool IsCategoryActive(this ViewDataDictionary viewData, string category, object id = null)
|
||||
{
|
||||
if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY)) return false;
|
||||
var activeId = viewData[ACTIVE_ID_KEY];
|
||||
@ -65,12 +65,12 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
return categoryMatch && idMatch;
|
||||
}
|
||||
|
||||
public static bool IsActiveCategory<T>(this ViewDataDictionary viewData, T category, object id = null)
|
||||
public static bool IsCategoryActive<T>(this ViewDataDictionary viewData, T category, object id = null)
|
||||
{
|
||||
return IsActiveCategory(viewData, category.ToString(), id);
|
||||
return IsCategoryActive(viewData, category.ToString(), id);
|
||||
}
|
||||
|
||||
public static bool IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null)
|
||||
public static bool IsPageActive(this ViewDataDictionary viewData, string page, string category, object id = null)
|
||||
{
|
||||
if (!viewData.ContainsKey(ACTIVE_PAGE_KEY)) return false;
|
||||
var activeId = viewData[ACTIVE_ID_KEY];
|
||||
@ -82,7 +82,7 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
return categoryAndPageMatch && idMatch;
|
||||
}
|
||||
|
||||
public static bool IsActivePage<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null)
|
||||
public static bool IsPageActive<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null)
|
||||
where T : IConvertible
|
||||
{
|
||||
return pages.Any(page => ActivePageClass(viewData, page.ToString(), page.GetType().ToString(), id) == ACTIVE_CLASS);
|
||||
@ -95,7 +95,7 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
|
||||
public static string ActiveCategoryClass(this ViewDataDictionary viewData, string category, object id = null)
|
||||
{
|
||||
return IsActiveCategory(viewData, category, id) ? ACTIVE_CLASS : null;
|
||||
return IsCategoryActive(viewData, category, id) ? ACTIVE_CLASS : null;
|
||||
}
|
||||
|
||||
public static string ActivePageClass<T>(this ViewDataDictionary viewData, T page, object id = null)
|
||||
@ -106,12 +106,42 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
|
||||
public static string ActivePageClass(this ViewDataDictionary viewData, string page, string category, object id = null)
|
||||
{
|
||||
return IsActivePage(viewData, page, category, id) ? ACTIVE_CLASS : null;
|
||||
return IsPageActive(viewData, page, category, id) ? ACTIVE_CLASS : null;
|
||||
}
|
||||
|
||||
|
||||
public static string ActivePageClass<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null) where T : IConvertible
|
||||
{
|
||||
return IsActivePage(viewData, pages, id) ? ACTIVE_CLASS : null;
|
||||
return IsPageActive(viewData, pages, id) ? ACTIVE_CLASS : null;
|
||||
}
|
||||
|
||||
[Obsolete("Use ActiveCategoryClass instead")]
|
||||
public static string IsActiveCategory<T>(this ViewDataDictionary viewData, T category, object id = null)
|
||||
{
|
||||
return ActiveCategoryClass(viewData, category, id);
|
||||
}
|
||||
|
||||
[Obsolete("Use ActiveCategoryClass instead")]
|
||||
public static string IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null)
|
||||
{
|
||||
return ActiveCategoryClass(viewData, category, id);
|
||||
}
|
||||
|
||||
[Obsolete("Use ActivePageClass instead")]
|
||||
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null) where T : IConvertible
|
||||
{
|
||||
return ActivePageClass(viewData, page, id);
|
||||
}
|
||||
|
||||
[Obsolete("Use ActivePageClass instead")]
|
||||
public static string IsActivePage<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null) where T : IConvertible
|
||||
{
|
||||
return ActivePageClass(viewData, pages, id);
|
||||
}
|
||||
|
||||
[Obsolete("Use ActivePageClass instead")]
|
||||
public static string IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null)
|
||||
{
|
||||
return ActivePageClass(viewData, page, category, id);
|
||||
}
|
||||
|
||||
public static HtmlString ToBrowserDate(this DateTimeOffset date, string netFormat, string jsDateFormat = "short", string jsTimeFormat = "short")
|
||||
|
@ -14,14 +14,6 @@ namespace BTCPayServer.Abstractions.Models
|
||||
|
||||
public string SeverityCSS => ToString(Severity);
|
||||
|
||||
private void ParseNonJsonStatus(string s)
|
||||
{
|
||||
Message = s;
|
||||
Severity = s.StartsWith("Error", StringComparison.InvariantCultureIgnoreCase)
|
||||
? StatusSeverity.Error
|
||||
: StatusSeverity.Success;
|
||||
}
|
||||
|
||||
public static string ToString(StatusSeverity severity)
|
||||
{
|
||||
switch (severity)
|
||||
|
16
BTCPayServer.Abstractions/Models/UploadImageResultModel.cs
Normal file
16
BTCPayServer.Abstractions/Models/UploadImageResultModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Models;
|
||||
|
||||
public class UploadImageResultModel
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Response { get; set; } = string.Empty;
|
||||
public IStoredFile? StoredFile { get; set; }
|
||||
}
|
@ -16,6 +16,8 @@ namespace BTCPayServer.Abstractions.Services
|
||||
_htmlHelper = htmlHelper;
|
||||
_jsonHelper = jsonHelper;
|
||||
_htmlSanitizer = htmlSanitizer;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public IHtmlContent Raw(string value)
|
||||
@ -32,5 +34,39 @@ namespace BTCPayServer.Abstractions.Services
|
||||
{
|
||||
return _htmlHelper.Raw(_jsonHelper.Serialize(model));
|
||||
}
|
||||
|
||||
public IHtmlContent Meta(string inputHtml) => _htmlHelper.Raw(RawMeta(inputHtml, out _));
|
||||
|
||||
public string RawMeta(string inputHtml, out bool isHtmlModified)
|
||||
{
|
||||
bool bHtmlModified;
|
||||
HtmlSanitizer sane = new HtmlSanitizer();
|
||||
|
||||
sane.AllowedTags.Clear();
|
||||
sane.AllowedTags.Add("meta");
|
||||
|
||||
sane.AllowedAttributes.Clear();
|
||||
sane.AllowedAttributes.Add("name");
|
||||
sane.AllowedAttributes.Add("http-equiv");
|
||||
sane.AllowedAttributes.Add("content");
|
||||
sane.AllowedAttributes.Add("value");
|
||||
sane.AllowedAttributes.Add("property");
|
||||
|
||||
sane.AllowDataAttributes = false;
|
||||
|
||||
sane.RemovingTag += (sender, e) => bHtmlModified = true;
|
||||
sane.RemovingAtRule += (sender, e) => bHtmlModified = true;
|
||||
sane.RemovingAttribute += (sender, e) => bHtmlModified = true;
|
||||
sane.RemovingComment += (sender, e) => bHtmlModified = true;
|
||||
sane.RemovingCssClass += (sender, e) => bHtmlModified = true;
|
||||
sane.RemovingStyle += (sender, e) => bHtmlModified = true;
|
||||
|
||||
bHtmlModified = false;
|
||||
|
||||
var sRet = sane.Sanitize(inputHtml);
|
||||
isHtmlModified = bHtmlModified;
|
||||
|
||||
return sRet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Services
|
||||
{
|
||||
public class UIExtension : IUIExtension
|
||||
{
|
||||
[Obsolete("Use extension method BTCPayServer.Extensions.AddUIExtension(this IServiceCollection services, string location, string partialViewName) instead")]
|
||||
public UIExtension(string partial, string location)
|
||||
{
|
||||
Partial = partial;
|
||||
|
@ -16,7 +16,7 @@
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.7.4</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">2.0.1</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -30,8 +30,8 @@
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.1" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.37" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.2" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.48" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -78,4 +78,14 @@ public partial class BTCPayServerClient
|
||||
if (appId == null) throw new ArgumentNullException(nameof(appId));
|
||||
await SendHttpRequest($"api/v1/apps/{appId}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
|
||||
public virtual async Task<FileData> UploadAppItemImage(string appId, string filePath, string mimeType, CancellationToken token = default)
|
||||
{
|
||||
return await UploadFileRequest<FileData>($"api/v1/apps/{appId}/image", filePath, mimeType, "file", HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAppItemImage(string appId, string fileId, CancellationToken token = default)
|
||||
{
|
||||
await SendHttpRequest($"api/v1/apps/{appId}/image/{fileId}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,13 @@ 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, object> { { "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,6 +21,13 @@ 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, object> { { "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)
|
||||
{
|
||||
|
@ -16,6 +16,14 @@ public partial class BTCPayServerClient
|
||||
{
|
||||
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, object> { { "type", type.ToString() } };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -19,14 +19,14 @@ public partial class BTCPayServerClient
|
||||
await SendHttpRequest($"api/v1/stores/{storeId}/payout-processors/{processor}/{paymentMethod}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<LightningAutomatedPayoutSettings>> GetStoreLightningAutomatedPayoutProcessors(string storeId, string? paymentMethod = null, CancellationToken token = default)
|
||||
public virtual async Task<IEnumerable<LightningAutomatedPayoutSettings>> GetStoreLightningAutomatedPayoutProcessors(string storeId, string? payoutMethodId = null, CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<IEnumerable<LightningAutomatedPayoutSettings>>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory{(paymentMethod is null ? string.Empty : $"/{paymentMethod}")}", null, HttpMethod.Get, token);
|
||||
return await SendHttpRequest<IEnumerable<LightningAutomatedPayoutSettings>>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory{(payoutMethodId is null ? string.Empty : $"/{payoutMethodId}")}", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningAutomatedPayoutSettings> UpdateStoreLightningAutomatedPayoutProcessors(string storeId, string paymentMethod, LightningAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
public virtual async Task<LightningAutomatedPayoutSettings> UpdateStoreLightningAutomatedPayoutProcessors(string storeId, string payoutMethodId, LightningAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
{
|
||||
return await SendHttpRequest<LightningAutomatedPayoutSettings>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{paymentMethod}", request, HttpMethod.Put, token);
|
||||
return await SendHttpRequest<LightningAutomatedPayoutSettings>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{payoutMethodId}", request, HttpMethod.Put, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainAutomatedPayoutSettings> UpdateStoreOnChainAutomatedPayoutProcessors(string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
|
@ -29,4 +29,10 @@ public partial class BTCPayServerClient
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
await SendHttpRequest<StoreUserData>($"api/v1/stores/{storeId}/users", request, HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task UpdateStoreUser(string storeId, string userId, StoreUserData request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
await SendHttpRequest<StoreUserData>($"api/v1/stores/{storeId}/users/{userId}", request, HttpMethod.Put, token);
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ public partial class BTCPayServerClient
|
||||
protected virtual async Task<T> UploadFileRequest<T>(string apiPath, string filePath, string mimeType, string formFieldName, HttpMethod method = null, CancellationToken token = default)
|
||||
{
|
||||
using MultipartFormDataContent multipartContent = new();
|
||||
var fileContent = new StreamContent(File.OpenRead(filePath));
|
||||
using var fileContent = new StreamContent(File.OpenRead(filePath));
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType);
|
||||
multipartContent.Add(fileContent, formFieldName, fileName);
|
||||
|
13
BTCPayServer.Client/Models/AppCartItem.cs
Normal file
13
BTCPayServer.Client/Models/AppCartItem.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class AppCartItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int Count { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Price { get; set; }
|
||||
}
|
35
BTCPayServer.Client/Models/AppItem.cs
Normal file
35
BTCPayServer.Client/Models/AppItem.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public enum AppItemPriceType
|
||||
{
|
||||
Fixed,
|
||||
Topup,
|
||||
Minimum
|
||||
}
|
||||
|
||||
public class AppItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Categories { get; set; }
|
||||
public string Image { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public AppItemPriceType PriceType { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Price { get; set; }
|
||||
public string BuyButtonText { get; set; }
|
||||
public int? Inventory { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
25
BTCPayServer.Client/Models/CreatePosInvoiceRequest.cs
Normal file
25
BTCPayServer.Client/Models/CreatePosInvoiceRequest.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class CreatePosInvoiceRequest
|
||||
{
|
||||
public string AppId { get; set; }
|
||||
public List<AppCartItem> Cart { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||
public List<decimal> Amounts { get; set; }
|
||||
public int? DiscountPercent { get; set; }
|
||||
public int? TipPercent { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? DiscountAmount { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Tip { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Subtotal { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Total { get; set; }
|
||||
public string PosData { get; set; }
|
||||
}
|
||||
|
@ -32,12 +32,14 @@ public abstract class CrowdfundBaseData : AppBaseData
|
||||
public bool? SortPerksByPopularity { get; set; }
|
||||
public string[]? Sounds { get; set; }
|
||||
public string[]? AnimationColors { get; set; }
|
||||
public string? HtmlLang { get; set; }
|
||||
public string? HtmlMetaTags { get; set; }
|
||||
public string? FormId { get; set; }
|
||||
}
|
||||
|
||||
public class CrowdfundAppData : CrowdfundBaseData
|
||||
{
|
||||
public object? Perks { get; set; }
|
||||
public AppItem[]? Perks { get; set; }
|
||||
}
|
||||
|
||||
public class CrowdfundAppRequest : CrowdfundBaseData, IAppRequest
|
||||
|
30
BTCPayServer.Client/Models/HistogramData.cs
Normal file
30
BTCPayServer.Client/Models/HistogramData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public enum HistogramType
|
||||
{
|
||||
Week,
|
||||
Month,
|
||||
YTD,
|
||||
Year,
|
||||
TwoYears,
|
||||
Day
|
||||
}
|
||||
|
||||
public class HistogramData
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public HistogramType Type { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||
public List<decimal> Series { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(DateTimeToUnixTimeConverter))]
|
||||
public List<DateTimeOffset> Labels { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Balance { get; set; }
|
||||
}
|
@ -11,7 +11,6 @@ public class LightningAutomatedPayoutSettings
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
|
||||
public int? CancelPayoutAfterFailures { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool ProcessNewPayoutsInstantly { get; set; }
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string Body { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public bool Seen { get; set; }
|
||||
public Uri Link { get; set; }
|
||||
public string Link { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
@ -11,17 +10,17 @@ namespace BTCPayServer.Client.Models
|
||||
static PermissionMetadata()
|
||||
{
|
||||
Dictionary<string, PermissionMetadata> nodes = new Dictionary<string, PermissionMetadata>();
|
||||
foreach (var policy in Client.Policies.AllPolicies)
|
||||
foreach (var policy in Policies.AllPolicies)
|
||||
{
|
||||
nodes.Add(policy, new PermissionMetadata() { PermissionName = policy });
|
||||
}
|
||||
foreach (var n in nodes)
|
||||
{
|
||||
foreach (var policy in Client.Policies.AllPolicies)
|
||||
foreach (var policy in Policies.AllPolicies)
|
||||
{
|
||||
if (policy.Equals(n.Key, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (Client.Permission.Create(n.Key).Contains(Client.Permission.Create(policy)))
|
||||
if (Permission.Create(n.Key).Contains(Permission.Create(policy)))
|
||||
n.Value.SubPermissions.Add(policy);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
@ -26,12 +24,14 @@ public abstract class PointOfSaleBaseData : AppBaseData
|
||||
public string? Description { get; set; }
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
public int[]? CustomTipPercentages { get; set; }
|
||||
public string? HtmlLang { get; set; }
|
||||
public string? HtmlMetaTags { get; set; }
|
||||
public string? FormId { get; set; }
|
||||
}
|
||||
|
||||
public class PointOfSaleAppData : PointOfSaleBaseData
|
||||
{
|
||||
public object? Items { get; set; }
|
||||
public AppItem[]? Items { get; set; }
|
||||
}
|
||||
|
||||
public class PointOfSaleAppRequest : PointOfSaleBaseData, IAppRequest
|
||||
|
@ -32,7 +32,7 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public class SyncStatus
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
public virtual bool Available { get; set; }
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,25 @@ namespace BTCPayServer.Client.Models
|
||||
/// </summary>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the store role of the user
|
||||
/// </summary>
|
||||
public string Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the email AND username of the user
|
||||
/// </summary>
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the name of the user
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the image url of the user
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
}
|
||||
|
||||
public class RoleData
|
||||
|
@ -3,7 +3,6 @@ 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
|
||||
|
50
BTCPayServer.Client/PosDataParser.cs
Normal file
50
BTCPayServer.Client/PosDataParser.cs
Normal file
@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ 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;
|
||||
|
@ -3,13 +3,9 @@ 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
|
||||
{
|
||||
|
@ -1,13 +1,8 @@
|
||||
<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="NBXplorer.Client" Version="4.3.6" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Altcoins\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class CustomThreadPool : IDisposable
|
||||
{
|
||||
readonly CancellationTokenSource _Cancel = new CancellationTokenSource();
|
||||
readonly TaskCompletionSource<bool> _Exited;
|
||||
int _ExitedCount = 0;
|
||||
readonly Thread[] _Threads;
|
||||
Exception _UnhandledException;
|
||||
readonly BlockingCollection<(Action, TaskCompletionSource<object>)> _Actions = new BlockingCollection<(Action, TaskCompletionSource<object>)>(new ConcurrentQueue<(Action, TaskCompletionSource<object>)>());
|
||||
|
||||
public CustomThreadPool(int threadCount, string threadName)
|
||||
{
|
||||
if (threadCount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(threadCount));
|
||||
_Exited = new TaskCompletionSource<bool>();
|
||||
_Threads = Enumerable.Range(0, threadCount).Select(_ => new Thread(RunLoop) { Name = threadName }).ToArray();
|
||||
foreach (var t in _Threads)
|
||||
t.Start();
|
||||
}
|
||||
|
||||
public void Do(Action act)
|
||||
{
|
||||
DoAsync(act).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T Do<T>(Func<T> act)
|
||||
{
|
||||
return DoAsync(act).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<T> DoAsync<T>(Func<T> act)
|
||||
{
|
||||
TaskCompletionSource<object> done = new TaskCompletionSource<object>();
|
||||
_Actions.Add((() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
done.TrySetResult(act());
|
||||
}
|
||||
catch (Exception ex) { done.TrySetException(ex); }
|
||||
}
|
||||
, done));
|
||||
return (T)(await done.Task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
public Task DoAsync(Action act)
|
||||
{
|
||||
return DoAsync<object>(() =>
|
||||
{
|
||||
act();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void RunLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var act in _Actions.GetConsumingEnumerable(_Cancel.Token))
|
||||
{
|
||||
act.Item1();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (_Cancel.IsCancellationRequested) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Cancel.Cancel();
|
||||
_UnhandledException = ex;
|
||||
}
|
||||
if (Interlocked.Increment(ref _ExitedCount) == _Threads.Length)
|
||||
{
|
||||
foreach (var action in _Actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
action.Item2.TrySetCanceled();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
_Exited.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Cancel.Cancel();
|
||||
_Exited.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -4,7 +4,6 @@ using System.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Bson;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
@ -61,6 +63,7 @@ namespace BTCPayServer.Data
|
||||
public DbSet<LightningAddressData> LightningAddresses { get; set; }
|
||||
public DbSet<PayoutProcessorData> PayoutProcessors { get; set; }
|
||||
public DbSet<FormData> Forms { get; set; }
|
||||
public DbSet<PendingTransaction> PendingTransactions { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
@ -106,7 +109,7 @@ namespace BTCPayServer.Data
|
||||
WebhookData.OnModelCreating(builder, Database);
|
||||
FormData.OnModelCreating(builder, Database);
|
||||
StoreRole.OnModelCreating(builder, Database);
|
||||
PendingTransaction.OnModelCreating(builder, Database);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,12 +3,12 @@
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.24" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.31" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
|
@ -126,6 +126,7 @@ namespace BTCPayServer.Data
|
||||
{ Type: "LN" } or { Type: "LNURL" } => 11,
|
||||
{ Type: "CHAIN", CryptoCode: var code } when code == "XMR" => 12,
|
||||
{ Type: "CHAIN" } => 8,
|
||||
{ Type: "LEGACY" } => 18,
|
||||
_ => 8
|
||||
};
|
||||
}
|
||||
@ -137,9 +138,10 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
return paymentType switch
|
||||
{
|
||||
"BTCLike" => $"{cryptoCode}-CHAIN",
|
||||
"BTCLike" or "MoneroLike" or "ZcashLike" => $"{cryptoCode}-CHAIN",
|
||||
"LightningLike" or "LightningNetwork" => $"{cryptoCode}-LN",
|
||||
"LNURLPAY" => $"{cryptoCode}-LNURL",
|
||||
"EthereumLike" => $"{cryptoCode}-LEGACY",
|
||||
_ => throw new NotSupportedException("Unknown payment type " + paymentType)
|
||||
};
|
||||
}
|
||||
@ -147,6 +149,23 @@ namespace BTCPayServer.Data
|
||||
return $"{splitted[0]}-CHAIN";
|
||||
throw new NotSupportedException("Unknown payment id " + paymentMethodId);
|
||||
}
|
||||
public static string TryMigratePaymentMethodId(string paymentMethodId)
|
||||
{
|
||||
var splitted = paymentMethodId.Split(new[] { '_', '-' });
|
||||
if (splitted is [var cryptoCode, var paymentType])
|
||||
{
|
||||
return paymentType switch
|
||||
{
|
||||
"BTCLike" or "MoneroLike" or "ZcashLike" => $"{cryptoCode}-CHAIN",
|
||||
"LightningLike" or "LightningNetwork" => $"{cryptoCode}-LN",
|
||||
"LNURLPAY" => $"{cryptoCode}-LNURL",
|
||||
_ => paymentMethodId
|
||||
};
|
||||
}
|
||||
if (splitted.Length == 1)
|
||||
return $"{splitted[0]}-CHAIN";
|
||||
return paymentMethodId;
|
||||
}
|
||||
|
||||
// Make postgres happy
|
||||
public static string SanitizeJSON(string json) => json.Replace("\\u0000", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
|
@ -92,7 +92,7 @@ namespace BTCPayServer.Data
|
||||
blob.Remove("output");
|
||||
blob.Remove("outpoint");
|
||||
// Convert from sats to btc
|
||||
if (cryptoData["value"] is not (null or { Type: JTokenType.Null }))
|
||||
if (cryptoData["value"] is not (null or { Type: JTokenType.Null } or { Type: JTokenType.Object }))
|
||||
{
|
||||
var v = cryptoData["value"].Value<long>();
|
||||
Amount = (decimal)v / (decimal)Money.COIN;
|
||||
@ -103,7 +103,22 @@ namespace BTCPayServer.Data
|
||||
blob.ConvertNumberToString("paymentMethodFee");
|
||||
blob.Remove("networkFee");
|
||||
blob.RemoveIfNull("paymentMethodFee");
|
||||
}
|
||||
}
|
||||
// Liquid
|
||||
else if (cryptoData["value"] is { Type: JTokenType.Object })
|
||||
{
|
||||
var v = cryptoData["value"]["value"].Value<long>();
|
||||
var assetId = cryptoData["value"]["assetId"].Value<string>();
|
||||
divisibility = GetDivisibility(assetId) ?? 8;
|
||||
Amount = (decimal)v / (decimal)Math.Pow(10.0, divisibility);
|
||||
cryptoData.Remove("value");
|
||||
cryptoData["assetId"] = assetId;
|
||||
blob["paymentMethodFee"] = blob["networkFee"];
|
||||
blob.RemoveIfValue<decimal>("paymentMethodFee", 0.0m);
|
||||
blob.ConvertNumberToString("paymentMethodFee");
|
||||
blob.Remove("networkFee");
|
||||
blob.RemoveIfNull("paymentMethodFee");
|
||||
}
|
||||
// Convert from millisats to btc
|
||||
else if (cryptoData["amount"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
@ -164,6 +179,17 @@ namespace BTCPayServer.Data
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return true;
|
||||
}
|
||||
|
||||
private int? GetDivisibility(string assetId) =>
|
||||
assetId switch
|
||||
{
|
||||
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2" => 8,
|
||||
"aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf" => 2,
|
||||
"0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a" => 8,
|
||||
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" => 8,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
[NotMapped]
|
||||
public bool Migrated { get; set; }
|
||||
[NotMapped]
|
||||
|
@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@ -73,7 +71,7 @@ namespace BTCPayServer.Data
|
||||
|
||||
builder.Entity<PayoutData>()
|
||||
.Property(o => o.Blob)
|
||||
.HasColumnType("JSONB");
|
||||
.HasColumnType("jsonb");
|
||||
builder.Entity<PayoutData>()
|
||||
.Property(o => o.Proof)
|
||||
.HasColumnType("JSONB");
|
||||
|
63
BTCPayServer.Data/Data/PendingTransaction.cs
Normal file
63
BTCPayServer.Data/Data/PendingTransaction.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data;
|
||||
|
||||
public class PendingTransaction: IHasBlob<PendingTransactionBlob>
|
||||
{
|
||||
public string TransactionId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public DateTimeOffset? Expiry { get; set; }
|
||||
public PendingTransactionState State { get; set; }
|
||||
public string[] OutpointsUsed { get; set; }
|
||||
|
||||
[NotMapped][Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
public string Blob2 { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<PendingTransaction>()
|
||||
.HasOne(o => o.Store)
|
||||
.WithMany(i => i.PendingTransactions)
|
||||
.HasForeignKey(i => i.StoreId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<PendingTransaction>().HasKey(transaction => new {transaction.CryptoCode, transaction.TransactionId});
|
||||
|
||||
builder.Entity<PendingTransaction>()
|
||||
.Property(o => o.Blob2)
|
||||
.HasColumnType("JSONB");
|
||||
builder.Entity<PendingTransaction>()
|
||||
.Property(o => o.OutpointsUsed)
|
||||
.HasColumnType("text[]");
|
||||
}
|
||||
}
|
||||
public enum PendingTransactionState
|
||||
{
|
||||
Pending,
|
||||
Cancelled,
|
||||
Expired,
|
||||
Invalidated,
|
||||
Signed,
|
||||
Broadcast
|
||||
}
|
||||
|
||||
public class PendingTransactionBlob
|
||||
{
|
||||
public string PSBT { get; set; }
|
||||
public List<CollectedSignature> CollectedSignatures { get; set; } = new();
|
||||
}
|
||||
|
||||
public class CollectedSignature
|
||||
{
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string ReceivedPSBT { get; set; }
|
||||
}
|
@ -49,6 +49,7 @@ namespace BTCPayServer.Data
|
||||
public IEnumerable<FormData> Forms { get; set; }
|
||||
public IEnumerable<StoreRole> StoreRoles { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public IEnumerable<PendingTransaction> PendingTransactions { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20241029163147_AddingPendingTransactionsTable")]
|
||||
public partial class AddingPendingTransactionsTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PendingTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
TransactionId = table.Column<string>(type: "text", nullable: false),
|
||||
CryptoCode = table.Column<string>(type: "text", nullable: false),
|
||||
StoreId = table.Column<string>(type: "text", nullable: true),
|
||||
Expiry = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
State = table.Column<int>(type: "integer", nullable: false),
|
||||
OutpointsUsed = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
Blob2 = table.Column<string>(type: "JSONB", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PendingTransactions", x => new { x.CryptoCode, x.TransactionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_PendingTransactions_Stores_StoreId",
|
||||
column: x => x.StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PendingTransactions_StoreId",
|
||||
table: "PendingTransactions",
|
||||
column: "StoreId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PendingTransactions");
|
||||
}
|
||||
}
|
||||
}
|
@ -560,7 +560,7 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Blob")
|
||||
.HasColumnType("JSONB");
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.HasColumnType("text");
|
||||
@ -637,6 +637,36 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("PayoutProcessors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingTransaction", b =>
|
||||
{
|
||||
b.Property<string>("CryptoCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TransactionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Blob2")
|
||||
.HasColumnType("JSONB");
|
||||
|
||||
b.Property<DateTimeOffset?>("Expiry")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string[]>("OutpointsUsed")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("CryptoCode", "TransactionId");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("PendingTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -1324,6 +1354,16 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingTransaction", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithMany("PendingTransactions")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1582,6 +1622,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Navigation("Payouts");
|
||||
|
||||
b.Navigation("PendingTransactions");
|
||||
|
||||
b.Navigation("PullPayments");
|
||||
|
||||
b.Navigation("Settings");
|
||||
|
@ -6,9 +6,10 @@
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.37" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.48" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1175,13 +1175,6 @@
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"USDt",
|
||||
"code":"USDT",
|
||||
"divisibility":8,
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"LCAD",
|
||||
"code":"LCAD",
|
||||
@ -1315,13 +1308,6 @@
|
||||
"symbol": null,
|
||||
"crypto": true
|
||||
},
|
||||
{
|
||||
"name":"USDt",
|
||||
"code":"USDT20",
|
||||
"divisibility":6,
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"FaucetToken",
|
||||
"code":"FAU",
|
||||
|
@ -5,6 +5,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -18,14 +21,74 @@ namespace BTCPayServer.Services.Rates
|
||||
public string Symbol { get; set; }
|
||||
public bool Crypto { get; set; }
|
||||
}
|
||||
public class CurrencyNameTable
|
||||
public interface CurrencyDataProvider
|
||||
{
|
||||
public static CurrencyNameTable Instance = new();
|
||||
public CurrencyNameTable()
|
||||
Task<CurrencyData[]> LoadCurrencyData(CancellationToken cancellationToken);
|
||||
}
|
||||
public class InMemoryCurrencyDataProvider : CurrencyDataProvider
|
||||
{
|
||||
private readonly CurrencyData[] _currencyData;
|
||||
|
||||
public InMemoryCurrencyDataProvider(CurrencyData[] currencyData)
|
||||
{
|
||||
_Currencies = LoadCurrency().ToDictionary(k => k.Code, StringComparer.InvariantCultureIgnoreCase);
|
||||
_currencyData = currencyData;
|
||||
}
|
||||
|
||||
public Task<CurrencyData[]> LoadCurrencyData(CancellationToken cancellationToken) => Task.FromResult(_currencyData);
|
||||
}
|
||||
public class AssemblyCurrencyDataProvider : CurrencyDataProvider
|
||||
{
|
||||
private readonly Assembly _assembly;
|
||||
private readonly string _manifestResourceStream;
|
||||
|
||||
public AssemblyCurrencyDataProvider(Assembly assembly, string manifestResourceStream)
|
||||
{
|
||||
_assembly = assembly;
|
||||
_manifestResourceStream = manifestResourceStream;
|
||||
}
|
||||
public Task<CurrencyData[]> LoadCurrencyData(CancellationToken cancellationToken)
|
||||
{
|
||||
var stream = _assembly.GetManifestResourceStream(_manifestResourceStream);
|
||||
if (stream is null)
|
||||
throw new InvalidOperationException("Unknown manifestResourceStream");
|
||||
string content = null;
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
var currencies = JsonConvert.DeserializeObject<CurrencyData[]>(content);
|
||||
return Task.FromResult(currencies.ToArray());
|
||||
}
|
||||
}
|
||||
public class CurrencyNameTable
|
||||
{
|
||||
public CurrencyNameTable(IEnumerable<CurrencyDataProvider> currencyDataProviders, ILogger<CurrencyNameTable> logger)
|
||||
{
|
||||
_currencyDataProviders = currencyDataProviders;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ReloadCurrencyData(CancellationToken cancellationToken)
|
||||
{
|
||||
var currencies = new Dictionary<string, CurrencyData>(StringComparer.InvariantCultureIgnoreCase);
|
||||
var loadings = _currencyDataProviders.Select(c => (Task: c.LoadCurrencyData(cancellationToken), Prov: c)).ToList();
|
||||
foreach (var loading in loadings)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var curr in await loading.Task)
|
||||
{
|
||||
currencies.TryAdd(curr.Code, curr);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error loading currency data for " + loading.Prov.GetType().FullName);
|
||||
}
|
||||
}
|
||||
_Currencies = currencies;
|
||||
}
|
||||
static readonly Dictionary<string, IFormatProvider> _CurrencyProviders = new();
|
||||
|
||||
public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback)
|
||||
@ -123,20 +186,9 @@ namespace BTCPayServer.Services.Rates
|
||||
currencyProviders.TryAdd(code, number);
|
||||
}
|
||||
|
||||
readonly Dictionary<string, CurrencyData> _Currencies;
|
||||
|
||||
static CurrencyData[] LoadCurrency()
|
||||
{
|
||||
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Rating.Currencies.json");
|
||||
string content = null;
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
var currencies = JsonConvert.DeserializeObject<CurrencyData[]>(content);
|
||||
return currencies;
|
||||
}
|
||||
Dictionary<string, CurrencyData> _Currencies = new();
|
||||
private readonly IEnumerable<CurrencyDataProvider> _currencyDataProviders;
|
||||
private readonly ILogger<CurrencyNameTable> _logger;
|
||||
|
||||
public IEnumerable<CurrencyData> Currencies => _Currencies.Values;
|
||||
|
||||
|
@ -1,11 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Rating
|
||||
{
|
||||
public class CurrencyPair
|
||||
{
|
||||
private static readonly HashSet<string> _knownCurrencies;
|
||||
|
||||
static CurrencyPair()
|
||||
{
|
||||
var prov = new AssemblyCurrencyDataProvider(typeof(BTCPayServer.Rating.BidAsk).Assembly, "BTCPayServer.Rating.Currencies.json");
|
||||
// It's OK this is sync function
|
||||
_knownCurrencies = prov.LoadCurrencyData(default).GetAwaiter().GetResult()
|
||||
.Select(c => c.Code).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
public CurrencyPair(string left, string right)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(right);
|
||||
@ -49,10 +60,9 @@ namespace BTCPayServer.Rating
|
||||
for (int i = 3; i < 5; i++)
|
||||
{
|
||||
var potentialCryptoName = currencyPair.Substring(0, i);
|
||||
var currency = CurrencyNameTable.Instance.GetCurrencyData(potentialCryptoName, false);
|
||||
if (currency != null)
|
||||
if (_knownCurrencies.Contains(potentialCryptoName))
|
||||
{
|
||||
value = new CurrencyPair(currency.Code, currencyPair.Substring(i));
|
||||
value = new CurrencyPair(potentialCryptoName, currencyPair.Substring(i));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
38
BTCPayServer.Rating/Providers/BareBitcoinRateProvider.cs
Normal file
38
BTCPayServer.Rating/Providers/BareBitcoinRateProvider.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class BareBitcoinRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public RateSourceInfo RateSourceInfo => new("barebitcoin", "Bare Bitcoin", "https://api.bb.no/v1/price/nok");
|
||||
|
||||
public BareBitcoinRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
|
||||
// Extract bid/ask prices from JSON response
|
||||
var bid = (decimal)jobj["bid"];
|
||||
var ask = (decimal)jobj["ask"];
|
||||
|
||||
// Create currency pair for BTC/NOK
|
||||
var pair = new CurrencyPair("BTC", "NOK");
|
||||
|
||||
// Return single pair rate with bid/ask
|
||||
return new[] { new PairRate(pair, new BidAsk(bid, ask)) };
|
||||
}
|
||||
}
|
||||
}
|
39
BTCPayServer.Rating/Providers/BitmyntRateProvider.cs
Normal file
39
BTCPayServer.Rating/Providers/BitmyntRateProvider.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class BitmyntRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public RateSourceInfo RateSourceInfo => new("bitmynt", "Bitmynt", "https://ny.bitmynt.no/data/rates.json");
|
||||
|
||||
public BitmyntRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
|
||||
// Extract bid and ask prices from current_rate object
|
||||
var currentRate = jobj["current_rate"];
|
||||
var bid = currentRate["bid"].Value<decimal>();
|
||||
var ask = currentRate["ask"].Value<decimal>();
|
||||
|
||||
// Create currency pair for BTC/NOK
|
||||
var pair = new CurrencyPair("BTC", "NOK");
|
||||
|
||||
// Return single pair rate with bid/ask
|
||||
return new[] { new PairRate(pair, new BidAsk(bid, ask)) };
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
@ -25,7 +25,7 @@ using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
using WalletSettingsViewModel = BTCPayServer.Models.StoreViewModels.WalletSettingsViewModel;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
@ -94,16 +94,16 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get enabled state from settings
|
||||
response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = await controller.WalletSettings(user.StoreId, cryptoCode);
|
||||
var onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.NotNull(onchainSettingsModel?.DerivationScheme);
|
||||
Assert.True(onchainSettingsModel.Enabled);
|
||||
|
||||
// Disable wallet
|
||||
onchainSettingsModel.Enabled = false;
|
||||
response = controller.UpdateWalletSettings(onchainSettingsModel).GetAwaiter().GetResult();
|
||||
response = await controller.UpdateWalletSettings(onchainSettingsModel);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = await controller.WalletSettings(user.StoreId, cryptoCode);
|
||||
onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.NotNull(onchainSettingsModel?.DerivationScheme);
|
||||
Assert.False(onchainSettingsModel.Enabled);
|
||||
@ -125,7 +125,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
|
||||
// Removing the derivation scheme, should redirect to store page
|
||||
response = controller.ConfirmDeleteWallet(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = await controller.ConfirmDeleteWallet(user.StoreId, cryptoCode);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Setting it again should show the confirmation page
|
||||
@ -175,7 +175,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("ElectrumFile", settingsVm.Source);
|
||||
|
||||
// Now let's check that no data has been lost in the process
|
||||
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(storeId);
|
||||
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var onchainBTC = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
@ -207,7 +207,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
});
|
||||
var wallet = tester.PayTester.GetController<UIWalletsController>();
|
||||
var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC,
|
||||
var psbt = await wallet.CreatePSBT(btcNetwork, onchainBTC,
|
||||
new WalletSendModel()
|
||||
{
|
||||
Outputs = new List<WalletSendModel.TransactionOutput>
|
||||
@ -220,7 +220,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
},
|
||||
FeeSatoshiPerByte = 1
|
||||
}, default).GetAwaiter().GetResult();
|
||||
}, default);
|
||||
|
||||
Assert.NotNull(psbt);
|
||||
|
||||
@ -320,10 +320,10 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
var checkout =
|
||||
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
|
||||
(Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Single(checkout.AvailableCryptos);
|
||||
Assert.Equal("LTC", checkout.CryptoCode);
|
||||
Assert.Single(checkout.AvailablePaymentMethods);
|
||||
Assert.Equal("LTC", checkout.PaymentMethodCurrency);
|
||||
|
||||
//////////////////////
|
||||
|
||||
@ -337,7 +337,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
|
||||
checkout = (Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal("Processing", checkout.Status);
|
||||
});
|
||||
@ -441,136 +441,135 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
public async Task CanPayWithTwoCurrencies()
|
||||
{
|
||||
using (var tester = CreateServerTester())
|
||||
using var tester = CreateServerTester();
|
||||
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
await cashCow.GenerateAsync(2); // get some money in case
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
await cashCow.GenerateAsync(2); // get some money in case
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
|
||||
|
||||
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
|
||||
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
var checkout =
|
||||
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Single(checkout.AvailableCryptos);
|
||||
Assert.Equal("BTC", checkout.CryptoCode);
|
||||
|
||||
Assert.Single(invoice.PaymentCodes);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.PaymentSubtotals);
|
||||
Assert.Single(invoice.PaymentTotals);
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
|
||||
//////////////////////
|
||||
|
||||
// Retry now with LTC enabled
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
cashCow = tester.ExplorerNode;
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
|
||||
cashCow = tester.LTCExplorerNode;
|
||||
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
|
||||
Assert.NotNull(ltcCryptoInfo);
|
||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
|
||||
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(Money.Zero, invoice.BtcDue);
|
||||
var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC");
|
||||
Assert.Equal(Money.Zero, ltcPaid.Due);
|
||||
Assert.Equal(secondPayment, ltcPaid.CryptoPaid);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC")
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
var checkout =
|
||||
(Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, null)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal(2, checkout.AvailableCryptos.Count);
|
||||
Assert.Equal("LTC", checkout.CryptoCode);
|
||||
Assert.Single(checkout.AvailablePaymentMethods);
|
||||
Assert.Equal("BTC", checkout.PaymentMethodCurrency);
|
||||
|
||||
Assert.Equal(2, invoice.PaymentCodes.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.PaymentSubtotals.Count());
|
||||
Assert.Equal(2, invoice.PaymentTotals.Count());
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
Assert.Single(invoice.PaymentCodes);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.PaymentSubtotals);
|
||||
Assert.Single(invoice.PaymentTotals);
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
|
||||
//////////////////////
|
||||
|
||||
// Check if we can disable LTC
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
// Retry now with LTC enabled
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
cashCow = tester.ExplorerNode;
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
|
||||
cashCow = tester.LTCExplorerNode;
|
||||
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
|
||||
Assert.NotNull(ltcCryptoInfo);
|
||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
|
||||
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(Money.Zero, invoice.BtcDue);
|
||||
var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC");
|
||||
Assert.Equal(Money.Zero, ltcPaid.Due);
|
||||
Assert.Equal(secondPayment, ltcPaid.CryptoPaid);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
checkout = (Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC")
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal(2, checkout.AvailablePaymentMethods.Count);
|
||||
Assert.Equal("LTC", checkout.PaymentMethodCurrency);
|
||||
|
||||
Assert.Equal(2, invoice.PaymentCodes.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.PaymentSubtotals.Count());
|
||||
Assert.Equal(2, invoice.PaymentTotals.Count());
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
|
||||
// Check if we can disable LTC
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true,
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true,
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
|
||||
{
|
||||
{"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}}
|
||||
}
|
||||
}, Facade.Merchant);
|
||||
{"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}}
|
||||
}
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC"));
|
||||
Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC"));
|
||||
}
|
||||
Assert.Single(invoice.CryptoInfo, c => c.CryptoCode == "BTC");
|
||||
Assert.DoesNotContain(invoice.CryptoInfo, c => c.CryptoCode == "LTC");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -745,7 +744,7 @@ noninventoryitem:
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem")));
|
||||
var inventoryItemInvoice =
|
||||
Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem")));
|
||||
Assert.Single(invoices, invoice => invoice.ItemCode.Equals("inventoryitem"));
|
||||
Assert.NotNull(inventoryItemInvoice);
|
||||
|
||||
//let's mark the inventoryitem invoice as invalid, this should return the item to back in stock
|
||||
@ -759,39 +758,6 @@ noninventoryitem:
|
||||
AppService.Parse(vmpos.Template).Single(item => item.Id == "inventoryitem").Inventory);
|
||||
}, 10000);
|
||||
|
||||
//test payment methods option
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "BTC";
|
||||
vmpos.Template = @"
|
||||
btconly:
|
||||
price: 1.0
|
||||
title: good apple
|
||||
payment_methods:
|
||||
- BTC
|
||||
normal:
|
||||
price: 1.0";
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "btconly").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "normal").Result);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal");
|
||||
var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly");
|
||||
Assert.Single(btcOnlyInvoice.CryptoInfo);
|
||||
Assert.Equal("BTC",
|
||||
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
|
||||
Assert.Equal("BTC-CHAIN",
|
||||
btcOnlyInvoice.CryptoInfo.First().PaymentType);
|
||||
|
||||
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
|
||||
Assert.Contains(
|
||||
normalInvoice.CryptoInfo,
|
||||
s => "BTC-CHAIN" == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
||||
s.CryptoCode));
|
||||
|
||||
//test topup option
|
||||
vmpos.Template = @"
|
||||
a:
|
||||
@ -821,13 +787,13 @@ g:
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.DoesNotContain("custom", vmpos.Template);
|
||||
var items = AppService.Parse(vmpos.Template);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.PriceType == AppItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.PriceType == AppItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.PriceType == AppItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.PriceType == AppItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.PriceType == AppItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.PriceType == AppItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.PriceType == AppItemPriceType.Topup);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Static, choiceKey: "g").Result);
|
||||
@ -837,5 +803,65 @@ g:
|
||||
Assert.Equal("new", topupInvoice.Status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUsePoSAppJsonEndpoint()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var pos = user.GetController<UIPointOfSaleController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = PointOfSaleAppType.AppType;
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = appType;
|
||||
var redirect = Assert.IsType<RedirectResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
Assert.EndsWith("/settings/pos", redirect.Url);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
pos.HttpContext.SetAppData(appData);
|
||||
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "App POS";
|
||||
vmpos.Currency = "EUR";
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
|
||||
// Failing requests
|
||||
var (invoiceId1, error1) = await PosJsonRequest(tester, app.Id, "amount=-21&discount=10&tip=2");
|
||||
Assert.Null(invoiceId1);
|
||||
Assert.Equal("Negative amount is not allowed", error1);
|
||||
var (invoiceId2, error2) = await PosJsonRequest(tester, app.Id, "amount=21&discount=-10&tip=-2");
|
||||
Assert.Null(invoiceId2);
|
||||
Assert.Equal("Negative tip or discount is not allowed", error2);
|
||||
|
||||
// Successful request
|
||||
var (invoiceId3, error3) = await PosJsonRequest(tester, app.Id, "amount=21");
|
||||
Assert.NotNull(invoiceId3);
|
||||
Assert.Null(error3);
|
||||
|
||||
// Check generated invoice
|
||||
var invoices = await user.BitPay.GetInvoicesAsync();
|
||||
var invoice = invoices.First();
|
||||
Assert.Equal(invoiceId3, invoice.Id);
|
||||
Assert.Equal(21.00m, invoice.Price);
|
||||
Assert.Equal("EUR", invoice.Currency);
|
||||
}
|
||||
|
||||
private async Task<(string invoiceId, string error)> PosJsonRequest(ServerTester tester, string appId, string query)
|
||||
{
|
||||
var uriBuilder = new UriBuilder(tester.PayTester.ServerUri) { Path = $"/apps/{appId}/pos/light", Query = query };
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri);
|
||||
request.Headers.Add("Accept", "application/json");
|
||||
var response = await tester.PayTester.HttpClient.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var json = JObject.Parse(content);
|
||||
return (json["invoiceId"]?.Value<string>(), json["error"]?.Value<string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ namespace BTCPayServer.Tests
|
||||
//no tether on our regtest, lets create it and set it
|
||||
var tether = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT");
|
||||
var lbtc = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("LBTC");
|
||||
var etb = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("ETB");
|
||||
var issueAssetResult = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
|
||||
tether.AssetId = uint256.Parse(issueAssetResult.Result["asset"].ToString());
|
||||
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network)
|
||||
@ -71,15 +70,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(tether.AssetId, tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT").AssetId);
|
||||
Assert.Equal(tether.AssetId, ((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network).AssetId);
|
||||
|
||||
var issueAssetResult2 = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
|
||||
etb.AssetId = uint256.Parse(issueAssetResult2.Result["asset"].ToString());
|
||||
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("ETB").Network)
|
||||
.AssetId = etb.AssetId;
|
||||
|
||||
|
||||
user.RegisterDerivationScheme("LBTC");
|
||||
user.RegisterDerivationScheme("USDT");
|
||||
user.RegisterDerivationScheme("ETB");
|
||||
|
||||
//test: register 2 assets on the same elements network and make sure paying an invoice on one does not affect the other in any way
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
|
||||
@ -109,11 +103,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)).Payments);
|
||||
});
|
||||
|
||||
//test precision based on https://github.com/ElementsProject/elements/issues/805#issuecomment-601277606
|
||||
var etbBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "ETB").PaymentUrls.BIP21, etb.NBitcoinNetwork);
|
||||
//precision = 2, 1ETB = 0.00000100
|
||||
Assert.Equal(100, etbBip21.Amount.Satoshi);
|
||||
|
||||
|
||||
var lbtcBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "LBTC").PaymentUrls.BIP21, lbtc.NBitcoinNetwork);
|
||||
//precision = 8, 0.1 = 0.1
|
||||
|
@ -7,6 +7,15 @@
|
||||
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="BTCPayServer.Tests.OutputPathAttribute">
|
||||
<!-- _Parameter1, _Parameter2, etc. correspond to the
|
||||
matching parameter of a constructor of that .NET attribute type -->
|
||||
<_Parameter1>$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(OutputPath)'))</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
|
||||
<Target Name="CopyAditionalFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
|
||||
<ItemGroup>
|
||||
@ -30,13 +39,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.16" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.22.0" />
|
||||
<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">
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
@ -61,4 +71,7 @@
|
||||
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="obj\Debug\net8.0\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -7,12 +7,14 @@ using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
@ -27,7 +29,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -162,6 +163,8 @@ namespace BTCPayServer.Tests
|
||||
HttpClient.BaseAddress = ServerUri;
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
|
||||
var confBuilder = new DefaultConfiguration() { Logger = LoggerProvider.CreateLogger("Console") }.CreateConfigurationBuilder(new[] { "--datadir", _Directory, "--conf", confPath, "--disable-registration", DisableRegistration ? "true" : "false" });
|
||||
// This make sure that tests work outside of this assembly (ie, test project it a plugin)
|
||||
confBuilder.SetBasePath(TestUtils.TestDirectory);
|
||||
#if DEBUG
|
||||
confBuilder.AddJsonFile("appsettings.dev.json", true, false);
|
||||
#endif
|
||||
@ -265,7 +268,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
private string FindBTCPayServerDirectory()
|
||||
{
|
||||
var solutionDirectory = TestUtils.TryGetSolutionDirectoryInfo(Directory.GetCurrentDirectory());
|
||||
var solutionDirectory = TestUtils.TryGetSolutionDirectoryInfo();
|
||||
return Path.Combine(solutionDirectory.FullName, "BTCPayServer");
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal($"bitcoin:{address}", clipboard);
|
||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC-CHAIN"));
|
||||
|
||||
// Contact option
|
||||
var contactLink = s.Driver.FindElement(By.Id("ContactLink"));
|
||||
Assert.Equal("Contact us", contactLink.Text);
|
||||
Assert.Matches(supportUrl.Replace("{InvoiceId}", invoiceId), contactLink.GetAttribute("href"));
|
||||
|
||||
// Details should show exchange rate
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
@ -138,7 +143,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("resubmit a payment", expiredSection.Text);
|
||||
Assert.DoesNotContain("This invoice expired with partial payment", expiredSection.Text);
|
||||
});
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
@ -172,9 +176,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("This invoice expired with partial payment", expiredSection.Text);
|
||||
Assert.DoesNotContain("resubmit a payment", expiredSection.Text);
|
||||
});
|
||||
var contactLink = s.Driver.FindElement(By.Id("ContactLink"));
|
||||
Assert.Equal("Contact us", contactLink.Text);
|
||||
Assert.Matches(supportUrl.Replace("{InvoiceId}", invoiceId), contactLink.GetAttribute("href"));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
@ -243,7 +244,6 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
s.Driver.FindElement(By.Id("confetti"));
|
||||
s.Driver.FindElement(By.Id("ReceiptLink"));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// BIP21
|
||||
|
@ -81,7 +81,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(app.Archived);
|
||||
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
crowdfundViewModel.Enabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
Assert.IsType<ViewResult>(await crowdfund.ViewCrowdfund(app.Id));
|
||||
// Delete
|
||||
Assert.IsType<NotFoundResult>(apps2.DeleteApp(app.Id));
|
||||
@ -121,7 +121,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.Enabled = false;
|
||||
crowdfundViewModel.EndDate = null;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
|
||||
var anonAppPubsController = tester.PayTester.GetController<UICrowdfundController>();
|
||||
var crowdfundController = user.GetController<UICrowdfundController>();
|
||||
@ -146,7 +146,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.StartDate = DateTime.Today.AddDays(2);
|
||||
crowdfundViewModel.Enabled = true;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(0.01)
|
||||
@ -157,7 +157,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
|
||||
crowdfundViewModel.Enabled = true;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(0.01)
|
||||
@ -170,7 +170,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.TargetAmount = 1;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.EnforceTargetAmount = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(1.01)
|
||||
@ -214,7 +214,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.UseAllStoreInvoices = true;
|
||||
crowdfundViewModel.EnforceTargetAmount = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
|
||||
var publicApps = user.GetController<UICrowdfundController>();
|
||||
|
||||
@ -263,12 +263,12 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
|
||||
TestLogs.LogInformation("Because UseAllStoreInvoices is true, let's make sure the invoice is tagged");
|
||||
var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
||||
var invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
||||
Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
|
||||
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
|
||||
|
||||
crowdfundViewModel.UseAllStoreInvoices = false;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
|
||||
TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
|
||||
@ -281,13 +281,13 @@ namespace BTCPayServer.Tests
|
||||
TransactionSpeed = "high",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
||||
invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
||||
Assert.DoesNotContain(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
|
||||
|
||||
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
|
||||
crowdfundViewModel.EnforceTargetAmount = false;
|
||||
crowdfundViewModel.UseAllStoreInvoices = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
|
||||
{
|
||||
Buyer = new Buyer { email = "test@fwf.com" },
|
||||
@ -356,7 +356,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.FormId = lstForms[0].Id;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.Enabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
|
||||
var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01).AssertViewModelAsync<FormViewModel>();
|
||||
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "", vm2);
|
||||
@ -411,7 +411,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.Enabled = true;
|
||||
crowdfundViewModel.PerksTemplate = "[{\"id\": \"xxx\",\"title\": \"Perk 1\",\"priceType\": \"Fixed\",\"price\": \"0.001\",\"image\": \"\",\"description\": \"\",\"categories\": [],\"disabled\": false}]";
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
|
||||
|
||||
var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01, "xxx").AssertViewModelAsync<FormViewModel>();
|
||||
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "xxx", vm2);
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0.101-bookworm-slim AS builder
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0.404-bookworm-slim AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
@ -3,7 +3,6 @@ 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;
|
||||
@ -11,7 +10,6 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -20,39 +18,28 @@ 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.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Scripting.Parser;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -116,11 +103,13 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var compose1 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.yml"));
|
||||
var compose2 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.altcoins.yml"));
|
||||
var compose3 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.mutinynet.yml"));
|
||||
var compose4 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.testnet.yml"));
|
||||
|
||||
List<DockerImage> GetImages(string content)
|
||||
{
|
||||
List<DockerImage> images = new List<DockerImage>();
|
||||
foreach (var line in content.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
var images = new List<DockerImage>();
|
||||
foreach (var line in content.Split(["\n", "\r\n"], StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var l = line.Trim();
|
||||
if (l.StartsWith("image:", StringComparison.OrdinalIgnoreCase))
|
||||
@ -133,13 +122,15 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var img1 = GetImages(compose1);
|
||||
var img2 = GetImages(compose2);
|
||||
var groups = img1.Concat(img2).GroupBy(g => g.Name);
|
||||
var img3 = GetImages(compose3);
|
||||
var img4 = GetImages(compose4);
|
||||
var groups = img1.Concat(img2).Concat(img3).Concat(img4).GroupBy(g => g.Name);
|
||||
foreach (var g in groups)
|
||||
{
|
||||
var tags = new HashSet<String>(g.Select(o => o.Tag));
|
||||
var tags = new HashSet<string>(g.Select(o => o.Tag));
|
||||
if (tags.Count != 1)
|
||||
{
|
||||
Assert.Fail($"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})");
|
||||
Assert.Fail($"All docker images '{g.Key}' across the docker-compose.yml files should have the same tags. (Found {string.Join(',', tags)})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,10 +178,10 @@ namespace BTCPayServer.Tests
|
||||
public void CanRandomizeByPercentage()
|
||||
{
|
||||
var generated = Enumerable.Range(0, 1000).Select(_ => MempoolSpaceFeeProvider.RandomizeByPercentage(100.0m, 10.0m)).ToArray();
|
||||
Assert.Empty(generated.Where(g => g < 90m));
|
||||
Assert.Empty(generated.Where(g => g > 110m));
|
||||
Assert.NotEmpty(generated.Where(g => g < 91m));
|
||||
Assert.NotEmpty(generated.Where(g => g > 109m));
|
||||
Assert.DoesNotContain(generated, g => g < 90m);
|
||||
Assert.DoesNotContain(generated, g => g > 110m);
|
||||
Assert.Contains(generated, g => g < 91m);
|
||||
Assert.Contains(generated, g => g > 109m);
|
||||
}
|
||||
|
||||
private void CanParseDecimalsCore(string str, decimal expected)
|
||||
@ -678,10 +669,29 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(utxo54, utxos[53]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResourceTrackerTest()
|
||||
{
|
||||
var tracker = new ResourceTracker<string>();
|
||||
var t1 = tracker.StartTracking();
|
||||
Assert.True(t1.TryTrack("1"));
|
||||
Assert.False(t1.TryTrack("1"));
|
||||
var t2 = tracker.StartTracking();
|
||||
Assert.True(t2.TryTrack("2"));
|
||||
Assert.False(t2.TryTrack("1"));
|
||||
Assert.True(t1.Contains("1"));
|
||||
Assert.True(t2.Contains("2"));
|
||||
Assert.True(tracker.Contains("1"));
|
||||
Assert.True(tracker.Contains("2"));
|
||||
t1.Dispose();
|
||||
Assert.False(tracker.Contains("1"));
|
||||
Assert.True(tracker.Contains("2"));
|
||||
Assert.True(t2.TryTrack("1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAcceptInvoiceWithTolerance()
|
||||
{
|
||||
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
@ -738,10 +748,29 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0xFF, 0xF3, 0xE4, 0x64, 0x00, 0x20, 0xAD, 0xBD, 0x04, 0x00 }, "music.mp3"));
|
||||
}
|
||||
|
||||
CurrencyNameTable GetCurrencyNameTable()
|
||||
{
|
||||
ServiceCollection services = new ServiceCollection();
|
||||
services.AddLogging(o => o.AddProvider(this.TestLogProvider));
|
||||
BTCPayServerServices.RegisterCurrencyData(services);
|
||||
// One test fail without.
|
||||
services.AddCurrencyData(new CurrencyData()
|
||||
{
|
||||
Code = "USDt",
|
||||
Name = "USDt",
|
||||
Divisibility = 8,
|
||||
Symbol = null,
|
||||
Crypto = true
|
||||
});
|
||||
var table = services.BuildServiceProvider().GetRequiredService<CurrencyNameTable>();
|
||||
table.ReloadCurrencyData(default).GetAwaiter().GetResult();
|
||||
return table;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoundupCurrenciesCorrectly()
|
||||
{
|
||||
DisplayFormatter displayFormatter = new(CurrencyNameTable.Instance);
|
||||
DisplayFormatter displayFormatter = new(GetCurrencyNameTable());
|
||||
foreach (var test in new[]
|
||||
{
|
||||
(0.0005m, "0.0005 USD", "USD"), (0.001m, "0.001 USD", "USD"), (0.01m, "0.01 USD", "USD"),
|
||||
@ -754,8 +783,8 @@ namespace BTCPayServer.Tests
|
||||
actual = actual.Replace("¥", "¥"); // Hack so JPY test pass on linux as well
|
||||
Assert.Equal(test.Item2, actual);
|
||||
}
|
||||
Assert.Equal(0, CurrencyNameTable.Instance.GetNumberFormatInfo("ARS").CurrencyDecimalDigits);
|
||||
Assert.Equal(0, CurrencyNameTable.Instance.GetNumberFormatInfo("COP").CurrencyDecimalDigits);
|
||||
Assert.Equal(0, GetCurrencyNameTable().GetNumberFormatInfo("ARS").CurrencyDecimalDigits);
|
||||
Assert.Equal(0, GetCurrencyNameTable().GetNumberFormatInfo("COP").CurrencyDecimalDigits);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -768,9 +797,9 @@ namespace BTCPayServer.Tests
|
||||
}), BTCPayLogs);
|
||||
await tor.Refresh();
|
||||
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.BTCPayServer);
|
||||
Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.P2P);
|
||||
Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.RPC);
|
||||
Assert.True(tor.Services.Count(t => t.ServiceType == TorServiceType.Other) > 1);
|
||||
|
||||
tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
|
||||
@ -781,24 +810,24 @@ namespace BTCPayServer.Tests
|
||||
}), BTCPayLogs);
|
||||
await Task.WhenAll(tor.StartAsync(CancellationToken.None));
|
||||
|
||||
var btcpayS = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
var btcpayS = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.BTCPayServer);
|
||||
Assert.Null(btcpayS.Network);
|
||||
Assert.Equal("host.onion", btcpayS.OnionHost);
|
||||
Assert.Equal(80, btcpayS.VirtualPort);
|
||||
|
||||
var p2p = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
var p2p = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.P2P);
|
||||
Assert.NotNull(p2p.Network);
|
||||
Assert.Equal("BTC", p2p.Network.CryptoCode);
|
||||
Assert.Equal("host2.onion", p2p.OnionHost);
|
||||
Assert.Equal(81, p2p.VirtualPort);
|
||||
|
||||
var rpc = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
var rpc = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.RPC);
|
||||
Assert.NotNull(p2p.Network);
|
||||
Assert.Equal("BTC", rpc.Network.CryptoCode);
|
||||
Assert.Equal("host3.onion", rpc.OnionHost);
|
||||
Assert.Equal(82, rpc.VirtualPort);
|
||||
|
||||
var unknown = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.Other));
|
||||
var unknown = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.Other);
|
||||
Assert.Null(unknown.Network);
|
||||
Assert.Equal("host4.onion", unknown.OnionHost);
|
||||
Assert.Equal(83, unknown.VirtualPort);
|
||||
@ -829,6 +858,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<MultisigDerivationStrategy>(((P2WSHDerivationStrategy)strategyBase).Inner);
|
||||
Assert.Equal(expected, strategyBase.ToString());
|
||||
|
||||
foreach (var space in new[] { "\r\n", " ", "\t" })
|
||||
{
|
||||
var expectedWithNewLines = $"2-of-tpubDDXgATYzdQkHHhZZCMcNJj8BGDENvzMVou5v9NdxiP4rxDLj33nS233dGFW4htpVZSJ6zds9eVqAV9RyRHHiKtwQKX8eR4n4KN3Dwmj7A3h-{space}tpubDC8a54NFtQtMQAZ97VhoU9V6jVTvi9w4Y5SaAXJSBYETKg3AoX5CCKndznhPWxJUBToPCpT44s86QbKdGpKAnSjcMTGW4kE6UQ8vpBjcybW-tpubDChjnP9LXNrJp43biqjY7FH93wgRRNrNxB4Q8pH7PPRy8UPcH2S6V46WGVJ47zVGF7SyBJNCpnaogsFbsybVQckGtVhCkng3EtFn8qmxptS";
|
||||
strategyBase = parser.Parse(expectedWithNewLines);
|
||||
Assert.Equal(expected, strategyBase.ToString());
|
||||
}
|
||||
|
||||
var inner = (MultisigDerivationStrategy)((P2WSHDerivationStrategy)strategyBase).Inner;
|
||||
Assert.False(inner.IsLegacy);
|
||||
Assert.Equal(3, inner.Keys.Count);
|
||||
@ -845,7 +881,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
|
||||
|
||||
// Failure cases
|
||||
Assert.Throws<FormatException>(() => { parser.Parse("xpub 661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw"); }); // invalid format because of space
|
||||
Assert.Throws<FormatException>(() => { parser.Parse("xpubZ661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw"); });
|
||||
Assert.Throws<ParsingException>(() => { parser.ParseOutputDescriptor("invalid"); }); // invalid in general
|
||||
Assert.Throws<ParsingException>(() => { parser.ParseOutputDescriptor("wpkh([8b60afd1/49h/0h/0h]xpub661MyMwAFXkMnyoBjyHndD3QwRbcGVBsTGeNZN6QGVHcfz4MPzBUxjSevweNFQx7SqmMHLdSA4FteGsRrEriu4pnVZMZWnruFFAYZATtcDw/0/*)#9x4vkw48"); }); // invalid checksum
|
||||
}
|
||||
@ -1377,7 +1413,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
var btcPayNetworkProvider = CreateNetworkProvider(ChainName.Regtest);
|
||||
foreach (var network in btcPayNetworkProvider.GetAll())
|
||||
{
|
||||
var cd = CurrencyNameTable.Instance.GetCurrencyData(network.CryptoCode, false);
|
||||
var cd = GetCurrencyNameTable().GetCurrencyData(network.CryptoCode, false);
|
||||
Assert.NotNull(cd);
|
||||
Assert.Equal(network.Divisibility, cd.Divisibility);
|
||||
Assert.True(cd.Crypto);
|
||||
@ -1445,8 +1481,8 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
Assert.True(CurrencyValue.TryParse("1usd", out result));
|
||||
Assert.Equal("1 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1.501 usd", out result));
|
||||
Assert.Equal("1.50 USD", result.ToString());
|
||||
Assert.False(CurrencyValue.TryParse("1.501 WTFF", out result));
|
||||
Assert.Equal("1.501 USD", result.ToString());
|
||||
Assert.True(CurrencyValue.TryParse("1.501 WTFF", out result));
|
||||
Assert.False(CurrencyValue.TryParse("1,501 usd", out result));
|
||||
Assert.False(CurrencyValue.TryParse("1.501", out result));
|
||||
}
|
||||
@ -1625,7 +1661,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
{
|
||||
Assert.Equal(tuple.expectedOutput, UIInvoiceController.PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||
Assert.Equal(tuple.expectedOutput, PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||
});
|
||||
}
|
||||
[Fact]
|
||||
|
@ -17,6 +17,7 @@ using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.Lightning;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services;
|
||||
@ -317,6 +318,20 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await client.GetFiles());
|
||||
storeData = await client.GetStore(store.Id);
|
||||
Assert.Null(storeData.LogoUrl);
|
||||
|
||||
// App Item Image
|
||||
var app = await client.CreatePointOfSaleApp(store.Id, new PointOfSaleAppRequest { AppName = "Test App" });
|
||||
await AssertValidationError(["file"],
|
||||
async () => await client.UploadAppItemImage(app.Id, filePath, "text/csv")
|
||||
);
|
||||
|
||||
var fileData = await client.UploadAppItemImage(app.Id, logoPath, "image/png");
|
||||
Assert.Equal("logo.png", fileData.OriginalName);
|
||||
files = await client.GetFiles();
|
||||
Assert.Single(files);
|
||||
|
||||
await client.DeleteAppItemImage(app.Id, fileData.Id);
|
||||
Assert.Empty(await client.GetFiles());
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -744,9 +759,9 @@ namespace BTCPayServer.Tests
|
||||
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 item1 = new AppItem { Id = "item1", Title = "Item 1", Price = 1, PriceType = AppItemPriceType.Fixed };
|
||||
var item2 = new AppItem { Id = "item2", Title = "Item 2", Price = 2, PriceType = AppItemPriceType.Fixed };
|
||||
var item3 = new AppItem { Id = "item3", Title = "Item 3", Price = 3, PriceType = AppItemPriceType.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" });
|
||||
@ -984,7 +999,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("ServerAdmin", admin.Roles);
|
||||
Assert.NotNull(admin.Created);
|
||||
Assert.True((DateTimeOffset.Now - admin.Created).Value.Seconds < 10);
|
||||
|
||||
|
||||
// Creating a new user without proper creds is now impossible (unauthorized)
|
||||
// Because if registration are locked and that an admin exists, we don't accept unauthenticated connection
|
||||
var ex = await AssertAPIError("unauthenticated",
|
||||
@ -1036,7 +1051,14 @@ namespace BTCPayServer.Tests
|
||||
Password = "afewfoiewiou",
|
||||
IsAdministrator = true
|
||||
});
|
||||
|
||||
// Create user without password
|
||||
await adminClient.CreateUser(new CreateApplicationUserRequest
|
||||
{
|
||||
Email = "nopassword@gmail.com"
|
||||
});
|
||||
|
||||
// Regular user
|
||||
var user1Acc = tester.NewAccount();
|
||||
user1Acc.UserId = user1.Id;
|
||||
user1Acc.IsAdmin = false;
|
||||
@ -1399,10 +1421,17 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(0, card.Version);
|
||||
var card1keys = new[] { card.K0, card.K1, card.K2, card.K3, card.K4 };
|
||||
Assert.DoesNotContain(null, card1keys);
|
||||
|
||||
var card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||
{
|
||||
UID = uid
|
||||
});
|
||||
Assert.Equal(0, card2.Version);
|
||||
card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||
{
|
||||
UID = uid,
|
||||
OnExisting = OnExistingBehavior.UpdateVersion
|
||||
});
|
||||
Assert.Equal(1, card2.Version);
|
||||
Assert.StartsWith("lnurlw://", card2.LNURLW);
|
||||
Assert.EndsWith("/boltcard", card2.LNURLW);
|
||||
@ -1979,7 +2008,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("BTC-CHAIN", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.Contains("BTC-LN", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.NotNull(serverInfoData.SyncStatus);
|
||||
Assert.Single(serverInfoData.SyncStatus.Select(s => s.CryptoCode == "BTC"));
|
||||
Assert.Single(serverInfoData.SyncStatus.Select(s => s.PaymentMethodId == "BTC-CHAIN"));
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -2733,7 +2762,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.EndsWith($"/i/{newInvoice.Id}", newInvoice.CheckoutLink);
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(user.UserId, user.StoreId);
|
||||
var model = (PaymentModel)((ViewResult)await controller.Checkout(newInvoice.Id)).Model;
|
||||
var model = (CheckoutModel)((ViewResult)await controller.Checkout(newInvoice.Id)).Model;
|
||||
Assert.Equal("it-IT", model.DefaultLang);
|
||||
Assert.Equal("http://toto.com/lol", model.MerchantRefLink);
|
||||
|
||||
@ -2980,9 +3009,10 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// check list for store with paid invoice
|
||||
var merchantInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC");
|
||||
merchantPendingInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC", true);
|
||||
Assert.NotEmpty(merchantInvoices);
|
||||
Assert.Empty(merchantPendingInvoices);
|
||||
merchantPendingInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC", true);
|
||||
Assert.True(merchantPendingInvoices.Length < merchantInvoices.Length);
|
||||
Assert.All(merchantPendingInvoices, m => Assert.Equal(LightningInvoiceStatus.Unpaid, m.Status));
|
||||
// if the test ran too many times the invoice might be on a later page
|
||||
if (merchantInvoices.Length < 100)
|
||||
Assert.Contains(merchantInvoices, i => i.Id == merchantInvoice.Id);
|
||||
@ -3000,6 +3030,18 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
// Disable for now see #6518
|
||||
//// balance
|
||||
//await TestUtils.EventuallyAsync(async () =>
|
||||
//{
|
||||
// var balance = await client.GetLightningNodeBalance(user.StoreId, "BTC");
|
||||
// var localBalance = balance.OffchainBalance.Local.ToDecimal(LightMoneyUnit.BTC);
|
||||
// var histogram = await client.GetLightningNodeHistogram(user.StoreId, "BTC");
|
||||
// Assert.Equal(histogram.Balance, histogram.Series.Last());
|
||||
// Assert.Equal(localBalance, histogram.Balance);
|
||||
// Assert.Equal(localBalance, histogram.Series.Last());
|
||||
//});
|
||||
|
||||
// As admin, can use the internal node through our store.
|
||||
await user.MakeAdmin(true);
|
||||
await user.RegisterInternalLightningNodeAsync("BTC");
|
||||
@ -3021,6 +3063,10 @@ namespace BTCPayServer.Tests
|
||||
client = await guest.CreateClient(Policies.CanUseLightningNodeInStore);
|
||||
// Can use lightning node is only granted to store's owner
|
||||
await AssertPermissionError("btcpay.store.canuselightningnode", () => client.GetLightningNodeInfo(user.StoreId, "BTC"));
|
||||
|
||||
// balance and histogram should not be accessible with view only clients
|
||||
await AssertPermissionError("btcpay.store.canuselightningnode", () => client.GetLightningNodeBalance(user.StoreId, "BTC"));
|
||||
await AssertPermissionError("btcpay.store.canuselightningnode", () => client.GetLightningNodeHistogram(user.StoreId, "BTC"));
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 20 * 1000)]
|
||||
@ -3046,7 +3092,7 @@ namespace BTCPayServer.Tests
|
||||
new CreateInvoiceRequest
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 100,
|
||||
Amount = 0.1m,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions
|
||||
{
|
||||
PaymentMethods = new[] { "BTC-LN" },
|
||||
@ -3268,6 +3314,12 @@ namespace BTCPayServer.Tests
|
||||
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())});
|
||||
var method2 = await client.UpdateStorePaymentMethod(store.Id, "BTC-CHAIN", new UpdatePaymentMethodRequest() { Enabled = true, Config = new JObject() { ["derivationScheme"] = xpub.ToString(), ["label"] = "test", ["accountKeyPath"] = "aaaaaaaa/84'/0'/0'" } });
|
||||
Assert.Equal("aaaaaaaa", method2.Config["accountKeySettings"][0]["rootFingerprint"].ToString());
|
||||
Assert.Equal("84'/0'/0'", method2.Config["accountKeySettings"][0]["accountKeyPath"].ToString());
|
||||
var method3 = await client.UpdateStorePaymentMethod(store.Id, "BTC-CHAIN", new UpdatePaymentMethodRequest() { Enabled = true, Config = new JObject() { ["derivationScheme"] = xpub.ToString() } });
|
||||
|
||||
Assert.Equal(method.ToJson(), method3.ToJson());
|
||||
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC")).Addresses.First().Address);
|
||||
await AssertHttpError(403, async () =>
|
||||
@ -3332,7 +3384,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Equal(24, generateResponse.Mnemonic.Words.Length);
|
||||
Assert.Equal(Wordlist.Japanese, generateResponse.Mnemonic.WordList);
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -3536,8 +3587,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
var overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.Equal(0m, overview.Balance);
|
||||
|
||||
|
||||
|
||||
var fee = await client.GetOnChainFeeRate(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.NotNull(fee.FeeRate);
|
||||
|
||||
@ -3583,6 +3633,17 @@ namespace BTCPayServer.Tests
|
||||
overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.Equal(0.01m, overview.Balance);
|
||||
|
||||
// histogram should not be accessible with view only clients
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletHistogram(walletId.StoreId, walletId.CryptoCode);
|
||||
});
|
||||
var histogram = await client.GetOnChainWalletHistogram(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.Equal(histogram.Balance, histogram.Series.Last());
|
||||
Assert.Equal(0.01m, histogram.Balance);
|
||||
Assert.Equal(0.01m, histogram.Series.Last());
|
||||
Assert.Equal(0, histogram.Series.First());
|
||||
|
||||
//the simplest request:
|
||||
var nodeAddress = await tester.ExplorerNode.GetNewAddressAsync();
|
||||
var createTxRequest = new CreateOnChainTransactionRequest()
|
||||
@ -3774,9 +3835,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await tester.WaitForEvent<NewBlockEvent>(async () =>
|
||||
{
|
||||
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
}, bevent => bevent.CryptoCode.Equals("BTC", StringComparison.Ordinal));
|
||||
}, bevent => bevent.PaymentMethodId == PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
|
||||
Assert.Contains(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
|
||||
@ -3812,7 +3872,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
void VerifyLightning(GenericPaymentMethodData[] methods)
|
||||
{
|
||||
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-LN"));
|
||||
var m = Assert.Single(methods, m => m.PaymentMethodId == "BTC-LN");
|
||||
Assert.Equal("Internal Node", m.Config["internalNodeRef"].Value<string>());
|
||||
}
|
||||
|
||||
@ -3824,7 +3884,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
void VerifyOnChain(GenericPaymentMethodData[] dictionary)
|
||||
{
|
||||
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-CHAIN"));
|
||||
var m = Assert.Single(methods, m => m.PaymentMethodId == "BTC-CHAIN");
|
||||
var paymentMethodBaseData = Assert.IsType<JObject>(m.Config);
|
||||
Assert.Equal(wallet.Config.AccountDerivation, paymentMethodBaseData["accountDerivation"].Value<string>());
|
||||
}
|
||||
@ -3930,7 +3990,12 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings, Policies.CanModifyProfile);
|
||||
await client.UpdateCurrentUser(new UpdateApplicationUserRequest
|
||||
{
|
||||
Name = "The Admin",
|
||||
ImageUrl = "avatar.jpg"
|
||||
});
|
||||
|
||||
var roles = await client.GetServerRoles();
|
||||
Assert.Equal(4, roles.Count);
|
||||
@ -3944,6 +4009,9 @@ namespace BTCPayServer.Tests
|
||||
var storeUser = Assert.Single(users);
|
||||
Assert.Equal(user.UserId, storeUser.UserId);
|
||||
Assert.Equal(ownerRole.Id, storeUser.Role);
|
||||
Assert.Equal(user.Email, storeUser.Email);
|
||||
Assert.Equal("The Admin", storeUser.Name);
|
||||
Assert.Equal("avatar.jpg", storeUser.ImageUrl);
|
||||
var manager = tester.NewAccount();
|
||||
await manager.GrantAccessAsync();
|
||||
var employee = tester.NewAccount();
|
||||
@ -3974,7 +4042,14 @@ namespace BTCPayServer.Tests
|
||||
// add users to store
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = managerRole.Id, UserId = manager.UserId });
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = employeeRole.Id, UserId = employee.UserId });
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = guestRole.Id, UserId = guest.UserId });
|
||||
|
||||
// add with email
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = guestRole.Id, UserId = guest.Email });
|
||||
|
||||
// test unknown user
|
||||
await AssertAPIError("user-not-found", async () => await client.AddStoreUser(user.StoreId, new StoreUserData { Role = managerRole.Id, UserId = "unknown" }));
|
||||
await AssertAPIError("user-not-found", async () => await client.UpdateStoreUser(user.StoreId, "unknown", new StoreUserData { Role = ownerRole.Id }));
|
||||
await AssertAPIError("user-not-found", async () => await client.RemoveStoreUser(user.StoreId, "unknown"));
|
||||
|
||||
//test no access to api for employee
|
||||
await AssertPermissionError(Policies.CanViewStoreSettings, async () => await employeeClient.GetStore(user.StoreId));
|
||||
@ -3995,9 +4070,14 @@ namespace BTCPayServer.Tests
|
||||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await managerClient.RemoveStoreUser(user.StoreId, user.UserId));
|
||||
|
||||
// updates
|
||||
await client.UpdateStoreUser(user.StoreId, employee.UserId, new StoreUserData { Role = ownerRole.Id });
|
||||
await employeeClient.GetStore(user.StoreId);
|
||||
|
||||
// remove
|
||||
await client.RemoveStoreUser(user.StoreId, employee.UserId);
|
||||
await AssertHttpError(403, async () => await employeeClient.GetStore(user.StoreId));
|
||||
|
||||
// test duplicate add
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = ownerRole.Id, UserId = employee.UserId });
|
||||
await AssertAPIError("duplicate-store-user-role", async () =>
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = ownerRole.Id, UserId = employee.UserId }));
|
||||
@ -4120,6 +4200,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.True((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
||||
Assert.True((await unapprovedUserApiKeyClient.GetCurrentUser()).Approved);
|
||||
Assert.True((await unapprovedUserBasicAuthClient.GetCurrentUser()).Approved);
|
||||
var err = await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(unapprovedUser.UserId, true, CancellationToken.None);
|
||||
});
|
||||
Assert.Equal("User is already approved", err.APIError.Message);
|
||||
|
||||
// un-approve
|
||||
Assert.True(await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None));
|
||||
@ -4132,6 +4217,11 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await unapprovedUserBasicAuthClient.GetCurrentUser();
|
||||
});
|
||||
err = await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None);
|
||||
});
|
||||
Assert.Equal("User is already unapproved", err.APIError.Message);
|
||||
|
||||
// reset policies to not require approval
|
||||
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = false });
|
||||
@ -4148,10 +4238,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(await adminClient.GetNotifications(false));
|
||||
|
||||
// try unapproving user which does not have the RequiresApproval flag
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
err = await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(newUser.UserId, false, CancellationToken.None);
|
||||
});
|
||||
Assert.Equal("Unapproving user failed: No approval required", err.APIError.Message);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -4171,13 +4262,17 @@ namespace BTCPayServer.Tests
|
||||
await admin.GrantAccessAsync(true);
|
||||
|
||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
admin.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
||||
admin.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
var payoutAmount = LightMoney.Satoshis(1000);
|
||||
var inv = await tester.MerchantLnd.Client.CreateInvoice(payoutAmount, "Donation to merchant", TimeSpan.FromHours(1), default);
|
||||
var resp = await tester.CustomerLightningD.Pay(inv.BOLT11);
|
||||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
|
||||
var ppService = tester.PayTester.GetService<HostedServices.PullPaymentHostedService>();
|
||||
var serializers = tester.PayTester.GetService<BTCPayNetworkJsonSerializerSettings>();
|
||||
var store = tester.PayTester.GetService<StoreRepository>();
|
||||
var dbContextFactory = tester.PayTester.GetService<Data.ApplicationDbContextFactory>();
|
||||
|
||||
Assert.True(await store.InternalNodePayoutAuthorized(admin.StoreId));
|
||||
Assert.False(await store.InternalNodePayoutAuthorized("blah"));
|
||||
await admin.MakeAdmin(false);
|
||||
@ -4201,8 +4296,8 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var payoutC =
|
||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||
Assert.Equal(PayoutState.Completed, payoutC.State);
|
||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).SingleOrDefault(data => data.Id == payout.Id);
|
||||
Assert.Equal(PayoutState.Completed, payoutC?.State);
|
||||
});
|
||||
|
||||
payout = await adminClient.CreatePayout(admin.StoreId,
|
||||
@ -4244,6 +4339,36 @@ namespace BTCPayServer.Tests
|
||||
Destination = customerInvoice.BOLT11
|
||||
});
|
||||
Assert.Equal(payout2.OriginalAmount, new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC));
|
||||
|
||||
// Checking if we can disable a payout...
|
||||
var allLNPayouts = await ppService.GetPayouts(new ()
|
||||
{
|
||||
PayoutIds = new[] { payout2.Id },
|
||||
Processor = LightningAutomatedPayoutSenderFactory.ProcessorName
|
||||
});
|
||||
Assert.NotEmpty(allLNPayouts);
|
||||
var b = JsonConvert.DeserializeObject<Data.PayoutBlob>(allLNPayouts[0].Blob);
|
||||
b.DisableProcessor(LightningAutomatedPayoutSenderFactory.ProcessorName);
|
||||
Assert.Equal(1, b.IncrementErrorCount());
|
||||
Assert.Equal(2, b.IncrementErrorCount());
|
||||
allLNPayouts[0].Blob = JsonConvert.SerializeObject(b);
|
||||
Assert.Equal(3, JsonConvert.DeserializeObject<Data.PayoutBlob>(allLNPayouts[0].Blob).IncrementErrorCount());
|
||||
using var ctx = dbContextFactory.CreateContext();
|
||||
var p = ctx.Payouts.Find(allLNPayouts[0].Id);
|
||||
p.Blob = allLNPayouts[0].Blob;
|
||||
await ctx.SaveChangesAsync();
|
||||
var allLNPayouts2 = await ppService.GetPayouts(new()
|
||||
{
|
||||
PayoutIds = new[] { payout2.Id },
|
||||
Processor = LightningAutomatedPayoutSenderFactory.ProcessorName
|
||||
});
|
||||
Assert.DoesNotContain(allLNPayouts[0].Id, allLNPayouts2.Select(a => a.Id));
|
||||
allLNPayouts2 = await ppService.GetPayouts(new()
|
||||
{
|
||||
PayoutIds = new[] { payout2.Id },
|
||||
Processor = "hello"
|
||||
});
|
||||
Assert.Contains(allLNPayouts[0].Id, allLNPayouts2.Select(a => a.Id));
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -4312,8 +4437,8 @@ namespace BTCPayServer.Tests
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
|
||||
Assert.Equal(3, payouts.Length);
|
||||
Assert.Empty(payouts.Where(data => data.State == PayoutState.AwaitingApproval));
|
||||
Assert.Empty(payouts.Where(data => data.PayoutAmount is null));
|
||||
Assert.DoesNotContain(payouts, data => data.State == PayoutState.AwaitingApproval);
|
||||
Assert.DoesNotContain(payouts, data => data.PayoutAmount is null);
|
||||
|
||||
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
@ -4356,7 +4481,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
|
||||
Assert.Single(payouts, data => data.State == PayoutState.InProgress);
|
||||
});
|
||||
|
||||
uint256 txid = null;
|
||||
@ -4370,7 +4495,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
Assert.DoesNotContain(payouts, data => data.State != PayoutState.InProgress);
|
||||
});
|
||||
|
||||
// settings that were added later
|
||||
@ -4436,7 +4561,7 @@ namespace BTCPayServer.Tests
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
try
|
||||
{
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
|
||||
Assert.Single(payouts, data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id);
|
||||
}
|
||||
catch (SingleException)
|
||||
{
|
||||
@ -4480,7 +4605,7 @@ namespace BTCPayServer.Tests
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id));
|
||||
Assert.Single(payouts, data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id);
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
@ -4513,7 +4638,7 @@ namespace BTCPayServer.Tests
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
Assert.DoesNotContain(payouts, data => data.State != PayoutState.InProgress);
|
||||
|
||||
}
|
||||
|
||||
@ -4590,11 +4715,11 @@ namespace BTCPayServer.Tests
|
||||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest() { Type = "newtype", Id = test1.Id });
|
||||
|
||||
testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test);
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol"));
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.ObjectData["testData"]?.Value<string>() == "test1"));
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol");
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.ObjectData["testData"]?.Value<string>() == "test1");
|
||||
testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test, false);
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol"));
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.ObjectData is null));
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol");
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.ObjectData is null);
|
||||
|
||||
async Task TestWalletRepository()
|
||||
{
|
||||
|
@ -74,9 +74,8 @@ namespace BTCPayServer.Tests
|
||||
tester.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
tester.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
|
||||
text = tester.Driver.PageSource;
|
||||
Assert.DoesNotContain("Select-English (Custom)", text);
|
||||
Assert.Contains("English (Custom) deleted", text);
|
||||
Assert.Contains("Dictionary English (Custom) deleted", tester.FindAlertMessage().Text);
|
||||
Assert.DoesNotContain("Select-English (Custom)", tester.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Tests.Mocks
|
||||
|
||||
public string Action(UrlActionContext actionContext)
|
||||
{
|
||||
return $"{_BaseUrl}mock";
|
||||
return $"/mock";
|
||||
}
|
||||
|
||||
public string Content(string contentPath)
|
||||
|
13
BTCPayServer.Tests/OutputPathAttribute.cs
Normal file
13
BTCPayServer.Tests/OutputPathAttribute.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class OutputPathAttribute : Attribute
|
||||
{
|
||||
public OutputPathAttribute(string builtPath)
|
||||
{
|
||||
BuiltPath = builtPath;
|
||||
}
|
||||
public string BuiltPath { get; }
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static BTCPayServer.Tests.UnitTest1;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -76,9 +77,8 @@ fruit tea:
|
||||
Assert.Null( parsedDefault[0].BuyButtonText);
|
||||
Assert.Equal( "~/img/pos-sample/green-tea.jpg" ,parsedDefault[0].Image);
|
||||
Assert.Equal( 1 ,parsedDefault[0].Price);
|
||||
Assert.Equal( ViewPointOfSaleViewModel.ItemPriceType.Fixed ,parsedDefault[0].PriceType);
|
||||
Assert.Equal( AppItemPriceType.Fixed ,parsedDefault[0].PriceType);
|
||||
Assert.Null( parsedDefault[0].AdditionalData);
|
||||
Assert.Null( parsedDefault[0].PaymentMethods);
|
||||
|
||||
|
||||
Assert.Equal( "Herbal Tea" ,parsedDefault[4].Title);
|
||||
@ -87,9 +87,8 @@ fruit tea:
|
||||
Assert.Null( parsedDefault[4].BuyButtonText);
|
||||
Assert.Equal( "~/img/pos-sample/herbal-tea.jpg" ,parsedDefault[4].Image);
|
||||
Assert.Equal( 1.8m ,parsedDefault[4].Price);
|
||||
Assert.Equal( ViewPointOfSaleViewModel.ItemPriceType.Minimum ,parsedDefault[4].PriceType);
|
||||
Assert.Equal( AppItemPriceType.Minimum ,parsedDefault[4].PriceType);
|
||||
Assert.Null( parsedDefault[4].AdditionalData);
|
||||
Assert.Null( parsedDefault[4].PaymentMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -404,10 +404,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(paymentValueRowColumn.Text.Contains("payjoin",
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
s.GoToWallet(receiverWalletId, WalletsNavPages.Transactions);
|
||||
s.Driver.WaitForElement(By.CssSelector("#WalletTransactionsList tr"));
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.GoToWallet(receiverWalletId, WalletsNavPages.Transactions);
|
||||
s.Driver.WaitForElement(By.CssSelector("#WalletTransactionsList tr"));
|
||||
Assert.Contains("payjoin", s.Driver.PageSource);
|
||||
// Either the invoice id or the payjoin-exposed label, depending on the input having been used
|
||||
Assert.Matches(new Regex($"({invoiceId}|payjoin-exposed)"), s.Driver.PageSource);
|
||||
@ -481,12 +481,12 @@ namespace BTCPayServer.Tests
|
||||
var request = await fakeServer.GetNextRequest();
|
||||
Assert.Equal("1", request.Request.Query["v"][0]);
|
||||
Assert.Equal(changeIndex.ToString(), request.Request.Query["additionalfeeoutputindex"][0]);
|
||||
Assert.Equal("1146", request.Request.Query["maxadditionalfeecontribution"][0]);
|
||||
Assert.Equal("1853", request.Request.Query["maxadditionalfeecontribution"][0]);
|
||||
|
||||
TestLogs.LogInformation("The payjoin receiver tries to make us pay lots of fee");
|
||||
var originalPSBT = await ParsePSBT(request);
|
||||
var proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
proposalTx.Outputs[changeIndex].Value -= Money.Satoshis(1147);
|
||||
proposalTx.Outputs[changeIndex].Value -= Money.Satoshis(1854);
|
||||
await request.Response.WriteAsync(PSBT.FromTransaction(proposalTx, Network.RegTest).ToBase64(), Encoding.UTF8);
|
||||
fakeServer.Done();
|
||||
var ex = await Assert.ThrowsAsync<PayjoinSenderException>(async () => await requesting);
|
||||
|
@ -45,7 +45,7 @@ namespace BTCPayServer.Tests
|
||||
var runInBrowser = config["RunSeleniumInBrowser"] == "true";
|
||||
// Reset this using `dotnet user-secrets remove RunSeleniumInBrowser`
|
||||
|
||||
var chromeDriverPath = config["ChromeDriverDirectory"] ?? (Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory());
|
||||
var chromeDriverPath = config["ChromeDriverDirectory"] ?? (Server.PayTester.InContainer ? "/usr/bin" : TestUtils.TestDirectory);
|
||||
|
||||
var options = new ChromeOptions();
|
||||
if (!runInBrowser)
|
||||
@ -132,11 +132,11 @@ retry:
|
||||
/// Because for some reason, the selenium container can't resolve the tests container domain name
|
||||
/// </summary>
|
||||
public Uri ServerUri;
|
||||
internal IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
|
||||
public IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
|
||||
{
|
||||
return FindAlertMessage(new[] { severity });
|
||||
}
|
||||
internal IWebElement FindAlertMessage(params StatusMessageModel.StatusSeverity[] severity)
|
||||
public IWebElement FindAlertMessage(params StatusMessageModel.StatusSeverity[] severity)
|
||||
{
|
||||
var className = string.Join(", ", severity.Select(statusSeverity => $".alert-{StatusMessageModel.ToString(statusSeverity)}"));
|
||||
IWebElement el;
|
||||
@ -182,13 +182,18 @@ retry:
|
||||
Driver.FindElement(By.Id("RegisterButton")).Click();
|
||||
Driver.AssertNoError();
|
||||
CreatedUser = usr;
|
||||
Password = "123456";
|
||||
IsAdmin = isAdmin;
|
||||
return usr;
|
||||
}
|
||||
string CreatedUser;
|
||||
|
||||
public string Password { get; private set; }
|
||||
public bool IsAdmin { get; private set; }
|
||||
|
||||
public TestAccount AsTestAccount()
|
||||
{
|
||||
return new TestAccount(Server) { RegisterDetails = new Models.AccountViewModels.RegisterViewModel() { Password = "123456", Email = CreatedUser } };
|
||||
return new TestAccount(Server) { StoreId = StoreId, Email = CreatedUser, Password = Password, RegisterDetails = new Models.AccountViewModels.RegisterViewModel() { Password = "123456", Email = CreatedUser }, IsAdmin = IsAdmin };
|
||||
}
|
||||
|
||||
public (string storeName, string storeId) CreateNewStore(bool keepId = true)
|
||||
@ -638,7 +643,15 @@ retry:
|
||||
GoToUrl(url);
|
||||
Assert.DoesNotMatch("404 - Page not found</h", Driver.PageSource);
|
||||
if (shouldHaveAccess)
|
||||
{
|
||||
Assert.DoesNotMatch("- Denied</h", Driver.PageSource);
|
||||
// check associated link is active if present
|
||||
var sidebarLink = Driver.FindElements(By.CssSelector($"#mainNav a[href=\"{url}\"]")).FirstOrDefault();
|
||||
if (sidebarLink != null)
|
||||
{
|
||||
Assert.Contains("active", sidebarLink.GetAttribute("class"));
|
||||
}
|
||||
}
|
||||
else
|
||||
Assert.Contains("- Denied</h", Driver.PageSource);
|
||||
}
|
||||
|
@ -95,7 +95,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
|
||||
@ -114,7 +113,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
|
||||
new SelectElement(s.Driver.FindElement(By.Id("FormId"))).SelectByValue("Email");
|
||||
s.Driver.TakeScreenshot().SaveAsFile("C:\\Users\\NicolasDorier\\Downloads\\chromedriver-win64\\1.png");
|
||||
s.ClickPagePrimary();
|
||||
|
||||
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
|
||||
@ -385,10 +383,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("Account successfully created.", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
|
||||
// We should be logged in now
|
||||
s.GoToHome();
|
||||
@ -1244,15 +1238,17 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Tea shop");
|
||||
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1)")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("BuyButtonText"));
|
||||
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
|
||||
s.Driver.FindElement(By.Id("EditorCategories-ts-control")).SendKeys("Drinks");
|
||||
s.Driver.FindElement(By.CssSelector(".offcanvas-header button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("CodeTabButton"));
|
||||
s.Driver.ScrollTo(By.Id("CodeTabButton"));
|
||||
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\": \\[\r?\n\\s*\"Drinks\"\\s*\\]", template);
|
||||
|
||||
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
@ -1457,12 +1453,15 @@ namespace BTCPayServer.Tests
|
||||
s.GoToUrl(editUrl);
|
||||
s.Driver.ScrollTo(By.Id("btAddItem"));
|
||||
s.Driver.FindElement(By.Id("btAddItem")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("EditorTitle"));
|
||||
s.Driver.FindElement(By.Id("EditorTitle")).SendKeys("Perk 1");
|
||||
s.Driver.FindElement(By.Id("EditorAmount")).SendKeys("20");
|
||||
// Test autogenerated ID
|
||||
Assert.Equal("perk-1", s.Driver.FindElement(By.Id("EditorId")).GetAttribute("value"));
|
||||
s.Driver.FindElement(By.Id("EditorId")).Clear();
|
||||
s.Driver.FindElement(By.Id("EditorId")).SendKeys("Perk-1");
|
||||
s.Driver.FindElement(By.CssSelector(".offcanvas-header button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("CodeTabButton"));
|
||||
s.Driver.ScrollTo(By.Id("CodeTabButton"));
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");
|
||||
@ -1874,19 +1873,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("test-label", s.Driver.PageSource);
|
||||
});
|
||||
|
||||
// Let's try to remove a label
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
s.Driver.WaitForElement(By.CssSelector("[data-value='test-label']")).Click();
|
||||
await Task.Delay(500);
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-value=\"test-label\"]').nextSibling.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Delete', keyCode: 46}));");
|
||||
|
||||
});
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.DoesNotContain("test-label", s.Driver.PageSource);
|
||||
});
|
||||
// Remove a label
|
||||
s.Driver.WaitForElement(By.CssSelector("[data-value='test-label']")).Click();
|
||||
await Task.Delay(500);
|
||||
s.Driver.ExecuteJavaScript("var l=document.querySelector('[data-value=\"test-label\"]');l.click();l.nextSibling.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Delete', keyCode: 8}));");
|
||||
await Task.Delay(500);
|
||||
await s.Driver.Navigate().RefreshAsync();
|
||||
Assert.DoesNotContain("test-label", s.Driver.PageSource);
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("GoBack")));
|
||||
|
||||
//send money to addr and ensure it changed
|
||||
@ -2181,6 +2174,55 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("PP1 Edited", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanUseAwaitProgressForInProgressPayout()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GenerateWallet(isHotWallet: true);
|
||||
await s.FundStoreWallet(denomination: 50.0m);
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PayoutProcessors);
|
||||
s.Driver.FindElement(By.Id("Configure-BTC-CHAIN")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("ProcessNewPayoutsInstantly"), true);
|
||||
s.ClickPagePrimary();
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.ClickPagePrimary();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||
s.ClickPagePrimary();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString() + Keys.Enter);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id("InProgress-view")).Click();
|
||||
|
||||
// Waiting for the payment processor to process the payment
|
||||
int i = 0;
|
||||
while (!s.Driver.PageSource.Contains("mass-action-select-all"))
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
i++;
|
||||
Thread.Sleep(1000);
|
||||
if (i > 10)
|
||||
break;
|
||||
}
|
||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("InProgress-mark-awaiting-payment")).Click();
|
||||
s.Driver.FindElement(By.Id("AwaitingPayment-view")).Click();
|
||||
Assert.Contains("PP1", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
@ -2487,7 +2529,16 @@ namespace BTCPayServer.Tests
|
||||
$"LNurl w payout test {DateTime.UtcNow.Ticks}",
|
||||
TimeSpan.FromHours(1), CancellationToken.None));
|
||||
var response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient, null,null);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
// Oops!
|
||||
Assert.Equal("The request has been approved. The sender needs to send the payment manually. (Or activate the lightning automated payment processor)", response.Reason);
|
||||
var account = await s.AsTestAccount().CreateClient();
|
||||
await account.UpdateStoreLightningAutomatedPayoutProcessors(s.StoreId, "BTC-LN", new()
|
||||
{
|
||||
ProcessNewPayoutsInstantly = true,
|
||||
IntervalSeconds = TimeSpan.FromSeconds(60)
|
||||
});
|
||||
// Now it should process to complete
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.Contains(bolt2.BOLT11, s.Driver.PageSource);
|
||||
@ -2577,7 +2628,9 @@ namespace BTCPayServer.Tests
|
||||
$"LNurl w payout test {DateTime.UtcNow.Ticks}",
|
||||
TimeSpan.FromHours(1), CancellationToken.None));
|
||||
response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient, null,null);
|
||||
TestUtils.Eventually(() =>
|
||||
// Nope, you need to approve the claim automatically
|
||||
Assert.Equal("The request has been recorded, but still need to be approved before execution.", response.Reason);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.Contains(bolt2.BOLT11, s.Driver.PageSource);
|
||||
@ -2898,6 +2951,16 @@ namespace BTCPayServer.Tests
|
||||
// Unauthenticated user can't access recent transactions
|
||||
s.GoToUrl(keypadUrl);
|
||||
s.Driver.ElementDoesNotExist(By.Id("RecentTransactionsToggle"));
|
||||
|
||||
// But they can generate invoices
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='1']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='2']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='3']")).Click();
|
||||
s.Driver.FindElement(By.Id("pay-button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout"));
|
||||
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
|
||||
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
|
||||
Assert.Contains("1,23 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -3264,6 +3327,7 @@ namespace BTCPayServer.Tests
|
||||
public async Task CanUseLNAddress()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.DeleteStore = false;
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
@ -3416,7 +3480,13 @@ namespace BTCPayServer.Tests
|
||||
var succ = JsonConvert.DeserializeObject<LNURLPayRequest.LNURLPayRequestCallbackResponse>(str);
|
||||
Assert.NotNull(succ.Pr);
|
||||
Assert.Equal(new LightMoney(2001), BOLT11PaymentRequest.Parse(succ.Pr, Network.RegTest).MinimumAmount);
|
||||
await s.Server.CustomerLightningD.Pay(succ.Pr);
|
||||
}
|
||||
|
||||
// Can we find our comment and address in the payment list?
|
||||
s.GoToInvoices();
|
||||
var source = s.Driver.PageSource;
|
||||
Assert.Contains(lnUsername, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -3765,7 +3835,7 @@ retry:
|
||||
(_, string posId) = s.CreateApp("PointOfSale");
|
||||
(_, string crowdfundId) = s.CreateApp("Crowdfund");
|
||||
|
||||
string GetStorePath(string subPath) => $"/stores/{storeId}/{subPath}";
|
||||
string GetStorePath(string subPath) => $"/stores/{storeId}" + (string.IsNullOrEmpty(subPath) ? "" : $"/{subPath}");
|
||||
|
||||
// Owner access
|
||||
s.AssertPageAccess(true, GetStorePath(""));
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@ -15,8 +14,6 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
@ -26,10 +23,8 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
@ -609,9 +604,7 @@ retry:
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethodId == $"{cryptoCode}-LN");
|
||||
var bolt11 = method.Destination;
|
||||
TestLogs.LogInformation("PAYING");
|
||||
await parent.CustomerLightningD.Pay(bolt11);
|
||||
TestLogs.LogInformation("PAID");
|
||||
await WaitInvoicePaid(invoiceId);
|
||||
}
|
||||
|
||||
|
@ -727,5 +727,39 @@
|
||||
},
|
||||
"version": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "payment",
|
||||
"input": {
|
||||
"output": null,
|
||||
"version": 1,
|
||||
"outpoint": null,
|
||||
"accounted": true,
|
||||
"cryptoCode": "XMR",
|
||||
"networkFee": 0.0000000019,
|
||||
"receivedTimeMs": 1705500405468,
|
||||
"cryptoPaymentData": "{\"Amount\":62700000000,\"Address\":\"85CjjvQyW7PjNmiFRKZuEHKzZjiB3rjSu6n8zPzji4PtQxw1CyEY5H5FBge6GRUMJqR7FqsgBHU7H1FpEppvZXS6HGpFF6t\",\"SubaddressIndex\":23,\"SubaccountIndex\":0,\"BlockHeight\":3063946,\"ConfirmationCount\":10,\"TransactionId\":\"cc2e9ef03864c6af5e0d6f1c730ba142144f6588c50035b2996a59a6f3771b06\",\"LockTime\":0}",
|
||||
"cryptoPaymentDataType": "MoneroLike"
|
||||
},
|
||||
"expected": {
|
||||
"divisibility": 12,
|
||||
"destination": "85CjjvQyW7PjNmiFRKZuEHKzZjiB3rjSu6n8zPzji4PtQxw1CyEY5H5FBge6GRUMJqR7FqsgBHU7H1FpEppvZXS6HGpFF6t",
|
||||
"details": {
|
||||
"blockHeight": 3063946,
|
||||
"confirmationCount": 10,
|
||||
"lockTime": 0,
|
||||
"subaccountIndex": 0,
|
||||
"subaddressIndex": 23,
|
||||
"transactionId": "cc2e9ef03864c6af5e0d6f1c730ba142144f6588c50035b2996a59a6f3771b06"
|
||||
},
|
||||
"version": 2
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Amount": "0.0627",
|
||||
"PaymentMethodId": "XMR-CHAIN",
|
||||
"Currency": "XMR",
|
||||
"Status": "Settled",
|
||||
"Accounted": null
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1,3 +1,5 @@
|
||||
Password => Cyphercode
|
||||
Email address => Cypher ID
|
||||
Welcome to {0} => Yo at {0}
|
||||
{
|
||||
"Password" : "Cyphercode",
|
||||
"Email address" : "Cypher ID",
|
||||
"Welcome to {0}" : "Yo at {0}"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
Id,Blob,Created,ExceptionStatus,Status,StoreDataId,Archived,Blob2
|
||||
Q7RqoHLngK9svM4MgRyi9y,\x1f8b0800000000000003c454cb76a24010dde72b7258cf180d08929d0a89899ae323ea4962163c0ae808ddd8344426c72f9bc57cd2fcc2340d31ea38b398cd2cfb5657d5add7fdf9fdc7fbd9f9b9845ce9ea5c1a6b9335e90db0dfd7936ca80cfd498ef45cfa52fc4818a1702bbec9893feb76d9ace3abd3d6e06e456dd76b2fece46eb8eee4d7032f9bae5f6fd44dbfb330ddd2995017a870c6691896f16200774442e4e41cae0b8c5a0cf8436dea6a4d56344d5135596e2ac286704690030f282abe349a724bd6e5a67c298cb089117746041fd815a5b2bb109304b1b6eb524812514347ef1bb2a6cd0686663e7a8b4779dcf3bd45eb5ae9bcb181e10f50c93ca6c44d1d768b3d422391817b172d2b2831880c489c229e0d4085478577890b7be51691823c418e1572d4b3c2043e60caabe29856ab578893520a58b4459a4d0d89a35bc1c54e73dec5534c84e5de8a8e520ad88c2c149ec0bb24c58ce6272c4f283e814e59399ddfe220762a48d5ebcb3f9b1a274ca380e08f24bbbaf9ec0c8b59fbdbc3d70965a20953566c8d2fbab589535b35dab61f78e9a4bb50c76b2af7bda9f18a56caca9ca1acbdf202e7a6375ccd4d50eca775539e8fbf69fa4a514c326e0e8d7870d7efb747fd66369faba38d366b4c641dc6c8c67660240b3d35c78dbe1be85dc38efcc9d7e7f832095ea4d38c1088457b5f4a9d87ee52ba5afe277a4b69fb71c1164b05270c6f5275370ec425e7cab6eb706ce511605660cf2fe575829762d7b243d85fe10a1e1e2e19475d44c161b3c9a0c818301627571717919530a0981f0767b342d8af39242ab9b0cd3588d3ad9762e0f150f784218f1f4d41b160c2685a26c57b8632c5a7b000cd80ce68789817610cace642446a36737875e5bf1aa17e99dfa179cc48b568d55df1c9ed1e7fd7a7f296cb9e0d8105a410bbf7edcee4014c4aefc60e3baa5860ffa454c279bbbb978860c4d59a77d7dce9e24e135bf54a130354487aa14855323898bf95f18916c3aeac3d2b090e7fc0860176c13d1ed2e76a40566dd0f1563d9010a88585f0356af5b3ed2f000000ffff030035140a5d88060000,2018-10-01 11:32:12+00,,expired,3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd,f,
|
||||
Q7RqoHLngK9svM4MgRyi9y,\x1f8b080000000000000ac454cb76da400cfd97595302b1c1981d6012124c0e8f104e12b2f043b627d833663c767073f2655df493fa0bd50049714fbae9262b1f4ba3ab2be94abf7efc7c25d4275d3233e65b3eb2593836b362a24fc27949cd92d44826b9802bf544cbc2e5602097fdb0bde8d8d71be1fa416fe566d7936dbfbcb08362b17dbe6cefc6fdd5d0c7402e7c102a90e5718c3829803fe531f54ad26dd4887024906ebb65b6eb9a6e187adbd0b4965e2394159c7a704b1374375b5a4733b596765e23b04b290651ce4e7dbaae7c3ea43ca3b2e7fb02b24c71ed9b634b338ca56d19c3fb6075afcd4661b0ea5ce8fd17695ba14d91612ab89f7bf28a055c247b64d2c57648482cc83c44710104be539601f7e1bd94342a33ea3931e9064e9c810242c6a46bd4b12e2f1702982a922c171679ab11372fb111d5247bdb8d937c60ee0dc3c4a171c532e03993a2acd81e685af95fc87d2b4fa3a8ac861c1b73fe99b159314e23ce8e60481dbb6a39d2797f8103a5c5be868554f30b5595bb3477db56cf0da3209f0f56edd95668e360613dd38dbe192e69d1db049177399a6cee86a0bb0fdb967637fb6e981b5d1ff2596b62a5f6f578dc9b8e5bc5dd5d7bba3396cdb966c28cbacc8dac6c65e6c35973ec47e6c0729370feed313dcfa227f2091b0a6af4af6bd2bf1dac4977fd45d4d6e46dbf378ecc151f062ff80b3b0fd203d783fd2825a74c8049fc7f7cc29d802067bee3c6f021ad836972a20b013e15e0c9e5dc46f448ca34eb9e9d254e264130542926de5016d63d9e605eb9bb00b52c0d946680f1375cd200c5aba8605629724cc24e8c7bd8e3ca8228402c455cc943190359f721e175577a58c0e1599d8b10f379a24c253f88e6550d427dfeb5ebc7ea2720238e87e215151700ccf7af9b55eeffb3e108677f3103c44b38a378437124c38f0bf67ebd3a0d75bd22aa8eacba284770a5e3c3089c0227af0471f48c9c2cfae3859d04e683ffd7508fd281e2a0ac8ad26e790cc261ea5c35eb8db7df000000ffff0300d1083b8e01060000,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,
|
||||
|
|
@ -9,3 +9,4 @@ dc130d025a4bd2e7ee83b7707d850e7c1a52872c222a5bc2e05d3357e79aa762-1,\x1f8b0800000
|
||||
afc39884e024cbb3a48ca997b7e878b199d85fe97f90f94471364cc866ba83ad-1,\x1f8b080000000000000374904d6ac3301085f73945d03a14c992f593655cb20aa5942cbd1949a362d258c191534cc8c9bae8917a854a2e0ddd7476ef3de6d3e87d7d7c5e17cb25b9e070ee624fd64bb62a7a4087dd05fdbe3b6231eb9ad5a21246ce698fe93d0e872d968c3ed032d59cc4319d62d7a7ec13f09a5b90d269e1b8544c88600c55014d1dbc36c632a5515b651418ed4058ee6c25906a91258740d90ca6e44e1e672eb5402b47efc3f23e13351a270cc7bc2aa407164ce0ca04e1a9ac38b34885e65c4aabd50f0f9c8b639fd067641a469c4d374ca7149be8cbc7c866df903ff6334c47ecd3232428e9b5254dec43371c21e5ea9a426bc95ae59e562d79d96cb398c12d79baf795bddc57b676f80a6eca32c0db196fffbcb39f4ebf97ecba0392c5ed1b0000ffff030018cc54a1b1010000,Q7RqoHLngK9svM4MgRyi9y,t,,
|
||||
6f2b8513ebfdb14b41d4de235f050cd028400cb01fde700d72c6bedc2ad8af1e-5,\x1f8b08000000000000037490bf6ac3301087f73c45d01cc249966425635c3285528a472ffa732a26b5151c39c5843c59873e525fa192434397de22ddef83efa4fbfefcba2e964b72c1e1dc869e6c977495fb012db6177475db610e85908a6d24c04c7b8c1f6138ee313358432e369330c65368fb987242517ba79876d6a0952503578243ea0d80e5c0944b2708e159e1d0714eb931d43b34b4508219e941cc62200ff378f752c719c2a368a937942b445158855e6967b8b05c0a2e9934c0b59585960c0b55d272a3cabb4f5b1bc63ea24b4aafdfcf38a776984e3154c1e59f915d5d913ff18b9e3aece3938e3ad36b43aad0fb76e8744cbbabb2ae215b5835e475b74fb7388c989ae7c7b6325d677ec0376da7d4ce936fff0ca9a7d3ef330eed11c9e2f6030000ffff0300b09cf40daf010000,Q7RqoHLngK9svM4MgRyi9y,f,,
|
||||
3a6659e189f83f649c0010305def1b8fb78efdbe8c0ce44d56c8c22fff742b55-61,\x1f8b08000000000000037490bd6ec3201485f73c45c41c5580f973c6b8ca14555595d10be65e222b8d891c9cca8af2641dfa487d8582ab585d7a27ce39f001e7fbf3ebb6582ec915fb4b1b3ab25eb255d63d3a6caf08fbf684d994525121a85653da61fc08fd718b39a34f340f9f9230c47368bb987c22256fb4f09e7bc79d914a000aea8c6bd083c1461bcfd2524241593a5f3a250aef8d2991c952a9c21630812999c9c3c4d55615cad37998b625131eb8e5c080496c941525958c97b2708c838286022ac5b56060f42fcf3a17862e222464ec079c4cd78fe718aa00f96364b3afc81ffbd58e27ece2b38d36a7b79a54a1f36d7fb2315557655a4dd64c30b1aac9db669b84b7ef174cea652e2c99a9b064edf060ddf8d873ffe7a2fd787e3c65d71e912cee3f000000ffff0300a991d8c4b2010000,Q7RqoHLngK9svM4MgRyi9y,t,,
|
||||
26c879f3d27a894a62f8730c84205ac9dec38b7bbc0a11ccc0c196d1259b25aa-1,\x1f8b08000000000000036c90cf6ed4301087dfc5e7086c27f19fdccaa2b0b4dbaa452b964ab94cec314d378943e204d26a9f8c038fc42be02eea01099f3c9faddfcc7cbf7ffe7a260b8e53e37b52b0848c68b059d0ee9b0eafa78804cfa9d69aa954c884f418befbf158229282bea109f173187cd3075210009eeb9ae796696198a1c63046a136b256b5492d1a9d03a72aa346a64e099e81d2122c77a9d4467141193d1ff237758e99fddcb6090163fcdc07b4a408e38c0931e33a04bff1364e4176eff61bf2ca6e61edb00fef21407c7aae88f1bd6bc60e425c70f3925291422415196b176f0eda096305d68e384d9154e4f3e5ae94878b61f1eeca680fdf862ffb3be5ecc5aacb9b0f87fce89e9aedfd63d64cf869fbb46c1fe67ab854dd74d5fd58efef4ad61c9c6bacce1eafa1bbc1beac486c70c4f516c2c3b9017d1b3da567bc403b6384715098260c1fedf98770944b8d1a2dcd585427a347ed7269a9a15c73953191096ab25a720e9866b904c6104daa98c9b9b0ff647325b253ac5bfc0a667d5dfaf43f65fb7578311a85ee9a2392d31f000000ffff03004a65a9b61e020000,Q7RqoHLngK9svM4MgRyi9y,t,,
|
||||
|
|
@ -20,10 +20,9 @@ namespace BTCPayServer.Tests
|
||||
#else
|
||||
public const int TestTimeout = 90_000;
|
||||
#endif
|
||||
public static DirectoryInfo TryGetSolutionDirectoryInfo(string currentPath = null)
|
||||
public static DirectoryInfo TryGetSolutionDirectoryInfo()
|
||||
{
|
||||
var directory = new DirectoryInfo(
|
||||
currentPath ?? Directory.GetCurrentDirectory());
|
||||
var directory = new DirectoryInfo(TestDirectory);
|
||||
while (directory != null && !directory.GetFiles("*.sln").Any())
|
||||
{
|
||||
directory = directory.Parent;
|
||||
@ -31,10 +30,15 @@ namespace BTCPayServer.Tests
|
||||
return directory;
|
||||
}
|
||||
|
||||
static TestUtils()
|
||||
{
|
||||
TestDirectory = ((OutputPathAttribute)typeof(TestUtils).Assembly.GetCustomAttributes(typeof(OutputPathAttribute), true)[0]).BuiltPath;
|
||||
}
|
||||
public readonly static string TestDirectory;
|
||||
|
||||
public static string GetTestDataFullPath(string relativeFilePath)
|
||||
{
|
||||
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
|
||||
var directory = new DirectoryInfo(TestDirectory);
|
||||
while (directory != null && !directory.GetFiles("*.csproj").Any())
|
||||
{
|
||||
directory = directory.Parent;
|
||||
|
@ -208,6 +208,18 @@ namespace BTCPayServer.Tests
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "LBP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 LBP (I hope)
|
||||
}
|
||||
else if (name == "bitmynt")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "NOK") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 NOK
|
||||
}
|
||||
else if (name == "barebitcoin")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "NOK") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 NOK
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name == "kraken")
|
||||
@ -234,7 +246,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
// Kraken emit one request only after first GetRates
|
||||
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
await factory.Providers["kraken"].GetRatesAsync(default);
|
||||
|
||||
var p = new KrakenExchangeRateProvider();
|
||||
var rates = await p.GetRatesAsync(default);
|
||||
@ -339,15 +351,15 @@ retry:
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
public async Task CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
||||
Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule));
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" })
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD" })
|
||||
{
|
||||
var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule, null, default).GetAwaiter().GetResult();
|
||||
var result = await fetcher.FetchRate(CurrencyPair.Parse(pair), rule, null, default);
|
||||
Assert.NotNull(result.BidAsk);
|
||||
Assert.Empty(result.Errors);
|
||||
}
|
||||
@ -488,10 +500,14 @@ retry:
|
||||
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/tom-select@{version}/dist/js/tom-select.complete.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "dom-confetti", "dom-confetti.min.js").Trim();
|
||||
version = Regex.Match(actual, "Original file: /npm/dom-confetti@([0-9]+.[0-9]+.[0-9]+)/lib/main.js").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/dom-confetti@{version}")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
// This test is flaky probably because of the CDN sending the wrong file's version in some regions.
|
||||
// https://app.circleci.com/pipelines/github/btcpayserver/btcpayserver/13750/workflows/44aaf31d-0057-4fd8-a5bb-1a2c47fc530f/jobs/42963
|
||||
// It works locally depending on where you live.
|
||||
|
||||
//actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "dom-confetti", "dom-confetti.min.js").Trim();
|
||||
//version = Regex.Match(actual, "Original file: /npm/dom-confetti@([0-9]+.[0-9]+.[0-9]+)/lib/main.js").Groups[1].Value;
|
||||
//expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/dom-confetti@{version}")).Content.ReadAsStringAsync()).Trim();
|
||||
//EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-sortable", "sortable.min.js").Trim();
|
||||
version = Regex.Match(actual, "Sortable ([0-9]+.[0-9]+.[0-9]+) ").Groups[1].Value;
|
||||
@ -597,7 +613,7 @@ retry:
|
||||
|
||||
foreach (var rate in rates)
|
||||
{
|
||||
Assert.Single(rates.Where(r => r == rate));
|
||||
Assert.Single(rates, r => r == rate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,10 +38,8 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Plugins.PayButton;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
@ -70,7 +68,6 @@ using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Schema;
|
||||
using Npgsql;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
@ -197,7 +194,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanStoreArbitrarySettingsWithStore()
|
||||
public async Task CanStoreArbitrarySettingsWithStore()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
@ -449,25 +446,25 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<ViewResult>(storeResponse);
|
||||
Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
|
||||
await storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
|
||||
{
|
||||
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
|
||||
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
|
||||
}, "test", "BTC").GetAwaiter().GetResult();
|
||||
}, "test", "BTC");
|
||||
Assert.False(storeController.TempData.ContainsKey(WellKnownTempData.ErrorMessage));
|
||||
storeController.TempData.Clear();
|
||||
Assert.True(storeController.ModelState.IsValid);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(storeController.SetupLightningNode(user.StoreId,
|
||||
Assert.IsType<RedirectToActionResult>(await storeController.SetupLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel
|
||||
{
|
||||
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
|
||||
}, "save", "BTC").GetAwaiter().GetResult());
|
||||
}, "save", "BTC"));
|
||||
|
||||
// Make sure old connection string format does not work
|
||||
Assert.IsType<RedirectToActionResult>(storeController.SetupLightningNode(user.StoreId,
|
||||
Assert.IsType<RedirectToActionResult>(await storeController.SetupLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
"save", "BTC").GetAwaiter().GetResult());
|
||||
"save", "BTC"));
|
||||
|
||||
storeResponse = storeController.LightningSettings(user.StoreId, "BTC");
|
||||
var storeVm =
|
||||
@ -648,7 +645,7 @@ namespace BTCPayServer.Tests
|
||||
var store2 = acc.GetController<UIStoresController>();
|
||||
await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id);
|
||||
Assert.Contains(nameof(PairingResult.ReusedKey),
|
||||
(string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase);
|
||||
store2.TempData[WellKnownTempData.ErrorMessage].ToString(), StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout * 2)]
|
||||
@ -841,6 +838,8 @@ namespace BTCPayServer.Tests
|
||||
AssertSearchInvoice(acc, false, invoice.Id, "exceptionstatus:paidOver");
|
||||
AssertSearchInvoice(acc, true, invoice.Id, "unusual:true");
|
||||
AssertSearchInvoice(acc, false, invoice.Id, "unusual:false");
|
||||
AssertSearchInvoice(acc, true, invoice.Id, "status:settled,exceptionstatus:paidPartial");
|
||||
AssertSearchInvoice(acc, true, invoice.Id, "status:settled,status:invalid,exceptionstatus:paidPartial,exceptionstatus:paidOver");
|
||||
|
||||
var time = invoice.InvoiceTime;
|
||||
AssertSearchInvoice(acc, true, invoice.Id, $"startdate:{time.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
@ -1102,7 +1101,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CheckCORSSetOnBitpayAPI()
|
||||
public async Task CheckCORSSetOnBitpayAPI()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
@ -1146,9 +1145,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Test request pairing code client side
|
||||
var storeController = user.GetController<UIStoresController>();
|
||||
storeController
|
||||
.CreateToken(user.StoreId, new CreateTokenViewModel() { Label = "test2", StoreId = user.StoreId })
|
||||
.GetAwaiter().GetResult();
|
||||
await storeController
|
||||
.CreateToken(user.StoreId, new CreateTokenViewModel() { Label = "test2", StoreId = user.StoreId });
|
||||
Assert.NotNull(storeController.GeneratedPairingCode);
|
||||
|
||||
|
||||
@ -1172,17 +1170,17 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Can generate API Key
|
||||
var repo = tester.PayTester.GetService<TokenRepository>();
|
||||
Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.Empty(await repo.GetLegacyAPIKeys(user.StoreId));
|
||||
Assert.IsType<RedirectToActionResult>(await user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId));
|
||||
|
||||
var apiKey = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
var apiKey = Assert.Single(await repo.GetLegacyAPIKeys(user.StoreId));
|
||||
///////
|
||||
|
||||
// Generating a new one remove the previous
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId).GetAwaiter().GetResult());
|
||||
var apiKey2 = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.IsType<RedirectToActionResult>(await user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId));
|
||||
var apiKey2 = Assert.Single(await repo.GetLegacyAPIKeys(user.StoreId));
|
||||
Assert.NotEqual(apiKey, apiKey2);
|
||||
////////
|
||||
|
||||
@ -1196,7 +1194,7 @@ namespace BTCPayServer.Tests
|
||||
var invoice = new Invoice() { Price = 5000.0m, Currency = "USD" };
|
||||
message.Content = new StringContent(JsonConvert.SerializeObject(invoice), Encoding.UTF8,
|
||||
"application/json");
|
||||
var result = client.SendAsync(message).GetAwaiter().GetResult();
|
||||
var result = await client.SendAsync(message);
|
||||
result.EnsureSuccessStatusCode();
|
||||
/////////////////////
|
||||
|
||||
@ -1210,7 +1208,7 @@ namespace BTCPayServer.Tests
|
||||
mess.Headers.Add("x-identity",
|
||||
"04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99");
|
||||
mess.Method = HttpMethod.Get;
|
||||
result = client.SendAsync(mess).GetAwaiter().GetResult();
|
||||
result = await client.SendAsync(mess);
|
||||
Assert.Equal(System.Net.HttpStatusCode.Unauthorized, result.StatusCode);
|
||||
|
||||
//
|
||||
@ -1542,8 +1540,8 @@ namespace BTCPayServer.Tests
|
||||
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
|
||||
var vm = await user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
|
||||
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
|
||||
Assert.Equal(PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(), criteria.PaymentMethod);
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria, m => m.PaymentMethod == btcMethod.ToString());
|
||||
Assert.Equal(btcMethod.ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "5 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
||||
@ -1587,14 +1585,14 @@ namespace BTCPayServer.Tests
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
var checkout = (await user.GetController<UIInvoiceController>().Checkout(invoice.Id)).AssertViewModel<PaymentModel>();
|
||||
var checkout = (await user.GetController<UIInvoiceController>().Checkout(invoice.Id)).AssertViewModel<CheckoutModel>();
|
||||
Assert.Equal(lnMethod, checkout.PaymentMethodId);
|
||||
|
||||
// If we change store's default, it should change the checkout's default
|
||||
vm.DefaultPaymentMethod = btcMethod;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
||||
.Result);
|
||||
checkout = (await user.GetController<UIInvoiceController>().Checkout(invoice.Id)).AssertViewModel<PaymentModel>();
|
||||
checkout = (await user.GetController<UIInvoiceController>().Checkout(invoice.Id)).AssertViewModel<CheckoutModel>();
|
||||
Assert.Equal(btcMethod, checkout.PaymentMethodId);
|
||||
}
|
||||
|
||||
@ -1625,7 +1623,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// validate that invoice data model doesn't have lightning string initially
|
||||
var res = await user.GetController<UIInvoiceController>().Checkout(invoice.Id);
|
||||
var paymentMethodFirst = Assert.IsType<PaymentModel>(
|
||||
var paymentMethodFirst = Assert.IsType<CheckoutModel>(
|
||||
Assert.IsType<ViewResult>(res).Model
|
||||
);
|
||||
Assert.DoesNotContain("&lightning=", paymentMethodFirst.InvoiceBitcoinUrlQR);
|
||||
@ -1641,7 +1639,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// validate that QR code now has both onchain and offchain payment urls
|
||||
res = await user.GetController<UIInvoiceController>().Checkout(invoice.Id);
|
||||
var paymentMethodUnified = Assert.IsType<PaymentModel>(
|
||||
var paymentMethodUnified = Assert.IsType<CheckoutModel>(
|
||||
Assert.IsType<ViewResult>(res).Model
|
||||
);
|
||||
Assert.StartsWith("bitcoin:bcrt", paymentMethodUnified.InvoiceBitcoinUrl);
|
||||
@ -1655,8 +1653,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Standard for all uppercase characters in QR codes is still not implemented in all wallets
|
||||
// But we're proceeding with BECH32 being uppercase
|
||||
Assert.Equal($"bitcoin:{paymentMethodUnified.BtcAddress}", paymentMethodUnified.InvoiceBitcoinUrl.Split('?')[0]);
|
||||
Assert.Equal($"bitcoin:{paymentMethodUnified.BtcAddress.ToUpperInvariant()}", paymentMethodUnified.InvoiceBitcoinUrlQR.Split('?')[0]);
|
||||
Assert.Equal($"bitcoin:{paymentMethodUnified.Address}", paymentMethodUnified.InvoiceBitcoinUrl.Split('?')[0]);
|
||||
Assert.Equal($"bitcoin:{paymentMethodUnified.Address.ToUpperInvariant()}", paymentMethodUnified.InvoiceBitcoinUrlQR.Split('?')[0]);
|
||||
|
||||
// Fallback lightning invoice should be uppercase inside the QR code, lowercase in payment URI
|
||||
var lightningFallback = paymentMethodUnified.InvoiceBitcoinUrl.Split(new[] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
@ -2178,19 +2176,19 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
|
||||
Assert.True(invoice.MinerFees.ContainsKey("BTC"));
|
||||
Assert.Contains(Math.Round(invoice.MinerFees["BTC"].SatoshiPerBytes), new[] { 100.0m, 20.0m });
|
||||
TestUtils.Eventually(() =>
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
var textSearchResult = await tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] { user.StoreId },
|
||||
TextSearch = invoice.OrderId
|
||||
}).GetAwaiter().GetResult();
|
||||
});
|
||||
Assert.Single(textSearchResult);
|
||||
textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
textSearchResult = await tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] { user.StoreId },
|
||||
TextSearch = invoice.Id
|
||||
}).GetAwaiter().GetResult();
|
||||
});
|
||||
|
||||
Assert.Single(textSearchResult);
|
||||
});
|
||||
@ -2218,11 +2216,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
var invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
var invoiceEntity = await repo.GetInvoice(invoice.Id, true);
|
||||
|
||||
Money secondPayment = Money.Zero;
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("new", localInvoice.Status);
|
||||
@ -2234,7 +2232,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
invoiceEntity = await repo.GetInvoice(invoice.Id, true);
|
||||
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
|
||||
secondPayment = localInvoice.BtcDue;
|
||||
});
|
||||
@ -2277,18 +2275,18 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var txId = await cashCow.SendToAddressAsync(invoiceAddress, invoice.BtcDue + Money.Coins(1));
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
var textSearchResult = await tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] { user.StoreId },
|
||||
TextSearch = txId.ToString()
|
||||
}).GetAwaiter().GetResult();
|
||||
});
|
||||
Assert.Single(textSearchResult);
|
||||
});
|
||||
|
||||
@ -2424,7 +2422,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CheckOnionlocationForNonOnionHtmlRequests()
|
||||
public async Task CheckOnionlocationForNonOnionHtmlRequests()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
@ -2909,6 +2907,11 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
Assert.True(await invoiceMigrator.IsComplete());
|
||||
});
|
||||
var invoiceRepo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var invoice = await invoiceRepo.GetInvoice("Q7RqoHLngK9svM4MgRyi9y");
|
||||
var p = invoice.Payments.First(p => p.Id == "26c879f3d27a894a62f8730c84205ac9dec38b7bbc0a11ccc0c196d1259b25aa-1");
|
||||
var details = p.GetDetails<BitcoinLikePaymentData>(handlers.GetBitcoinHandler("BTC"));
|
||||
Assert.Equal("6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d", details.AssetId.ToString());
|
||||
}
|
||||
|
||||
private static async Task RestartMigration(ServerTester tester)
|
||||
@ -3046,7 +3049,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanUseLocalProviderFiles()
|
||||
public async Task CanUseLocalProviderFiles()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
@ -3245,8 +3248,14 @@ namespace BTCPayServer.Tests
|
||||
var date2018 = new DateTimeOffset(2018, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
report = await GetReport(acc, new() { ViewName = "Payments", TimePeriod = new TimePeriod() { From = date2018, To = date2018 + TimeSpan.FromDays(365) } });
|
||||
var invoiceIdIndex = report.GetIndex("InvoiceId");
|
||||
var invoiceCurrencyAmountIndex = report.GetIndex("InvoiceCurrencyAmount");
|
||||
var rateIndex = report.GetIndex("Rate");
|
||||
var oldPaymentsCount = report.Data.Count(d => d[invoiceIdIndex].Value<string>() == "Q7RqoHLngK9svM4MgRyi9y");
|
||||
Assert.Equal(8, oldPaymentsCount); // 10 payments, but 2 unaccounted
|
||||
Assert.Equal(9, oldPaymentsCount); // 11 payments, but 2 unaccounted
|
||||
Assert.Single(report.Data, d =>
|
||||
d[invoiceIdIndex].Value<string>() == "Q7RqoHLngK9svM4MgRyi9y" &&
|
||||
GetAmount(rateIndex, d) == 6596.35m &&
|
||||
GetAmount(invoiceCurrencyAmountIndex, d) == 1.18m);
|
||||
|
||||
var addr = await tester.ExplorerNode.GetNewAddressAsync();
|
||||
// Two invoices get refunded
|
||||
@ -3265,12 +3274,12 @@ namespace BTCPayServer.Tests
|
||||
var fullyPaidIndex = report.GetIndex("FullyPaid");
|
||||
var completedIndex = report.GetIndex("Completed");
|
||||
var limitIndex = report.GetIndex("Limit");
|
||||
var d = Assert.Single(report.Data.Where(d => d[report.GetIndex("InvoiceId")].Value<string>() == inv.Id));
|
||||
var d = Assert.Single(report.Data, d => d[report.GetIndex("InvoiceId")].Value<string>() == inv.Id);
|
||||
Assert.Equal(fullyPaid, (bool)d[fullyPaidIndex]);
|
||||
Assert.Equal(currency, d[currencyIndex].Value<string>());
|
||||
Assert.Equal(completed, (((JObject)d[completedIndex])["v"]).Value<decimal>());
|
||||
Assert.Equal(awaiting, (((JObject)d[awaitingIndex])["v"]).Value<decimal>());
|
||||
Assert.Equal(limit, (((JObject)d[limitIndex])["v"]).Value<decimal>());
|
||||
Assert.Equal(completed, GetAmount(completedIndex, d));
|
||||
Assert.Equal(awaiting, GetAmount(awaitingIndex, d));
|
||||
Assert.Equal(limit, GetAmount(limitIndex, d));
|
||||
}
|
||||
|
||||
await AssertData("USD", awaiting: 0.0m, limit: 10.0m, completed: 0.0m, fullyPaid: false);
|
||||
@ -3291,6 +3300,12 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static decimal GetAmount(int idx, JArray d)
|
||||
{
|
||||
var jobj = (JObject)d[idx];
|
||||
return Math.Round(jobj["v"].Value<decimal>(), jobj["d"].Value<int>());
|
||||
}
|
||||
|
||||
private async Task<StoreReportResponse> GetReport(TestAccount acc, StoreReportRequest req)
|
||||
{
|
||||
var controller = acc.GetController<UIReportsController>();
|
||||
|
@ -16,11 +16,13 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Localization;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -349,45 +351,66 @@ retry:
|
||||
{
|
||||
defaultTranslatedKeys.Add(k);
|
||||
}
|
||||
|
||||
AddLocalizers(defaultTranslatedKeys, txt);
|
||||
}
|
||||
|
||||
// Go through all cshtml file, search for text-translate or ViewLocalizer usage
|
||||
using (var tester = CreateServerTester())
|
||||
using (var tester = CreateServerTester(newDb: true))
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var engine = tester.PayTester.GetService<RazorProjectEngine>();
|
||||
foreach (var file in soldir.EnumerateFiles("*.cshtml", SearchOption.AllDirectories))
|
||||
var files = soldir.EnumerateFiles("*.cshtml", SearchOption.AllDirectories)
|
||||
.Union(soldir.EnumerateFiles("*.razor", SearchOption.AllDirectories));
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filePath = file.FullName;
|
||||
var txt = File.ReadAllText(file.FullName);
|
||||
if (txt.Contains("ViewLocalizer"))
|
||||
{
|
||||
var matches = Regex.Matches(txt, "ViewLocalizer\\[\"(.*?)\"[\\],]");
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
defaultTranslatedKeys.Add(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
AddLocalizers(defaultTranslatedKeys, txt);
|
||||
|
||||
filePath = filePath.Replace(Path.Combine(soldir.FullName, "BTCPayServer"), "/");
|
||||
var item = engine.FileSystem.GetItem(filePath);
|
||||
|
||||
|
||||
var node = (DocumentIntermediateNode)engine.Process(item).Items[typeof(DocumentIntermediateNode)];
|
||||
var w = new TranslatedKeyNodeWalker(defaultTranslatedKeys, txt);
|
||||
w.Visit(node);
|
||||
}
|
||||
|
||||
}
|
||||
defaultTranslatedKeys = defaultTranslatedKeys.Select(d => d.Trim()).Distinct().OrderBy(o => o).ToList();
|
||||
defaultTranslatedKeys = defaultTranslatedKeys.Select(d => d.Trim().Replace("\r\n", "\n")).Distinct().OrderBy(o => o).ToList();
|
||||
JObject obj = new JObject();
|
||||
foreach (var v in defaultTranslatedKeys)
|
||||
{
|
||||
obj.Add(v, "");
|
||||
}
|
||||
|
||||
var path = Path.Combine(soldir.FullName, "BTCPayServer/Services/Translations.Default.cs");
|
||||
var defaultTranslation = File.ReadAllText(path);
|
||||
var startIdx = defaultTranslation.IndexOf("\"\"\"");
|
||||
var endIdx = defaultTranslation.LastIndexOf("\"\"\"");
|
||||
var content = defaultTranslation.Substring(0, startIdx + 3);
|
||||
content += "\n" + String.Join('\n', defaultTranslatedKeys) + "\n";
|
||||
content += "\n" + obj.ToString(Formatting.Indented) + "\n";
|
||||
content += defaultTranslation.Substring(endIdx);
|
||||
File.WriteAllText(path, content);
|
||||
}
|
||||
|
||||
private static void AddLocalizers(List<string> defaultTranslatedKeys, string txt)
|
||||
{
|
||||
foreach (string localizer in new[] { "ViewLocalizer", "StringLocalizer" })
|
||||
{
|
||||
if (txt.Contains(localizer))
|
||||
{
|
||||
var matches = Regex.Matches(txt, localizer + "\\[\"(.*?)\"[\\],]");
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
var k = match.Groups[1].Value;
|
||||
k = k.Replace("\\", "");
|
||||
defaultTranslatedKeys.Add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayNameWalker : CSharpSyntaxWalker
|
||||
{
|
||||
public List<string> Keys = new List<string>();
|
||||
|
@ -72,7 +72,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:26.0
|
||||
image: btcpayserver/bitcoin:28.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -98,7 +98,7 @@ services:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.4
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -134,7 +134,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:26.0
|
||||
image: btcpayserver/bitcoin:28.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -162,7 +162,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v24.05
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -180,6 +180,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -190,7 +191,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v24.05
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -208,6 +209,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -227,7 +229,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.18.1-beta
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -252,9 +254,12 @@ services:
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "30894:9735"
|
||||
- "53280:10009"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
@ -262,7 +267,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.18.1-beta
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -287,8 +292,10 @@ services:
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
|
298
BTCPayServer.Tests/docker-compose.mutinynet.yml
Normal file
298
BTCPayServer.Tests/docker-compose.mutinynet.yml
Normal file
@ -0,0 +1,298 @@
|
||||
version: "3"
|
||||
|
||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||
# The Visual Studio launch setting `Docker-signet` is configured to use this environment.
|
||||
services:
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
- tor
|
||||
|
||||
sshd:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: sshd.Dockerfile
|
||||
ports:
|
||||
- "21622:22"
|
||||
expose:
|
||||
- 22
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/mutinynet:c23afab47fbe
|
||||
environment:
|
||||
BITCOIN_NETWORK: signet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
[signet]
|
||||
signetchallenge=512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae
|
||||
addnode=45.79.52.207:38333
|
||||
dnsseed=0
|
||||
signetblocktime=30
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:125.0
|
||||
extra_hosts:
|
||||
- "tests:172.23.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: signet
|
||||
NBXPLORER_CHAINS: "btc"
|
||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_MINGAPSIZE: 5
|
||||
NBXPLORER_MAXGAPSIZE: 10
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer_mutinynet
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/mutinynet:c23afab47fbe
|
||||
environment:
|
||||
BITCOIN_NETWORK: signet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
rpcallowip=0.0.0.0/0
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
zmqpubrawtx=tcp://0.0.0.0:28333
|
||||
deprecatedrpc=signrawtransaction
|
||||
fallbackfee=0.0002
|
||||
[signet]
|
||||
signetchallenge=512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae
|
||||
addnode=45.79.52.207:38333
|
||||
dnsseed=0
|
||||
signetblocktime=30
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "39388:39388"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "28332" # ZMQ
|
||||
- "28333" # ZMQ
|
||||
volumes:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "signet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=customer_lightningd:9735
|
||||
log-level=debug
|
||||
funding-confirms=1
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "customer_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "signet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=merchant_lightningd:9735
|
||||
funding-confirms=1
|
||||
log-level=debug
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
postgres:
|
||||
image: postgres:13.13
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "signet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=merchant_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=merchant_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=merchant_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "30894:9735"
|
||||
- "53280:10009"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "signet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://customer_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=customer_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=customer_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=customer_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
tor:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/tor:0.4.6.5
|
||||
container_name: tor
|
||||
environment:
|
||||
TOR_PASSWORD: btcpayserver
|
||||
ports:
|
||||
- "9050:9050" # SOCKS
|
||||
- "9051:9051" # Tor Control
|
||||
volumes:
|
||||
- "tor_datadir:/home/tor/.tor"
|
||||
- "torrcdir:/usr/local/etc/tor"
|
||||
- "tor_servicesdir:/var/lib/tor/hidden_services"
|
||||
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
elementsd_liquid_datadir:
|
||||
customer_lightningd_datadir:
|
||||
merchant_lightningd_datadir:
|
||||
lightning_charge_datadir:
|
||||
customer_lnd_datadir:
|
||||
merchant_lnd_datadir:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
288
BTCPayServer.Tests/docker-compose.testnet.yml
Normal file
288
BTCPayServer.Tests/docker-compose.testnet.yml
Normal file
@ -0,0 +1,288 @@
|
||||
version: "3"
|
||||
|
||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||
# The Visual Studio launch setting `Docker-testnet` is configured to use this environment.
|
||||
services:
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
- tor
|
||||
|
||||
sshd:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: sshd.Dockerfile
|
||||
ports:
|
||||
- "21622:22"
|
||||
expose:
|
||||
- 22
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:28.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: testnet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:125.0
|
||||
extra_hosts:
|
||||
- "tests:172.23.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: testnet
|
||||
NBXPLORER_CHAINS: "btc"
|
||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_MINGAPSIZE: 5
|
||||
NBXPLORER_MAXGAPSIZE: 10
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer_testnet
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:28.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: testnet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
rpcallowip=0.0.0.0/0
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
zmqpubrawtx=tcp://0.0.0.0:28333
|
||||
deprecatedrpc=signrawtransaction
|
||||
fallbackfee=0.0002
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "39388:39388"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "28332" # ZMQ
|
||||
- "28333" # ZMQ
|
||||
volumes:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "testnet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=customer_lightningd:9735
|
||||
log-level=debug
|
||||
funding-confirms=1
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "customer_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "testnet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=merchant_lightningd:9735
|
||||
funding-confirms=1
|
||||
log-level=debug
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
postgres:
|
||||
image: postgres:13.13
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "testnet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=merchant_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=merchant_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=merchant_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "30894:9735"
|
||||
- "53280:10009"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "testnet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://customer_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=customer_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=customer_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=customer_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
tor:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/tor:0.4.6.5
|
||||
container_name: tor
|
||||
environment:
|
||||
TOR_PASSWORD: btcpayserver
|
||||
ports:
|
||||
- "9050:9050" # SOCKS
|
||||
- "9051:9051" # Tor Control
|
||||
volumes:
|
||||
- "tor_datadir:/home/tor/.tor"
|
||||
- "torrcdir:/usr/local/etc/tor"
|
||||
- "tor_servicesdir:/var/lib/tor/hidden_services"
|
||||
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
elementsd_liquid_datadir:
|
||||
customer_lightningd_datadir:
|
||||
merchant_lightningd_datadir:
|
||||
lightning_charge_datadir:
|
||||
customer_lnd_datadir:
|
||||
merchant_lnd_datadir:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
@ -69,7 +69,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:26.0
|
||||
image: btcpayserver/bitcoin:28.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -95,7 +95,7 @@ services:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.4
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -120,7 +120,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:26.0
|
||||
image: btcpayserver/bitcoin:28.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -148,7 +148,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v24.05
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -166,6 +166,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -176,7 +177,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v24.05
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -194,6 +195,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -213,7 +215,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.18.1-beta
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -239,6 +241,7 @@ services:
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "53280:10009"
|
||||
- "30894:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
@ -250,7 +253,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.18.1-beta
|
||||
image: btcpayserver/lnd:v0.18.4-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -275,6 +278,7 @@ services:
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
|
@ -1,6 +1,5 @@
|
||||
{
|
||||
"maxParallelThreads": 4,
|
||||
"longRunningTestSeconds": 60,
|
||||
"diagnosticMessages": true,
|
||||
"methodDisplay": "method"
|
||||
}
|
||||
|
@ -8,6 +8,19 @@
|
||||
<RunAnalyzersDuringLiveAnalysis>False</RunAnalyzersDuringLiveAnalysis>
|
||||
<RunAnalyzersDuringBuild>False</RunAnalyzersDuringBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Pre-compiling views should only be done for Release builds without dotnet watch or design time build .-->
|
||||
<!-- Runtime compiling is only useful for debugging with hot reload of the views -->
|
||||
<PropertyGroup Condition="'$(RazorCompileOnBuild)'=='' AND ('$(Configuration)' == 'Debug' OR '$(DotNetWatchBuild)' == 'true' OR '$(DesignTimeBuild)' == 'true')">
|
||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RazorCompileOnBuild)'==''">
|
||||
<RazorCompileOnBuild>true</RazorCompileOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RazorCompileOnBuild)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);RAZOR_COMPILE_ON_BUILD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Condition="'$(GitCommit)' != ''" Include="BTCPayServer.GitCommitAttribute">
|
||||
<_Parameter1>$(GitCommit)</_Parameter1>
|
||||
@ -37,19 +50,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.23" />
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.25" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.4" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.0" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.9" />
|
||||
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.2" />
|
||||
<PackageReference Include="LNURL" Version="0.0.34" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<PackageReference Include="Fido2" Version="3.0.1" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
|
||||
<PackageReference Include="LNURL" Version="0.0.36" />
|
||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||
<PackageReference Include="BTCPayServer.NETCore.Plugins.Mvc" Version="1.4.4" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
@ -60,16 +73,15 @@
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00968" />
|
||||
<PackageReference Include="SSH.NET" Version="2023.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.7" />
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.24.2" />
|
||||
<PackageReference Condition="'$(RazorCompileOnBuild)' == 'false'" Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -4,10 +4,12 @@
|
||||
@using BTCPayServer.Services.Notifications;
|
||||
@using Microsoft.AspNetCore.Identity;
|
||||
@using Microsoft.AspNetCore.Routing;
|
||||
@using Microsoft.Extensions.Localization
|
||||
@implements IDisposable
|
||||
@inject AuthenticationStateProvider _AuthenticationStateProvider
|
||||
@inject NotificationManager _NotificationManager
|
||||
@inject UserManager<ApplicationUser> _UserManager
|
||||
@inject IStringLocalizer StringLocalizer
|
||||
@inject IJSRuntime _JSRuntime
|
||||
@inject LinkGenerator _LinkGenerator
|
||||
@inject BTCPayServerOptions _BTCPayServerOptions
|
||||
@ -16,13 +18,13 @@
|
||||
<div id="Notifications">
|
||||
@if (UnseenCount == "0")
|
||||
{
|
||||
<a href="@NotificationsUrl" id="NotificationsHandle" class="mainMenuButton" title="Notifications">
|
||||
<a href="@NotificationsUrl" id="NotificationsHandle" class="mainMenuButton" title="@StringLocalizer["Notifications"]">
|
||||
<Icon Symbol="nav-notifications" />
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button id="NotificationsHandle" class="mainMenuButton" title="Notifications" type="button" data-bs-toggle="dropdown">
|
||||
<button id="NotificationsHandle" class="mainMenuButton" title="@StringLocalizer["Notifications"]" type="button" data-bs-toggle="dropdown">
|
||||
<Icon Symbol="nav-notifications" />
|
||||
<span class="badge rounded-pill bg-danger p-1 ms-1" id="NotificationsBadge">@UnseenCount</span>
|
||||
</button>
|
||||
@ -31,8 +33,8 @@
|
||||
{
|
||||
<div class="dropdown-menu text-center" id="NotificationsDropdown" aria-labelledby="NotificationsHandle">
|
||||
<div class="d-flex gap-3 align-items-center justify-content-between py-3 px-4 border-bottom border-light">
|
||||
<h5 class="m-0">Notifications</h5>
|
||||
<a class="btn btn-link p-0" @onclick="MarkAllAsSeen" id="NotificationsMarkAllAsSeen">Mark all as seen</a>
|
||||
<h5 class="m-0" text-translate="true">Notifications</h5>
|
||||
<a class="btn btn-link p-0" @onclick="MarkAllAsSeen" id="NotificationsMarkAllAsSeen" text-translate="true">Mark all as seen</a>
|
||||
</div>
|
||||
<div id="NotificationsList" v-pre>
|
||||
@foreach (var n in Last5)
|
||||
@ -54,7 +56,7 @@
|
||||
</div>
|
||||
|
||||
<div class="p-3">
|
||||
<a href="@NotificationsUrl">View all</a>
|
||||
<a href="@NotificationsUrl" text-translate="true">View all</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
@using Microsoft.AspNetCore.Http
|
||||
|
||||
@inject IHttpContextAccessor HttpContextAccessor;
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
|
||||
@if (Users?.Any() is true)
|
||||
{
|
||||
<div @attributes="Attrs" class="@CssClass">
|
||||
<label for="SignedInUser" class="form-label">Signed in user</label>
|
||||
<label for="SignedInUser" class="form-label" text-translate="true">Signed in user</label>
|
||||
<select id="SignedInUser" class="form-select" value="@_userId" @onchange="@(e => _userId = e.Value?.ToString())">
|
||||
<option value="">None, just open the URL</option>
|
||||
<option value="" text-translate="true">None, just open the URL</option>
|
||||
@foreach (var u in Users)
|
||||
{
|
||||
<option value="@u.Key">@u.Value</option>
|
||||
|
@ -5,11 +5,13 @@
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.AspNetCore.Mvc
|
||||
@using Microsoft.AspNetCore.Routing
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject UserManager<ApplicationUser> UserManager;
|
||||
@inject UserLoginCodeService UserLoginCodeService;
|
||||
@inject LinkGenerator LinkGenerator;
|
||||
@inject IHttpContextAccessor HttpContextAccessor;
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject UserLoginCodeService UserLoginCodeService
|
||||
@inject LinkGenerator LinkGenerator
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
@inject IStringLocalizer StringLocalizer
|
||||
@implements IDisposable
|
||||
|
||||
@if (!string.IsNullOrEmpty(_data))
|
||||
@ -18,7 +20,7 @@
|
||||
<div class="qr-container mb-2">
|
||||
<QrCode Data="@_data" Size="Size"/>
|
||||
</div>
|
||||
<p class="text-center text-muted mb-1" id="progress">Valid for @_seconds seconds</p>
|
||||
<p class="text-center text-muted mb-1" id="progress">@StringLocalizer["Valid for {0} seconds", _seconds]</p>
|
||||
<div class="progress only-for-js" data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated @(Percent < 15 ? "bg-warning" : null)" role="progressbar" style="width:@Percent%" id="progressbar"></div>
|
||||
</div>
|
||||
|
@ -110,5 +110,10 @@ namespace BTCPayServer
|
||||
{
|
||||
return ColorTranslator.FromHtml(html);
|
||||
}
|
||||
|
||||
public string ToHtml(Color color)
|
||||
{
|
||||
return ColorTranslator.ToHtml(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,5 @@
|
||||
using System;
|
||||
using System.Security.AccessControl;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Components.AppSales;
|
||||
|
||||
|
@ -2,23 +2,30 @@
|
||||
@using BTCPayServer.Abstractions.TagHelpers
|
||||
@using BTCPayServer.Plugins.Crowdfund
|
||||
@model BTCPayServer.Components.AppSales.AppSalesViewModel
|
||||
@{
|
||||
var label = Model.AppType == CrowdfundAppType.AppType ? "Contributions" : "Sales";
|
||||
}
|
||||
|
||||
<div id="AppSales-@Model.Id" class="widget app-sales">
|
||||
<header class="mb-3">
|
||||
<h3>@Model.Name @label</h3>
|
||||
<h3>
|
||||
@Model.Name
|
||||
@if (Model.AppType == CrowdfundAppType.AppType)
|
||||
{
|
||||
<span text-translate="true">Contributions</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">Sales</span>
|
||||
}
|
||||
</h3>
|
||||
@if (!string.IsNullOrEmpty(Model.AppUrl))
|
||||
{
|
||||
<a href="@Model.AppUrl">Manage</a>
|
||||
<a href="@Model.AppUrl" text-translate="true">Manage</a>
|
||||
}
|
||||
</header>
|
||||
@if (Model.InitialRendering)
|
||||
{
|
||||
<div class="loading d-flex justify-content-center p-3">
|
||||
<div class="spinner-border text-light" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<span class="visually-hidden" text-translate="true">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<script src="~/Components/AppSales/Default.cshtml.js" asp-append-version="true"></script>
|
||||
@ -39,7 +46,15 @@
|
||||
{
|
||||
<header class="mb-3">
|
||||
<span>
|
||||
<span class="sales-count">@Model.SalesCount</span> Total @label
|
||||
<span class="sales-count">@Model.SalesCount</span>
|
||||
@if (Model.AppType == CrowdfundAppType.AppType)
|
||||
{
|
||||
<span text-translate="true">Total Contributions</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">Total Sales</span>
|
||||
}
|
||||
</span>
|
||||
<div class="btn-group only-for-js" role="group" aria-label="Filter">
|
||||
<input type="radio" class="btn-check" name="AppSalesPeriod-@Model.Id" id="AppSalesPeriodWeek-@Model.Id" value="@AppSalesPeriod.Week" @(Model.Period == AppSalesPeriod.Week ? "checked" : "")>
|
||||
|
@ -42,11 +42,19 @@ if (!window.appSales) {
|
||||
render(data, period);
|
||||
}
|
||||
};
|
||||
|
||||
function addEventListeners() {
|
||||
delegate('change', `#${id} [name="AppSalesPeriod-${appId}"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
});
|
||||
}
|
||||
|
||||
delegate('change', `#${id} [name="AppSalesPeriod-${appId}"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
});
|
||||
if (document.readyState === "loading") {
|
||||
window.addEventListener("DOMContentLoaded", addEventListeners);
|
||||
} else {
|
||||
addEventListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
@using BTCPayServer.Plugins.Crowdfund
|
||||
@model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel
|
||||
@{
|
||||
var label = Model.AppType == CrowdfundAppType.AppType ? "contribution" : "sale";
|
||||
}
|
||||
|
||||
<div id="AppTopItems-@Model.Id" class="widget app-top-items">
|
||||
<header class="mb-3">
|
||||
<h3>Top @(Model.AppType == CrowdfundAppType.AppType ? "Perks" : "Items")</h3>
|
||||
<h3>
|
||||
@if (Model.AppType == CrowdfundAppType.AppType)
|
||||
{
|
||||
<span text-translate="true">Top Perks</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">Top Items</span>
|
||||
}
|
||||
</h3>
|
||||
</header>
|
||||
@if (Model.InitialRendering)
|
||||
{
|
||||
<div class="loading d-flex justify-content-center p-3">
|
||||
<div class="spinner-border text-light" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<span class="visually-hidden" text-translate="true">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<script src="~/Components/AppTopItems/Default.cshtml.js" asp-append-version="true"></script>
|
||||
@ -45,7 +51,31 @@
|
||||
@entry.Title
|
||||
</span>
|
||||
<span class="app-item-value" data-sensitive>
|
||||
<span class="text-muted">@entry.SalesCount @($"{label}{(entry.SalesCount == 1 ? "" : "s")}"),</span>
|
||||
<span class="text-muted">
|
||||
@entry.SalesCount
|
||||
@if (Model.AppType == CrowdfundAppType.AppType)
|
||||
{
|
||||
if (entry.SalesCount == 1)
|
||||
{
|
||||
<span text-translate="true">contribution</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">contributions</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry.SalesCount == 1)
|
||||
{
|
||||
<span text-translate="true">sale</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">sales</span>
|
||||
}
|
||||
},
|
||||
</span>
|
||||
@entry.TotalFormatted
|
||||
</span>
|
||||
</div>
|
||||
@ -55,7 +85,14 @@
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3">
|
||||
No @($"{label}s") have been made yet.
|
||||
@if (Model.AppType == CrowdfundAppType.AppType)
|
||||
{
|
||||
<span text-translate="true">No contributions have been made yet.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">No sales have been made yet.</span>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
@using BTCPayServer.Payments
|
||||
@model BTCPayServer.Components.InvoiceStatus.InvoiceStatusViewModel
|
||||
@inject Dictionary<PaymentMethodId, IPaymentModelExtension> Extensions
|
||||
@inject Dictionary<PaymentMethodId, ICheckoutModelExtension> Extensions
|
||||
|
||||
@{
|
||||
var state = Model.State.ToString();
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="d-inline-flex align-items-center gap-2">
|
||||
@if (Model.IsArchived)
|
||||
{
|
||||
<span class="badge bg-warning">archived</span>
|
||||
<span class="badge bg-warning" text-translate="true">archived</span>
|
||||
}
|
||||
<div class="badge badge-@badgeClass" data-invoice-state-badge="@Model.InvoiceId">
|
||||
@if (canMark)
|
||||
@ -21,13 +21,13 @@
|
||||
<div class="dropdown-menu">
|
||||
@if (Model.State.CanMarkInvalid())
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="invalid">
|
||||
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="invalid" text-translate="true">
|
||||
Mark as invalid
|
||||
</button>
|
||||
}
|
||||
@if (Model.State.CanMarkComplete())
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="settled">
|
||||
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="settled" text-translate="true">
|
||||
Mark as settled
|
||||
</button>
|
||||
}
|
||||
@ -62,6 +62,6 @@
|
||||
}
|
||||
@if (Model.HasRefund)
|
||||
{
|
||||
<span class="badge bg-warning">Refund</span>
|
||||
<span class="badge bg-warning" text-translate="true">Refund</span>
|
||||
}
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@
|
||||
walletId = Model.WalletObjectId.WalletId
|
||||
}): string.Empty;
|
||||
}
|
||||
<input id="@elementId" placeholder="Select labels" autocomplete="off" value="@string.Join(",", Model.SelectedLabels)"
|
||||
<input id="@elementId" placeholder=@StringLocalizer["Select labels"] autocomplete="off" value="@string.Join(",", Model.SelectedLabels)"
|
||||
class="only-for-js form-control label-manager ts-wrapper @(Model.DisplayInline ? "ts-inline" : "")"
|
||||
data-fetch-url="@fetchUrl"
|
||||
data-update-url="@updateUrl"
|
||||
|
@ -43,33 +43,33 @@
|
||||
<span text-translate="true">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
@if (ViewData.IsActivePage([StoreNavPages.General, StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Roles, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails, StoreNavPages.Forms]))
|
||||
@if (ViewData.IsPageActive([StoreNavPages.General, StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Roles, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails, StoreNavPages.Forms]))
|
||||
{
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Model.Store.Id" text-translate="true">Rates</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Model.Store.Id" text-translate="true">Checkout Appearance</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Model.Store.Id" text-translate="true">Access Tokens</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Users)" asp-controller="UIStores" asp-action="StoreUsers" asp-route-storeId="@Model.Store.Id" text-translate="true">Users</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Roles))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Roles)" asp-controller="UIStores" asp-action="ListRoles" asp-route-storeId="@Model.Store.Id" text-translate="true">Roles</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Webhooks)" asp-controller="UIStores" asp-action="Webhooks" asp-route-storeId="@Model.Store.Id" text-translate="true">Webhooks</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.PayoutProcessors))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.PayoutProcessors)" asp-controller="UIPayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-route-storeId="@Model.Store.Id" text-translate="true">Payout Processors</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Emails))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Emails)" asp-controller="UIStores" asp-action="StoreEmailSettings" asp-route-storeId="@Model.Store.Id" text-translate="true">Emails</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Forms))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Forms)" asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@Model.Store.Id" text-translate="true">Forms</a>
|
||||
</li>
|
||||
}
|
||||
@ -104,16 +104,21 @@
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
@if (ViewData.IsActiveCategory(typeof(WalletsNavPages), scheme.WalletId.ToString()) || ViewData.IsActivePage([WalletsNavPages.Settings], scheme.WalletId.ToString()) || ViewData.IsActivePage([StoreNavPages.OnchainSettings], categoryId))
|
||||
@if (ViewData.IsCategoryActive(typeof(WalletsNavPages), scheme.WalletId.ToString()) || ViewData.IsPageActive([WalletsNavPages.Settings], scheme.WalletId.ToString()) || ViewData.IsPageActive([StoreNavPages.OnchainSettings], categoryId))
|
||||
{
|
||||
@if (!scheme.ReadonlyWallet)
|
||||
{
|
||||
<li class="nav-item nav-item-sub">
|
||||
<a id="WalletNav-Send" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Send, WalletsNavPages.PSBT], scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId" text-translate="true">Send</a>
|
||||
</li>
|
||||
}
|
||||
|
||||
|
||||
<li class="nav-item nav-item-sub">
|
||||
<a id="WalletNav-Send" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Send, WalletsNavPages.PSBT], scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId">Send</a>
|
||||
<a id="WalletNav-Receive" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Receive, scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId" text-translate="true">Receive</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub">
|
||||
<a id="WalletNav-Receive" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Receive, scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId">Receive</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub">
|
||||
<a id="WalletNav-Settings" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Settings, scheme.WalletId.ToString()) @ViewData.ActivePageClass(StoreNavPages.OnchainSettings, categoryId)" asp-area="" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.WalletId.CryptoCode" asp-route-storeId="@scheme.WalletId.StoreId">Settings</a>
|
||||
<a id="WalletNav-Settings" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Settings, scheme.WalletId.ToString()) @ViewData.ActivePageClass(StoreNavPages.OnchainSettings, categoryId)" asp-area="" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.WalletId.CryptoCode" asp-route-storeId="@scheme.WalletId.StoreId" text-translate="true">Settings</a>
|
||||
</li>
|
||||
<vc:ui-extension-point location="wallet-nav" model="@Model" />
|
||||
}
|
||||
@ -140,10 +145,10 @@
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
@if (ViewData.IsActivePage([StoreNavPages.Lightning, StoreNavPages.LightningSettings], $"{Model.Store.Id}-{scheme.CryptoCode}"))
|
||||
@if (ViewData.IsPageActive([StoreNavPages.Lightning, StoreNavPages.LightningSettings], $"{Model.Store.Id}-{scheme.CryptoCode}"))
|
||||
{
|
||||
<li class="nav-item nav-item-sub">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.LightningSettings))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.LightningSettings)" asp-controller="UIStores" asp-action="LightningSettings" asp-route-storeId="@Model.Store.Id" asp-route-cryptoCode="@scheme.CryptoCode">Settings</a>
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.LightningSettings))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.LightningSettings)" asp-controller="UIStores" asp-action="LightningSettings" asp-route-storeId="@Model.Store.Id" asp-route-cryptoCode="@scheme.CryptoCode" text-translate="true">Settings</a>
|
||||
</li>
|
||||
<vc:ui-extension-point location="lightning-nav" model="@Model"/>
|
||||
}
|
||||
@ -290,7 +295,7 @@
|
||||
<span text-translate="true">Server Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
@if (ViewData.IsActiveCategory(typeof(ServerNavPages)) && !ViewData.IsActivePage([ServerNavPages.Plugins]))
|
||||
@if (ViewData.IsCategoryActive(typeof(ServerNavPages)) && !ViewData.IsPageActive([ServerNavPages.Plugins]))
|
||||
{
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
|
||||
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Users" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Users)" asp-action="ListUsers" text-translate="true">Users</a>
|
||||
@ -322,8 +327,9 @@
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
|
||||
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Files" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Files)" asp-action="Files" text-translate="true">Files</a>
|
||||
</li>
|
||||
|
||||
<vc:ui-extension-point location="server-nav" model="@Model"/>
|
||||
}
|
||||
<vc:ui-extension-point location="server-nav" model="@Model"/>
|
||||
<li class="nav-item dropup">
|
||||
<a class="nav-link @ViewData.ActivePageClass(ManageNavPages.Index)" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" id="Nav-Account">
|
||||
<vc:icon symbol="nav-account"/>
|
||||
@ -379,7 +385,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@if (ViewData.IsActiveCategory(typeof(ManageNavPages)) || ViewData.IsActivePage([ManageNavPages.ChangePassword]))
|
||||
@if (ViewData.IsCategoryActive(typeof(ManageNavPages)) || ViewData.IsPageActive([ManageNavPages.ChangePassword]))
|
||||
{
|
||||
<li class="nav-item nav-item-sub">
|
||||
<a id="SectionNav-@ManageNavPages.ChangePassword.ToString()" class="nav-link @ViewData.ActivePageClass(ManageNavPages.ChangePassword)" asp-controller="UIManage" asp-action="ChangePassword" text-translate="true">Password</a>
|
||||
|
@ -1,3 +1,5 @@
|
||||
@using System.Web
|
||||
@using BTCPayServer.TagHelpers
|
||||
@model BasePagingViewModel
|
||||
|
||||
@{
|
||||
@ -13,7 +15,7 @@
|
||||
@if (Model.Skip > 0)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">Prev</a>
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)" text-translate="true">Prev</a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item disabled">
|
||||
@ -35,7 +37,7 @@
|
||||
@if ((Model.Total is null && Model.CurrentPageCount >= Model.Count) || (Model.Total is not null && Model.Total.Value > Model.Skip + Model.Count))
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)">Next</a>
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)" text-translate="true">Next</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@ -45,7 +47,7 @@
|
||||
{
|
||||
<ul class="pagination ms-auto">
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Page Size</span>
|
||||
<span class="page-link" text-translate="true">Page Size</span>
|
||||
</li>
|
||||
@foreach (var pageSize in pageSizeOptions)
|
||||
{
|
||||
@ -85,10 +87,26 @@
|
||||
{
|
||||
// merge both, preferring the `query` properties in case of duplicate keys
|
||||
query = query.Concat(Model.PaginationQuery)
|
||||
.Where(e => e.Value != null)
|
||||
.GroupBy(e => e.Key)
|
||||
.ToDictionary(g => g.Key, g => g.First().Value);
|
||||
}
|
||||
|
||||
return Url.Action(null, query);
|
||||
return ReplaceQueryParameters(query);
|
||||
}
|
||||
|
||||
string ReplaceQueryParameters(Dictionary<string, object> query)
|
||||
{
|
||||
var uri = new Uri(ViewContext.HttpContext.Request.GetCurrentUrlWithQueryString());
|
||||
var queryParams = HttpUtility.ParseQueryString(uri.Query);
|
||||
foreach (var (key, value) in query)
|
||||
{
|
||||
if (value != null) queryParams[key] = value?.ToString();
|
||||
}
|
||||
var uriBuilder = new UriBuilder(uri)
|
||||
{
|
||||
Query = queryParams.ToString()!
|
||||
};
|
||||
return uriBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user