Compare commits

...

660 Commits

Author SHA1 Message Date
8ee707beee Bump NBitcoin (Fix perf problem on signing of big transaction) 2021-10-14 01:00:33 +09:00
e4f47da26a Fix bug: Importing seed with Is hot wallet checked was not working 2021-10-12 18:08:33 +09:00
b5ebd14589 Fix NRE in BaseService.StopAsync 2021-10-12 17:39:48 +09:00
67ba64b0a1 Fix build 2021-10-12 15:45:55 +09:00
b26e8311c1 Bump NBitcoin 2021-10-12 15:42:20 +09:00
0033aab03e Remove types from BTCPayServer.Client 2021-10-11 18:01:32 +09:00
1037fe0b14 Remove unused ExpireInvoiceResponse type 2021-10-11 17:58:01 +09:00
6c688b9684 Make sure cheater scan rpc capabilities 2021-10-11 17:49:04 +09:00
601e17ed0f Fix altcoins docker-compose 2021-10-11 17:39:12 +09:00
ad86c16bc9 Bump Tor 2021-10-11 12:35:26 +09:00
791db983c7 Bumping LND to 0.13.3-beta (#2964) 2021-10-11 12:32:58 +09:00
d7a7382d00 Introduce cheat mode (#2965) 2021-10-11 12:32:09 +09:00
e842a00402 Easier payment testing (#2672)
* Easier payment testing

* WIP, more TODOs and some cleanup. Help is appreciated.

* Added dummy button to expire monitoring (doesn't work yet)

* Added TODO

* Make fake tab default if present

* Split controller and change wording from fake to testing

* Extract and simplify checkout testing UI

* Restrict testing access to regtest

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-10-11 11:11:02 +09:00
78a8c8c1be Add Passport hardware wallet option to the wallet import screens (#2962)
* Add option for Passport

* Add Passport option to QR import

* Add Passport import option
2021-10-10 15:54:46 +09:00
a7c093a0eb Allow email notifications when creating invoices from Web UI (#2959)
Currently invoice email notifications are only sent when the invoice is created via the API. This commit adds an option to set an email address for notifications when an invoice is created from the Web UI.
2021-10-10 15:54:25 +09:00
86956c1e7b More CSP fixes (#2955)
* Fix CSP issue with time format switch on wallet transactions page

* Fix CSP issue with invoice modal link on invoices list page

* Fix CSP issue on FIDO2 auth page

* Fix JS error on FIDO2 auth page

* Minor UI code improvements
2021-10-10 15:52:39 +09:00
54539001f1 Allow User to delete own account (#2949)
* Allow User to delete own account

* Add User delete e2e test

* fix test

* Apply suggestions from code review

Co-authored-by: d11n <mail@dennisreimann.de>

Co-authored-by: d11n <mail@dennisreimann.de>
2021-10-09 12:18:37 +09:00
4321cbf41a Coin selection improvements (#2956)
* Improve coin selection toggling

* Improve coin selection display

Fixes #2948.

* Improve remove destination button

* Display hide unconfirmed only if there are any unconfirmed UTXOs

* Improve label styles

* Test fix

* Add top margin for non-JS coin selection
2021-10-09 10:39:34 +09:00
8d9941bfd2 Update toggle styling to have pointer cursor (#2957) 2021-10-08 14:26:17 +09:00
f9e38deee7 Fix warnings 2021-10-07 19:08:53 +09:00
89fd044b00 Replace pruning wallet by wipe transactions (#2857) 2021-10-07 16:54:22 +09:00
039f88d14c Match Lightning payment based on payment hash if BOLT11 is not the same. (#2773)
* Match Lightning payment based on payment hash if BOLT11 is not the same.

* Fixup

* Fixup

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2021-10-07 16:53:27 +09:00
d3f9eb38a9 Fix annoying err message in logs when a websocket is cancelled 2021-10-07 15:35:19 +09:00
768d97ac6c Fix remaining Safari inline JS CSP issues (#2954)
* fix modfiy webhook inline JS issue

* fix uncofirmed warning issue in Safari

* fix inline JS Safari issues in checkout
2021-10-07 12:27:48 +09:00
154078d46f Fix docker images 2021-10-07 11:20:50 +09:00
d74c6a30c8 Improve perk card styles 2021-10-06 19:32:26 +02:00
1407d5be8d Decrease font size and weight in selection overlay 2021-10-06 19:32:26 +02:00
721c06e157 Crowdfund: Hide theme switch when custom theme is set 2021-10-06 19:32:26 +02:00
a0265f18d9 Crowdfund: Better customization input grouping 2021-10-06 19:32:26 +02:00
8948475cad Crowdfund: Add theme switch to footer 2021-10-06 19:32:26 +02:00
faed5349fb Make theme switch a view component 2021-10-06 19:32:26 +02:00
7bcaf956e7 Grerenfield: Add availableStatusesForManualMarking to Invoice Data (#2934)
closes ##2933
2021-10-06 18:19:34 +09:00
31c2a80758 Add ability to set invoice status from details page (#2923)
* Add ability to set invoice status from details page

* Remove unnecessary "using" statements

* Add print styles

* Fix Safari issues

* Simplify JS

* Update status badge class names

* Update dropdown toggle padding

* Adjust dropdown menu padding
2021-10-06 14:49:57 +09:00
150e4b842c Make sure the process doesn't crash if exception raised in Subscribe 2021-10-06 13:22:55 +09:00
b970f64639 Remove build warnings 2021-10-06 12:53:41 +09:00
08bd13b2cd Make CanUseWebhooks more resilient 2021-10-06 11:25:21 +09:00
6e3d6125c2 Payment Settled Webhook event (#2944)
* Payment Settled Webhook event

resolves #2691

* Move payment methods to payment services
2021-10-05 18:10:41 +09:00
143d5f69c1 Fix CSP for inline handlers in Safari (#2946)
* Fix CSP for inline handlers on LND seed backup page

* Fix CSP for inline handlers on checkout page

* Fix CSP for inline handlers on wallet sign pages

* Fix CSP for inline handlers on invoices list page

* Fix CSP for inline handlers on payouts page

* Fix CSP for inline handlers on confirm API key page

* Fix CSP for inline handlers on store rates page

* Fix CSP for inline handlers on notifications page

* Fix CSP for inline handlers on dynamic DNS page

* Fix CSP for inline handlers on checkout experience page
2021-10-05 15:52:14 +09:00
156ddd24fa Make CanUsePayjoinForTopUp more resilient 2021-10-05 15:47:57 +09:00
cf2c147f4f Bumping versions of libraries and dockerfile 2021-10-05 15:29:26 +09:00
ce1903b2fa Bumping versions of libraries and dockerfile 2021-10-05 15:26:40 +09:00
ccdfe5ac86 Detect websocket connection dropping in vault 2021-10-05 14:41:03 +09:00
b0ef98dd63 Keep websocket connection alive on notifications and vault 2021-10-05 14:30:01 +09:00
5c8e62bd90 Websocket ping message for notifications and vault 2021-10-05 14:14:22 +09:00
6674d76d6d Add missing file 2021-10-05 13:58:40 +09:00
dea747a9a9 Keep connection alive on checkout page by sending ping messages 2021-10-05 13:37:30 +09:00
a32ace1dcb Shorten balance assignment 2021-10-04 17:10:07 +02:00
4785f0a4dd Remove unused Network property 2021-10-04 17:10:07 +02:00
8249d8a0f5 Fix indentation in view 2021-10-04 17:10:07 +02:00
234bc30369 Update in adherence to the design 2021-10-04 17:10:07 +02:00
b7a081b9a4 Show total balances for each currency on List Wallets Page 2021-10-04 17:10:07 +02:00
88c925017d Apply suggestions from code review 2021-10-04 17:06:11 +02:00
a3c2a9ac61 Censor based on permissions 2021-10-04 17:06:11 +02:00
7cad6302b7 Fixed wrong API permission
Viewing store payment methods needs CanViewStoreSettings instead of CanModifyStoreSettings as per the docs
2021-10-04 17:06:11 +02:00
4b34090376 Adjust invoice details header layout 2021-10-04 17:02:57 +02:00
ac554a27b6 Merge pull request #2947 from dennisreimann/faq-links
Fix FAQ links
2021-10-04 14:03:47 +02:00
e1ea7200cf Fix FAQ links
Once more (and for the last time ;)) those linsk changed in btcpayserver/btcpayserver-doc#967
2021-10-04 13:42:31 +02:00
802fec6bf3 Move payment related properties 2021-10-04 09:50:36 +02:00
67aee9bdc6 Improve display and structure of payment related configuration 2021-10-04 09:50:36 +02:00
975ad2f8ab Crowdfund: Display contributions value (#2938) 2021-10-04 07:54:06 +02:00
b0814166f5 Merge pull request #2941 from git-sgmoore/patch-1
Update README.md
2021-10-02 13:23:54 +02:00
8f397865fd Update README.md
Remove one apostrophe
2021-10-01 23:10:43 -07:00
8ffa7525c5 Merge pull request #2939 from dennisreimann/docs-links
Fix documentation links
2021-10-01 18:34:41 +02:00
6048a26511 Fix documentation links
Some links changed with the recent docs restructuring. I've also added recirects for these on the docs side now.
2021-10-01 18:18:54 +02:00
1a8a7fc27f Crowdfund: Limit max width of embedded content like video
Closes #2782.
2021-09-29 07:58:50 +02:00
64901dfc22 Bump postgres 2021-09-29 13:16:53 +09:00
6a28497d33 Fix: Impossible to see relative time of transaction in wallet list 2021-09-29 13:10:00 +09:00
ed1ec2300d Remove Coinswitch entirely 2021-09-28 10:33:11 +02:00
edfde494fa bump bitcoin core 2021-09-28 12:47:34 +09:00
a3cc573e4d Do not test hot reload in CanCreatePayRequest (unreliable) 2021-09-28 12:20:40 +09:00
44fe70ae44 Make CanCreatePayRequest more resilient 2021-09-28 12:05:46 +09:00
d05a9295b3 Make a test more resilient 2021-09-28 11:22:35 +09:00
7aa4cd8104 Plugins: Pass the current Model to the Ui extension points 2021-09-27 14:16:38 +02:00
4a6088b6b9 Bump NBX Client and Plugins packages 2021-09-27 10:26:42 +02:00
d949680d5a fix: Plugins disabled message never dissappers even after re-enabling it. 2021-09-27 09:03:59 +02:00
0cd7380af0 Make CSP accessible to plugins 2021-09-27 08:45:55 +02:00
a3afcd2a6e Crowdfund public UI re-design (#2918)
* Patch bootstrap-vue modal close button

For Bootstrap v5 compatibility, which bootstrap-vue does not have currently.

* Crowdfund: Use common global layout head (and themes)

* Crowdfund: Display disabled alert also for simple variant

* Crowdfund: Unify non-JS/simple and JS-enabled view

* Improve fireworks animation

* Improve layout

Inspired by the Bitcoin Smiles compaign, see  #2783

* Cleanup and remove views

* Fix typo

* Fix test
2021-09-27 11:46:56 +09:00
bd6c7a8c3d Properly handle InvoiceMetadata string properties (Fix #2906) 2021-09-27 11:44:55 +09:00
920955657d Fix camera not working on wallet send (Fix #2922) 2021-09-27 10:32:03 +09:00
d975ea1509 bump 2021-09-26 14:33:36 +09:00
ba5c49a8df Update Changelog 2021-09-26 14:33:36 +09:00
9f6c7180b2 Greenfield quality of life improvements from feedback (#2880)
* Greenfield quality of life improvements from feedback

fix #2854

* Greenfield quality of life improvements from feedback

fix #2855
2021-09-25 14:04:34 +09:00
ef70f4d547 Simplify code with PaymentMethodId.TryParse 2021-09-24 14:21:30 +09:00
8c061b1f07 Attempt cover scenarios of switching back to Bitcoin only after taint (#2881) 2021-09-24 14:16:25 +09:00
0aed8fdb5b Improve app update buttons (#2915)
Better responsive behaviour and clearer distinction between primary and secondary actions. Also improves the wording ("View Crowdfund/Point of Sale" vs. "View app") and gets rid of the superfluous "Back to apps list" button.
2021-09-24 14:13:38 +09:00
6125a99381 Should not be able to activate a payment method on an invoice which is not new 2021-09-24 00:12:12 +09:00
83b54b7be1 Fix infinite loop happening if payment method unavailable with on invoices with lazy activation (#2914) 2021-09-24 00:00:55 +09:00
047222b3ee Fix offcanvas on desktop 2021-09-23 13:45:05 +02:00
2fe1ab83f5 Improve offcanvas nav animation 2021-09-23 13:45:05 +02:00
5372058c33 Use offcanvas nav for mobile 2021-09-23 13:45:05 +02:00
a08261a798 Update dark mode footer colors 2021-09-23 13:44:48 +02:00
869985f005 Finetune footer; remove obsolete images 2021-09-23 13:44:48 +02:00
97b5c473c1 Removes the "Let's Get In Touch!" block from the homepage 2021-09-23 13:44:48 +02:00
337db3daab Align social links to appear above copyright 2021-09-23 13:44:48 +02:00
10da82607d Adds social links to Layout
This removes the logo credits in the base Layout and adds the
links to Github, Mattermost and Twitter.
2021-09-23 13:44:48 +02:00
7f49824783 Improve public LN node info (#2876)
* Add store name to LN info unavailable pae

* Display multiple node info items if available

Allows to view clearnet and Tor connection info side by side.

* Re-add preferOnion for certain cases

* HTML compatible node ids

* Display more node connection failure details

* Fix syntax error

* Update BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>

* View updates

* Revert previous variable change

* Keep logic out of the view

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2021-09-23 20:36:42 +09:00
5d8bc73063 Reset "all stores" values to default 2021-09-23 10:11:37 +02:00
82675384c4 Don't adjust store mode if user is performing an action 2021-09-23 10:11:37 +02:00
3dc663f4d8 Adjust view 2021-09-23 10:11:37 +02:00
2b0fcdf95c [WIP] Fix issues with Authorization Request page
closes #2858
2021-09-23 10:11:37 +02:00
d0120f1427 Fix Selenium flakyness on Circle CI (#2904) 2021-09-22 21:31:44 +09:00
ac34109da3 Support preview/codegen for custom currencies in pay button (#2896) 2021-09-22 13:10:52 +09:00
d5c96eee32 Add test 2021-09-21 09:45:41 +02:00
7b252369e9 Add GetPOSItems method 2021-09-21 09:45:41 +02:00
9d48358f2a Rename Unavailable to Disabled 2021-09-21 09:45:41 +02:00
2dcd7db797 Filter unavailable items in controller 2021-09-21 09:45:41 +02:00
c267cf0e9c Rename "hidden" to "unavailable" 2021-09-21 09:45:41 +02:00
403820cf14 Add ability to hide a product from POS app
See discussion here: https://github.com/btcpayserver/btcpayserver/discussions/2827
2021-09-21 09:45:41 +02:00
6d667e2d78 bump redoc 2021-09-21 12:52:31 +09:00
258a19fdf5 Make CSP more specific for docs 2021-09-21 12:47:46 +09:00
1a593a1ced Fix documentation page broken by CSP 2021-09-21 12:43:50 +09:00
b8ac57a5b3 Merge pull request #2893 from bolatovumar/fix/show-qr-padding
Remove padding from "Show QR" button
2021-09-20 11:37:48 +02:00
b93f3ff445 Remove padding from "Show QR" button 2021-09-18 21:31:23 -07:00
5984edb7f4 Disallow cancelling payment request when "Allow payee to create invoices in their own denomination" is not enabled (#2843)
* Disallow cancelling payment request when "Allow payee to create invoices in their own denomination" is not enabled

close #2802

* Disallow cancelling pending invoice on BE

* Update non-JS version of payment request view to disallow cancelling

* Update CanCancelPaymentWhenPossible

* Fix typo
2021-09-17 10:24:48 +09:00
603bd2692e Summernote: Fix source map reference; remove unused files 2021-09-16 07:30:33 +02:00
2e61bdf88f Update swagger example 2021-09-16 07:29:18 +02:00
e6aa73197a Move GetDefaultPaymentId out of InvoiceExtensions 2021-09-16 07:29:18 +02:00
b2f9353be1 Add tests 2021-09-16 07:29:18 +02:00
5cba59932b Add ability to specify default payment method through Greenfield API 2021-09-16 07:29:18 +02:00
809340e629 Add ability to select default payment method for invoice through UI 2021-09-16 07:29:18 +02:00
bb6a188883 bump nbx in both docker compose envs 2021-09-16 07:19:54 +02:00
24674d354c removes slack + updates columns 2021-09-16 07:18:31 +02:00
e93562b1db Do not publish NewTransactionEvent if GetValidOutputs did not match with the network's. 2021-09-15 07:28:17 +02:00
f7099cf6aa Bump NBX and elements 2021-09-15 07:28:17 +02:00
d88f012d82 Improve warning when creating invoice without wallet (#2844)
* Improve warning when creating invoice without wallet

close #2834

* Update link class name
2021-09-13 14:24:10 +09:00
f96767d3dc Bootstrap update (#2870)
* Update Bootstrap

* Fix notification checkbox styling
2021-09-13 14:23:10 +09:00
6666786b7a Unify Fido2 authentication under two-factor tab (#2866)
* Unify Fido2 authentication under two-factor tab

Closes #2754.

* Improve UI and wording

* Improve register FIDO2 device page
2021-09-13 10:16:52 +09:00
eccbe8e018 Sanitize UrlRoot in PayButton 2021-09-12 20:33:51 +09:00
aac87539ae Fix pay button CSP issue when using modal (#2872)
* Fix pay button CSP issue when using modal

Fixes #2864.

* Use event handler, refactor csp tags

* Fix script indentation

* Fix onsubmit event handler integration

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2021-09-12 20:31:35 +09:00
a8995d2bed Fix unit test fail bc shitcoin 2021-09-10 15:35:51 +09:00
7f40698bba Fix interactive XSS when entering javascript: in store's website. 2021-09-10 10:56:48 +09:00
c4f4c3138c Update Changelog 2021-09-10 00:27:27 +09:00
3b9c7db481 The page could crash if the user clicks too many time on Notificate 'Mark as Seen' 2021-09-10 00:22:33 +09:00
649f650da6 Fix build 2021-09-10 00:16:28 +09:00
4c83265de4 Update Changelog 2021-09-10 00:14:45 +09:00
ad7b62fa3d Fix CSP when there is a theme 2021-09-10 00:14:26 +09:00
650df97e50 Fix Summernote XSS possibility (#2859) 2021-09-09 22:49:30 +09:00
6f75125cf5 bump 2021-09-09 22:17:46 +09:00
26c05a8d5f Changelog 2021-09-09 22:17:22 +09:00
fc4e47cec6 Add CSP at the website level (#2863) 2021-09-09 21:51:28 +09:00
c39f1341aa Fix warnings 2021-09-09 20:31:35 +09:00
a39d1e0886 fix broken plugin page 2021-09-09 13:12:44 +02:00
435f51a777 Fix for crowdfund perk editor
With no title entered, the editor got stuck in an endless loop, because it recursively invoked the save function without checking for the ID fallback being present.

Fixes #2862.
2021-09-09 10:59:03 +02:00
de2c12c1df Fix warning 2021-09-08 18:45:12 +09:00
ae43af999b update HWI lib 2021-09-08 18:27:36 +09:00
14a10eeef8 Remove useless code (#2852) 2021-09-07 19:18:56 +09:00
180be49824 Add "#nullable enable" 2021-09-07 10:39:36 +02:00
5faa756f1c Add check for empty theme URI before saving theme settings 2021-09-07 10:39:36 +02:00
06db29dd43 Delete confirmation modals (#2614)
* Refactor confirm view: separate modal

* Add delete confirmation modals for apps and FIDO2

* Add delete confirmation modals for 2FA actions

* Add delete confirmation modals for api keys and webhooks

* Add delete confirmation modals for stores and store users

* Add delete confirmation modals for LND seed and SSH

* Add delete confirmation modals for rate rule scripting

* Test fixes and improvements

* Add delete confirmation modals for dynamic DNS

* Add delete confirmation modals for store access tokens

* Add confirmation modals for pull payment archiving

* Refactor confirm modal code

* Add confirmation input, update wording

* Update modal styles

* Upgrade ChromeDriver

* Simplify and unify confirmation input

* Test fixes

* Fix wording

* Add modals for wallet replace and removal
2021-09-07 11:55:53 +09:00
6d317937c7 Do not generate payment methods when 0 amount invoice (#2776)
* Do not generate payment methods when 0 amount invoice

* Add test for 0 amoutn invoices
2021-09-07 00:23:41 +09:00
522c990278 Allow creating top-up invoice when min/max payment amount criteria is active
closes #2831
2021-09-06 10:44:10 +02:00
e584ebe7de Decouple taproot activation from support in generate wallet (#2837) 2021-09-04 22:07:09 +09:00
1422bd8540 Decrease notification badge padding (#2841)
Fixes #2840.
2021-09-04 22:06:56 +09:00
fe18e71538 Refactor themes (#2794)
* Remove Bootstrap/Creative CSS file customizability

Customizations should be done using themes

* Remove deprecated Casa and Classic themes

They are still available in the design system repo and should be added as custom theme CSS file

* Use either standard or custom theme

* Remove deprecated themes

* Improve theme select UI

* Finish and refactor theme switching

* updates theme copy

* Update BTCPayServer/Views/Server/Theme.cshtml

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

* Combine creative.css and site.css

Co-authored-by: dstrukt <gfxdsign@gmail.com>
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-09-03 16:16:36 +09:00
e5699f674b Taproot support for wallets (#2830)
* Support taproot for HotWallet

* Support taproot for hardware wallets

* Fix NBX version

* Undo formatting

* Do not show Taproot when not supported

* Create taproot wallet from xpub

* Bug Fix
2021-09-03 15:37:12 +09:00
203db44b4e Patch summernote to fix issues in Chrome/Chromium
As we are using a customized version for the Bootstrap 5 compatibility anyways, with this we are also including the changes proposed in summernote/summernote#4034 to fix #2816.
2021-09-02 15:35:32 +02:00
69c5f5b9e5 Reduce navbar padding 2021-09-02 12:36:02 +02:00
5a16dfced3 Cleanup site.css 2021-09-02 12:36:02 +02:00
cb46ef9e6c Revert semibold links 2021-09-02 12:36:02 +02:00
3b810e9b8d revert left sidebar font size 2021-09-02 12:36:02 +02:00
663dcecb8e Cleanup home styles 2021-09-02 12:36:02 +02:00
fbb5671f89 Cleanup styles 2021-09-02 12:36:02 +02:00
47d348359f Use util classes for navbar padding 2021-09-02 12:36:02 +02:00
ab2f460f35 converts to rem 2021-09-02 12:36:02 +02:00
c77e3a9396 navbar queries added 2021-09-02 12:36:02 +02:00
014c43f79b updates section 2021-09-02 12:36:02 +02:00
21fb3813dd adds section + media query 2021-09-02 12:36:02 +02:00
9a039a747a Move site customizations to design system 2021-09-02 12:36:02 +02:00
17578a4615 Bootstrap updates
- Increase vertical navbar padding
- Update alert close button styles
- Use semibold font for links
2021-09-02 12:36:02 +02:00
abb68d6f59 Improve fee rate component 2021-09-02 12:36:02 +02:00
3c443344a5 Ensure round notification badge 2021-09-02 12:36:02 +02:00
2887017d12 updates line-height, removes sm-table hack 2021-09-02 12:36:02 +02:00
f2286fb1be updates copy to reduce button size 2021-09-02 12:36:02 +02:00
bdd11a14ff adjusts logo size for future size update 2021-09-02 12:36:02 +02:00
18f6e5af4d left sidebar navigation updates 2021-09-02 12:36:02 +02:00
83b07e0caf Refactor tables, remove old customizations 2021-09-02 12:36:02 +02:00
840a5ac4b4 Refactor Bootstrap colors 2021-09-02 12:36:02 +02:00
4923311168 Update Bootstrap and theme variables 2021-09-02 12:36:02 +02:00
748423dfe8 Add UpdateOnChainPaymentMethodRequest 2021-09-02 10:42:41 +02:00
2f4e610900 Add UpdateLightningNetworkPaymentMethodRequest 2021-09-02 10:42:41 +02:00
d39ec97b9a Remove "enabled" option from address preview endpoint 2021-09-02 10:42:41 +02:00
0d08bd3ad1 Minor syntax changes 2021-09-02 10:39:35 +02:00
3343738bb3 Remove back links to POSTed pages 2021-09-02 10:39:35 +02:00
373b0b7850 Remove superfluous export command 2021-09-02 10:39:35 +02:00
aa24da72b9 Improve raw export options 2021-09-02 10:39:35 +02:00
aecc5f3c6e Update wording 2021-09-02 10:39:35 +02:00
3d4ef48ceb View cleanup 2021-09-02 10:39:35 +02:00
9bb74a17d8 Refine views and use cases 2021-09-02 10:39:35 +02:00
b654dfb237 Cleanup initial PSBT input view 2021-09-02 10:39:35 +02:00
1bca8c81a4 Fix Selenium test 2021-09-02 10:39:35 +02:00
4d35fd4ab3 Fix test 2021-09-02 10:39:35 +02:00
bf5ea23bc6 Streamline views 2021-09-02 10:39:35 +02:00
b4c1f695a8 Remove psbt ready view
The transaction info is now shown on the decoded PSBT page.
2021-09-02 10:39:35 +02:00
de3d966835 WIP 2021-09-02 10:39:35 +02:00
bcdb1ab1d8 Improve review/broadcast flow and fix test 2021-09-02 10:39:35 +02:00
63944792b0 Move signing views into wizard 2021-09-02 10:39:35 +02:00
3895b133a3 Move PSBT flow into wizard 2021-09-02 10:39:35 +02:00
4371b81ef3 Adjust existing PSBT views for consistency 2021-09-02 10:39:35 +02:00
cd93a5ab6b Add separate signing flow for PSBT 2021-09-02 10:39:35 +02:00
d9a8443081 lang update 2021-08-31 16:02:26 +09:00
918d3b46f0 The checkout would crash for some client if automatic detection of language was checked, and the browser was not setting the accepted language 2021-08-31 16:01:16 +09:00
f53597845e bump btcpayserver 2021-08-31 15:54:38 +09:00
e65a3efc3c Add payjoin fix to changelog 2021-08-31 15:38:14 +09:00
d74fcad9f4 update changelog 2021-08-31 15:36:01 +09:00
19b88fd986 Fix: Impossible to send to two more than two destination (Fix #2825) 2021-08-31 15:23:56 +09:00
12105ab85c Fix rounding error for the first refund option (#2778) (#2810)
* Fix rounding error for the first refund option (#2778)

* Fix rounding issue
2021-08-31 15:08:33 +09:00
723817e3f8 Fix payment request cloning and unexpire if necessary (#2820)
* Unexpire payment requests without expiry date

* Unset expiry date when cloning payment request

* Syntax and code improvements
2021-08-31 15:07:54 +09:00
101fc51787 Bump NBX (#2818) 2021-08-30 16:42:22 +09:00
58a5f88943 Hide due amount on invoice if it's zero (#2798)
address #2790
2021-08-27 16:02:20 +09:00
2e35d0e178 Fix coldcard wallet export path (#2809) 2021-08-27 16:00:57 +09:00
319efbeb4b Bump c-lightning on docker-compose altcoins 2021-08-26 15:55:07 +09:00
04e6833e22 Bump c-lightning on docker-compose altcoins 2021-08-26 14:44:57 +09:00
748c882ba1 Bump libraries 2021-08-26 14:34:20 +09:00
ab7d2959ea Update clightning 2021-08-23 13:09:29 +02:00
56d3485d49 Removed the tagline character limit in the crowdfunding app 2021-08-23 13:08:55 +02:00
119ab7b2c0 attempt EnsureNewLightningInvoiceOnPartialPayment test fix 2021-08-23 12:34:36 +02:00
d1ea4e4fa4 Fix bug with top-up invoices when used with "Only enable the payment method after user explicitly chooses it" enabled (#2780)
* Fix bug with top-up invoices when used with "Only enable the payment method after user explicitly chooses it" enabled

* Remove unused "using" directives

* Add "#nullable enable" directive

* check for top-up invoice in LightningLikePaymentHandler
2021-08-23 15:13:26 +09:00
de5ab80038 Fix typo 2021-08-15 08:26:02 +02:00
2d23f3e5d4 bump 2021-08-13 15:51:46 +09:00
77d1580ee3 Fix Display app on website root feature 2021-08-13 15:50:26 +09:00
a20906bc12 Fix paybutton like when no amount set 2021-08-12 10:04:23 +09:00
ffbf70d72b Bump BTCPayServer.Lightning nuget 2021-08-10 10:11:16 +02:00
caa3ff616c Add fix to changelog 2021-08-10 10:11:16 +02:00
7a102ee920 Bumpo Clightning to 0.10.1 2021-08-10 10:11:16 +02:00
8451f34302 Revert "Bumpo Clightning to 0.10.1"
This reverts commit e4bd8e310624aee71a84bf7210da79d57d857625.
2021-08-10 09:31:14 +02:00
e4bd8e3106 Bumpo Clightning to 0.10.1 2021-08-10 09:16:25 +02:00
bc4945c584 Fix hotwallet migration 2 2021-08-10 12:13:00 +09:00
fa96deb1de Fix hotwallet migration 2021-08-10 12:07:13 +09:00
a27217dc62 bump 2021-08-09 23:55:31 +09:00
2dfb637e2f Enhance files actions to accept arrays of fileids (#2735)
* Enhanced Files action by modifying it to accept a list of SelectedFileIds

* Added checks to verify all files passed to the files action exist, Updated tests

* Enhanced Files action to accept an array of fileIds

* Removed redundant fileId list
2021-08-09 23:40:55 +09:00
9387c2c771 1.2.0 Changelog (#2737)
Co-authored-by: d11n <mail@dennisreimann.de>
Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
Co-authored-by: Pavlenex <pavlenex@btcpayserver.org>
2021-08-09 15:54:03 +02:00
e7e8ed55c2 Remove support for payout to a Bitcoin Url (#2766) 2021-08-09 22:43:38 +09:00
cf067ca51b Update successful refund message (#2764) 2021-08-09 20:57:29 +09:00
1b7517c05c Add example for orderId query string param in /api/v1/stores/{storeId}/invoices (#2759) 2021-08-09 20:33:56 +09:00
103b3b916b Show new store warning icon only if neither on-chain wallet nor LN is configured (#2760)
close #2755
2021-08-09 20:32:43 +09:00
fa91174b1a Fix finnish 2021-08-09 16:59:52 +09:00
10e3595a82 Fix bulgarian 2021-08-09 16:40:42 +09:00
965beebc66 Fix kazath 2021-08-09 16:37:55 +09:00
83ab1a3b7c Use ulong for configKey of LND rather than uint 2021-08-07 21:52:49 +09:00
7ac83575d4 Update langs 2021-08-05 20:19:51 +09:00
831f73d715 Fix: Swagger Onchain fee rate endpoint mislabeled 2021-08-05 09:28:19 +02:00
245b4ebe63 Remove missing js file from bundles 2021-08-05 07:59:56 +02:00
060f30d0bf Payouts: Unify confirm external payment and manually mark as paid actions (#2751) 2021-08-05 14:47:25 +09:00
f84b2c5160 Add proper error message if node/nbxplorer doesn't support taproot (#2752) 2021-08-05 13:56:31 +09:00
4e56ef636b Make system plugins shown as system plugins
fixes #2741
2021-08-04 16:50:25 +02:00
59d0cf666f Update Clightning to altcoin compose too 2021-08-04 16:12:39 +02:00
54cc574405 Update to Clightning 0.10.0 2021-08-04 15:02:24 +02:00
97d16523b5 Wallet send UI improvements (#2750)
* Remove autocompletion for output fields

* Use consistent wording for available balance

* Set min value and hide number spins for amount inputs

* Move add destination button to destination input field

* Fix fiat amount display for multiple destinations case

* Improve display of multiple destinations

* Fix formatting
2021-08-04 20:58:46 +09:00
80086d76a8 Improve Payment Request view (#2748)
* Improve Payment Request view

Closes #2747.

* Fix payment request invoice listing condition
2021-08-04 16:13:33 +09:00
5bf1161884 PoS UI fixes (#2744)
Fixes  #2743
2021-08-04 13:24:25 +09:00
f49954223a Merge pull request #2746 from dennisreimann/wallet-import-wording
Improve wallet import wording
2021-08-03 14:23:57 +02:00
98d9efc8d6 Merge pull request #2745 from dennisreimann/api-keys-text
Add a period in API Keys description
2021-08-03 14:22:38 +02:00
59f338a0b7 Improve wallet import wording
Closes #2740.
2021-08-03 14:22:26 +02:00
e1eac5c390 Add a period in API Keys description
Fixes #2742
2021-08-03 14:10:48 +02:00
4c818d0359 Implement topup invoices (#2730) 2021-08-03 17:03:00 +09:00
63d4ccc058 Fix missing master fingerprint/account key path on wallet import (#2727)
* Fix missing master fingerprint/account key path on wallet import

Re-adds the view model properties that got removed in 8a1d5bbc57176377a3e76fe3f55f6942bc3fe457 and passes them through the confirm addresses form.

* Add test

This test fails on current master
2021-08-03 14:27:04 +09:00
1b85ab8b54 Improve UI of immature balance, show available balance in wallet list (#2732) 2021-08-01 22:13:12 +09:00
906c7eb7ec Show Immature Balance in walletsend page (#2731)
* Wallet Send page also shows immature balance

* changes ans to and

* Immature Balance msg is not tooltip

* Make msg more clear
2021-08-01 21:12:00 +09:00
2207d836f1 Update NBXplorer again 2021-08-01 14:32:04 +09:00
a9cd27e012 Bumping LND to 0.13.1-beta (#2729) 2021-08-01 09:20:40 +09:00
eb24cf5f84 Merge pull request #2728 from dennisreimann/bg-image
Add white background to Baillie Gifford logo
2021-07-30 19:56:46 +02:00
fae396dc0d Add white background to supporter logo
Addresses btcpayserver/foundation.btcpayserver.org#19. This SVG is also used in the docs.
2021-07-30 19:43:31 +02:00
c70393c7ef Add BG to supporters readme and login page (#2726)
* add Baillie Gifford supporter in readme and login page

* re-format img
2021-07-30 17:48:07 +02:00
a9da79cc58 Use PaymentUrlBuilder for ensuring proper formatting of BIP21 addresses (#2723) 2021-07-30 18:47:02 +09:00
4c57405945 Properly clip taxIncluded and invoice's amount (#2724) 2021-07-30 18:46:49 +09:00
ad8f347989 Refactoring: Allow ViewsRazor extension to be used by plugins
Moves the `ViewsRazor` extension into Abstractions, so that it can be used by plugins.

Separated out of #2701, prerequisite for the LNbank plugin integration.
2021-07-30 08:27:33 +02:00
68595be323 Improved the names of the Invoice webhooks to reflect the event type in code + better descriptions (#2598)
* Improved the names of the Invoice webhooks to reflect the event type in code + better descriptions

* Gave proper name and better description.

* Update BTCPayServer/wwwroot/swagger/v1/swagger.template.webhooks.json

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2021-07-30 14:33:23 +09:00
69b855a1e7 Improves create crowdfund view (#2647)
* adds sizings

* adds section headers

* adds section headers and re-ordering

* more re-ordering

* redorder change

* adds switches

* reorders checkmark

* removes spacer + updates header

* adds sizings

* adds section headers

* adds section headers and re-ordering

* more re-ordering

* redorder change

* adds switches

* reorders checkmark

* removes spacer + updates header

* Upgrade flatpickr and adapt styles

* Improve display names

* Toggle and collapse additional options

* Add missing link attributes

* adds switch + title for enabling public crowdfund

* Add helper text for public crowdfund option

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-07-30 14:31:44 +09:00
6ea96efe68 Update NBitcoin, fix warnings on nullable (#2718) 2021-07-29 20:29:34 +09:00
499a231432 Fix: Payment Request status does not update on invoice marked events or when pr amount is changed (#2700)
fixes #2689
2021-07-28 22:38:26 +09:00
0054fe0886 Refactor copy to clipboard
Separated out of #2701, prerequisite for the LNbank plugin integration.
2021-07-28 08:44:32 +02:00
c59798e9c4 GreenField: Generate Store OnChain Wallet (#2708)
* GreenField: Generate Store OnChain Wallet

* Greenfield: Do not generate wallet if already configured
2021-07-27 23:53:44 +09:00
80483ba76f Start with camera off 2021-07-27 15:31:12 +02:00
5c792c9e09 Make QR Scanner able to switch between sources 2021-07-27 15:31:12 +02:00
fdae221ca5 Allow QR code multiple display modes 2021-07-27 15:31:12 +02:00
ba165ddd4f Local Greenfield Client for Plugins (#2410)
* wip

* Local GreenField Client for Plugins

* support notification handlers being missing

* Initial support for scoped btcpay client

* test out scoped local client

* wip

* small fix

* Throw exception if using local greenfield client and it has not been implemented yet

* adapt based on new changes in BTCPay

* update

* fix tests

* Allow Local client to bypass authorization handler

* Add Misc endpoints to Local API Client

* Add new endpoints

* Apply code review changes
2021-07-27 21:11:47 +09:00
14e4d2d675 Make CSSThemeManager really only focus on theme (#2457)
* Make Settings Repository cache in memory

* Make use of SettingsRepo directly instead of CssThemeManager

* Completely remove CssThemeManager
2021-07-27 21:08:54 +09:00
d505771d96 Make CanUseInternalLightningNode a sub policy of CanModifyServerSettings (#2709)
SInce CanModifyServerSettings can trivially modify any policy around lightning node sharing, it should automatically have access to the lightning node usage perm
2021-07-27 19:23:20 +09:00
d8c1c51a21 Auto-detect language on payment page (#2552)
* Auto-detect language on payment page

based on the requst Accept-Language header, which is the language you configured in your browser/OS and this 99.99% accurate

* Update BTCPayServer/Services/LanguageService.cs

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

* Update BTCPayServer/Services/LanguageService.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Update BTCPayServer/Services/LanguageService.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Update BTCPayServer/Services/LanguageService.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Added loop for all locales in Accept-Language sorted by weight + check if know this language

* New public method so a unit test can be created for it

* Unit test for language detection

* Fix language service when not in browser context

* fall back to default lang

* Auto-detect setting + ?lang=auto support

* Added invoice param "?lang=auto" info to docs

* Using null-coalescing assignment operator

* Reduce complexity and http dependency in language service

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2021-07-27 08:17:56 +02:00
71cbe716f9 fix sync modal styling 2021-07-26 16:44:41 +02:00
f4df850d25 Greenfield: Rename enabledOnly to enabled?
Allows the filter to work in both directions instead
2021-07-26 12:03:11 +02:00
4d538c61b1 Refactor Get Store Payment Methods
Add tests + docs + new pluggbale format for fetching payment method data + client
2021-07-26 12:03:11 +02:00
17e6179fec Add "stores/{storeId}/payment-methods" endpoint
address #2545
2021-07-26 12:03:11 +02:00
b7b2f16925 Improves create point of sale view (#2646)
* re-ordering

* adds section header

* updates label on "products"

* changes button to primary

* moves description

* updates partial

* re-ordering + section headers

* more section heads and ordering

* redorders

* Toggle custom amount and tips settings

* Use display name for point of sale app type

* Use switches for enabling options

* Add space before required indicator

* Set and consolidate view model display names

* Move redirects and custom CSS to additional options

* Revert to checkbox for discounts

* adds padding

* removes bs-parent for multiple open elements on accordion

* adds helper text to discount checkbox

* updates "default view" label text

* wording cleanup

* more wording adjustments

* updates

* Add display names for app types

* Extract template editor inline styles

* updates helper text

* Display names for app types

* Typo fix

* Move template back to editor

* Fix selenium test

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-07-23 12:57:19 +02:00
a7f6bcf36c Updated status message when adding files and related test 2021-07-22 08:49:21 +02:00
590ca4ef56 add support for uploading multiple files to server 2021-07-22 08:49:21 +02:00
04726b3ee4 Payouts: Detect External OnChain Payouts (#2462)
* Refactor and decouple Payout logic

So that we can support lightning + external payout payments

Fixes & refactoring

almost there

final

Remove uneeded payment method checks

Refactor payouts to handle custom payment method specific actions

External onchain payments to approved payouts will now require "confirmation" from the merchant that it was sent by them.

add pill tabs for payout status

* Improve some UX around feature

* add test and some fixes

* Only listen to address tracked source and determine based on wallet get tx call from nbx

* Simplify isInternal for Payout detection

* fix test

* Fix Noreferrer test

* Make EnsureNewLightningInvoiceOnPartialPayment more resilient

* Make notifications section test more resilient in CanUsePullPaymentsViaUI
2021-07-16 09:57:37 +02:00
eb2b523800 Re-enable "Create" button for invoices on correct form input (#2694)
* Re-enable "Create" button for invoices on correct form input

fix #2693

* Use a more specific selector for form inputs
2021-07-14 23:49:06 +09:00
73b461f8d0 Greenfield: add text search terms to an invoice (#2648) 2021-07-14 23:32:20 +09:00
15be593bbd Save paymentRequestId in Metadata when creating invoice for Payment Request (#2644)
* Save paymentRequestId in Metadata when creating invoice

* Added Payment Request ID + link on invoice detail page

* Added paymentRequestId to the webhook payload

* Removed PaymentRequestId from webhook payload (rolled back previous change)

* Using strongly typed InvoiceMetadata

* Added OrderUrl metadata field to invoice + link

* Added Metadata.OrderUrl to docs

* Made orderUrl visible when no orderId is present
2021-07-14 20:43:13 +09:00
73c89ac28d Auto-select store when creating a new invoice (#2680)
* Auto-select store when creating a new invoice

* Set types as nullable in InvoiceController.UI.cs
2021-07-14 20:40:18 +09:00
45679fa29e fix typo in test/[...]/index.cshtml
Fix typo reported by following comment: 179520a211 (r53423646)
2021-07-14 11:04:21 +02:00
861e5b1530 Add webhook delivery status indicator (#2679)
* Add webhook delivery status indicator

As discussed in: https://github.com/btcpayserver/btcpayserver/discussions/2616

* Add relative delivery time to tooltips
2021-07-12 21:58:11 +09:00
d5019f61ce Expose ExplorerClientProvider to plugins via interface 2021-07-12 14:17:25 +02:00
78dd1b0476 Fix store javascript after bootstrap5 update 2021-07-12 14:16:41 +02:00
72d5c11811 Greenfield: Add CanModifyInvoices Permissions (#2595) 2021-07-11 00:30:01 +09:00
aefb81b7f0 Add "skip" and "limit" params for onchain txs API endpoint (#2688)
Discussed here: https://github.com/btcpayserver/btcpayserver/discussions/2667
2021-07-10 13:04:01 +09:00
55cc32ce0f Fix new docker compose incompatibility 2021-07-08 12:53:47 +02:00
060fc46e4f Fix system plugins resource loading 2021-07-08 12:53:34 +02:00
4bb3d60b6c Add misc/permissions to document the hierarchical structure (#2654) 2021-07-08 14:34:10 +09:00
4222d24d51 Fix: Invoice Search Text crashes invoice creation when value is too long (#2675)
This fix truncates the data to the column's max length to avoid this situation. Fixes #2641. Maybe also fixes #2585
2021-07-08 12:59:44 +09:00
6c76866f28 Update InvoiceConfirmed to InvoiceSettled in swagger (#2681)
fix #2580
2021-07-08 12:41:03 +09:00
50be6595bd update InvoiceStatusMark Swagger definition (#2674)
close #2597
2021-07-07 13:27:04 +09:00
91136d2551 remove unused code in fireworks.js (#2673)
relates to #2639
2021-07-07 13:25:34 +09:00
2e822c5878 Hiding NetworkFee row in invoice if not applicable 2021-07-06 21:20:13 +02:00
894dca2eef Update BTCPayServer/Views/Shared/Bitcoin/ViewBitcoinLikePaymentData.cshtml 2021-07-06 21:20:13 +02:00
48355129c5 Properly counting transactions that impact NetworkFee 2021-07-06 21:20:13 +02:00
edc9429e84 Displaying NetworkFee if it was present for the invoice 2021-07-06 21:20:13 +02:00
f1a222fbb3 New unit test to scan for external links/forms and if they have rel="noreferrer noopener" (#2668)
* Unit test to check for (possibly) external links

* Add rel="noreferrer noopener" to all external links so unit test passes

* Update BTCPayServer.Tests/UnitTest1.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Update BTCPayServer.Tests/UnitTest1.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Fixed bad merge from master

* PascalCasing

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2021-07-06 10:35:42 +02:00
40bbc5850f Greenfield doc error. Should be singular. Rewording to focus on a single result. (#2657)
* Should be singular. Rewording to focus on a single result.

* Update BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.json

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Update BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.json

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2021-07-06 10:19:18 +02:00
70baa4087c Hide referer URL to hide our BTCPay Server URL (#2655) 2021-07-05 13:51:00 +09:00
8e4f56bd52 Improves notification view (#2645)
* removes hr, adds notification settings link

* adds if else to show "no notifications" or table view

* adds icon to settings button

* Fix link to notification settings

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-07-05 12:22:24 +09:00
3800780ef2 PoS: Fix images in cart view (#2649)
Fixes an issue I overlooked in #2449 and which was brought up by @dstrukt [on Mattermost](https://chat.btcpayserver.org/btcpayserver/pl/jzxqs7xw33rjfe5itorxygifbe).
2021-07-02 11:28:38 +09:00
6e154f6cbc Merge pull request #2642 from dennisreimann/patch-1
2FA: Fix login partial name
2021-06-30 12:26:19 +01:00
673b6d6733 2FA: Fix login partial name
Fixes a typo in the filename, which leads to an exception on login when 2FA is enabled.
2021-06-30 13:11:27 +02:00
6185b93b59 Upgrade to Bootstrap v5.0.2 (#2638) 2021-06-30 19:03:43 +09:00
9bbaae9dea Fix visual bug with invoices search help text overlapping invoice action buttons (#2583)
* Fix visual bug with invoices search help text overlapping invoice action buttons

close #2582

* Update BTCPayServer/Views/Invoice/ListInvoices.cshtml

Co-authored-by: d11n <mail@dennisreimann.de>

Co-authored-by: d11n <mail@dennisreimann.de>
2021-06-30 17:05:34 +09:00
6c856aba48 Introduce Server paging for Payouts List (#2564)
* Introduce Server paging for Payouts List

* Add paging params

* Minor code and formatting improvements

* View updates

* Apply suggestions from code review

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

* fix tests

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-06-30 16:59:01 +09:00
33de4cccfc Add permission key to API page (#2599)
* Better API key page

Now we can see the permission code + Fixed a margin-bottom when selecting a specific store

* Improve responsiveness of API key permissions

* Improve webhook editing UX

Jumps to edited item after submit

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-06-30 16:57:38 +09:00
6a79c8a27a Improve create payment request view (#2627)
* formatting

* more edits

* adds section headers

* updates wording

* Update BTCPayServer/Views/PaymentRequest/EditPaymentRequest.cshtml

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

Co-authored-by: d11n <mail@dennisreimann.de>
Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-06-30 16:56:28 +09:00
631deb9ce7 Removes lightning setup & wallet setup back button (#2628)
* removes back button

* removes wallet back on setupwallet page

* removes navbar section completely

* remove back button on modify wallet page

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-06-30 14:48:28 +09:00
cb2dd464f1 GreenField: Add StoreId to Invoice model (#2592) 2021-06-24 23:15:51 +09:00
dca376cb46 Add files via upload (#2615) 2021-06-24 23:13:06 +09:00
89d6c83504 Improves Create Store & Create App views (#2608)
* improves create store

* redorders form elements
2021-06-24 23:06:55 +09:00
2ea3baf36e Minor UI improvements (#2606)
* Added "Checkout" button to invoice detail page

* Added missing icon

All other pages have a "plus" icon on the primary button, except this page

* Button icons are always in front, not after like here was
2021-06-24 19:52:41 +09:00
3338dcac1c Richtext editor fixes (#2613)
* UPdate summernote with Bootstrap 5 integration

* Unify summernote options
2021-06-24 19:51:35 +09:00
0b9a2ee8ec Merge pull request #2581 from JimiHFord/fix-invoice-typo
fix invoice typo
2021-06-20 08:14:26 +02:00
78ac1ee15d fix invoice typo 2021-06-19 20:02:55 -07:00
886e9ab511 Update WebhookDataBase Swagger definition (#2578)
close #2577
2021-06-18 11:01:23 +09:00
3c80621dac Add payjoin option to hot wallet setup (#2450)
* Add payjoin option to hot wallet setup

Enables payjoin by default when creating a hot wallet and offers the user an opt-out.

Test fix

* Display PayJoin option only if it is available

* Test fixes

* Update hot wallet checks

* Test fix after rebase

* Use toggle buttons for enabling options
2021-06-18 10:25:17 +09:00
6b4ff4ce2c Merge pull request #2575 from NicolasDorier/refactor/wallet-cleanup
Cleanup some old code in wallet setup
2021-06-17 21:20:28 +09:00
b22aa778e1 Better webhook UI. Full URLs are visible. (#2572)
* Better webhook UI. Full URLs are visible.

* Using Bootstrap CSS class instead of a new class

* Created the generic class .sm:text-nowrap

* Renamed class

* Changed "sm" to "md"
2021-06-17 19:43:47 +09:00
1c440ed36c Update Maintenance.cshtml (#2576)
* Update Maintenance.cshtml

* fix BTCPay Server maintenance error

* Rephrase "derivation settings" to "wallet settings"

* Pay button text fix

* rephrase CoinSwitch description
2021-06-17 19:40:08 +09:00
70f56d5920 Encrypt WalletSetupViewModel.Config 2021-06-17 19:26:50 +09:00
8a1d5bbc57 Remove outdated code in UpdateWallet 2021-06-17 16:39:55 +09:00
956370592f Create a dedicated IsHotwalletProperty in the DerivationSchemeSettings 2021-06-17 15:36:22 +09:00
afd479ac69 Remove the DerivationSchemeViewmodel.Enabled property 2021-06-17 15:08:04 +09:00
39a4be5641 Remove HintAddress 2021-06-17 14:11:01 +09:00
70fcd053cd Signing context cleanup (#2568)
* Signing Context cleanup

Removes extra NBXSeedAvailable property in SigningContext, which got introduced in #2559. It is not needed since each view model involved already has it.

* Fix button in link
2021-06-15 17:35:23 +09:00
c9869922f7 Merge pull request #2569 from pavlenex/pnxbet
add pnxbet to readme
2021-06-14 18:27:15 +02:00
8b6a333cd2 add pnxbet to readme 2021-06-14 18:17:40 +02:00
ae329e371a fix link test 2021-06-14 11:32:35 +02:00
cf7c3c2bf7 LightningSupportedPaymentMethod.CryptoCode is never null 2021-06-14 18:22:00 +09:00
3d21d2724e Fix bug with LN payment method API endpoint throwing 500 (#2567)
close #2566
2021-06-14 18:19:52 +09:00
df64a93808 add pnxbet supporter 2021-06-14 08:42:05 +02:00
6068d384a4 fix missing bundle tag helper 2021-06-14 08:18:03 +02:00
3c0292f074 Wallet: Signing UI improvements (#2559)
* Refactoring to generalize wizard layout

* Wallet: Add intermediate signing options view

* Update BTCPayServer/Views/Wallets/WalletSigningOptions.cshtml

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

* Skip signing options for hot wallets

* Update signing options wordings, add PSBT doc link

* Fix test

* Remove form route params

* Use decode command for PSBT

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-06-14 14:06:56 +09:00
371acc84a8 Do not retrieve all payouts in GetPullPayment every time 2021-06-10 18:54:27 +09:00
cd9feccf6e Mark Payouts as Paid (#2539)
* Mark Payouts as Paid

This PR allows users to mark payouts as paid manually through the UI  and through the API. It also sets up the payout proof system to be able store a manual proof that will in a later PR allow you to specify a proof of payment (link or text)

* add docs, test and greenfield client

* remove extra docs stuff

* Update BTCPayServer.Tests/GreenfieldAPITests.cs

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

* clean up pull payment/payouts fetch code

* Ensure payoutis are retrieved with pull payment

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-06-10 18:43:45 +09:00
f1f3dffc97 Column improvements quick patch (#2562)
* removes title, switches search input with title

* adds CSS change

* Update BTCPayServer/Views/Server/ListUsers.cshtml

Breakpoint improvement

Co-authored-by: d11n <mail@dennisreimann.de>

* Update BTCPayServer/wwwroot/main/site.css

Co-authored-by: d11n <mail@dennisreimann.de>

* github revision fix

* spacing

* Simplify file details display

* Resolve users list conflict

* Indent fix

* Fix Tor casing

Co-authored-by: d11n <mail@dennisreimann.de>
2021-06-09 17:53:24 +09:00
e4f298acac Set lightning invoice fallback in QR code as uppercase (#2492)
* Set lightning invoice fallback in QR code as uppercase

On the lightning payment view the invoice is encoded as uppercase inside the QR code; however this is not true for the on-chain payment QR code if the invoice is set as fallback option.

This commit sets the fallback invoice as uppercase inside the QR code.

* Adds test case for uppercase fallback lightning invoice

* Apply suggestions from rockstar's review

Co-authored-by: Kukks <evilkukka@gmail.com>
2021-06-08 12:24:17 +09:00
e93b030bfe Update HWI library, warn users to run newer version (#2544)
* Update HWI library, warn users to run newer version

* Update BTCPayServer/wwwroot/js/vaultbridge.ui.js

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

* Update BTCPayServer/wwwroot/js/vaultbridge.ui.js

Co-authored-by: Pavlenex <pavle@pavle.org>

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
Co-authored-by: Pavlenex <pavle@pavle.org>
2021-06-06 21:02:15 +09:00
64a7abe53a Bootstrap migration fixups (#2534)
* Remove xxl breakpoint

* Remove code bg

* Form updates

* Update PoS accordion

* Update forms

* Fix webhook password toggle

* Update highlight js styles

* Page updates

* Style unformatted checkboxes

* Fix typo

* Update accordions

* Update policies domain mapping

* Update toggles and checkboxes

* Update storage pages

* Fix specter logo filename casing

* Update checkout experience view

* Update webhook view

* Re-add used negative margins

* Update bootstrap

* POS layout fixes

* Decrease size of info icon in main headline

* Update BTCPayServer/Views/Stores/ModifyWebhook.cshtml

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-06-06 20:44:54 +09:00
784a40e2ed Server Settings Bootstrap/UI Tweaks (#2558)
* server > services columns cleanup

* server > services column patch

* server > files adds text-end to table action column

* server > maintenance updates column

* removes title, switches search input with title

* adds CSS change

* Update BTCPayServer/Views/Server/ListUsers.cshtml

Breakpoint improvement

Co-authored-by: d11n <mail@dennisreimann.de>

* Update BTCPayServer/wwwroot/main/site.css

Co-authored-by: d11n <mail@dennisreimann.de>

Co-authored-by: d11n <mail@dennisreimann.de>
2021-06-06 20:41:14 +09:00
9ecd27dc85 Merge pull request #2474 from bolatovumar/feat/test-webhooks
Add ability to send test webhook call
2021-06-04 13:01:57 +02:00
72ef23786b Merge pull request #2340 from bolatovumar/feat/greenfield-api-delete-user
Add DELETE action for api/v1/users endpoint
2021-06-04 12:30:36 +02:00
28da78fc78 Improve tests and small refactoring 2021-06-04 12:20:45 +02:00
c345e81902 Simplify template 2021-06-03 21:16:27 -07:00
1b39742919 Simplify FirstOrDefault method call 2021-06-03 21:16:27 -07:00
bc33b32522 Add delivery ID to test webhook succes message 2021-06-03 21:16:27 -07:00
4671f08b64 Augment webhook test error message 2021-06-03 21:16:26 -07:00
9f6f74df59 Show success/error message after sending test webhook 2021-06-03 21:16:26 -07:00
872c99e033 Rename SendDelivery to SendAndSaveDelivery 2021-06-03 21:16:26 -07:00
cb6e8a6937 Simplify TestWebhook method 2021-06-03 21:16:26 -07:00
7c16f8f134 Allow specifying WebhookDeliveryData webhookid right away 2021-06-03 21:16:25 -07:00
ad11b61af2 Formatting 2021-06-03 21:16:25 -07:00
120b82ae00 Update webhook test page design 2021-06-03 21:16:25 -07:00
7cf6c97d3f Return view after webhook test POST call 2021-06-03 21:16:25 -07:00
2262acf12b Add ability to send test webhook call 2021-06-03 21:16:24 -07:00
ca3f97c42f Add missing API key type description 2021-06-02 20:12:53 -07:00
8731055786 Use UserNotFound instead of NotFound 2021-06-02 20:02:31 -07:00
e943c55a91 Add basic tests for delete user endpoint 2021-06-02 20:02:31 -07:00
be05b6aa90 Specify correct return type on delete user methods 2021-06-02 20:02:31 -07:00
0b4d94faf2 Remove unnecessary null check 2021-06-02 20:02:30 -07:00
9f8f677125 Remove unused IsAdmin method 2021-06-02 20:02:30 -07:00
949d6bf584 Replace admin check with CanModifyServerSettings authorization policy 2021-06-02 20:02:30 -07:00
9fc2d2b76b Remove CanDeleteUser constraint from "/api/v1/users/{userId}" endpoint 2021-06-02 20:02:29 -07:00
d9935ada9d Add "/api/v1/users/me" endpoint 2021-06-02 20:02:29 -07:00
b4076b53e8 Add IsAdminUser method to UserService 2021-06-02 20:02:28 -07:00
53c81918a5 Enable nullable reference checking in UsersController 2021-06-02 20:02:28 -07:00
7c11736992 Enable nullable reference checking in UserService 2021-06-02 20:02:28 -07:00
49f168b7b3 Update delete user swagger description 2021-06-02 20:02:27 -07:00
104092702a Simplify DeleteUser method 2021-06-02 20:02:27 -07:00
2ed8341403 Add IsRoleAdmin to user service 2021-06-02 20:02:26 -07:00
e5a196918f Add user service 2021-06-02 20:02:26 -07:00
907ae760e0 Add CanDeleteUser policy 2021-06-02 20:02:25 -07:00
37f7c4e0f9 Add DELETE user Swagger docs 2021-06-02 20:02:25 -07:00
fe107cd23f Add DELETE action for api/v1/users endpoint 2021-06-02 20:02:22 -07:00
a830c1e812 Use ~ prefix for local PoS image references 2021-06-01 14:34:06 +02:00
8eb85acb88 Self-host PoS sample images
As brought up in #2447 and previously been worked on in #1039.
2021-06-01 14:34:06 +02:00
ea8f510eff Merge pull request #2536 from pavlenex/remove-supporters
Remove supporters
2021-05-30 17:43:58 +02:00
cbec44bb73 Fix QR Code shower 2021-05-30 10:38:21 +02:00
ac44ed56e0 fixx broken shopify links 2021-05-26 13:11:59 +02:00
dbf2f99c4e Fixing issue with mysql migration and maxLength (#2541) 2021-05-25 16:03:49 +09:00
3b04ca229f remove unused images 2021-05-20 11:05:11 +02:00
5b1a171d27 remove supporters from the front page
- BTSE (Expired)
- OkCoin (Expired)
- DGLab (Expired)
- ACINQ (Remove due to front-page tier)
2021-05-20 11:02:18 +02:00
9138c6c70a remove past supporters from the readme
Remove:
- BTSE
- OKCoin
- DGLabs
2021-05-20 10:59:27 +02:00
f1092f910a bump 2021-05-19 20:19:22 +09:00
ebc9ddfeb1 changelog 1.1.2 2021-05-19 20:18:09 +09:00
201b4d6ec0 Fix Shopify parsing (#2530) 2021-05-19 16:26:54 +09:00
3b375929c1 Support wider server sync info in greenfield server info (#2511)
fixes #2498
2021-05-19 13:07:28 +09:00
ed7031981b Bootstrap v5 migration (#2490)
* Swap bootstrap asset files

* Update themes and color definitions

* Move general bootstrap customizations

* Theme updates

Theme updates

* Remove BuildBundlerMinifier

This lead to an error, because BuildBundlerMinifier and BundlerMinifier.Core seem to conflict here. Details: https://stackoverflow.com/a/61119586

* Rewplace btn-block class with w-100

* Update badge classes

* Remove old font family head variable

* Update margin classes

* Cleanups

* Update float classes

* Update text classes

* Update padding classes

* Update border classes

* UPdate dropdown classes

* Update select classes

* Update neutral custom props

* Update bootstrap and customizations

* Update ChromeDriver; disable smooth scroll

https://github.com/SeleniumHQ/selenium/issues/8295

* Improve alert messages

* Improve bootstrap customizations

* Disable reduced motion

See also 7358282f

* Update Bootstrap data attributes

* Update file inputs

* Update input groups

* Replace deprecated jumbotron class

* Update variables; re-add negative margin util classes

* Update cards

* Update form labels

* Debug alerts

* Fix aria-labelledby associations

* Dropdown-related test fixes

* Fix CanUseWebhooks test

* Test fixes

* Nav updates

* Fix nav usage in wallet send and payouts

* Update alert and modal close buttons

* Re-add backdrop properties

* Upgrade Bootstrap to v5 final

* Update screen reader classes

* Update font-weight classes

* Update monospace font classes

* Update accordians

* Update close icon usage

* Cleanup

* Update scripts and style integrations

* Update input group texts

* Update LN node setup page

* Update more form control classes

* Update inline forms

* Add js specific test

* Upgrade Vue.js

* Remove unused JS

* Upgrade Bootstrap to v5.0.1

* Try container related test updates

* Separate jQuery bundle

* Remove jQuery from LND seed backup page

* Remove unused code

* Refactor email autofill js

* Refactor camera scanner JS

* Re-add tests

* Re-add BuildBundlerMinifier

* Do not minify bundles containing Bootstrap

Details https://github.com/madskristensen/BundlerMinifier/issues/558

* Update bundles

* Cleanup JS test

* Cleanup tests involving dropdowns

* Cleanup tests involving collapses

* Cleanup locale additions in ConfigureCore

* Cleanup bundles

* Remove duplicate status message

* Cleanup formatting

* Fix missing validation scripts

* Remove unused unminified Bootstrap js files

* Fix classic theme

* Fix Casa theme

* Fix PoS validation
2021-05-19 11:39:27 +09:00
0eace936b0 Bump 2021-05-14 17:21:32 +09:00
0d70743a47 Do not spam logs with events 2021-05-14 17:17:46 +09:00
778a0f7e8d Fix: Lightning Listener not listening on Lightning payment after activating when using lazy payments (#2524) 2021-05-14 17:17:07 +09:00
a8cf6ee8a2 add coinswitch changelog 2021-05-14 10:03:13 +02:00
06a150a558 Changelog for 1.1.1 2021-05-14 16:20:47 +09:00
c551e5cd0a Make sure to only select accounted payments where we should (#2523) 2021-05-14 16:16:19 +09:00
776ded0b7e Fix Coinswitch enabledflag and nav category 2021-05-14 09:08:32 +02:00
9e3d99ec39 Do not automatically apply our datetimeoffset binder 2021-05-14 15:47:19 +09:00
6c9c7328bb Fix: Payout Transaction not matching when rate provided longer decima… (#2518)
* Fix: Payout Transaction not matching when rate provided longer decimal precision

fixes #2513 most likely

* Do not sue rounding and ensure crypto amount is saved with correct decimal places

* Round pull payment payout to the nearest digit supported by network

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2021-05-13 17:50:08 +09:00
2961a2ed53 Merge pull request #2506 from Kukks/fix-pay-button-steps
Fix Pay Button steps and range validation
2021-05-13 17:33:34 +09:00
12836b9f60 Merge pull request #2512 from NicolasDorier/udoqnf
Make sure BTCPayServer does not take the culture of the server
2021-05-13 17:32:05 +09:00
17cef99ce8 Spelling improvement (#2520) 2021-05-13 17:31:08 +09:00
2194fe43d2 Do not show payments which are not accounted in Payment request 2021-05-12 17:46:57 +09:00
06fef563e3 Fixed 2 typos (#2514)
* Fixed 2 typos

* Update BTCPayServer/Controllers/ManageController.APIKeys.cs

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

* Update BTCPayServer/Controllers/ManageController.APIKeys.cs

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-05-07 18:51:10 +02:00
c38eeddc55 Update BC-UR bundle and support decoding hex format of wallet (#2505) 2021-05-08 00:35:43 +09:00
84ec33afb3 Update BitCore branding (#2501)
Co-authored-by: dalijolijo <dalijolijo@v220200132547106119.bestsrv.de>
2021-05-08 00:34:38 +09:00
4b30132d06 Make sure BTCPayServer does not take the culture of the server 2021-05-06 20:28:02 +09:00
38942aef25 remove log 2021-05-05 12:19:35 +02:00
200e8330d8 Fix Pay Button steps and range validation
There were numerous issues around different button types and the amount range validation. Also fixes #2503
2021-05-05 11:27:02 +02:00
86092fc955 Wrap more plugin loading logic in try catch to not crash fatally 2021-05-03 08:35:54 +02:00
50cd4cb3ce bump version 2021-04-29 12:52:55 +09:00
7c857118fa Update Changelog (#2480) 2021-04-29 12:51:35 +09:00
4e1b18e2bb do not crash invoice if wellknown metadata keys used with different e… (#2448)
* do not crash invoice if wellknown metadata keys used with different expected types

* fix

* add bits from alt PR
2021-04-28 16:49:10 +09:00
5fe3c1c61f U2fremove (#2496)
* Remove U2F support and JS

* fix final changes

* fix more final stuff
2021-04-28 16:22:09 +09:00
270bd98a10 Remove test code from Fido2 that got merged 2021-04-28 06:26:59 +02:00
02bf5afe0b Migrate existing U2F to Fido2 (#2484)
* Migrate existing U2F to Fido2

This seamlessly switches all u2f registrations over to the new FIDO2 support. Please note that I have not yet added a way to drop the u2f DB and its UI so that we can test the migration works properly for all.

* add testing logic

* fix u2f tests

* remove duplicate status message

* fix test and namespaces

* fix test
2021-04-28 13:14:15 +09:00
c878f63f99 Fix: Double entry for Api Key nav item in Account navigation menu 2021-04-27 14:06:34 +02:00
8083935b47 Greenfield: Deleting a store in the server, should delete only webhooks of this store 2021-04-27 18:55:38 +09:00
c7d11132e5 Merge pull request #2491 from dennisreimann/wallet-enabling-fix
Offer enabling toggle only for wallets that are set up
2021-04-27 11:16:10 +02:00
69442a49f1 Offer enabling toggle only for setup wallets 2021-04-27 09:27:52 +02:00
5f9ef0ff62 Greenfield: Human friendly error if webhook or delivery not found 2021-04-27 15:38:42 +09:00
0888cc4f97 Fix: Add Fido2 log filter to tests 2021-04-27 08:18:01 +02:00
6821a9a0a4 Fix: Coinswitch plugin missing nav specification 2021-04-27 07:37:08 +02:00
003e8a979b Fix: Plugins integrations formatting text wrap 2021-04-27 07:36:42 +02:00
08e8050ca5 Add button to copy API key to clipboard (#2439)
* Add button to copy API key to clipboard

* Update "copy API key to clipboard" button appearance
2021-04-27 11:30:26 +09:00
8d5c3c5cdd ModelBinders should not throw exception, but return error on ModelState 2021-04-26 12:55:03 +09:00
dcc4214dcb Make sure model binder error are returning error 422, use DateTimeOffsetModelBinder 2021-04-26 12:37:56 +09:00
ded55a1440 customized api/v1/invoices query parameters to filter results (#2461)
* customized api/v1/invoices query parameters to filter results

* customized api/v1/invoices query parameters to filter results

* update swagger and make parameters as arrays

* change startDate and endDate types to UnixTimestamp

* update invoice status type in swagger and better handle dateTimeoffset

* change status type to array of InvoiceStatus to match controller

* change status type to array of InvoiceStatus to match controller

Co-authored-by: somera <somera@tesla.com>
2021-04-26 11:32:44 +09:00
e0ff03068a add altcoin support for althash / htmlcoin (#2485)
* Switch Althash, keypath, hitbtc ticker

* add Althash

* add Althash

* switch to coingecko provider

* add Althash logo

* Fix stacking issue with rate call

* bump NBitcoin to 5.0.77

* bump NBXplorer.Client to 3.0.21
2021-04-26 11:31:00 +09:00
60c05cab55 Merge pull request #2477 from dennisreimann/lightning-setup-redesign
Lightning setup redesign
2021-04-22 17:39:05 +09:00
9c480580ba Merge pull request #2483 from dennisreimann/mobile-unread-count
UI: Fix unread count badge on mobile
2021-04-21 17:43:12 +02:00
f3c3f397eb UI: Fix unread count badge on mobile 2021-04-21 17:38:17 +02:00
f6c7f61ec3 Update BTCPayServer/Views/Stores/SetupLightningNode.cshtml
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-21 14:52:45 +02:00
aae5cce175 Improve custom node details toggling 2021-04-21 14:38:53 +02:00
8a9bea4603 Wording and display improvements 2021-04-20 14:52:00 +02:00
0e3f6acb0a Final tweaks and test fix 2021-04-20 12:23:50 +02:00
bbe621109f Lightning setup redesign 2021-04-20 12:09:27 +02:00
35f47bdfb7 Add and update theme colors 2021-04-20 12:09:26 +02:00
8ca0be5a4d Merge pull request #2453 from bolatovumar/unify-pos-app-card-markup
Unify POS app card markup
2021-04-20 18:29:50 +09:00
b6bd7cce6d Merge pull request #2446 from NicolasDorier/fweihnq
Give better error for greenfield API
2021-04-20 18:25:10 +09:00
a7cbb3941a Give better error for greenfield API 2021-04-20 18:21:26 +09:00
8957720c94 Merge pull request #2469 from dennisreimann/store-payment-method-toggling
Move payment method enabling toggle to store overview
2021-04-20 17:25:34 +09:00
452915478b UI: Fix duplicate status messages (#2481)
The status message is included in the NavLayout, hence the pages that use this layout should not include the status message.

Fixes #2478.
2021-04-20 16:16:21 +09:00
962c80fff8 Remove permission check
Ensured via the controller, see https://github.com/btcpayserver/btcpayserver/pull/2469#discussion_r616289628
2021-04-20 09:10:23 +02:00
aef06c7f61 Wording updates
Thanks @Zaxounette!
2021-04-20 09:09:32 +02:00
52be5746c6 Update BTCPayServer.Tests/SeleniumTester.cs
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-20 09:03:40 +02:00
b2ff64733d Update BTCPayServer/Controllers/StoresController.Onchain.cs
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-20 09:03:39 +02:00
85cf36ac4a Update BTCPayServer.Tests/SeleniumTester.cs
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-20 09:03:38 +02:00
5fda21373e Move enabling toggle to store overview 2021-04-20 09:03:37 +02:00
1ada87ca31 Fix: Do not crash if plugin folder mismatches plugin identifier 2021-04-20 08:38:37 +02:00
0554565b30 FIDO2/WebAuthN Support (#2356)
* FIDO2/WebAuthN Support

This adds initial support for WebAuthN/FIDO2 as another MFA mode. U2F is still intact and runs alongside it for now. Once this is merged, I will start work on migrating U2F support to happen over the FIDO2 protocol instead.

* Refactor and future proof system (prep work of seamless u2f migration)

* attempt js fix for mobile devices

* Apply suggestions from code review

Co-authored-by: d11n <mail@dennisreimann.de>

* fix fido name saving

* do not spam logs and hide loader when failed

* PR Changes

* Apply suggestions from code review

Co-authored-by: d11n <mail@dennisreimann.de>

* attempt fido2 bump

* add name if not named for credentials

Co-authored-by: d11n <mail@dennisreimann.de>
2021-04-20 14:06:32 +09:00
315284d5f5 Fix typo of webhook events property 2021-04-20 12:36:20 +09:00
99b1e391a6 bump NBitcoin 2021-04-20 12:05:48 +09:00
9b50e85d0d Merge pull request #2455 from btcpayserver/feat/improving-files-page
Improving File Storage page
2021-04-20 11:06:14 +09:00
e394474dad Update BTCPayServer/Views/Server/Files.cshtml
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-20 11:05:53 +09:00
7d1761de4f Coin Selection: Confirmed filter (#2467)
Allows provides confirmation count to Greenfield UTXO GET api
2021-04-20 11:02:06 +09:00
dfd4c967b7 fix null reference exception when no tor settings are provided 2021-04-19 07:43:11 +02:00
71305461d4 Cleaning up edit pages for storage providers not to have summary error
Error sandwich be gone
2021-04-18 14:05:12 -05:00
04a29ece3b Only display storage providers that can actually be set in dropdown 2021-04-18 14:03:22 -05:00
99dd6f3e50 Apply suggestions from code review
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-18 13:42:25 -05:00
8f6048191d Improving navigation between files and storage services and rewording info text 2021-04-18 13:42:17 -05:00
ee0fa71605 Refactor TorService (#2388)
* TorServices Refactor to make value passing easier

* Allow specifying Tor services through config

Format:

BTCPAY_TORSERVICES: "BTCPAYSERVER:URL.ONION:VIRTUALPORT;BTC-P2P:URL.ONION:VIRTUALPORT;BTC-RPC:URL.ONION:VIRTUALPORT;SOMEOTHERONIONSERVICE:URL.ONION:VIRTUALPORT"

* add tests

* Optimize Tor Services loader and ensure it is loaded as a hosted service

* Remove Task from Tor service loader

* Use options to parse Tor services

* Fix booboo

* Fix test after fixing booboo

* Adding timeout on long running CanEnumeratetorServices test

(cherry picked from commit 274b77e3175960158b803410037e2c7ff31984be)

* Renaming timeout variable to better name

* Only allow one of torrcfile or torservices

Co-authored-by: Kukks <evilkukka@gmail.com>
Co-authored-by: rockstardev <rockstardev@users.noreply.github.com>
2021-04-18 11:26:06 +09:00
475a68924e Allow accessing "misc/lang" endpoint with Greenfield auth schemes (#2471)
closes #2437
2021-04-18 11:22:50 +09:00
c0a544351b Can disable the modification of SSH settings in BTCPay Server (#2468)
* Can disable the modification of SSH settings in BTCPay Server

* Update BTCPayServer/Controllers/ServerController.cs

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

* Update BTCPayServer/Controllers/ServerController.cs

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

* Update BTCPayServer/Views/Server/SSHService.cshtml

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-04-17 13:29:50 +09:00
15201a927e UI fixes (#2463)
* Fix formatting in bootstrap.css

This looks like it got introduced when resolving a merge conflict.

* Fix typos in views

* Fix semibold style definition
2021-04-16 15:02:40 +09:00
efa567f965 Unify POS app card markup 2021-04-14 20:00:01 -07:00
e1df1c255a Add user id in log when log in 2021-04-15 11:19:06 +09:00
64e34d0ef5 Seaparate CoinSwitch as a plugin (#2390)
* Separate coinswitch as a system plugin

* Decouple Coinswitch from Checkout UI

* remove dummy csproj

* Remove CoinSwitchTests.cs per @NicolasDorier feedback

Co-authored-by: rockstardev <rockstardev@users.noreply.github.com>
2021-04-13 20:19:48 +09:00
2e12befb8b Refactor and decouple Payout logic (#2046)
* Refactor and decouple Payout logic

So that we can support lightning and more complex flows like allowing external payments to payouts.

* fix dropdown align

* switch to simpler buttons

* rebase fixes

add some comments

* rebase fixes

add some comments

* simplify enum caveman logic

* reduce code duplication and db round trips

* Fix pull payment date format

* fix issue with payouts to send page not working correctly

* try fix some style issue

* fix bip21parse
2021-04-13 17:36:49 +09:00
98eee27b93 Remove warnings 2021-04-13 17:06:11 +09:00
8ed853a3b3 Attempt to make selenium less flaky 2021-04-13 15:12:09 +09:00
8fd4a816a6 Allow Payjoin for wallet receive addresses (#2425)
* Allow Payjoin for wallet receive addresses

* wip

* show bip21 and additional work

* style better

* add to docs

* pr changes

* remove from state when unreserved
2021-04-13 12:26:36 +09:00
b12c4c5fa0 Improve and unify page headers (#2412)
* Improve and unify page headers

* Altcoin test fixes

* Update BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Update BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Fix missing store name in pairing view

* Fix CanUsePairing test

* Bump header navigation font size

* Use partial tag instead of Html.PartialAsync in views

As suggested by @nicolasdorier. These are equivalent, see details [here](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-3.1#partial-tag-helper).

* Fix docs link

As in #2432.

* Update BTCPayServer/Views/Wallets/SignWithSeed.cshtml

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

* Update BTCPayServer/Views/Wallets/WalletSendVault.cshtml

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

* Update BTCPayServer/Views/Wallets/WalletTransactions.cshtml

Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-04-08 22:32:42 +09:00
6473da7114 Merge pull request #2444 from bolatovumar/increase-landing-page-copy-contrast
Increase landing page masthead text contrast
2021-04-08 13:38:44 +02:00
bb78ae59d4 Merge pull request #2432 from dennisreimann/fix-hot-wallet-docs-link
Fix hot wallet docs link
2021-04-08 12:21:11 +02:00
459f3c4a93 Document and handle Invoice Metadata better (#2401)
* Document and handle Invoice Metadata better

PosData would crash in certain scenarios when created via API. Invoice metadata known fields were not completely documented

* Fix value setter when null

* fix swagger conformity

* make all swagger invoice metadata optional looking in json
2021-04-08 16:42:18 +09:00
e014b30915 Remove on code warning 2021-04-08 14:33:58 +09:00
cc5a388106 Properly escape script inputs for shopify integration 2021-04-08 13:44:51 +09:00
5de93f8cc4 Abstract Store integrations (#2384)
* Decouple Shopify from Store

* Decouple shopify from store blob

* Update BTCPayServer.Tests.csproj

* Make sure shopify obj is set

* make shopify a system plugin
2021-04-08 13:37:05 +09:00
ad1b708da5 Provide more data through OnChain Wallet API (#2420)
Provides unconf/conf balanaces, keypath + address + timestamp of utxos
2021-04-08 12:43:51 +09:00
c7ff36b314 Increase landing page masthead text contrast 2021-04-07 19:37:35 -07:00
f367480857 GreenField: Add FeeRate To Wallets API (#2375)
* GreenField: Add FeeRate To Wallets API

closes #1846

* make dedicated endpoint for fee rate

* remove unused call
2021-04-07 15:16:17 +09:00
475809b1a0 Make Invoice Create Faster And Fix Gap Limit Issue (#1843)
* Make Invoice Create Faster And Fix Gap Limit Issue

This make address reserve only when user "activate" paymet method in ui. optional setting in store checkout ui.

* Fix swagger documentation around Lazy payment methods

* fix changed code signature

* Add missing GreenField API for activate feature

* Fix checkout experience styling for activate feature

* Fix issue with Checkout activate button

* Make lightning also work with activation

* Make sure PreparePaymentModel is still called on payment handlers even when unactivated

* Make payment  link return empty if not activated

* Add activate payment method method to client and add test

* remove debugger

* add e2e test

* Rearranging lazy payments position in UI to be near dependent Unified QR code

* fix rebase conflicts

* Make lazy payment method mode activate on UI load.

Co-authored-by: Kukks <evilkukka@gmail.com>
Co-authored-by: rockstardev <rockstardev@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <kukks@btcpayserver.org>
2021-04-07 13:08:42 +09:00
fce7fdb3b7 Update link 2021-04-06 16:32:04 +02:00
b2c72f1d75 Merge pull request #2435 from bolatovumar/fix/2434
Set new store hints consistently for API and GUI
2021-04-06 14:50:11 +02:00
f0ac7d2c16 Set new store hints consistently for API and GUI
closes #2434
2021-04-05 21:24:34 -07:00
7099a3b394 Fix hot wallet docs link
# Conflicts:
#	BTCPayServer/Views/Server/Policies.cshtml
2021-04-05 21:29:41 +02:00
3e985d8554 Fix crash when sat is negative (liquid only) (#2418) 2021-04-05 12:39:37 +09:00
f94e8c9719 Fix typo in PoS cart view (#2428)
Noticed by @MaxHillebrand 👀
2021-04-05 12:39:02 +09:00
bbe1442c28 Merge pull request #2422 from kristapsk/explorer.bc-2.jp
Fix block explorer links for signet
2021-04-03 12:47:01 +02:00
3a248d7707 Fix block explorer links for signet 2021-04-03 09:19:36 +03:00
0bb1f16d2a Merge pull request #2416 from g33kme/patch-1
Update swagger.template.stores-wallet.on-chain.json
2021-04-01 13:59:45 +02:00
0837756152 Update swagger.template.stores-wallet.on-chain.json 2021-04-01 13:48:06 +02:00
23236c96cb Remove internal setters in BTCPayNetwork 2021-04-01 08:56:22 +02:00
6ead5c3800 Plugin FailSafe (#2351)
This introduces the concept of plugins being disabled in the case of an unrecoverable runtime error caused by a plugin.
2021-04-01 12:27:22 +09:00
64db865e1e Changelog 2021-04-01 12:16:23 +09:00
1b2399745d Remove stable and latest branch 2021-04-01 12:06:29 +09:00
94acf60100 Properly show browser date 2021-04-01 11:55:43 +09:00
a4298e8c19 Ensure root app mapping works (#2415)
close #2414
2021-04-01 11:49:46 +09:00
76985838c4 Improve Lightning setup page (#2348)
* Redesign Lightning setup page

* Improve Lightning setup page

Closes #1811.

* Test fix

* Fix LightningNetworkPaymentMethodAPITests

* Bootstrap customization fix
2021-03-31 20:23:36 +09:00
d24964e900 Merge pull request #2378 from dennisreimann/ui-improvements
UI: Header and navigation improvements
2021-03-31 16:08:37 +09:00
f4fde8f5f7 Merge pull request #2386 from btcpayserver/feat/lnd-v0.12.1-beta
Bumping LND to v0.12.1
2021-03-31 12:35:39 +09:00
3461dd6464 Changelog for 1.0.7.1 2021-03-30 23:54:43 +09:00
560671b57f Remove autocorrect and autocapitalize for seed input 2021-03-30 19:21:29 +09:00
abf3962d91 Remove old styles 2021-03-30 11:38:04 +02:00
f53a85fcd1 Bootstrap customization fix 2021-03-30 11:38:03 +02:00
73730355b8 Add active indicator for main navigation 2021-03-30 11:38:02 +02:00
8827721605 Layout: Update header and navigation 2021-03-30 11:38:01 +02:00
2497413c60 Fix test 2021-03-30 17:15:03 +09:00
a6537ef282 Update lightning charge 2021-03-30 17:08:58 +09:00
c92adc36c6 Remove useless file 2021-03-30 15:23:36 +09:00
a64f71f186 Add user email search and sort (#2305)
* Add user sort by email

* Add user search by email
2021-03-30 14:32:44 +09:00
0c766a2714 Merge pull request #2287 from bumbummen99/patch-3
Fix payment request template
2021-03-30 14:30:56 +09:00
6b7c49431a Merge pull request #2339 from AlexGidge/1725-pull-payments-display-date
Changed display date format on View Pull Payments screen
2021-03-30 14:29:46 +09:00
4b37121b75 Updated "required" form input styling (#2373) 2021-03-30 14:27:42 +09:00
120c7b9730 Ensure submitting empty currency does not break update PoS page (#2376) 2021-03-30 14:26:33 +09:00
1aa233ca47 Order files list by descending timestamp (#2374)
closes #2273
2021-03-30 14:26:02 +09:00
14e6e492dd Refactor domain mapping (#2407) 2021-03-30 11:47:03 +09:00
17bdf55bcc Remove misleading title from hint icon (#2393)
Brought up by @Zaxounette [on Mattermost](https://chat.btcpayserver.org/btcpayserver/pl/yrxqodfswbby5qj69zhpojuefc).
2021-03-30 11:18:24 +09:00
1ae6508a43 Make dates/timespan swagger docs more clear (#2399)
* Make dates/tiemspan swagger docs more clear

* fix schema conformity
2021-03-30 11:18:00 +09:00
b7b6cef880 Fix ratelimiter for forgotpassword 2021-03-28 20:56:46 +09:00
52b5c56da9 Bumping LND to use the latest docker image 2021-03-26 08:52:17 -05:00
85ba9e96a0 Rate limit password forgot 2021-03-26 18:01:59 +09:00
b4e15cb27f Fix Pay-Button url preview (#2397)
* Fix #2396

Adresses #2396 by creating a new element and setting the url as text rather as html.

* Fix closing tag
2021-03-26 16:37:15 +09:00
068cfe5e3e Decrease command timeout for selenium 2021-03-26 14:31:36 +09:00
c0449a633d Revert "Fix docker-compose for arm64 dev env"
This reverts commit 7dfce5e3061d5e0ada87ede81960f0a50d86615d.
2021-03-26 13:22:54 +09:00
7dfce5e306 Fix docker-compose for arm64 dev env 2021-03-26 12:44:55 +09:00
fd53f7476e Fix flaky test (#2392) 2021-03-24 21:33:49 +09:00
ceb541ad8a Upgrade to Bootstrap v4.6 (#2379)
Also upgrades jQuery from v3.2.1 to v3.6.0
2021-03-24 18:47:55 +09:00
d92ced6c6b Make CanUsePayjoin2 more resilient 2021-03-24 14:18:54 +09:00
0847088391 Make CanUsePayjoin2 more resilient 2021-03-24 13:55:46 +09:00
a128685b83 If an input already used in a payjoin is reused in another, we should not attempt to broadcast the original transaction. 2021-03-24 13:48:33 +09:00
749d26fafa Revert "Fix Payjoin test randomly crashing"
This reverts commit 485faf014183321420da8ae7babf5eb9e2668034.
2021-03-23 23:59:26 +09:00
485faf0141 Fix Payjoin test randomly crashing 2021-03-23 18:34:34 +09:00
af9d896510 Do not use Random 2021-03-23 17:53:23 +09:00
1fc114fec7 Check the authentication cookie every 5 min rather than 30min 2021-03-23 17:43:13 +09:00
e6938cef6f Merge pull request #2389 from pavlenex/supportercc
Add new supporter to readme
2021-03-22 17:17:42 +01:00
ffafd291ee replace the svg with an improved one 2021-03-22 17:05:46 +01:00
f532759543 add Coincards supporter to readme 2021-03-22 11:28:04 +01:00
db3ba6db3c Merge pull request #2385 from AryanJ-NYC/fix-swag-docs
fix swagger docs missing query param
2021-03-21 16:44:06 +01:00
0a333f8476 Merge pull request #2383 from bolatovumar/fix/2382
Update Swagger for "/api/v1/stores/{storeId}/payment-methods/LightningNetwork"
2021-03-21 10:50:11 +01:00
738aaeed12 Update Swagger for "/api/v1/stores/{storeId}/payment-methods/LightningNetwork" 2021-03-20 19:53:03 -07:00
ce6c9c91fc Make sure payment method uppercase logic only happens for BTC 2021-03-20 06:52:18 +01:00
7035b71ccd Fix POS item newline break (#2366)
* Fix POS item newline break

fixes #2365

* Update TemplateEditor.cshtml

* fix template editor with "

* apply sanitize on save
2021-03-19 23:25:04 +09:00
923a567822 Make cookies secure 2021-03-19 20:54:30 +09:00
9b24e9378f Explicitely disallow \ for in filename 2021-03-19 20:22:24 +09:00
779f21a1ca Make sure cookie are HttpOnly 2021-03-19 20:09:55 +09:00
73d70aa5e5 Better validate file names 2021-03-19 18:55:21 +09:00
fc78eacf8f Merge pull request #2329 from btcpayserver/addbettermenu
Make main menu show text instead of icons when on small screens
2021-03-18 19:16:21 +01:00
006af636e6 Merge pull request #2372 from nosovk/patch-1
turn of autocomplete for PrivKey input
2021-03-16 17:29:25 +01:00
4575fda10a turn of autocomplete for "BIP39 Seed (12/24 word mnemonic phrase) or HD private key" input
autocomplete not pretend to be safe store for your wallet key
2021-03-16 16:05:16 +02:00
6c960628c2 Update pull payment template 2021-03-15 13:27:14 +01:00
00aa7deaae Fix payment request template
Fix payment request template body/page height and footer style.
2021-03-15 13:25:20 +01:00
f722956864 Merge pull request #2341 from britttttk/improve-sign-seed-copy
Fix typos on wallet sign with seed page
2021-03-15 09:14:54 +01:00
8f520bff12 Merge pull request #2350 from btcpayserver/fix/selenium-tests
Attempting selenium test fix for CanUseLightningSatsFeature
2021-03-14 18:57:41 -05:00
8398534fa0 Attempting selenium test fix for CanUseLightningSatsFeature
Swithcing to using WaitForElement and simplifying finding of alert message
2021-03-14 18:43:16 -05:00
e1fed90b71 bump versions 2021-03-11 22:48:40 +09:00
5ba6e53379 Changelog for 1.0.7.0 2021-03-11 22:45:47 +09:00
d33bdfd50c Make CanUseCoinSelection less flaky 2021-03-11 22:20:25 +09:00
4f5392eb74 Fix test 2021-03-11 22:18:02 +09:00
064087a7c0 fix test 2021-03-11 22:08:36 +09:00
e13821ba49 Fix view-seed 2021-03-11 21:58:20 +09:00
c2b85779c3 Rewrite the CanUseHotWallet, check if the derivationscheme is actually a hotwallet, before retrieving the seed 2021-03-11 21:46:32 +09:00
cdfdad3e3d GreenField API: Wallet API (#2246)
* GreenField: Wallet API

* more work

* wip

* rough fiunish of transaction sending api

* Allow to create tx without broadcasting and small fixes

* Refactor Wallet Receive feature ad add greenfield api for address reserve for wallet

* add wallet api client

* add docs

* fix json converter tags

* fixes and add wallet tests

* fix tests

* fix rebase

* fixes

* just pass the tests already

* ugggh

* small cleanup

* revert int support in numeric string converter and make block id as native number in json

* fix LN endpoint

* try fix flaky test

* Revert "try fix flaky test"

This reverts commit 2e0d256325b892f7741325dcbab01196f74d182a.

* try fix other flaky test

* return proepr error if fee rate could not be fetched

* try fix test again

* reduce fee related logic for wallet api

* try reduce code changes for pr scope

* change auth logic for initial release of wallet api
2021-03-11 21:34:52 +09:00
1f7992e5da Remove some code duplication 2021-03-11 21:29:13 +09:00
4bad7d7c52 Fix issue with store payment methods after having payment method that is no longer supported (#2349) 2021-03-10 19:51:51 +09:00
1fcf39d4ab Fix new store incorrectly showing lightning enabled even if not 2021-03-09 17:56:17 +09:00
a64e304d16 Fix new store showing incorrectly being paired to internal node 2021-03-09 17:54:38 +09:00
30c7cbba96 Fix issue around new bech uppercase + vault supported flag (#2337) 2021-03-09 12:45:56 +09:00
a7d324901d Reverted "Last Refreshed" back to "Last Updated"
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-03-08 08:45:16 +00:00
15c87dc0b6 Fix typos on wallet sign with seed page 2021-03-07 23:30:40 -07:00
0d2de4c421 #1725 - Changed to display in user's local date time format 2021-03-07 23:19:39 +00:00
da95bd6127 #1725 - Added start date & changed display timezone 2021-03-07 22:51:50 +00:00
e31b5529b0 Fixed a typo in README.md. (#2331) 2021-03-07 20:54:36 +09:00
0836df6974 Update invoice log row styling (#2334)
closes #2328
2021-03-07 20:50:55 +09:00
de4cd55adf Update coingecko exchanges 2021-03-06 13:58:02 +09:00
6b156f2144 Reenabling uppercase BECH32 in QR codes (#2181)
* Reenabling uppercase BECH32 in QR codes

* Fixing the test now that we're uppercasing BECH32 address

* Implementing Nicolas' feedback

Co-authored-by: rockstardev <rockstardev@users.noreply.github.com>
2021-03-06 13:52:25 +09:00
2b1efd9347 Merge pull request #2296 from dennisreimann/wallet-setup-finetuning
Wallet setup finetuning
2021-03-06 13:51:01 +09:00
97dd10edc0 Fix some NRE in the FileService 2021-03-06 13:36:36 +09:00
cc4e46d212 Fix NRE if invoice not found 2021-03-06 13:25:40 +09:00
dc0671942d Make main menu show text instead of icons when on small screens 2021-03-05 08:48:15 +01:00
f79c8ab641 Xpub import: Toggle multi-sig examples 2021-03-04 11:07:05 +01:00
314fda7877 Update import wallet cells 2021-03-03 22:27:39 +01:00
aaf77515fc Update icons 2021-03-03 21:29:03 +01:00
b5f7b1aad4 Hover and active states for wizard navigation 2021-03-03 21:17:25 +01:00
d36974a47e Fix test 2021-03-02 12:26:19 +01:00
5bd16f990c Add replace confirmation; distinguish wallet types 2021-03-02 12:26:18 +01:00
28d7924077 Fix AltcoinTests 2021-03-02 12:26:17 +01:00
89ecba961c Remove old AddDerivationSchemes views 2021-03-02 12:26:16 +01:00
3481a5fd19 Fix wording 2021-03-02 12:26:15 +01:00
70a21c5136 Refactoring: Move checking condition up 2021-03-02 12:26:15 +01:00
2e2c9764f3 Remove old Stores.BTCLike controller 2021-03-02 12:26:14 +01:00
bd447b6c79 Fix test 2021-03-02 11:50:01 +09:00
4f6ec3aa32 Merge pull request #2325 from btcpayserver/fix/nextnetworkfee-null
Returning 0 for NextNetworkFee if it's null
2021-03-02 11:19:43 +09:00
e37b3179ba Reactivate CanGetRateCryptoCurrenciesByDefault for DSH 2021-03-02 11:14:52 +09:00
808214f973 Fix Rates Test (Dash rate source switched to bitfinex) (#2324)
fixes #2323
2021-03-02 11:13:05 +09:00
7e714f1ef8 Refactor how we handle and validate LN ConnectionStrings (#2314)
* Refactor how we handle and validate LN ConnectionStrings

* Migrate existing connection string to Internal Node if they are the same. Cleanup some obsolete fields

* Fix typos, remove duplicated method

* Add a InternalNodeRef to LightningSupportedPaymentMethod
2021-03-02 11:11:58 +09:00
e65e46f664 NextNetworkFee is not directly initialized, falling back to null check 2021-03-01 09:56:57 -06:00
5e7eb6635f Initializing NextNetworkFee values if GetFee returns null 2021-03-01 09:33:02 -06:00
49ae62b02e Use library for Payjoin Sender (#2158)
* Use library for Payjoin Sender

* update payjoin sender to use new package and reduce code

* fix using statements
2021-03-01 22:44:53 +09:00
c9cfe5cc6e Fix direct URL for local storage with custom root path (#2318)
* Fix direct URL for local storage with custom root path

* Remove "Context.Request.PathBase" when generating file URL display string
2021-03-01 22:43:57 +09:00
e8df010449 Add redirectAutomatically to GreenField Invoice API (#2321) 2021-03-01 22:34:07 +09:00
949136b161 GreenField API: Configure Store Lightning Payment Method v2 (#2208)
* GreenField API: Configure Store Lightning Payment Method

* Remove internal ln node endpoint and use Auth service to check internal node usage

* fix test
2021-02-26 11:58:51 +09:00
1e00c63146 Merge pull request #2308 from dennisreimann/policies-ui
Improve policies structure and wordings
2021-02-26 11:49:00 +09:00
6d9b93a407 Merge pull request #2299 from dennisreimann/pos-item-button-text
PoS: Custom buy button text per product
2021-02-26 11:45:44 +09:00
37c39ad587 Merge pull request #2309 from btcpayserver/invoice-status-marker
Allow invoice to be marked even when new
2021-02-26 11:26:09 +09:00
e3e65878aa Allow invoice to be marked even when new
fixes #2019
2021-02-26 11:17:03 +09:00
6843b0eaab Merge pull request #2301 from btcpayserver/gf/paymenttypeparse
Make payment type parsing more dynamic
2021-02-26 11:00:27 +09:00
e25f76753a Update BTCPayServer.Tests/README.md
Co-authored-by: Max Hillebrand <30683012+MaxHillebrand@users.noreply.github.com>
2021-02-24 17:16:27 +01:00
75c2fabd7f Make Selenium test more robust
Fixes an issue similar to what we fixed in #2293.
2021-02-24 15:10:05 +01:00
35aeb19fcd Controller cleanups 2021-02-24 15:10:04 +01:00
5a00f6a4fc Fix missing view name 2021-02-24 15:10:03 +01:00
3a9fc52b8c Add "vertical-align: middle;" to pay button image CSS (#2312)
closes #2311
2021-02-24 15:10:02 +01:00
64a8de938b Update BTCPayServer.Tests/README.md
Co-authored-by: Pavlenex <pavle@pavle.org>
2021-02-24 15:09:45 +01:00
605cf407a8 Update README.md
Update README.md #2210
2021-02-24 15:06:16 +01:00
9ed5297e91 Update README.md
Using Polar to test Lightning payments.
2021-02-24 15:06:15 +01:00
07da404a23 Merge pull request #2310 from dennisreimann/webhooks-controller
Webhooks controller fix and cleanup
2021-02-24 22:03:01 +09:00
21467ef65d Add "vertical-align: middle;" to pay button image CSS (#2312)
closes #2311
2021-02-24 22:01:05 +09:00
4d5b2c4033 Make Selenium test more robust
Fixes an issue similar to what we fixed in #2293.
2021-02-23 16:57:21 +01:00
5e7836b293 Controller cleanups 2021-02-23 16:51:58 +01:00
a6fe61d508 Fix missing view name 2021-02-23 16:51:11 +01:00
c0aa320f0a Improve policies structure and wordings
Closes #2307.
2021-02-23 10:39:26 +01:00
4bcc18fb41 JavaScript formatting fixes 2021-02-23 09:51:25 +01:00
32370545cb Fix variable assignment in yaml parsing loop 2021-02-23 09:50:54 +01:00
2fd8c831c0 Merge pull request #2210 from radWorx/dev-with-polar
Update README.md
2021-02-22 10:22:16 +01:00
5b4877c402 PoS: Custom buy button text for custom price 2021-02-22 08:59:59 +01:00
71c11b34f4 Make payment type parsing more dynamic
fixes bug described in #2297
2021-02-19 08:23:55 +01:00
3123718166 Crowdfund: Add custom buy button text 2021-02-18 12:32:43 +01:00
dfbec71906 PoS: Add test for custom buy button text 2021-02-18 12:20:27 +01:00
757c087afd PoS: Custom buy button text per product
Closes #2232.
2021-02-18 10:54:06 +01:00
b30aa968b0 Merge pull request #2293 from dennisreimann/flaky-selenium
Tame flaky Selenium tests
2021-02-18 09:39:28 +01:00
1e902c8dee Tests: Toggle advanced settings via JS instead 2021-02-17 15:54:05 +01:00
db5f64432e Tests: Wait for advanced settings closing animation 2021-02-17 12:34:58 +01:00
6c9c463da9 Fix plugin projects reference in solution (#2292)
Fixes #2262.
2021-02-17 10:56:48 +01:00
c73878ccca Fix missing hot wallet option on seed import (#2284)
* Fix missing hot wallet option on seed import

Thanks @kukks for spotting!

* Tests: Wait for button to be ready for interaction

* Camelcase test selectors

* Tests: Remove general ImplicitWait

* Tests: Add WaitForAndClick helper

* Tests: Refactor SetCheckbox

* Tests: Add WaitForElement helper

* Tests: Refactor and use wait.UntilJsIsReady helper

* Fix missing helper in ethereum tests

* Tests: Some more refactorings
2021-02-17 12:14:29 +09:00
41e453306d Merge pull request #2288 from bumbummen99/patch-4
Fix view payment request loading spinner alignment
2021-02-17 12:10:18 +09:00
626c6007fd Merge pull request #2286 from btcpayserver/remove-upload-limit
Remove Max body request size
2021-02-16 10:40:43 +09:00
deb88032cb Update README.md
added missing space,
moved below Using the test lightning-cli
2021-02-15 15:15:38 -05:00
eca317b3c4 Fix view payment request loading spinner alignment 2021-02-15 16:37:38 +01:00
9300326483 Remove Max body request size
Upload limit by kestrel restricts plugin upload of 30mb+. this removes that limit
2021-02-15 13:42:08 +01:00
a890d5300b Update BTCPayServer.Tests/README.md
Co-authored-by: Pavlenex <pavle@pavle.org>
2021-01-19 13:30:45 -05:00
eb8411611a Update README.md
Update README.md #2210
2021-01-19 13:07:19 -05:00
9360ddf294 Merge branch 'dev-with-polar' of https://github.com/radworx/btcpayserver into dev-with-polar 2021-01-19 13:04:32 -05:00
e3c138fa98 Update README.md
Update README.md #2210
2021-01-19 13:02:26 -05:00
8d03738a50 Update BTCPayServer.Tests/README.md
Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-01-18 22:52:21 -05:00
e5540ee79f Update BTCPayServer.Tests/README.md
Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-01-18 22:52:09 -05:00
2ccbb6d6a7 Update BTCPayServer.Tests/README.md
Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-01-18 22:51:59 -05:00
3dce7e7e9f Update README.md
Using Polar to test Lightning payments.
2021-01-18 22:15:52 -05:00
717 changed files with 58058 additions and 54012 deletions

View File

@ -31,9 +31,12 @@
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.19" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.19" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.18" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.2.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using BTCPayServer.Client;
namespace BTCPayServer.Abstractions.Contracts
{
public interface IBTCPayServerClientFactory
{
Task<BTCPayServerClient> Create(string userId, params string[] storeIds);
}
}

View File

@ -1,3 +1,4 @@
#nullable enable
using System.Threading;
using System.Threading.Tasks;
@ -5,8 +6,8 @@ namespace BTCPayServer.Abstractions.Contracts
{
public interface ISettingsRepository
{
Task<T> GetSettingAsync<T>(string name = null);
Task UpdateSetting<T>(T obj, string name = null);
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default);
Task<T?> GetSettingAsync<T>(string? name = null) where T : class;
Task UpdateSetting<T>(T obj, string? name = null) where T : class;
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default) where T : class;
}
}

View File

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace BTCPayServer.Abstractions.Contracts
{
public interface ISyncSummaryProvider
@ -5,5 +7,12 @@ namespace BTCPayServer.Abstractions.Contracts
bool AllAvailable();
string Partial { get; }
IEnumerable<ISyncStatus> GetStatuses();
}
public interface ISyncStatus
{
public string CryptoCode { get; set; }
public bool Available { get; }
}
}

View File

@ -3,16 +3,39 @@ using System.Globalization;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BTCPayServer.Views
namespace BTCPayServer.Abstractions.Extensions
{
public static class ViewsRazor
{
public const string ACTIVE_PAGE_KEY = "ActivePage";
public static void SetActivePageAndTitle<T>(this ViewDataDictionary viewData, T activePage, string title = null)
private const string ACTIVE_CATEGORY_KEY = "ActiveCategory";
private const string ACTIVE_PAGE_KEY = "ActivePage";
public static void SetActivePageAndTitle<T>(this ViewDataDictionary viewData, T activePage, string title = null, string mainTitle = null)
where T : IConvertible
{
// Browser Title
viewData["Title"] = title ?? activePage.ToString();
// Breadcrumb
viewData["MainTitle"] = mainTitle;
viewData["PageTitle"] = title;
// Navigation
viewData[ACTIVE_PAGE_KEY] = activePage;
SetActiveCategory(viewData, activePage.GetType());
}
public static void SetActiveCategory<T>(this ViewDataDictionary viewData, T activeCategory)
{
viewData[ACTIVE_CATEGORY_KEY] = activeCategory;
}
public static string IsActiveCategory<T>(this ViewDataDictionary viewData, T category)
{
if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY))
{
return null;
}
var activeCategory = (T)viewData[ACTIVE_CATEGORY_KEY];
return category.Equals(activeCategory) ? "active" : null;
}
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page)
@ -28,15 +51,16 @@ namespace BTCPayServer.Views
public static HtmlString ToBrowserDate(this DateTimeOffset date)
{
var hello = date.ToString("o", CultureInfo.InvariantCulture);
return new HtmlString($"<span class='localizeDate'>{hello}</span>");
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
}
public static HtmlString ToBrowserDate2(this DateTime date)
public static HtmlString ToBrowserDate(this DateTime date)
{
var hello = date.ToString("o", CultureInfo.InvariantCulture);
return new HtmlString($"<span class='localizeDate'>{hello}</span>");
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
}
public static string ToTimeAgo(this DateTimeOffset date)
{
var formatted = (DateTimeOffset.UtcNow - date).TimeString() + " ago";

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NBitcoin.Crypto;
namespace BTCPayServer.Security
{
@ -9,6 +10,8 @@ namespace BTCPayServer.Security
{
public ConsentSecurityPolicy(string name, string value)
{
if (value.Contains(';', StringComparison.OrdinalIgnoreCase))
throw new FormatException();
_Value = value;
_Name = name;
}
@ -67,10 +70,48 @@ namespace BTCPayServer.Security
}
readonly HashSet<ConsentSecurityPolicy> _Policies = new HashSet<ConsentSecurityPolicy>();
/// <summary>
/// Allow a specific script as event handler
/// </summary>
/// <param name="script"></param>
public void AllowUnsafeHashes(string script = null)
{
if (!allowUnsafeHashes)
{
Add("script-src", $"'unsafe-hashes'");
allowUnsafeHashes = true;
}
if (script != null)
{
var sha = GetSha256(script);
Add("script-src", $"'sha256-{sha}'");
}
}
bool allowUnsafeHashes = false;
/// <summary>
/// Allow the injection of script tag with the following script
/// </summary>
/// <param name="script"></param>
public void AllowInline(string script)
{
if (script is null)
throw new ArgumentNullException(nameof(script));
var sha = GetSha256(script);
Add("script-src", $"'sha256-{sha}'");
}
static string GetSha256(string script)
{
return Convert.ToBase64String(Hashes.SHA256(Encoding.UTF8.GetBytes(script.Replace("\r\n", "\n", StringComparison.Ordinal))));
}
public void Add(string name, string value)
{
Add(new ConsentSecurityPolicy(name, value));
}
public void Add(ConsentSecurityPolicy policy)
{
if (_Policies.Any(p => p.Name == policy.Name && p.Value == policy.Name))
return;
_Policies.Add(policy);
}
@ -87,34 +128,19 @@ namespace BTCPayServer.Security
{
value.Append(';');
}
List<string> values = new List<string>();
HashSet<string> values = new HashSet<string>();
List<string> valuesList = new List<string>();
values.Add(group.Key);
valuesList.Add(group.Key);
foreach (var v in group)
{
values.Add(v.Value);
if (values.Add(v.Value))
valuesList.Add(v.Value);
}
foreach (var i in authorized)
{
values.Add(i);
}
value.Append(String.Join(" ", values.OfType<object>().ToArray()));
value.Append(String.Join(" ", valuesList.OfType<object>().ToArray()));
firstGroup = false;
}
return value.ToString();
}
internal void Clear()
{
authorized.Clear();
_Policies.Clear();
}
readonly HashSet<string> authorized = new HashSet<string>();
internal void AddAllAuthorized(string v)
{
authorized.Add(v);
}
public IEnumerable<string> Authorized => authorized;
}
}

View File

@ -13,7 +13,7 @@
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(Version)' == '' ">1.2.0</Version>
<Version Condition=" '$(Version)' == '' ">1.4.0</Version>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@ -27,8 +27,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="5.0.73" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
<PackageReference Include="NBitcoin" Version="6.0.13" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>

View File

@ -1,21 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using NBitcoin;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, bool includeArchived = false,
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
InvoiceStatus[] status = null,
DateTimeOffset? startDate = null,
DateTimeOffset? endDate = null,
string textSearch = null,
bool includeArchived = false,
CancellationToken token = default)
{
Dictionary<string, object> queryPayload = new Dictionary<string, object>();
queryPayload.Add(nameof(includeArchived), includeArchived);
if (startDate is DateTimeOffset s)
queryPayload.Add(nameof(startDate), Utils.DateTimeToUnixTime(s));
if (endDate is DateTimeOffset e)
queryPayload.Add(nameof(endDate), Utils.DateTimeToUnixTime(e));
if (orderId != null)
queryPayload.Add(nameof(orderId), orderId);
if (textSearch != null)
queryPayload.Add(nameof(textSearch), textSearch);
if (status != null)
queryPayload.Add(nameof(status), status.Select(s=> s.ToString().ToLower()).ToArray());
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/invoices",
new Dictionary<string, object>() {{nameof(includeArchived), includeArchived}}), token);
queryPayload), token);
return await HandleResponse<IEnumerable<InvoiceData>>(response);
}
@ -70,7 +93,7 @@ namespace BTCPayServer.Client
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Status!= InvoiceStatus.Settled && request.Status!= InvoiceStatus.Invalid)
if (request.Status != InvoiceStatus.Settled && request.Status != InvoiceStatus.Invalid)
throw new ArgumentOutOfRangeException(nameof(request.Status), "Status can only be Invalid or Complete");
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/status", bodyPayload: request,
@ -85,5 +108,13 @@ namespace BTCPayServer.Client
method: HttpMethod.Post), token);
return await HandleResponse<InvoiceData>(response);
}
public virtual async Task ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods/{paymentMethod}/activate",
method: HttpMethod.Post), token);
await HandleResponse(response);
}
}
}

View File

@ -9,7 +9,7 @@ namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<LightningNodeInformationData> GetLightningNodeInfo(string cryptoCode,
public virtual async Task<LightningNodeInformationData> GetLightningNodeInfo(string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
@ -18,7 +18,7 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningNodeInformationData>(response);
}
public async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
public virtual async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
CancellationToken token = default)
{
if (request == null)
@ -29,7 +29,7 @@ namespace BTCPayServer.Client
await HandleResponse(response);
}
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string cryptoCode,
public virtual async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
@ -38,16 +38,16 @@ namespace BTCPayServer.Client
return await HandleResponse<IEnumerable<LightningChannelData>>(response);
}
public async Task<string> OpenLightningChannel(string cryptoCode, OpenLightningChannelRequest request,
public virtual async Task OpenLightningChannel(string cryptoCode, OpenLightningChannelRequest request,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/channels", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<string>(response);
await HandleResponse(response);
}
public async Task<string> GetLightningDepositAddress(string cryptoCode, CancellationToken token = default)
public virtual async Task<string> GetLightningDepositAddress(string cryptoCode, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/address", method: HttpMethod.Post), token);
@ -55,7 +55,7 @@ namespace BTCPayServer.Client
}
public async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
public virtual async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)
@ -66,7 +66,7 @@ namespace BTCPayServer.Client
await HandleResponse(response);
}
public async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,
public virtual async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,
string invoiceId, CancellationToken token = default)
{
if (invoiceId == null)
@ -77,7 +77,7 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningInvoiceData>(response);
}
public async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)

View File

@ -9,7 +9,7 @@ namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<LightningNodeInformationData> GetLightningNodeInfo(string storeId, string cryptoCode,
public virtual async Task<LightningNodeInformationData> GetLightningNodeInfo(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
@ -18,7 +18,7 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningNodeInformationData>(response);
}
public async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
public virtual async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
CancellationToken token = default)
{
if (request == null)
@ -29,7 +29,7 @@ namespace BTCPayServer.Client
await HandleResponse(response);
}
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string storeId, string cryptoCode,
public virtual async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
@ -38,16 +38,16 @@ namespace BTCPayServer.Client
return await HandleResponse<IEnumerable<LightningChannelData>>(response);
}
public async Task<string> OpenLightningChannel(string storeId, string cryptoCode, OpenLightningChannelRequest request,
public virtual async Task OpenLightningChannel(string storeId, string cryptoCode, OpenLightningChannelRequest request,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/channels", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<string>(response);
await HandleResponse(response);
}
public async Task<string> GetLightningDepositAddress(string storeId, string cryptoCode,
public virtual async Task<string> GetLightningDepositAddress(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
@ -56,7 +56,7 @@ namespace BTCPayServer.Client
return await HandleResponse<string>(response);
}
public async Task PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
public virtual async Task PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)
@ -67,7 +67,7 @@ namespace BTCPayServer.Client
await HandleResponse(response);
}
public async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
public virtual async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
string invoiceId, CancellationToken token = default)
{
if (invoiceId == null)
@ -78,7 +78,7 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningInvoiceData>(response);
}
public async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
CreateLightningInvoiceRequest request, CancellationToken token = default)
{
if (request == null)

View File

@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<LightningNetworkPaymentMethodData>>
GetStoreLightningNetworkPaymentMethods(string storeId, bool? enabled = null,
CancellationToken token = default)
{
var query = new Dictionary<string, object>();
if (enabled != null)
{
query.Add(nameof(enabled), enabled);
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork",
query), token);
return await HandleResponse<IEnumerable<LightningNetworkPaymentMethodData>>(response);
}
public virtual async Task<LightningNetworkPaymentMethodData> GetStoreLightningNetworkPaymentMethod(
string storeId,
string cryptoCode, CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}"), token);
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
}
public virtual async Task RemoveStoreLightningNetworkPaymentMethod(string storeId,
string cryptoCode, CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}",
method: HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethod(
string storeId,
string cryptoCode, UpdateLightningNetworkPaymentMethodRequest paymentMethod,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}",
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<PermissionMetadata[]> GetPermissionMetadata(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("misc/permissions"), token);
return await HandleResponse<PermissionMetadata[]>(response);
}
public virtual async Task<Language[]> GetAvailableLanguages(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("misc/lang"), token);
return await HandleResponse<Language[]>(response);
}
}
}

View File

@ -9,11 +9,19 @@ namespace BTCPayServer.Client
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
bool? enabled = null,
CancellationToken token = default)
{
var query = new Dictionary<string, object>();
if (enabled != null)
{
query.Add(nameof(enabled), enabled);
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain"), token);
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain",
query), token);
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
}
@ -37,7 +45,7 @@ namespace BTCPayServer.Client
}
public virtual async Task<OnChainPaymentMethodData> UpdateStoreOnChainPaymentMethod(string storeId,
string cryptoCode, OnChainPaymentMethodData paymentMethod,
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
@ -48,7 +56,7 @@ namespace BTCPayServer.Client
public virtual async Task<OnChainPaymentMethodPreviewResultData>
PreviewProposedStoreOnChainPaymentMethodAddresses(
string storeId, string cryptoCode, OnChainPaymentMethodData paymentMethod, int offset = 0,
string storeId, string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0,
int amount = 10,
CancellationToken token = default)
{
@ -70,5 +78,17 @@ namespace BTCPayServer.Client
method: HttpMethod.Get), token);
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
}
public virtual async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId,
string cryptoCode, GenerateOnChainWalletRequest request,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/generate",
bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
}
}
}

View File

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using NBitcoin;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<OnChainWalletOverviewData> ShowOnChainWalletOverview(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet"), token);
return await HandleResponse<OnChainWalletOverviewData>(response);
}
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
CancellationToken token = default)
{
Dictionary<string, object> queryParams = new Dictionary<string, object>();
if (blockTarget != null)
{
queryParams.Add("blockTarget",blockTarget);
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/feeRate", queryParams), token);
return await HandleResponse<OnChainWalletFeeRateData>(response);
}
public virtual async Task<OnChainWalletAddressData> GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, bool forceGenerate = false,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>()
{
{"forceGenerate", forceGenerate}
}), token);
return await HandleResponse<OnChainWalletAddressData>(response);
}
public virtual async Task UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address",method:HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null,
CancellationToken token = default)
{
var query = new Dictionary<string, object>();
if (statusFilter?.Any() is true)
{
query.Add(nameof(statusFilter), statusFilter);
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", query), token);
return await HandleResponse<IEnumerable<OnChainWalletTransactionData>>(response);
}
public virtual async Task<OnChainWalletTransactionData> GetOnChainWalletTransaction(
string storeId, string cryptoCode, string transactionId,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions/{transactionId}"), token);
return await HandleResponse<OnChainWalletTransactionData>(response);
}
public virtual async Task<IEnumerable<OnChainWalletUTXOData>> GetOnChainWalletUTXOs(string storeId,
string cryptoCode,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/utxos"), token);
return await HandleResponse<IEnumerable<OnChainWalletUTXOData>>(response);
}
public virtual async Task<OnChainWalletTransactionData> CreateOnChainTransaction(string storeId,
string cryptoCode, CreateOnChainTransactionRequest request,
CancellationToken token = default)
{
if (!request.ProceedWithBroadcast)
{
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
"Please use CreateOnChainTransactionButDoNotBroadcast when wanting to only create the transaction");
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
return await HandleResponse<OnChainWalletTransactionData>(response);
}
public virtual async Task<Transaction> CreateOnChainTransactionButDoNotBroadcast(string storeId,
string cryptoCode, CreateOnChainTransactionRequest request, Network network,
CancellationToken token = default)
{
if (request.ProceedWithBroadcast)
{
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
"Please use CreateOnChainTransaction when wanting to also broadcast the transaction");
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
return Transaction.Parse(await HandleResponse<string>(response), network);
}
}
}

View File

@ -9,18 +9,18 @@ namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<PullPaymentData> CreatePullPayment(string storeId, CreatePullPaymentRequest request, CancellationToken cancellationToken = default)
public virtual async Task<PullPaymentData> CreatePullPayment(string storeId, CreatePullPaymentRequest request, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/pull-payments", bodyPayload: request, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<PullPaymentData>(response);
}
public async Task<PullPaymentData> GetPullPayment(string pullPaymentId, CancellationToken cancellationToken = default)
public virtual async Task<PullPaymentData> GetPullPayment(string pullPaymentId, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}", method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PullPaymentData>(response);
}
public async Task<PullPaymentData[]> GetPullPayments(string storeId, bool includeArchived = false, CancellationToken cancellationToken = default)
public virtual async Task<PullPaymentData[]> GetPullPayments(string storeId, bool includeArchived = false, CancellationToken cancellationToken = default)
{
Dictionary<string, object> query = new Dictionary<string, object>();
query.Add("includeArchived", includeArchived);
@ -28,34 +28,44 @@ namespace BTCPayServer.Client
return await HandleResponse<PullPaymentData[]>(response);
}
public async Task ArchivePullPayment(string storeId, string pullPaymentId, CancellationToken cancellationToken = default)
public virtual async Task ArchivePullPayment(string storeId, string pullPaymentId, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}", method: HttpMethod.Delete), cancellationToken);
await HandleResponse(response);
}
public async Task<PayoutData[]> GetPayouts(string pullPaymentId, bool includeCancelled = false, CancellationToken cancellationToken = default)
public virtual async Task<PayoutData[]> GetPayouts(string pullPaymentId, bool includeCancelled = false, CancellationToken cancellationToken = default)
{
Dictionary<string, object> query = new Dictionary<string, object>();
query.Add("includeCancelled", includeCancelled);
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", queryPayload: query, method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PayoutData[]>(response);
}
public async Task<PayoutData> CreatePayout(string pullPaymentId, CreatePayoutRequest payoutRequest, CancellationToken cancellationToken = default)
public virtual async Task<PayoutData> CreatePayout(string pullPaymentId, CreatePayoutRequest payoutRequest, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", bodyPayload: payoutRequest, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<PayoutData>(response);
}
public async Task CancelPayout(string storeId, string payoutId, CancellationToken cancellationToken = default)
public virtual async Task CancelPayout(string storeId, string payoutId, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}", method: HttpMethod.Delete), cancellationToken);
await HandleResponse(response);
}
public async Task<PayoutData> ApprovePayout(string storeId, string payoutId, ApprovePayoutRequest request, CancellationToken cancellationToken = default)
public virtual async Task<PayoutData> ApprovePayout(string storeId, string payoutId, ApprovePayoutRequest request, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}", bodyPayload: request, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<PayoutData>(response);
}
public async Task MarkPayoutPaid(string storeId, string payoutId,
CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest(
$"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}/mark-paid",
method: HttpMethod.Post), cancellationToken);
await HandleResponse(response);
}
}
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
bool? enabled = null,
CancellationToken token = default)
{
var query = new Dictionary<string, object>();
if (enabled != null)
{
query.Add(nameof(enabled), enabled);
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods",
query), token);
return await HandleResponse<Dictionary<string, GenericPaymentMethodData>>(response);
}
}
}

View File

@ -1,3 +1,4 @@
#nullable enable
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -19,5 +20,16 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
return await HandleResponse<ApplicationUserData>(response);
}
public virtual async Task DeleteUser(string userId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{userId}", null, HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task DeleteCurrentUser(CancellationToken token = default)
{
await DeleteUser("me", token);
}
}
}

View File

@ -1,60 +1,58 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<StoreWebhookData> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create, CancellationToken token = default)
public virtual async Task<StoreWebhookData> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks", bodyPayload: create, method: HttpMethod.Post), token);
return await HandleResponse<StoreWebhookData>(response);
}
public async Task<StoreWebhookData> GetWebhook(string storeId, string webhookId, CancellationToken token = default)
public virtual async Task<StoreWebhookData> GetWebhook(string storeId, string webhookId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}"), token);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
return null;
return await HandleResponse<StoreWebhookData>(response);
}
public async Task<StoreWebhookData> UpdateWebhook(string storeId, string webhookId, Models.UpdateStoreWebhookRequest update, CancellationToken token = default)
public virtual async Task<StoreWebhookData> UpdateWebhook(string storeId, string webhookId, Models.UpdateStoreWebhookRequest update, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}", bodyPayload: update, method: HttpMethod.Put), token);
return await HandleResponse<StoreWebhookData>(response);
}
public async Task<bool> DeleteWebhook(string storeId, string webhookId, CancellationToken token = default)
public virtual async Task<bool> DeleteWebhook(string storeId, string webhookId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}", method: HttpMethod.Delete), token);
return response.IsSuccessStatusCode;
}
public async Task<StoreWebhookData[]> GetWebhooks(string storeId, CancellationToken token = default)
public virtual async Task<StoreWebhookData[]> GetWebhooks(string storeId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks"), token);
return await HandleResponse<StoreWebhookData[]>(response);
}
public async Task<WebhookDeliveryData[]> GetWebhookDeliveries(string storeId, string webhookId, CancellationToken token = default)
public virtual async Task<WebhookDeliveryData[]> GetWebhookDeliveries(string storeId, string webhookId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries"), token);
return await HandleResponse<WebhookDeliveryData[]>(response);
}
public async Task<WebhookDeliveryData> GetWebhookDelivery(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
public virtual async Task<WebhookDeliveryData> GetWebhookDelivery(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}"), token);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
return null;
return await HandleResponse<WebhookDeliveryData>(response);
}
public async Task<string> RedeliverWebhook(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
public virtual async Task<string> RedeliverWebhook(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver", null, HttpMethod.Post), token);
return await HandleResponse<string>(response);
}
public async Task<WebhookEvent> GetWebhookDeliveryRequest(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
public virtual async Task<WebhookEvent> GetWebhookDeliveryRequest(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request"), token);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
@ -69,6 +70,13 @@ namespace BTCPayServer.Client
return JsonConvert.DeserializeObject<T>(str);
}
public async Task<T> SendHttpRequest<T>(string path,
Dictionary<string, object> queryPayload = null,
HttpMethod method = null, CancellationToken cancellationToken = default)
{
using var resp = await _httpClient.SendAsync(CreateHttpRequest(path, queryPayload, method), cancellationToken);
return await HandleResponse<T>(resp);
}
protected virtual HttpRequestMessage CreateHttpRequest(string path,
Dictionary<string, object> queryPayload = null,
HttpMethod method = null)

View File

@ -0,0 +1,31 @@
using System;
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.JsonConverters
{
public class MnemonicJsonConverter : JsonConverter<Mnemonic>
{
public override Mnemonic ReadJson(JsonReader reader, Type objectType, Mnemonic existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return reader.TokenType switch
{
JsonToken.String => new Mnemonic((string)reader.Value),
JsonToken.Null => null,
_ => throw new JsonObjectException(reader.Path, "Mnemonic must be a json string")
};
}
public override void WriteJson(JsonWriter writer, Mnemonic value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(value.ToString());
else
{
writer.WriteNull();
}
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NBitcoin;
using Newtonsoft.Json;
public class WordcountJsonConverter : JsonConverter
{
static WordcountJsonConverter()
{
_Wordcount = new Dictionary<long, WordCount>()
{
{18, WordCount.Eighteen},
{15, WordCount.Fifteen},
{12, WordCount.Twelve},
{24, WordCount.TwentyFour},
{21, WordCount.TwentyOne}
};
_WordcountReverse = _Wordcount.ToDictionary(kv => kv.Value, kv => kv.Key);
}
public override bool CanConvert(Type objectType)
{
return typeof(NBitcoin.WordCount).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()) ||
typeof(NBitcoin.WordCount?).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return default;
if (reader.TokenType != JsonToken.Integer)
throw new NBitcoin.JsonConverters.JsonObjectException(
$"Unexpected json token type, expected Integer, actual {reader.TokenType}", reader);
if (!_Wordcount.TryGetValue((long)reader.Value, out var result))
throw new NBitcoin.JsonConverters.JsonObjectException(
$"Invalid WordCount, possible values {string.Join(", ", _Wordcount.Keys.ToArray())} (default: 12)",
reader);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is WordCount wc)
writer.WriteValue(_WordcountReverse[wc]);
}
readonly static Dictionary<long, WordCount> _Wordcount = new Dictionary<long, WordCount>()
{
{18, WordCount.Eighteen},
{15, WordCount.Fifteen},
{12, WordCount.Twelve},
{24, WordCount.TwentyFour},
{21, WordCount.TwentyOne}
};
readonly static Dictionary<WordCount, long> _WordcountReverse;
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NBitcoin;
using Newtonsoft.Json;
public class WordlistJsonConverter : JsonConverter
{
static WordlistJsonConverter()
{
_Wordlists = new Dictionary<string, Wordlist>(StringComparer.OrdinalIgnoreCase)
{
{"English", Wordlist.English},
{"Japanese", Wordlist.Japanese},
{"Spanish", Wordlist.Spanish},
{"ChineseSimplified", Wordlist.ChineseSimplified},
{"ChineseTraditional", Wordlist.ChineseTraditional},
{"French", Wordlist.French},
{"PortugueseBrazil", Wordlist.PortugueseBrazil},
{"Czech", Wordlist.Czech}
};
_WordlistsReverse = _Wordlists.ToDictionary(kv => kv.Value, kv => kv.Key);
}
public override bool CanConvert(Type objectType)
{
return typeof(Wordlist).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new NBitcoin.JsonConverters.JsonObjectException(
$"Unexpected json token type, expected String, actual {reader.TokenType}", reader);
if (!_Wordlists.TryGetValue((string)reader.Value, out var result))
throw new NBitcoin.JsonConverters.JsonObjectException(
$"Invalid wordlist, possible values {string.Join(", ", _Wordlists.Keys.ToArray())} (default: English)",
reader);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is Wordlist wl)
writer.WriteValue(_WordlistsReverse[wl]);
}
readonly static Dictionary<string, Wordlist> _Wordlists;
readonly static Dictionary<Wordlist, string> _WordlistsReverse;
}

View File

@ -7,33 +7,10 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
public class CreateInvoiceRequest
public class CreateInvoiceRequest : InvoiceDataBase
{
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
public JObject Metadata { get; set; }
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
public class CheckoutOptions
{
[JsonConverter(typeof(StringEnumConverter))]
public SpeedPolicy? SpeedPolicy { get; set; }
public string[] PaymentMethods { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("expirationMinutes")]
public TimeSpan? Expiration { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("monitoringMinutes")]
public TimeSpan? Monitoring { get; set; }
public double? PaymentTolerance { get; set; }
[JsonProperty("redirectURL")]
public string RedirectURL { get; set; }
public string DefaultLanguage { get; set; }
}
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Amount { get; set; }
public string[] AdditionalSearchTerms { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class CreateOnChainTransactionRequest
{
public class CreateOnChainTransactionRequestDestination
{
public string Destination { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Amount { get; set; }
public bool SubtractFromAmount { get; set; }
}
[JsonConverter(typeof(FeeRateJsonConverter))]
public FeeRate FeeRate { get; set; }
public bool ProceedWithPayjoin { get; set; }= true;
public bool ProceedWithBroadcast { get; set; } = true;
public bool NoChange { get; set; } = false;
[JsonProperty(ItemConverterType = typeof(OutpointJsonConverter))]
public List<OutPoint> SelectedInputs { get; set; } = null;
public List<CreateOnChainTransactionRequestDestination> Destinations { get; set; }
[JsonProperty("rbf")]
public bool? RBF { get; set; } = null;
}
}

View File

@ -0,0 +1,25 @@
using BTCPayServer.Client.JsonConverters;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client
{
public class GenerateOnChainWalletRequest
{
public int AccountNumber { get; set; } = 0;
[JsonConverter(typeof(MnemonicJsonConverter))]
public Mnemonic ExistingMnemonic { get; set; }
[JsonConverter(typeof(WordlistJsonConverter))]
public NBitcoin.Wordlist WordList { get; set; }
[JsonConverter(typeof(WordcountJsonConverter))]
public NBitcoin.WordCount? WordCount { get; set; } = NBitcoin.WordCount.Twelve;
[JsonConverter(typeof(StringEnumConverter))]
public NBitcoin.ScriptPubKeyType ScriptPubKeyType { get; set; } = ScriptPubKeyType.Segwit;
public string Passphrase { get; set; }
public bool ImportKeysToRPC { get; set; }
public bool SavePrivateKeys { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace BTCPayServer.Client.Models
{
public class GenericPaymentMethodData
{
public bool Enabled { get; set; }
public object Data { get; set; }
public string CryptoCode { get; set; }
}
}

View File

@ -1,12 +1,54 @@
using System;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
public class InvoiceData : CreateInvoiceRequest
public enum InvoiceType
{
Standard,
TopUp
}
public class InvoiceDataBase
{
[JsonConverter(typeof(StringEnumConverter))]
public InvoiceType Type { get; set; }
public string Currency { get; set; }
public JObject Metadata { get; set; }
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
public class CheckoutOptions
{
[JsonConverter(typeof(StringEnumConverter))]
public SpeedPolicy? SpeedPolicy { get; set; }
public string[] PaymentMethods { get; set; }
public string DefaultPaymentMethod { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("expirationMinutes")]
public TimeSpan? Expiration { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("monitoringMinutes")]
public TimeSpan? Monitoring { get; set; }
public double? PaymentTolerance { get; set; }
[JsonProperty("redirectURL")]
public string RedirectURL { get; set; }
public bool? RedirectAutomatically { get; set; }
public string DefaultLanguage { get; set; }
}
}
public class InvoiceData : InvoiceDataBase
{
public string Id { get; set; }
public string StoreId { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public string CheckoutLink { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public InvoiceStatus Status { get; set; }
@ -18,6 +60,8 @@ namespace BTCPayServer.Client.Models
public DateTimeOffset ExpirationTime { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset CreatedTime { get; set; }
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
public InvoiceStatus[] AvailableStatusesForManualMarking { get; set; }
}
public enum InvoiceStatus
{

View File

@ -8,6 +8,7 @@ namespace BTCPayServer.Client.Models
{
public class InvoicePaymentMethodDataModel
{
public bool Activated { get; set; }
public string Destination { get; set; }
public string PaymentLink { get; set; }

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
public class LabelData
{
public string Type { get; set; }
public string Text { get; set; }
[JsonExtensionData] public Dictionary<string, JToken> AdditionalData { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class Language
{
public Language(string code, string displayName)
{
DisplayName = displayName;
Code = code;
}
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("currentLanguage")]
public string DisplayName { get; set; }
}
}

View File

@ -0,0 +1,12 @@
namespace BTCPayServer.Client.Models
{
public class LightningNetworkPaymentMethodBaseData
{
public string ConnectionString { get; set; }
public LightningNetworkPaymentMethodBaseData()
{
}
}
}

View File

@ -0,0 +1,29 @@
namespace BTCPayServer.Client.Models
{
public class LightningNetworkPaymentMethodData: LightningNetworkPaymentMethodBaseData
{
/// <summary>
/// Whether the payment method is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Crypto code of the payment method
/// </summary>
public string CryptoCode { get; set; }
public LightningNetworkPaymentMethodData()
{
}
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
{
Enabled = enabled;
CryptoCode = cryptoCode;
ConnectionString = connectionString;
PaymentMethod = paymentMethod;
}
public string PaymentMethod { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainPaymentMethodBaseData
{
/// <summary>
/// The derivation scheme
/// </summary>
public string DerivationScheme { get; set; }
public string Label { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public RootedKeyPath AccountKeyPath { get; set; }
public OnChainPaymentMethodBaseData()
{
}
}
}

View File

@ -1,39 +1,47 @@
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainPaymentMethodData
public class OnChainPaymentMethodDataPreview : OnChainPaymentMethodBaseData
{
/// <summary>
/// Crypto code of the payment method
/// </summary>
public string CryptoCode { get; set; }
public OnChainPaymentMethodDataPreview()
{
}
public OnChainPaymentMethodDataPreview(string cryptoCode, string derivationScheme, string label, RootedKeyPath accountKeyPath)
{
Label = label;
AccountKeyPath = accountKeyPath;
CryptoCode = cryptoCode;
DerivationScheme = derivationScheme;
}
}
public class OnChainPaymentMethodData : OnChainPaymentMethodDataPreview
{
/// <summary>
/// Whether the payment method is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Crypto code of the payment method
/// </summary>
public string CryptoCode { get; set; }
/// <summary>
/// The derivation scheme
/// </summary>
public string DerivationScheme { get; set; }
public string Label { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public RootedKeyPath AccountKeyPath { get; set; }
public string PaymentMethod { get; set; }
public OnChainPaymentMethodData()
{
}
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled)
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled, string label, RootedKeyPath accountKeyPath, string paymentMethod) :
base(cryptoCode, derivationScheme, label, accountKeyPath)
{
Enabled = enabled;
CryptoCode = cryptoCode;
DerivationScheme = derivationScheme;
PaymentMethod = paymentMethod;
}
}
}

View File

@ -0,0 +1,23 @@
using BTCPayServer.Client.JsonConverters;
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainPaymentMethodDataWithSensitiveData : OnChainPaymentMethodData
{
public OnChainPaymentMethodDataWithSensitiveData()
{
}
public OnChainPaymentMethodDataWithSensitiveData(string cryptoCode, string derivationScheme, bool enabled,
string label, RootedKeyPath accountKeyPath, Mnemonic mnemonic, string paymentMethod) : base(cryptoCode, derivationScheme, enabled,
label, accountKeyPath, paymentMethod)
{
Mnemonic = mnemonic;
}
[JsonConverter(typeof(MnemonicJsonConverter))]
public Mnemonic Mnemonic { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainWalletAddressData
{
public string Address { get; set; }
[JsonConverter(typeof(KeyPathJsonConverter))]
public KeyPath KeyPath { get; set; }
public string PaymentLink { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainWalletFeeRateData
{
[JsonConverter(typeof(FeeRateJsonConverter))]
public FeeRate FeeRate { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainWalletOverviewData
{
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Balance { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal UnconfirmedBalance { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal ConfirmedBalance { get; set; }
public string Label { get; set; }
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainWalletTransactionData
{
[JsonConverter(typeof(UInt256JsonConverter))]
public uint256 TransactionHash { get; set; }
public string Comment { get; set; }
public Dictionary<string, LabelData> Labels { get; set; } = new Dictionary<string, LabelData>();
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(UInt256JsonConverter))]
public uint256 BlockHash { get; set; }
public int? BlockHeight { get; set; }
public int Confirmations { get; set; }
[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
public DateTimeOffset Timestamp { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public TransactionStatus Status { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainWalletUTXOData
{
public string Comment { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(OutpointJsonConverter))]
public OutPoint Outpoint { get; set; }
public string Link { get; set; }
public Dictionary<string, LabelData> Labels { get; set; }
[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
public DateTimeOffset Timestamp { get; set; }
[JsonConverter(typeof(KeyPathJsonConverter))]
public KeyPath KeyPath { get; set; }
public string Address { get; set; }
public int Confirmations { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class PermissionMetadata
{
static PermissionMetadata()
{
Dictionary<string, PermissionMetadata> nodes = new Dictionary<string, PermissionMetadata>();
foreach (var policy in Client.Policies.AllPolicies)
{
nodes.Add(policy, new PermissionMetadata() { PermissionName = policy });
}
foreach (var n in nodes)
{
foreach (var policy in Client.Policies.AllPolicies)
{
if (policy.Equals(n.Key, StringComparison.OrdinalIgnoreCase))
continue;
if (Client.Permission.Create(n.Key).Contains(Client.Permission.Create(policy)))
n.Value.SubPermissions.Add(policy);
}
}
foreach (var n in nodes)
{
n.Value.SubPermissions.Sort();
}
PermissionNodes = nodes.Values.OrderBy(v => v.PermissionName).ToArray();
}
public readonly static PermissionMetadata[] PermissionNodes;
[JsonProperty("name")]
public string PermissionName { get; set; }
[JsonProperty("included")]
public List<string> SubPermissions { get; set; } = new List<string>();
}
}

View File

@ -27,12 +27,17 @@ namespace BTCPayServer.Client.Models
/// <summary>
/// detailed sync information per chain
/// </summary>
public IEnumerable<ServerInfoSyncStatusData> SyncStatus { get; set; }
public IEnumerable<SyncStatus> SyncStatus { get; set; }
}
public class ServerInfoSyncStatusData
public class SyncStatus
{
public string CryptoCode { get; set; }
public virtual bool Available { get; set; }
}
public class ServerInfoSyncStatusData: SyncStatus
{
public int ChainHeight { get; set; }
public int? SyncHeight { get; set; }
public ServerInfoNodeData NodeInformation { get; set; }

View File

@ -35,6 +35,7 @@ namespace BTCPayServer.Client.Models
public bool LightningAmountInSatoshi { get; set; }
public bool LightningPrivateRouteHints { get; set; }
public bool OnChainWithLnInvoiceFallback { get; set; }
public bool LazyPaymentMethods { get; set; }
public bool RedirectAutomatically { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
@ -53,8 +54,6 @@ namespace BTCPayServer.Client.Models
public string HtmlTitle { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;

View File

@ -0,0 +1,9 @@
namespace BTCPayServer.Client.Models
{
public enum TransactionStatus
{
Unconfirmed,
Confirmed,
Replaced
}
}

View File

@ -0,0 +1,20 @@
namespace BTCPayServer.Client.Models
{
public class UpdateLightningNetworkPaymentMethodRequest: LightningNetworkPaymentMethodBaseData
{
/// <summary>
/// Whether the payment method is enabled
/// </summary>
public bool Enabled { get; set; }
public UpdateLightningNetworkPaymentMethodRequest()
{
}
public UpdateLightningNetworkPaymentMethodRequest(string connectionString, bool enabled)
{
Enabled = enabled;
ConnectionString = connectionString;
}
}
}

View File

@ -0,0 +1,25 @@
using NBitcoin;
namespace BTCPayServer.Client.Models
{
public class UpdateOnChainPaymentMethodRequest : OnChainPaymentMethodBaseData
{
/// <summary>
/// Whether the payment method is enabled
/// </summary>
public bool Enabled { get; set; }
public UpdateOnChainPaymentMethodRequest()
{
}
public UpdateOnChainPaymentMethodRequest(bool enabled, string derivationScheme, string label, RootedKeyPath accountKeyPath)
{
Enabled = enabled;
Label = label;
AccountKeyPath = accountKeyPath;
DerivationScheme = derivationScheme;
}
}
}

View File

@ -19,7 +19,31 @@ namespace BTCPayServer.Client.Models
}
public string DeliveryId { get; set; }
public string WebhookId { get; set; }
public string OrignalDeliveryId { get; set; }
string _OriginalDeliveryId;
public string OriginalDeliveryId
{
get
{
if (_OriginalDeliveryId is null)
{
// Due to a typo in old version, we serialized `orignalDeliveryId` rather than `orignalDeliveryId`
// We silently fix that here.
// Note we can remove this code later on, as old webhook event are unlikely to be useful to anyone,
// and having a null orignalDeliveryId is not end of the world
if (AdditionalData != null &&
AdditionalData.TryGetValue("orignalDeliveryId", out var tok))
{
_OriginalDeliveryId = tok.Value<string>();
AdditionalData.Remove("orignalDeliveryId");
}
}
return _OriginalDeliveryId;
}
set
{
_OriginalDeliveryId = value;
}
}
public bool IsRedelivery { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public WebhookEventType Type { get; set; }

View File

@ -11,6 +11,7 @@ namespace BTCPayServer.Client.Models
InvoiceProcessing,
InvoiceExpired,
InvoiceSettled,
InvoiceInvalid
InvoiceInvalid,
InvoicePaymentSettled,
}
}

View File

@ -10,72 +10,88 @@ namespace BTCPayServer.Client.Models
{
public WebhookInvoiceEvent()
{
}
public WebhookInvoiceEvent(WebhookEventType evtType)
{
this.Type = evtType;
}
[JsonProperty(Order = 1)]
public string StoreId { get; set; }
[JsonProperty(Order = 2)]
public string InvoiceId { get; set; }
[JsonProperty(Order = 1)] public string StoreId { get; set; }
[JsonProperty(Order = 2)] public string InvoiceId { get; set; }
}
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
{
public WebhookInvoiceSettledEvent()
{
}
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
{
}
public bool ManuallyMarked { get; set; }
}
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
{
public WebhookInvoiceInvalidEvent()
{
}
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
{
}
public bool ManuallyMarked { get; set; }
}
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
{
public WebhookInvoiceProcessingEvent()
{
}
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
{
}
public bool OverPaid { get; set; }
}
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
{
public WebhookInvoiceReceivedPaymentEvent()
{
}
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
{
}
public bool AfterExpiration { get; set; }
public string PaymentMethod { get; set; }
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
}
public class WebhookInvoicePaymentSettledEvent : WebhookInvoiceReceivedPaymentEvent
{
public WebhookInvoicePaymentSettledEvent()
{
}
public WebhookInvoicePaymentSettledEvent(WebhookEventType evtType) : base(evtType)
{
}
}
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
{
public WebhookInvoiceExpiredEvent()
{
}
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
{
}

View File

@ -17,6 +17,7 @@ namespace BTCPayServer.Client
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanViewInvoices = "btcpay.store.canviewinvoices";
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
public const string CanModifyInvoices = "btcpay.store.canmodifyinvoices";
public const string CanViewPaymentRequests = "btcpay.store.canviewpaymentrequests";
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
@ -24,6 +25,7 @@ namespace BTCPayServer.Client
public const string CanManageNotificationsForUser = "btcpay.user.canmanagenotificationsforuser";
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
public const string CanCreateUser = "btcpay.server.cancreateuser";
public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string Unrestricted = "unrestricted";
public static IEnumerable<string> AllPolicies
@ -32,6 +34,7 @@ namespace BTCPayServer.Client
{
yield return CanViewInvoices;
yield return CanCreateInvoice;
yield return CanModifyInvoices;
yield return CanModifyStoreWebhooks;
yield return CanModifyServerSettings;
yield return CanModifyStoreSettings;
@ -41,6 +44,7 @@ namespace BTCPayServer.Client
yield return CanModifyProfile;
yield return CanViewProfile;
yield return CanCreateUser;
yield return CanDeleteUser;
yield return CanManageNotificationsForUser;
yield return CanViewNotificationsForUser;
yield return Unrestricted;
@ -162,10 +166,12 @@ namespace BTCPayServer.Client
switch (subpolicy)
{
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyInvoices:
case Policies.CanModifyStoreWebhooks when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewInvoices when this.Policy == Policies.CanViewStoreSettings:
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanModifyInvoices when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewProfile when this.Policy == Policies.CanModifyProfile:
case Policies.CanModifyPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
@ -173,6 +179,7 @@ namespace BTCPayServer.Client
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
case Policies.CanViewNotificationsForUser when this.Policy == Policies.CanManageNotificationsForUser:
case Policies.CanUseInternalLightningNode when this.Policy == Policies.CanModifyServerSettings:
return true;
default:
return false;

View File

@ -0,0 +1,29 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitAlthash()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("HTML");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Althash",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.htmlcoin.com/api/tx/{0}" : "https://explorer.htmlcoin.com/api/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "htmlcoin",
DefaultRateRules = new[]
{
"HTML_X = HTML_USD",
"HTML_USD = hitbtc(HTML_USD)"
},
CryptoImagePath = "imlegacy/althash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("88'") : new KeyPath("1'")
});
}
}
}

View File

@ -11,8 +11,8 @@ namespace BTCPayServer
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcore",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
DisplayName = "BitCore",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.bitcore.cc/tx/{0}" : "https://explorer.bitcore.cc/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcore",
DefaultRateRules = new[]

View File

@ -20,7 +20,7 @@ namespace BTCPayServer
DefaultRateRules = new[]
{
"DASH_X = DASH_BTC * BTC_X",
"DASH_BTC = bittrex(DASH_BTC)"
"DASH_BTC = bitfinex(DSH_BTC)"
},
CryptoImagePath = "imlegacy/dash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),

View File

@ -26,7 +26,8 @@ namespace BTCPayServer
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true
SupportPayJoin = true,
VaultSupported = true
});
}
}

View File

@ -1,6 +1,7 @@
#if ALTCOINS
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Common;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
@ -24,38 +25,24 @@ namespace BTCPayServer
});
}
public override GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
public override List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
{
TransactionInformationSet Filter(TransactionInformationSet transactionInformationSet)
{
return new TransactionInformationSet()
{
Transactions =
transactionInformationSet.Transactions.FindAll(information =>
information.Outputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId) ||
information.Inputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId))
};
}
return new GetTransactionsResponse()
{
Height = response.Height,
ConfirmedTransactions = Filter(response.ConfirmedTransactions),
ReplacedTransactions = Filter(response.ReplacedTransactions),
UnconfirmedTransactions = Filter(response.UnconfirmedTransactions)
};
return transactionInformationSet.FindAll(information =>
information.Outputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId) ||
information.Inputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId));
}
public override string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{
//precision 0: 10 = 0.00000010
//precision 2: 10 = 0.00001000
//precision 8: 10 = 10
var money = new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
return $"{base.GenerateBIP21(cryptoInfoAddress, money)}&assetid={AssetId}";
var money = cryptoInfoDue is null ? null : new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
var builder = base.GenerateBIP21(cryptoInfoAddress, money);
builder.QueryParams.Add("assetid", AssetId.ToString());
return builder;
}
}
}

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BTCPayServer.Common;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
@ -54,7 +55,7 @@ namespace BTCPayServer
{
public Network NBitcoinNetwork { get { return NBXplorerNetwork?.NBitcoinNetwork; } }
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
public bool SupportRBF { get; internal set; }
public bool SupportRBF { get; set; }
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; set; }
@ -63,46 +64,12 @@ namespace BTCPayServer
public virtual bool WalletSupported { get; set; } = true;
public virtual bool ReadonlyWallet { get; set; } = false;
public int MaxTrackedConfirmation { get; internal set; } = 6;
public string UriScheme { get; internal set; }
public virtual bool VaultSupported { get; set; } = false;
public int MaxTrackedConfirmation { get; set; } = 6;
public string UriScheme { get; set; }
public bool SupportPayJoin { get; set; } = false;
public bool SupportLightning { get; set; } = true;
public KeyPath GetRootKeyPath(DerivationType type)
{
KeyPath baseKey;
if (!NBitcoinNetwork.Consensus.SupportSegwit)
{
baseKey = new KeyPath("44'");
}
else
{
switch (type)
{
case DerivationType.Legacy:
baseKey = new KeyPath("44'");
break;
case DerivationType.SegwitP2SH:
baseKey = new KeyPath("49'");
break;
case DerivationType.Segwit:
baseKey = new KeyPath("84'");
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
return baseKey
.Derive(CoinType);
}
public KeyPath GetRootKeyPath()
{
return new KeyPath(NBitcoinNetwork.Consensus.SupportSegwit ? "49'" : "44'")
.Derive(CoinType);
}
public override T ToObject<T>(string json)
{
return NBXplorerNetwork.Serializer.ToObject<T>(json);
@ -121,14 +88,20 @@ namespace BTCPayServer
});
}
public virtual string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
var builder = new PaymentUrlBuilder(UriScheme);
builder.Host = cryptoInfoAddress;
if (cryptoInfoDue != null && cryptoInfoDue != Money.Zero)
{
builder.QueryParams.Add("amount", cryptoInfoDue.ToString(false, true));
}
return builder;
}
public virtual GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
public virtual List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
{
return response;
return transactionInformationSet;
}
}
@ -152,7 +125,7 @@ namespace BTCPayServer
}
}
public string BlockExplorerLinkDefault { get; internal set; }
public string BlockExplorerLinkDefault { get; set; }
public string DisplayName { get; set; }
public int Divisibility { get; set; } = 8;
[Obsolete("Should not be needed")]

View File

@ -14,7 +14,7 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/tx/{0}" :
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://explorer.bc-2.jp/"
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://explorer.bc-2.jp/tx/{0}"
: "https://blockstream.info/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoin",
@ -24,6 +24,7 @@ namespace BTCPayServer
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
VaultSupported = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()

View File

@ -51,6 +51,7 @@ namespace BTCPayServer
InitMonacoin();
InitDash();
InitFeathercoin();
InitAlthash();
InitGroestlcoin();
InitViacoin();
InitMonero();
@ -131,5 +132,10 @@ namespace BTCPayServer
}
return network as T;
}
public bool TryGetNetwork<T>(string cryptoCode, out T network) where T : BTCPayNetworkBase
{
network = GetNetwork<T>(cryptoCode);
return network != null;
}
}
}

View File

@ -4,9 +4,9 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="3.0.20" />
<PackageReference Include="NBXplorer.Client" Version="4.1.2" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>
</ItemGroup>
</Project>
</Project>

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using NBitcoin;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer
{
@ -11,5 +13,23 @@ namespace BTCPayServer
hashSet.Add(item);
}
}
public static ScriptPubKeyType ScriptPubKeyType(this DerivationStrategyBase derivationStrategyBase)
{
if (IsSegwitCore(derivationStrategyBase))
{
return NBitcoin.ScriptPubKeyType.Segwit;
}
return (derivationStrategyBase is P2SHDerivationStrategy p2shStrat && IsSegwitCore(p2shStrat.Inner))
? NBitcoin.ScriptPubKeyType.SegwitP2SH
: NBitcoin.ScriptPubKeyType.Legacy;
}
private static bool IsSegwitCore(DerivationStrategyBase derivationStrategyBase)
{
return (derivationStrategyBase is P2WSHDerivationStrategy) ||
(derivationStrategyBase is DirectDerivationStrategy direct) && direct.Segwit;
}
}
}

View File

@ -0,0 +1,10 @@
using NBXplorer;
namespace BTCPayServer.Common
{
public interface IExplorerClientProvider
{
ExplorerClient GetExplorerClient(string cryptoCode);
ExplorerClient GetExplorerClient(BTCPayNetworkBase network);
}
}

View File

@ -0,0 +1,30 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BTCPayServer.Common
{
public class PaymentUrlBuilder
{
public PaymentUrlBuilder(string uriScheme)
{
UriScheme = uriScheme;
}
public string UriScheme { get; set; }
public Dictionary<string, string> QueryParams { get; set; } = new Dictionary<string, string>();
public string? Host { get; set; }
public override string ToString()
{
StringBuilder builder = new StringBuilder($"{UriScheme}:{Host}");
if (QueryParams.Count != 0)
{
var parts = QueryParams.Select(q => Uri.EscapeDataString(q.Key) + "=" + System.Web.NBitcoin.HttpUtility.UrlEncode(q.Value))
.ToArray();
builder.Append($"?{string.Join('&', parts)}");
}
return builder.ToString();
}
}
}

View File

@ -55,6 +55,7 @@ namespace BTCPayServer.Data
public DbSet<StoreWebhookData> StoreWebhooks { get; set; }
public DbSet<StoreData> Stores { get; set; }
public DbSet<U2FDevice> U2FDevices { get; set; }
public DbSet<Fido2Credential> Fido2Credentials { get; set; }
public DbSet<UserStore> UserStore { get; set; }
public DbSet<WalletData> Wallets { get; set; }
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
@ -99,6 +100,7 @@ namespace BTCPayServer.Data
StoreWebhookData.OnModelCreating(builder);
//StoreData.OnModelCreating(builder);
U2FDevice.OnModelCreating(builder);
Fido2Credential.OnModelCreating(builder);
Data.UserStore.OnModelCreating(builder);
//WalletData.OnModelCreating(builder);
WalletTransactionData.OnModelCreating(builder);

View File

@ -3,11 +3,11 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.19">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.19" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />

View File

@ -9,6 +9,7 @@ namespace BTCPayServer.Data
{
public bool RequiresEmailConfirmation { get; set; }
public List<StoredFile> StoredFiles { get; set; }
[Obsolete("U2F support has been replace with FIDO2")]
public List<U2FDevice> U2FDevices { get; set; }
public List<APIKeyData> APIKeys { get; set; }
public DateTimeOffset? Created { get; set; }
@ -16,5 +17,6 @@ namespace BTCPayServer.Data
public List<NotificationData> Notifications { get; set; }
public List<UserStore> UserStores { get; set; }
public List<Fido2Credential> Fido2Credentials { get; set; }
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
public class Fido2Credential
{
public string Name { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string ApplicationUserId { get; set; }
public byte[] Blob { get; set; }
public CredentialType Type { get; set; }
public enum CredentialType
{
FIDO2
}
public static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Fido2Credential>()
.HasOne(o => o.ApplicationUser)
.WithMany(i => i.Fido2Credentials)
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
}
public ApplicationUser ApplicationUser { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
@ -53,13 +54,4 @@ namespace BTCPayServer.Data
}
}
}
public enum PayoutState
{
AwaitingApproval,
AwaitingPayment,
InProgress,
Completed,
Cancelled
}
}

View File

@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using BTCPayServer.Client.Models;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
namespace BTCPayServer.Data
{
public class PullPaymentData
{
[Key]
@ -86,7 +88,6 @@ namespace BTCPayServer.Data
}
}
public static class PayoutExtensions
{
public static IQueryable<PayoutData> GetPayoutInPeriod(this IQueryable<PayoutData> payouts, PullPaymentData pp)

View File

@ -23,11 +23,12 @@ namespace BTCPayServer.Data
internal static void OnModelCreating(ModelBuilder builder)
{
#pragma warning disable CS0618 // Type or member is obsolete
builder.Entity<U2FDevice>()
.HasOne(o => o.ApplicationUser)
.WithMany(i => i.U2FDevices)
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
#pragma warning restore CS0618 // Type or member is obsolete
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20210314092253_Fido2Credentials")]
public partial class Fido2Credentials : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.CreateTable(
name: "Fido2Credentials",
columns: table => new
{
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Name = table.Column<string>(nullable: true),
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength),
Blob = table.Column<byte[]>(nullable: true),
Type = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Fido2Credentials", x => x.Id);
table.ForeignKey(
name: "FK_Fido2Credentials_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Fido2Credentials_ApplicationUserId",
table: "Fido2Credentials",
column: "ApplicationUserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Fido2Credentials");
}
}
}

View File

@ -170,6 +170,31 @@ namespace BTCPayServer.Migrations
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ApplicationUserId")
.HasColumnType("TEXT");
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("Fido2Credentials");
});
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
{
b.Property<string>("InvoiceDataId")
@ -958,6 +983,14 @@ namespace BTCPayServer.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
{
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
.WithMany("Fido2Credentials")
.HasForeignKey("ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")

View File

@ -13,7 +13,7 @@
<EmbeddedResource Include="Resources\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.19">
</PackageReference>
</ItemGroup>
</Project>

View File

@ -20,6 +20,7 @@ namespace BTCPayServer.Plugins.Test.Services
await using var context = _testPluginDbContextFactory.CreateContext();
await context.TestPluginRecords.AddAsync(new TestPluginData() {Timestamp = DateTimeOffset.UtcNow});
await context.SaveChangesAsync();
}

View File

@ -9,8 +9,8 @@
<div class="row">
<h2>Persisted Data</h2>
<p>The following is data persisted to the configured database but in an isolated DbContext. Every time you start BTCPayw with this plugin enabled, a timestamp is logged.</p>
<ul class="list-group">>
<p>The following is data persisted to the configured database but in an isolated DbContext. Every time you start BTCPay Server with this plugin enabled, a timestamp is logged.</p>
<ul class="list-group">
@foreach (var item in Model.Data)
{
<li class="list-group-item">@item.Id at @item.Timestamp.ToString("F")</li>

View File

@ -4,9 +4,9 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.11.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="NBitcoin" Version="5.0.73" />
<PackageReference Include="NBitcoin" Version="6.0.10" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>

View File

@ -1266,6 +1266,13 @@
"symbol":null,
"crypto":true
},
{
"name":"Althash",
"code":"HTML",
"divisibility":8,
"symbol":null,
"crypto":true
},
{
"name":"CHC",
"code":"CHC",

View File

@ -28,7 +28,6 @@ namespace BTCPayServer.Services.Rates
}
static readonly Dictionary<string, IFormatProvider> _CurrencyProviders = new Dictionary<string, IFormatProvider>();
public string FormatCurrency(string price, string currency)
{
return FormatCurrency(decimal.Parse(price, CultureInfo.InvariantCulture), currency);
@ -110,9 +109,8 @@ namespace BTCPayServer.Services.Rates
/// </summary>
/// <param name="value">The value</param>
/// <param name="currency">Currency code</param>
/// <param name="threeLetterSuffix">Add three letter suffix (like USD)</param>
/// <returns></returns>
public string DisplayFormatCurrency(decimal value, string currency, bool threeLetterSuffix = true)
public string DisplayFormatCurrency(decimal value, string currency)
{
var provider = GetNumberFormatInfo(currency, true);
var currencyData = GetCurrencyData(currency, true);

File diff suppressed because one or more lines are too long

View File

@ -1,61 +1,31 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Models;
using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Rating;
using BTCPayServer.Security.Bitpay;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Tests.Logging;
using BTCPayServer.U2F.Models;
using BTCPayServer.Validation;
using ExchangeSharp;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
using NBitcoin.Scripting.Parser;
using NBitpayClient;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
namespace BTCPayServer.Tests
{
@ -72,7 +42,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanAddDerivationSchemes()
public async Task CanSetupWallet()
{
using (var tester = ServerTester.Create())
{
@ -80,13 +50,14 @@ namespace BTCPayServer.Tests
tester.ActivateLightning();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var cryptoCode = "BTC";
await user.GrantAccessAsync(true);
user.RegisterDerivationScheme(cryptoCode);
user.RegisterDerivationScheme("LTC");
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>("BTC");
var invoice = user.BitPay.CreateInvoice(
new Invoice()
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 1.5m,
Currency = "USD",
@ -99,37 +70,44 @@ namespace BTCPayServer.Tests
Assert.Equal(3, invoice.CryptoInfo.Length);
var controller = user.GetController<StoresController>();
var lightningVM =
(LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC"))
.Model;
Assert.True(lightningVM.Enabled);
lightningVM.Enabled = false;
controller.AddLightningNode(user.StoreId, lightningVM, "save", "BTC").GetAwaiter().GetResult();
lightningVM =
(LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC"))
.Model;
Assert.False(lightningVM.Enabled);
var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(await controller.SetupLightningNode(user.StoreId, cryptoCode)).Model;
Assert.True(lightningVm.Enabled);
var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false);
Assert.IsType<RedirectToActionResult>(response);
// Only Enabling/Disabling the payment method must redirect to store page
var derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
Assert.True(derivationVM.Enabled);
derivationVM.Enabled = false;
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
.GetAwaiter().GetResult());
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
Assert.False(derivationVM.Enabled);
// Get enabled state from overview action
StoreViewModel storeModel;
response = await controller.UpdateStore();
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
var lnNode = storeModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode);
Assert.NotNull(lnNode);
Assert.False(lnNode.Enabled);
// Clicking next without changing anything should send to the confirmation screen
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
Assert.True(derivationVM.Confirmation);
WalletSetupViewModel setupVm;
var storeId = user.StoreId;
response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new WalletSetupRequest());
Assert.IsType<ViewResult>(response);
invoice = user.BitPay.CreateInvoice(
new Invoice()
// Get enabled state from overview action
response = await controller.UpdateStore();
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
var derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
Assert.NotNull(derivationScheme);
Assert.True(derivationScheme.Enabled);
// Disable wallet
response = controller.SetWalletEnabled(storeId, cryptoCode, false).GetAwaiter().GetResult();
Assert.IsType<RedirectToActionResult>(response);
response = await controller.UpdateStore();
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
Assert.NotNull(derivationScheme);
Assert.False(derivationScheme.Enabled);
var oldScheme = derivationScheme.Value;
invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 1.5m,
Currency = "USD",
@ -143,76 +121,57 @@ namespace BTCPayServer.Tests
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
// Removing the derivation scheme, should redirect to store page
var oldScheme = derivationVM.DerivationScheme;
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
derivationVM.DerivationScheme = null;
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
.GetAwaiter().GetResult());
response = controller.ConfirmDeleteWallet(user.StoreId, cryptoCode).GetAwaiter().GetResult();
Assert.IsType<RedirectToActionResult>(response);
// Setting it again should redirect to the confirmation page
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
derivationVM.DerivationScheme = oldScheme;
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
Assert.True(derivationVM.Confirmation);
// Setting it again should show the confirmation page
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, DerivationScheme = oldScheme });
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.True(setupVm.Confirmation);
// The following part posts a wallet update, confirms it and checks the result
//cobo vault file
// cobo vault file
var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}";
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content);
derivationVM.Enabled = true;
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
Assert.True(derivationVM.Confirmation);
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
.GetAwaiter().GetResult());
//wasabi wallet file
content =
"{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}";
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content);
derivationVM.Enabled = true;
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
Assert.True(derivationVM.Confirmation);
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
.GetAwaiter().GetResult());
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("cobovault.json", content)});
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.True(setupVm.Confirmation);
response = await controller.UpdateWallet(setupVm);
Assert.IsType<RedirectToActionResult>(response);
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.Equal("CoboVault", setupVm.Source);
// wasabi wallet file
content = "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}";
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("wasabi.json", content)});
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.True(setupVm.Confirmation);
response = await controller.UpdateWallet(setupVm);
Assert.IsType<RedirectToActionResult>(response);
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.Equal("WasabiFile", setupVm.Source);
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
content =
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content);
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
Assert.False(derivationVM
.Confirmation); // Should fail, we are giving a mainnet file to a testnet network
content = "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-ypub.json", content)});
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.False(setupVm.Confirmation); // Should fail, we are giving a mainnet file to a testnet network
// And with a good file? (upub)
content =
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
derivationVM = (DerivationSchemeViewModel)Assert
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content);
derivationVM.Enabled = true;
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
Assert.True(derivationVM.Confirmation);
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
.GetAwaiter().GetResult());
content = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-upub.json", content)});
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.True(setupVm.Confirmation);
response = await controller.UpdateWallet(setupVm);
Assert.IsType<RedirectToActionResult>(response);
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.Equal("ElectrumFile", setupVm.Source);
// Now let's check that no data has been lost in the process
var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult();
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks)
#pragma warning disable CS0618 // Type or member is obsolete
.OfType<DerivationSchemeSettings>().First(o => o.PaymentId.IsBTCOnChain);
@ -221,8 +180,8 @@ namespace BTCPayServer.Tests
Assert.Equal(expected.ToJson(), onchainBTC.ToJson());
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
invoice = user.BitPay.CreateInvoice(
new Invoice()
invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 1.5m,
Currency = "USD",
@ -233,7 +192,7 @@ namespace BTCPayServer.Tests
}, Facade.Merchant);
tester.ExplorerNode.Generate(1);
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == "BTC").Address,
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == cryptoCode).Address,
tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m));
TestUtils.Eventually(() =>
@ -245,9 +204,9 @@ namespace BTCPayServer.Tests
var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC,
new WalletSendModel()
{
Outputs = new List<WalletSendModel.TransactionOutput>()
Outputs = new List<WalletSendModel.TransactionOutput>
{
new WalletSendModel.TransactionOutput()
new WalletSendModel.TransactionOutput
{
Amount = 0.5m,
DestinationAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, btcNetwork.NBitcoinNetwork)
@ -287,7 +246,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess();
user.GrantAccess(true);
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("LTC");
@ -295,7 +254,6 @@ namespace BTCPayServer.Tests
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC"));
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")
{
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
@ -354,7 +312,7 @@ namespace BTCPayServer.Tests
var controller = tester.PayTester.GetController<InvoiceController>(null);
var checkout =
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null)
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
.GetAwaiter().GetResult()).Value;
Assert.Single(checkout.AvailableCryptos);
Assert.Equal("LTC", checkout.CryptoCode);
@ -371,7 +329,7 @@ namespace BTCPayServer.Tests
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status);
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null)
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
.GetAwaiter().GetResult()).Value;
Assert.Equal("paid", checkout.Status);
});
@ -876,7 +834,7 @@ normal:
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
});
var networkBTC = networkProvider.GetNetwork("BTC");
var networkLTC = networkProvider.GetNetwork("LTC");
@ -996,28 +954,6 @@ normal:
result = testnetParser.Parse(tpub);
Assert.Equal(tpub, result.ToString());
testnetParser.HintScriptPubKey = BitcoinAddress
.Create("tb1q4s33amqm8l7a07zdxcunqnn3gcsjcfz3xc573l", testnetParser.Network).ScriptPubKey;
result = testnetParser.Parse(tpub);
Assert.Equal(tpub, result.ToString());
testnetParser.HintScriptPubKey = BitcoinAddress
.Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", testnetParser.Network).ScriptPubKey;
result = testnetParser.Parse(tpub);
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
testnetParser.HintScriptPubKey = BitcoinAddress
.Create("mwD8bHS65cdgUf6rZUUSoVhi3wNQFu1Nfi", testnetParser.Network).ScriptPubKey;
result = testnetParser.Parse(tpub);
Assert.Equal($"{tpub}-[legacy]", result.ToString());
testnetParser.HintScriptPubKey = BitcoinAddress
.Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", testnetParser.Network).ScriptPubKey;
result = testnetParser.Parse($"{tpub}-[legacy]");
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
result = testnetParser.Parse(tpub);
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
var regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
var parsed =

View File

@ -73,14 +73,14 @@ namespace BTCPayServer.Tests
var seed = new Mnemonic(Wordlist.English);
s.Driver.FindElement(By.Id("ModifyETH")).Click();
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true);
s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true);
s.Driver.SetCheckbox(By.Id("StoreSeed"), true);
s.Driver.SetCheckbox(By.Id("Enabled"), true);
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("ModifyUSDT20")).Click();
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true);
s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true);
s.Driver.SetCheckbox(By.Id("StoreSeed"), true);
s.Driver.SetCheckbox(By.Id("Enabled"), true);
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.FindAlertMessage();

View File

@ -61,9 +61,9 @@ namespace BTCPayServer.Tests
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
//server management should show now
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
s.SetCheckbox(s, "btcpay.user.canviewprofile", true);
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.user.canviewprofile"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
@ -72,7 +72,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
@ -80,7 +80,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
@ -89,7 +89,12 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click();
//there should be a store already by default in the dropdown
var dropdown = s.Driver.FindElement(By.Name("PermissionValues[4].SpecificStores[0]"));
var src = s.Driver.PageSource;
var getPermissionValueIndex =
s.Driver.FindElement(By.CssSelector("input[value='btcpay.store.canmodifystoresettings']"))
.GetAttribute("name")
.Replace(".Permission", ".SpecificStores[0]");
var dropdown = s.Driver.FindElement(By.Name(getPermissionValueIndex));
var option = dropdown.FindElement(By.TagName("option"));
var storeId = option.GetAttribute("value");
option.Click();
@ -119,8 +124,8 @@ namespace BTCPayServer.Tests
//redirect
//appidentifier
var appidentifier = "testapp";
var callbackUrl = tester.PayTester.ServerUri + "postredirect-callback-test";
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
var callbackUrl = s.ServerUri + "postredirect-callback-test";
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, applicationDetails: (appidentifier, new Uri(callbackUrl))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);
Assert.Contains(appidentifier, s.Driver.PageSource);
@ -138,7 +143,7 @@ namespace BTCPayServer.Tests
await TestApiAgainstAccessToken(accessToken, tester, user,
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, applicationDetails: (null, new Uri(callbackUrl))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);
@ -149,7 +154,7 @@ namespace BTCPayServer.Tests
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", false);
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false);
Assert.Contains("change-store-mode", s.Driver.PageSource);
s.Driver.FindElement(By.Id("consent-yes")).Click();
Assert.Equal(callbackUrl, s.Driver.Url);
@ -159,7 +164,7 @@ namespace BTCPayServer.Tests
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
//let's test the app identifier system
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri(callbackUrl))).ToString();
//if it's the same, go to the confirm page
@ -168,7 +173,7 @@ namespace BTCPayServer.Tests
Assert.Equal(callbackUrl, s.Driver.Url);
//same app but different redirect = nono
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri("https://international.local/callback"))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);

View File

@ -19,11 +19,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="88.0.4324.9600" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="94.0.4606.6100" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>

View File

@ -85,7 +85,7 @@ namespace BTCPayServer.Tests
public HashSet<string> Chains { get; set; } = new HashSet<string>() { "BTC" };
public bool UseLightning { get; set; }
public bool AllowAdminRegistration { get; set; } = true;
public bool CheatMode { get; set; } = true;
public bool DisableRegistration { get; set; } = false;
public async Task StartAsync()
{
@ -112,7 +112,7 @@ namespace BTCPayServer.Tests
if (UseLightning)
{
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
config.AppendLine($"btc.lightning={IntegratedLightning}");
var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json");
File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true);
config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}");
@ -128,13 +128,13 @@ namespace BTCPayServer.Tests
config.AppendLine($"lbtc.explorer.url={LBTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"lbtc.explorer.cookiefile=0");
}
if (AllowAdminRegistration)
config.AppendLine("allow-admin-registration=1");
if (CheatMode)
config.AppendLine("cheatmode=1");
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
config.AppendLine($"socksendpoint={SocksEndpoint}");
config.AppendLine($"debuglog=debug.log");
config.AppendLine($"nocsp={NoCSP.ToString().ToLowerInvariant()}");
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
config.AppendLine($"sshpassword={SSHPassword}");
@ -167,6 +167,7 @@ namespace BTCPayServer.Tests
l.SetMinimumLevel(LogLevel.Information)
.AddFilter("Microsoft", LogLevel.Error)
.AddFilter("Hangfire", LogLevel.Error)
.AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error)
.AddProvider(Logs.LogProvider);
});
})
@ -269,7 +270,7 @@ namespace BTCPayServer.Tests
public InvoiceRepository InvoiceRepository { get; private set; }
public StoreRepository StoreRepository { get; private set; }
public BTCPayNetworkProvider Networks { get; private set; }
public Uri IntegratedLightning { get; internal set; }
public string IntegratedLightning { get; internal set; }
public bool InContainer { get; internal set; }
public T GetService<T>()
@ -282,6 +283,8 @@ namespace BTCPayServer.Tests
public string SSHPassword { get; internal set; }
public string SSHKeyFile { get; internal set; }
public string SSHConnection { get; set; }
public bool NoCSP { get; set; }
public T GetController<T>(string userId = null, string storeId = null, bool isAdmin = false) where T : Controller
{
var context = new DefaultHttpContext();

View File

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Payments;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Stores;
using NBitcoin;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
@ -100,6 +103,28 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")]
public async Task CanSetDefaultPaymentMethod()
{
using (var s = SeleniumTester.Create())
{
s.Server.ActivateLightning();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser(true);
var store = s.CreateNewStore();
s.AddLightningNode();
s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(store.storeName, defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId);
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")]
public async Task CanUseLightningSatsFeature()
@ -109,18 +134,17 @@ namespace BTCPayServer.Tests
s.Server.ActivateLightning();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
s.RegisterNewUser(true);
var store = s.CreateNewStore();
s.AddInternalLightningNode("BTC");
s.GoToStore(store.storeId, StoreNavPages.Checkout);
s.SetCheckbox(s, "LightningAmountInSatoshi", true);
var command = s.Driver.FindElement(By.Name("command"));
command.Click();
s.AddLightningNode();
s.GoToStore(store.storeId);
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
s.Driver.FindElement(By.Id("Save")).Click();
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
}
}
@ -138,7 +162,7 @@ namespace BTCPayServer.Tests
var invoiceId = s.CreateInvoice(store.storeId, 0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
s.Driver.Navigate()
.GoToUrl(new Uri(s.Server.PayTester.ServerUri, $"tests/index.html?invoice={invoiceId}"));
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}"));
TestUtils.Eventually(() =>
{
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
@ -159,7 +183,7 @@ namespace BTCPayServer.Tests
closebutton.Click();
s.Driver.AssertElementNotFound(By.Name("btcpay"));
Assert.Equal(s.Driver.Url,
new Uri(s.Server.PayTester.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
}
}
}

View File

@ -1,91 +0,0 @@
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class CoinSwitchTests
{
public CoinSwitchTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanSetCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.Null(storeBlob.CoinSwitchSettings);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
{
MerchantId = "aaa",
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.IsType<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);
Assert.Equal(storeBlob.CoinSwitchSettings.MerchantId,
updateModel.MerchantId);
}
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanToggleCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
{
MerchantId = "aaa",
Enabled = true
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.True(store.GetStoreBlob().CoinSwitchSettings.Enabled);
updateModel.Enabled = false;
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.False(store.GetStoreBlob().CoinSwitchSettings.Enabled);
}
}
}
}

View File

@ -23,7 +23,7 @@ namespace BTCPayServer.Tests
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateAndDeleteCrowdfundApp()
{
@ -63,7 +63,7 @@ namespace BTCPayServer.Tests
[Fact(Timeout = TestTimeout)]
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanContributeOnlyWhenAllowed()
{
@ -155,7 +155,7 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanComputeCrowdfundModel()
{
@ -165,7 +165,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
user.ModifyStore(s => s.NetworkFeeMode = NetworkFeeMode.Never);
await user.ModifyStore(s => s.NetworkFeeMode = NetworkFeeMode.Never);
var apps = user.GetController<AppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
vm.Name = "test";

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.202 AS builder
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.413 AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*

View File

@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;
using OpenQA.Selenium.Support.UI;
using Xunit;
namespace BTCPayServer.Tests
@ -26,46 +28,12 @@ namespace BTCPayServer.Tests
public static void AssertNoError(this IWebDriver driver)
{
try
{
Assert.NotEmpty(driver.FindElements(By.ClassName("navbar-brand")));
if (driver.PageSource.Contains("alert-danger"))
{
foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger")))
Assert.False(dangerAlert.Displayed, "No alert should be displayed");
}
}
catch
{
StringBuilder builder = new StringBuilder();
builder.AppendLine();
foreach (var logKind in new[] { LogType.Browser, LogType.Client, LogType.Driver, LogType.Server })
{
try
{
var logs = driver.Manage().Logs.GetLog(logKind);
builder.AppendLine($"Selenium [{logKind}]:");
foreach (var entry in logs)
{
builder.AppendLine($"[{entry.Level}]: {entry.Message}");
}
}
catch
{
// ignored
}
builder.AppendLine("---------");
}
Logs.Tester.LogInformation(builder.ToString());
builder = new StringBuilder();
builder.AppendLine("Selenium [Sources]:");
builder.AppendLine(driver.PageSource);
builder.AppendLine("---------");
Logs.Tester.LogInformation(builder.ToString());
throw;
}
Assert.NotEmpty(driver.FindElements(By.ClassName("navbar-brand")));
if (!driver.PageSource.Contains("alert-danger")) return;
foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger")))
Assert.False(dangerAlert.Displayed, $"No alert should be displayed, but found this on {driver.Url}: {dangerAlert.Text}");
}
public static T AssertViewModel<T>(this IActionResult result)
{
Assert.NotNull(result);
@ -80,6 +48,22 @@ namespace BTCPayServer.Tests
return Assert.IsType<T>(vr.Model);
}
// Sometimes, selenium is flaky...
public static IWebElement FindElementUntilNotStaled(this IWebDriver driver, By by, Action<IWebElement> act)
{
retry:
try
{
var el = driver.FindElement(by);
act(el);
return el;
}
catch (StaleElementReferenceException)
{
goto retry;
}
}
public static void AssertElementNotFound(this IWebDriver driver, By by)
{
DateTimeOffset now = DateTimeOffset.Now;
@ -105,5 +89,56 @@ namespace BTCPayServer.Tests
}
Assert.False(true, "Elements was found");
}
public static void UntilJsIsReady(this WebDriverWait wait)
{
wait.Until(d=>((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
wait.Until(d=>((IJavaScriptExecutor)d).ExecuteScript("return typeof(jQuery) === 'undefined' || jQuery.active === 0").Equals(true));
}
// Open collapse via JS, because if we click the link it triggers the toggle animation.
// This leads to Selenium trying to click the button while it is moving resulting in an error.
public static void ToggleCollapse(this IWebDriver driver, string collapseId)
{
driver.ExecuteJavaScript($"document.getElementById('{collapseId}').classList.add('show')");
}
public static IWebElement WaitForElement(this IWebDriver driver, By selector)
{
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
wait.UntilJsIsReady();
var el = driver.FindElement(selector);
wait.Until(d => el.Displayed);
return el;
}
public static void WaitForAndClick(this IWebDriver driver, By selector)
{
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
wait.UntilJsIsReady();
var el = driver.FindElement(selector);
wait.Until(d => el.Displayed && el.Enabled);
el.Click();
wait.UntilJsIsReady();
}
public static void SetCheckbox(this IWebDriver driver, By selector, bool value)
{
var element = driver.FindElement(selector);
if ((value && !element.Selected) || (!value && element.Selected))
{
driver.WaitForAndClick(selector);
}
if (value != element.Selected)
{
Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again");
driver.SetCheckbox(selector, value);
}
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
@ -11,6 +13,7 @@ using BTCPayServer.Events;
using BTCPayServer.JsonConverters;
using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs;
@ -18,7 +21,6 @@ using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.OpenAsset;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -38,6 +40,23 @@ namespace BTCPayServer.Tests
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task LocalClientTests()
{
using var tester = ServerTester.Create();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
await user.MakeAdmin();
var factory = tester.PayTester.GetService<IBTCPayServerClientFactory>();
Assert.NotNull(factory);
var client = await factory.Create(user.UserId);
var u = await client.GetCurrentUser();
var s = await client.GetStores();
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task ApiKeysControllerTests()
@ -67,6 +86,27 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanUseMiscAPIs()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var acc = tester.NewAccount();
await acc.GrantAccessAsync();
var unrestricted = await acc.CreateClient();
var langs = await unrestricted.GetAvailableLanguages();
Assert.NotEmpty(langs);
Assert.NotNull(langs[0].Code);
Assert.NotNull(langs[0].DisplayName);
var perms = await unrestricted.GetPermissionMetadata();
Assert.NotEmpty(perms);
var p = perms.First(p => p.PermissionName == "unrestricted");
Assert.True(p.SubPermissions.Count > 6);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
@ -125,6 +165,45 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanDeleteUsersViaApi()
{
using var tester = ServerTester.Create(newDb: true);
await tester.StartAsync();
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
// Should not be authorized to perform this action
await AssertHttpError(401,
async () => await unauthClient.DeleteUser("lol user id"));
var user = tester.NewAccount();
await user.GrantAccessAsync();
await user.MakeAdmin();
var adminClient = await user.CreateClient(Policies.Unrestricted);
//can't delete if the only admin
await AssertHttpError(403,
async () => await adminClient.DeleteCurrentUser());
// Should 404 if user doesn't exist
await AssertHttpError(404,
async () => await adminClient.DeleteUser("lol user id"));
user = tester.NewAccount();
await user.GrantAccessAsync();
var badClient = await user.CreateClient(Policies.CanCreateInvoice);
await AssertHttpError(403,
async () => await badClient.DeleteCurrentUser());
var goodClient = await user.CreateClient(Policies.CanDeleteUser, Policies.CanViewProfile);
await goodClient.DeleteCurrentUser();
await AssertHttpError(404,
async () => await adminClient.DeleteUser(user.UserId));
tester.Stores.Remove(user.StoreId);
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateUsersViaAPI()
@ -469,6 +548,30 @@ namespace BTCPayServer.Tests
{
Revision = payout.Revision
}));
// Create one pull payment with an amount of 9 decimals
var test3 = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
{
Name = "Test 2",
Amount = 12.303228134m,
Currency = "BTC",
PaymentMethods = new[] { "BTC" }
});
destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString();
payout = await unauthenticated.CreatePayout(test3.Id, new CreatePayoutRequest()
{
Destination = destination,
PaymentMethod = "BTC"
});
payout = await client.ApprovePayout(storeId, payout.Id, new ApprovePayoutRequest());
// The payout should round the value of the payment down to the network of the payment method
Assert.Equal(12.30322814m, payout.PaymentMethodAmount);
Assert.Equal(12.303228134m, payout.Amount);
await client.MarkPayoutPaid(storeId, payout.Id);
payout = (await client.GetPayouts(payout.PullPaymentId)).First(data => data.Id == payout.Id);
Assert.Equal(PayoutState.Completed, payout.State);
await AssertAPIError("invalid-state", async () => await client.MarkPayoutPaid(storeId, payout.Id));
}
}
@ -540,7 +643,7 @@ namespace BTCPayServer.Tests
}
}
private async Task AssertValidationError(string[] fields, Func<Task> act)
private async Task<GreenFieldValidationException> AssertValidationError(string[] fields, Func<Task> act)
{
var remainingFields = fields.ToHashSet();
var ex = await Assert.ThrowsAsync<GreenFieldValidationException>(act);
@ -550,6 +653,7 @@ namespace BTCPayServer.Tests
remainingFields.Remove(field);
}
Assert.Empty(remainingFields);
return ex;
}
private async Task AssertHttpError(int code, Func<Task> act)
@ -677,14 +781,15 @@ namespace BTCPayServer.Tests
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
req = await fakeServer.GetNextRequest();
req.Response.StatusCode = 404;
fakeServer.Done();
await TestUtils.EventuallyAsync(async () =>
{
// Releasing semaphore several times may help making this test less flaky
fakeServer.Done();
var newDelivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, newDeliveryId);
Assert.NotNull(newDelivery);
Assert.Equal(404, newDelivery.HttpCode);
var req = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
Assert.Equal(delivery.Id, req.OrignalDeliveryId);
Assert.Equal(delivery.Id, req.OriginalDeliveryId);
Assert.True(req.IsRedelivery);
Assert.Equal(WebhookDeliveryStatus.HttpError, newDelivery.Status);
});
@ -948,8 +1053,19 @@ namespace BTCPayServer.Tests
});
await user.RegisterDerivationSchemeAsync("BTC");
var newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\"}") });
new CreateInvoiceRequest()
{
Currency = "USD",
Amount = 1,
Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"),
Checkout = new CreateInvoiceRequest.CheckoutOptions()
{
RedirectAutomatically = true
},
AdditionalSearchTerms = new string[] { "Banana" }
});
Assert.True(newInvoice.Checkout.RedirectAutomatically);
Assert.Equal(user.StoreId, newInvoice.StoreId);
//list
var invoices = await viewOnly.GetInvoices(user.StoreId);
@ -957,6 +1073,76 @@ namespace BTCPayServer.Tests
Assert.Single(invoices);
Assert.Equal(newInvoice.Id, invoices.First().Id);
invoices = await viewOnly.GetInvoices(user.StoreId, textSearch: "Banana");
Assert.NotNull(invoices);
Assert.Single(invoices);
Assert.Equal(newInvoice.Id, invoices.First().Id);
invoices = await viewOnly.GetInvoices(user.StoreId, textSearch: "apples");
Assert.NotNull(invoices);
Assert.Empty(invoices);
//list Filtered
var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId,
orderId: null, status: null, startDate: DateTimeOffset.Now.AddHours(-1),
endDate: DateTimeOffset.Now.AddHours(1));
Assert.NotNull(invoicesFiltered);
Assert.Single(invoicesFiltered);
Assert.Equal(newInvoice.Id, invoicesFiltered.First().Id);
Assert.NotNull(invoicesFiltered);
Assert.Single(invoicesFiltered);
Assert.Equal(newInvoice.Id, invoicesFiltered.First().Id);
//list Yesterday
var invoicesYesterday = await viewOnly.GetInvoices(user.StoreId,
orderId: null, status: null, startDate: DateTimeOffset.Now.AddDays(-2),
endDate: DateTimeOffset.Now.AddDays(-1));
Assert.NotNull(invoicesYesterday);
Assert.Empty(invoicesYesterday);
// Error, startDate and endDate inverted
await AssertValidationError(new[] { "startDate", "endDate" },
() => viewOnly.GetInvoices(user.StoreId,
orderId: null, status: null, startDate: DateTimeOffset.Now.AddDays(-1),
endDate: DateTimeOffset.Now.AddDays(-2)));
await AssertValidationError(new[] { "startDate" },
() => viewOnly.SendHttpRequest<Client.Models.InvoiceData[]>($"api/v1/stores/{user.StoreId}/invoices", new Dictionary<string, object>()
{
{ "startDate", "blah" }
}));
//list Existing OrderId
var invoicesExistingOrderId =
await viewOnly.GetInvoices(user.StoreId, orderId: new []{newInvoice.Metadata["orderId"].ToString()});
Assert.NotNull(invoicesExistingOrderId);
Assert.Single(invoicesFiltered);
Assert.Equal(newInvoice.Id, invoicesFiltered.First().Id);
//list NonExisting OrderId
var invoicesNonExistingOrderId =
await viewOnly.GetInvoices(user.StoreId, orderId: new []{"NonExistingOrderId"});
Assert.NotNull(invoicesNonExistingOrderId);
Assert.Empty(invoicesNonExistingOrderId);
//list Existing Status
var invoicesExistingStatus =
await viewOnly.GetInvoices(user.StoreId, status: new []{newInvoice.Status});
Assert.NotNull(invoicesExistingStatus);
Assert.Single(invoicesExistingStatus);
Assert.Equal(newInvoice.Id, invoicesExistingStatus.First().Id);
//list NonExisting Status
var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId,
status: new []{BTCPayServer.Client.Models.InvoiceStatus.Invalid});
Assert.NotNull(invoicesNonExistingStatus);
Assert.Empty(invoicesNonExistingStatus);
//get
var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
Assert.Equal(newInvoice.Metadata, invoice.Metadata);
@ -968,38 +1154,51 @@ namespace BTCPayServer.Tests
//update
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
await AssertValidationError(new[] { nameof(MarkInvoiceStatusRequest.Status) }, async () =>
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
{
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
{
Status = InvoiceStatus.Settled
});
Status = InvoiceStatus.Settled
});
newInvoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
Assert.DoesNotContain(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
{
Status = InvoiceStatus.Invalid
});
newInvoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.DoesNotContain(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
await AssertHttpError(403, async () =>
{
await viewOnly.UpdateInvoice(user.StoreId, newInvoice.Id,
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
{
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
});
});
invoice = await client.UpdateInvoice(user.StoreId, newInvoice.Id,
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
{
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
});
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
Assert.Equal("updated", invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values<int>().Sum());
//also test the the metadata actually got saved
invoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
Assert.Equal("updated", invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values<int>().Sum());
//archive
await AssertHttpError(403, async () =>
{
@ -1069,20 +1268,66 @@ namespace BTCPayServer.Tests
var langs = tester.PayTester.GetService<LanguageService>();
foreach (var match in new[] { "it", "it-IT", "it-LOL" })
{
Assert.Equal("it-IT", langs.FindBestMatch(match).Code);
Assert.Equal("it-IT", langs.FindLanguage(match).Code);
}
foreach (var match in new[] { "pt-BR" })
{
Assert.Equal("pt-BR", langs.FindBestMatch(match).Code);
Assert.Equal("pt-BR", langs.FindLanguage(match).Code);
}
foreach (var match in new[] { "en", "en-US" })
{
Assert.Equal("en", langs.FindBestMatch(match).Code);
Assert.Equal("en", langs.FindLanguage(match).Code);
}
foreach (var match in new[] { "pt", "pt-pt", "pt-PT" })
{
Assert.Equal("pt-PT", langs.FindBestMatch(match).Code);
Assert.Equal("pt-PT", langs.FindLanguage(match).Code);
}
//payment method activation tests
var store = await client.GetStore(user.StoreId);
Assert.False(store.LazyPaymentMethods);
store.LazyPaymentMethods = true;
store = await client.UpdateStore(store.Id,
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
Assert.True(store.LazyPaymentMethods);
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = 1, Currency = "USD" });
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
Assert.Single(paymentMethods);
Assert.False(paymentMethods.First().Activated);
await client.ActivateInvoicePaymentMethod(user.StoreId, invoice.Id,
paymentMethods.First().PaymentMethod);
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
Assert.Single(paymentMethods);
Assert.True(paymentMethods.First().Activated);
var invoiceWithdefaultPaymentMethodLN = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
{
Currency = "USD",
Amount = 100,
Checkout = new CreateInvoiceRequest.CheckoutOptions()
{
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork" },
DefaultPaymentMethod = "BTC_LightningLike"
}
});
Assert.Equal("BTC_LightningLike", invoiceWithdefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
var invoiceWithdefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
{
Currency = "USD",
Amount = 100,
Checkout = new CreateInvoiceRequest.CheckoutOptions()
{
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork" },
DefaultPaymentMethod = "BTC"
}
});
Assert.Equal("BTC", invoiceWithdefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
}
}
@ -1105,7 +1350,6 @@ namespace BTCPayServer.Tests
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}");
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60)));
tester.PayTester.GetService<BTCPayServerEnvironment>().DevelopmentOverride = false;
// The default client is using charge, so we should not be able to query channels
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
@ -1175,8 +1419,7 @@ namespace BTCPayServer.Tests
Assert.NotEqual(0, info.BlockHeight);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task NotificationAPITests()
@ -1215,7 +1458,7 @@ namespace BTCPayServer.Tests
Assert.Empty(await viewOnlyClient.GetNotifications(true));
Assert.Empty(await viewOnlyClient.GetNotifications(false));
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task OnChainPaymentMethodAPITests()
@ -1223,58 +1466,452 @@ namespace BTCPayServer.Tests
using var tester = ServerTester.Create();
await tester.StartAsync();
var user = tester.NewAccount();
var user2 = tester.NewAccount();
await user.GrantAccessAsync(true);
await user2.GrantAccessAsync(false);
var client = await user.CreateClient(Policies.CanModifyStoreSettings);
var client2 = await user2.CreateClient(Policies.CanModifyStoreSettings);
var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings);
var store = await client.CreateStore(new CreateStoreRequest() {Name = "test store"});
var store = await client.CreateStore(new CreateStoreRequest() { Name = "test store" });
Assert.Empty(await client.GetStoreOnChainPaymentMethods(store.Id));
await AssertHttpError(403, async () =>
{
await viewOnlyClient.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", new OnChainPaymentMethodData() { });
});
await viewOnlyClient.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", new UpdateOnChainPaymentMethodRequest() { });
});
var xpriv = new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
.Derive(KeyPath.Parse("m/84'/0'/0'"));
.Derive(KeyPath.Parse("m/84'/1'/0'"));
var xpub = xpriv.Neuter().ToString(Network.RegTest);
var firstAddress = xpriv.Derive(KeyPath.Parse("0/0")).Neuter().GetPublicKey().GetAddress(ScriptPubKeyType.Segwit, Network.RegTest).ToString();
await AssertHttpError(404, async () =>
{
await client.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC");
});
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
new OnChainPaymentMethodData() {Enabled = true, DerivationScheme = xpub})).Addresses.First().Address);
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub});
Assert.Equal(xpub,method.DerivationScheme);
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub });
Assert.Equal(xpub, method.DerivationScheme);
method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") });
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") });
method = await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
Assert.Equal("lol", method.Label);
Assert.Equal(RootedKeyPath.Parse("01020304/1/2/3"), method.AccountKeyPath);
Assert.Equal(xpub,method.DerivationScheme);
Assert.Equal(xpub, method.DerivationScheme);
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC")).Addresses.First().Address);
await AssertHttpError(403, async () =>
{
await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
});
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
await AssertHttpError(404, async () =>
{
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
});
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
await AssertHttpError(404, async () =>
{
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
});
await AssertHttpError(403, async () =>
{
await viewOnlyClient.GenerateOnChainWallet(store.Id, "BTC", new GenerateOnChainWalletRequest() { });
});
await AssertValidationError(new []{"SavePrivateKeys", "ImportKeysToRPC"}, async () =>
{
await client2.GenerateOnChainWallet(user2.StoreId, "BTC", new GenerateOnChainWalletRequest()
{
SavePrivateKeys = true,
ImportKeysToRPC = true
});
});
var allMnemonic = new Mnemonic("all all all all all all all all all all all all");
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
var generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
new GenerateOnChainWalletRequest() {ExistingMnemonic = allMnemonic,});
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
Assert.Equal(generateResponse.DerivationScheme, xpub);
await AssertAPIError("already-configured", async () =>
{
await client.GenerateOnChainWallet(store.Id, "BTC",
new GenerateOnChainWalletRequest() {ExistingMnemonic = allMnemonic,});
});
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
new GenerateOnChainWalletRequest() {});
Assert.NotEqual(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
Assert.Equal(generateResponse.Mnemonic.DeriveExtKey().Derive(KeyPath.Parse("m/84'/1'/0'")).Neuter().ToString(Network.RegTest), generateResponse.DerivationScheme);
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1});
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
Assert.Equal(new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
.Derive(KeyPath.Parse("m/84'/1'/1'")).Neuter().ToString(Network.RegTest), generateResponse.DerivationScheme);
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
new GenerateOnChainWalletRequest() { WordList = Wordlist.Japanese, WordCount = WordCount.TwentyFour});
Assert.Equal(24,generateResponse.Mnemonic.Words.Length);
Assert.Equal(Wordlist.Japanese,generateResponse.Mnemonic.WordList);
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Lightning", "Lightning")]
[Trait("Integration", "Integration")]
public async Task LightningNetworkPaymentMethodAPITests()
{
using var tester = ServerTester.Create();
tester.ActivateLightning();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var admin = tester.NewAccount();
await admin.GrantAccessAsync(true);
var admin2 = tester.NewAccount();
await admin2.GrantAccessAsync(true);
var adminClient = await admin.CreateClient(Policies.CanModifyStoreSettings);
var admin2Client = await admin2.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
var viewOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
var store = await adminClient.GetStore(admin.StoreId);
Assert.Empty(await adminClient.GetStoreLightningNetworkPaymentMethods(store.Id));
await AssertHttpError(403, async () =>
{
await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest() { });
});
await AssertHttpError(404, async () =>
{
await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
});
await admin.RegisterLightningNodeAsync("BTC", false);
var method = await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
await AssertHttpError(403, async () =>
{
await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
});
await adminClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
await AssertHttpError(404, async () =>
{
await adminClient.GetStoreOnChainPaymentMethod(store.Id, "BTC");
});
// Let's verify that the admin client can't change LN to unsafe connection strings without modify server settings rights
foreach (var forbidden in new string[]
{
"type=clightning;server=tcp://127.0.0.1",
"type=clightning;server=tcp://test",
"type=clightning;server=tcp://test.lan",
"type=clightning;server=tcp://test.local",
"type=clightning;server=tcp://192.168.1.2",
"type=clightning;server=unix://8.8.8.8",
"type=clightning;server=unix://[::1]",
"type=clightning;server=unix://[0:0:0:0:0:0:0:1]",
})
{
var ex = await AssertValidationError(new[] { "ConnectionString" }, async () =>
{
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
{
ConnectionString = forbidden,
Enabled = true
});
});
Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message);
// However, the other client should work because he has `btcpay.server.canmodifyserversettings`
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
{
ConnectionString = forbidden,
Enabled = true
});
}
// Allowed ip should be ok
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
{
ConnectionString = "type=clightning;server=tcp://8.8.8.8",
Enabled = true
});
// If we strip the admin's right, he should not be able to set unsafe anymore, even if the API key is still valid
await admin2.MakeAdmin(false);
await AssertValidationError(new[] { "ConnectionString" }, async () =>
{
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
{
ConnectionString = "type=clightning;server=tcp://127.0.0.1",
Enabled = true
});
});
var settings = (await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
settings.AllowLightningInternalNodeForAll = false;
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
var nonAdminUser = tester.NewAccount();
await nonAdminUser.GrantAccessAsync(false);
var nonAdminUserClient = await nonAdminUser.CreateClient(Policies.CanModifyStoreSettings);
await AssertHttpError(404, async () =>
{
await nonAdminUserClient.GetStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC");
});
await Assert.ThrowsAsync<GreenFieldValidationException>(async () =>
{
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
{
Enabled = method.Enabled,
ConnectionString = method.ConnectionString
});
});
settings = await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>();
settings.AllowLightningInternalNodeForAll = true;
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
{
Enabled = method.Enabled,
ConnectionString = method.ConnectionString
});
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
public async Task WalletAPITests()
{
using var tester = ServerTester.Create();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync(true);
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings);
var walletId = await user.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
//view only clients can't do jack shit with this API
await AssertHttpError(403, async () =>
{
await viewOnlyClient.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
});
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);
await AssertHttpError(403, async () =>
{
await viewOnlyClient.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
});
var address = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
var address2 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
var address3 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true);
Assert.Equal(address.Address, address2.Address);
Assert.NotEqual(address.Address, address3.Address);
await AssertHttpError(403, async () =>
{
await viewOnlyClient.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode);
});
Assert.Empty(await client.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode));
uint256 txhash = null;
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
{
txhash = await tester.ExplorerNode.SendToAddressAsync(
BitcoinAddress.Create(address3.Address, tester.ExplorerClient.Network.NBitcoinNetwork),
new Money(0.01m, MoneyUnit.BTC));
});
await tester.ExplorerNode.GenerateAsync(1);
var address4 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, false);
Assert.NotEqual(address3.Address, address4.Address);
await client.UnReserveOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
var address5 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true);
Assert.Equal(address5.Address, address4.Address);
var utxo = Assert.Single(await client.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode));
Assert.Equal(0.01m, utxo.Amount);
Assert.Equal(txhash, utxo.Outpoint.Hash);
overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
Assert.Equal(0.01m, overview.Balance);
//the simplest request:
var nodeAddress = await tester.ExplorerNode.GetNewAddressAsync();
var createTxRequest = new CreateOnChainTransactionRequest()
{
Destinations =
new List<CreateOnChainTransactionRequest.CreateOnChainTransactionRequestDestination>()
{
new CreateOnChainTransactionRequest.CreateOnChainTransactionRequestDestination()
{
Destination = nodeAddress.ToString(), Amount = 0.001m
}
},
FeeRate = new FeeRate(5m) //only because regtest may fail but not required
};
await AssertHttpError(403, async () =>
{
await viewOnlyClient.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode, createTxRequest);
});
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
createTxRequest.ProceedWithBroadcast = false;
await client.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode,
createTxRequest);
});
Transaction tx;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
Assert.NotNull(tx);
Assert.Contains(tx.Outputs, txout => txout.IsTo(nodeAddress) && txout.Value.ToDecimal(MoneyUnit.BTC) == 0.001m);
Assert.True((await tester.ExplorerNode.TestMempoolAcceptAsync(tx)).IsAllowed);
// no change test
createTxRequest.NoChange = true;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
Assert.NotNull(tx);
Assert.True(Assert.Single(tx.Outputs).IsTo(nodeAddress));
Assert.True((await tester.ExplorerNode.TestMempoolAcceptAsync(tx)).IsAllowed);
createTxRequest.NoChange = false;
//coin selection
await AssertValidationError(new[] { nameof(createTxRequest.SelectedInputs) }, async () =>
{
createTxRequest.SelectedInputs = new List<OutPoint>();
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
createTxRequest.SelectedInputs = new List<OutPoint>()
{
utxo.Outpoint
};
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
createTxRequest.SelectedInputs = null;
//destination testing
await AssertValidationError(new[] { "Destinations" }, async () =>
{
createTxRequest.Destinations[0].Amount = utxo.Amount;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
createTxRequest.Destinations[0].SubtractFromAmount = true;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
await AssertValidationError(new[] { "Destinations[0]" }, async () =>
{
createTxRequest.Destinations[0].Amount = 0m;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
//dest can be a bip21
//cant use bip with subtractfromamount
createTxRequest.Destinations[0].Amount = null;
createTxRequest.Destinations[0].Destination = $"bitcoin:{nodeAddress}?amount=0.001";
await AssertValidationError(new[] { "Destinations[0]" }, async () =>
{
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
//if amt specified, it overrides bip21 amount
createTxRequest.Destinations[0].Amount = 0.0001m;
createTxRequest.Destinations[0].SubtractFromAmount = false;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
Assert.Contains(tx.Outputs, txout => txout.Value.GetValue(tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")) == 0.0001m);
//fee rate test
createTxRequest.FeeRate = FeeRate.Zero;
await AssertValidationError(new[] { "FeeRate" }, async () =>
{
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
createTxRequest.FeeRate = new FeeRate(5.0m);
createTxRequest.Destinations[0].Amount = 0.001m;
createTxRequest.Destinations[0].Destination = nodeAddress.ToString();
createTxRequest.Destinations[0].SubtractFromAmount = false;
await AssertHttpError(403, async () =>
{
await viewOnlyClient.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
});
createTxRequest.ProceedWithBroadcast = true;
var txdata =
await client.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode,
createTxRequest);
Assert.Equal(TransactionStatus.Unconfirmed, txdata.Status);
Assert.Null(txdata.BlockHeight);
Assert.Null(txdata.BlockHash);
Assert.NotNull(await tester.ExplorerClient.GetTransactionAsync(txdata.TransactionHash));
await AssertHttpError(403, async () =>
{
await viewOnlyClient.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
});
await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
await AssertHttpError(403, async () =>
{
await viewOnlyClient.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode);
});
Assert.True(Assert.Single(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
new[] { TransactionStatus.Confirmed })).TransactionHash == utxo.Outpoint.Hash);
Assert.Contains(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
new[] { TransactionStatus.Unconfirmed }), data => data.TransactionHash == txdata.TransactionHash);
Assert.Contains(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode), data => data.TransactionHash == txdata.TransactionHash);
await tester.WaitForEvent<NewBlockEvent>(async () =>
{
await tester.ExplorerNode.GenerateAsync(1);
}, bevent => bevent.CryptoCode.Equals("BTC", StringComparison.Ordinal));
Assert.Contains(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
new[] { TransactionStatus.Confirmed }), data => data.TransactionHash == txdata.TransactionHash);
}
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
@ -1291,7 +1928,6 @@ namespace BTCPayServer.Tests
Assert.True(jsonConverter.CanConvert(typeof(double)));
Assert.True(jsonConverter.CanConvert(typeof(double?)));
Assert.False(jsonConverter.CanConvert(typeof(float)));
Assert.False(jsonConverter.CanConvert(typeof(int)));
Assert.False(jsonConverter.CanConvert(typeof(string)));
var numberJson = "1";
@ -1316,6 +1952,79 @@ namespace BTCPayServer.Tests
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null));
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Lightning", "Lightning")]
[Trait("Integration", "Integration")]
public async Task StorePaymentMethodsAPITests()
{
using var tester = ServerTester.Create();
tester.ActivateLightning();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var admin = tester.NewAccount();
await admin.GrantAccessAsync(true);
var adminClient = await admin.CreateClient(Policies.Unrestricted);
var viewerOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
var store = await adminClient.GetStore(admin.StoreId);
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
await adminClient.UpdateStoreLightningNetworkPaymentMethod(admin.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest("Internal Node", true));
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
{
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
var lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
}
var methods = await adminClient.GetStorePaymentMethods(store.Id);
Assert.Single(methods);
VerifyLightning(methods);
var randK = new Mnemonic(Wordlist.English, WordCount.Twelve).DeriveExtKey().Neuter().ToString(Network.RegTest);
await adminClient.UpdateStoreOnChainPaymentMethod(admin.StoreId, "BTC",
new UpdateOnChainPaymentMethodRequest(true, randK, "testing", null));
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
{
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
var paymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
}
methods = await adminClient.GetStorePaymentMethods(store.Id);
Assert.Equal(2, methods.Count);
VerifyLightning(methods);
VerifyOnChain(methods);
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
VerifyLightning(methods);
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC",
new UpdateLightningNetworkPaymentMethodRequest(
tester.GetLightningConnectionString(LightningConnectionType.CLightning, true), true));
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
var lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.Equal("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
methods = await adminClient.GetStorePaymentMethods(store.Id);
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out item));
lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.NotEqual("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
}
}
}

View File

@ -0,0 +1,52 @@
using System.Threading.Tasks;
using BTCPayServer.Services;
using BTCPayServer.Tests.Logging;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class LanguageServiceTests
{
public const int TestTimeout = TestUtils.TestTimeout;
public LanguageServiceTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanAutoDetectLanguage()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var languageService = tester.PayTester.GetService<LanguageService>();
// Most common format. First option does not have a quality score. Others do in descending order.
// Result should be nl-NL (because the default weight is 1 for nl)
var lang1 = languageService.FindLanguageInAcceptLanguageHeader("nl,fr;q=0.7,en;q=0.5");
Assert.NotNull(lang1);
Assert.Equal("nl-NL", lang1?.Code);
// Most common format. First option does not have a quality score. Others do in descending order. This time the first option includes a country.
// Result should be nl-NL (because the default weight is 1 for nl-BE and it does not exist in BTCPay Server, but nl-NL does and applies too for language "nl")
var lang2 = languageService.FindLanguageInAcceptLanguageHeader("nl-BE,fr;q=0.7,en;q=0.5");
Assert.NotNull(lang2);
Assert.Equal("nl-NL", lang2?.Code);
// Unusual format, but still valid. All values have a quality score and not ordered.
// Result should be fr-FR (because 0.7 is the highest quality score)
var lang3 = languageService.FindLanguageInAcceptLanguageHeader("nl;q=0.1,fr;q=0.7,en;q=0.5");
Assert.NotNull(lang3);
Assert.Equal("fr-FR", lang3?.Code);
// Unusual format, but still valid. Some language is given that we don't have and a wildcard for everything else.
// Result should be NULL, because "xx" does not exist and * is a wildcard and has no meaning.
var lang4 = languageService.FindLanguageInAcceptLanguageHeader("xx,*;q=0.5");
Assert.Null(lang4);
}
}
}
}

View File

@ -0,0 +1,71 @@
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Services.Apps;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
using static BTCPayServer.Tests.UnitTest1;
namespace BTCPayServer.Tests
{
public class POSTests
{
public POSTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = LongRunningTestTimeout)]
public async Task CanUsePoSApp1()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var apps = user.GetController<AppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
vm.Name = "test";
vm.SelectedAppType = AppType.PointOfSale.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
.Apps[0].Id;
var vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
vmpos.Template = @"
apple:
price: 5.0
title: good apple
disabled: true
orange:
price: 10.0
donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
var publicApps = user.GetController<AppsPublicController>();
var vmview =
Assert.IsType<ViewPointOfSaleViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model);
// apple shouldn't be available since we it's set to "disabled: true" above
Assert.Equal(2, vmview.Items.Length);
Assert.Equal("orange", vmview.Items[0].Title);
Assert.Equal("donation", vmview.Items[1].Title);
// orange is available
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
// apple is not found
Assert.IsType<NotFoundResult>(publicApps
.ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
}
}
}
}

View File

@ -73,32 +73,30 @@ namespace BTCPayServer.Tests
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
var vmPSBT2 = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
{
SigningContext = new SigningContextModel()
SigningContext = new SigningContextModel
{
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
}
}).AssertViewModelAsync<WalletPSBTReadyViewModel>();
}).AssertViewModelAsync<WalletPSBTViewModel>();
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
Assert.Equal(vmPSBT.PSBT, vmPSBT2.SigningContext.PSBT);
var signedPSBT = unsignedPSBT.Clone();
signedPSBT.SignAll(user.DerivationScheme, user.GenerateWalletResponseV.AccountHDKey, user.GenerateWalletResponseV.AccountKeyPath);
vmPSBT.PSBT = signedPSBT.ToBase64();
var psbtReady = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
var psbtReady = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
{
SigningContext = new SigningContextModel()
SigningContext = new SigningContextModel
{
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
}
}).AssertViewModelAsync<WalletPSBTReadyViewModel>();
}).AssertViewModelAsync<WalletPSBTViewModel>();
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
Assert.Contains(psbtReady.Destinations, d => d.Positive);
var redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, psbtReady, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
vmPSBT.PSBT = unsignedPSBT.ToBase64();
var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
@ -119,21 +117,21 @@ namespace BTCPayServer.Tests
Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2);
var ready = (await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
var ready = (await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
{
SigningContext = new SigningContextModel(signedPSBT)
})).AssertViewModel<WalletPSBTReadyViewModel>();
})).AssertViewModel<WalletPSBTViewModel>();
Assert.Equal(signedPSBT.ToBase64(), ready.SigningContext.PSBT);
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
Assert.Equal(signedPSBT.ToBase64(), psbt);
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
var redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
//test base64 psbt file
Assert.False(string.IsNullOrEmpty(Assert.IsType<WalletPSBTViewModel>(
Assert.IsType<ViewResult>(
await walletController.WalletPSBT(walletId,
new WalletPSBTViewModel()
new WalletPSBTViewModel
{
UploadedPSBTFile = TestUtils.GetFormFile("base64", signedPSBT.ToBase64())
})).Model).PSBT));

View File

@ -8,10 +8,10 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Payments.PayJoin.Sender;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Wallets;
@ -19,6 +19,7 @@ using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Wallets;
using Microsoft.AspNetCore.Http;
using NBitcoin;
using BTCPayServer.BIP78.Sender;
using NBitcoin.Payment;
using NBitpayClient;
using NBXplorer.DerivationStrategy;
@ -86,6 +87,19 @@ namespace BTCPayServer.Tests
Assert.True(await repo.TryLock(outpoint));
Assert.True(await repo.TryUnlock(outpoint));
Assert.False(await repo.TryUnlock(outpoint));
// Make sure that if any can't be locked, all are not locked
var outpoint1 = RandomOutpoint();
var outpoint2 = RandomOutpoint();
Assert.True(await repo.TryLockInputs(new[] { outpoint1 }));
Assert.False(await repo.TryLockInputs(new[] { outpoint1, outpoint2 }));
Assert.True(await repo.TryLockInputs(new[] { outpoint2 }));
outpoint1 = RandomOutpoint();
outpoint2 = RandomOutpoint();
Assert.True(await repo.TryLockInputs(new[] { outpoint1 }));
Assert.False(await repo.TryLockInputs(new[] { outpoint2, outpoint1 }));
Assert.True(await repo.TryLockInputs(new[] { outpoint2 }));
}
}
@ -132,7 +146,7 @@ namespace BTCPayServer.Tests
{
var tx = network.NBitcoinNetwork.CreateTransaction();
tx.Inputs.Add(new OutPoint(RandomUtils.GetUInt256(), 0), Script.Empty);
tx.Outputs.Add(Money.Coins(1.0m), new Key().ScriptPubKey);
tx.Outputs.Add(Money.Coins(1.0m), new Key().GetScriptPubKey(ScriptPubKeyType.Legacy));
return tx;
}
@ -165,19 +179,21 @@ namespace BTCPayServer.Tests
var cashCow = tester.ExplorerNode;
cashCow.Generate(2); // get some money in case
var unsupportedFormats = Enum.GetValues(typeof(ScriptPubKeyType))
.AssertType<ScriptPubKeyType[]>()
.Where(type => !PayjoinClient.SupportedFormats.Contains(type));
var unsupportedFormats = new[] {ScriptPubKeyType.Legacy};
foreach (ScriptPubKeyType senderAddressType in Enum.GetValues(typeof(ScriptPubKeyType)))
{
if (senderAddressType == ScriptPubKeyType.TaprootBIP86)
continue;
var senderUser = tester.NewAccount();
senderUser.GrantAccess(true);
senderUser.RegisterDerivationScheme("BTC", senderAddressType);
foreach (ScriptPubKeyType receiverAddressType in Enum.GetValues(typeof(ScriptPubKeyType)))
{
if (receiverAddressType == ScriptPubKeyType.TaprootBIP86)
continue;
var senderCoin = await senderUser.ReceiveUTXO(Money.Satoshis(100000), network);
Logs.Tester.LogInformation($"Testing payjoin with sender: {senderAddressType} receiver: {receiverAddressType}");
@ -210,6 +226,57 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanUsePayjoinForTopUp()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(true);
var receiver = s.CreateNewStore();
var receiverSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
var receiverWalletId = new WalletId(receiver.storeId, "BTC");
var sender = s.CreateNewStore();
var senderSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
var senderWalletId = new WalletId(sender.storeId, "BTC");
await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(senderWalletId);
await s.FundStoreWallet(receiverWalletId);
var invoiceId = s.CreateInvoice(receiver.storeName, null, "BTC");
s.GoToInvoiceCheckout(invoiceId);
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href");
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
s.GoToWallet(senderWalletId, WalletsNavPages.Send);
s.Driver.FindElement(By.Id("bip21parse")).Click();
s.Driver.SwitchTo().Alert().SendKeys(bip21);
s.Driver.SwitchTo().Alert().Accept();
s.Driver.FindElementUntilNotStaled(By.Id("Outputs_0__Amount"), we => we.Clear());
s.Driver.FindElementUntilNotStaled(By.Id("Outputs_0__Amount"), we => we.SendKeys("0.023"));
s.Driver.FindElement(By.Id("SignTransaction")).Click();
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
return Task.CompletedTask;
});
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
var invoiceRepository = s.Server.PayTester.GetService<InvoiceRepository>();
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await invoiceRepository.GetInvoice(invoiceId);
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
Assert.Equal(0.023m, invoice.Price);
});
}
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanUsePayjoinViaUI()
@ -220,25 +287,21 @@ namespace BTCPayServer.Tests
var invoiceRepository = s.Server.PayTester.GetService<InvoiceRepository>();
s.RegisterNewUser(true);
foreach (var format in PayjoinClient.SupportedFormats)
foreach (var format in new []{ScriptPubKeyType.Segwit, ScriptPubKeyType.SegwitP2SH})
{
var receiver = s.CreateNewStore();
var receiverSeed = s.GenerateWallet("BTC", "", true, true, format);
var receiverWalletId = new WalletId(receiver.storeId, "BTC");
//payjoin is not enabled by default.
//payjoin is enabled by default.
var invoiceId = s.CreateInvoice(receiver.storeName);
s.GoToInvoiceCheckout(invoiceId);
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href");
Assert.DoesNotContain($"{PayjoinClient.BIP21EndpointKey}=", bip21);
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
s.GoToHome();
s.GoToStore(receiver.storeId);
//payjoin is not enabled by default.
Assert.False(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
s.SetCheckbox(s, "PayJoinEnabled", true);
s.Driver.FindElement(By.Id("Save")).Click();
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
var sender = s.CreateNewStore();
@ -259,10 +322,7 @@ namespace BTCPayServer.Tests
s.Driver.SwitchTo().Alert().Accept();
Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinBIP21"))
.GetAttribute("value")));
s.Driver.FindElement(By.Id("SendMenu")).Click();
var nbxSeedButton = s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]"));
new WebDriverWait(s.Driver, SeleniumTester.ImplicitWait).Until(d=> nbxSeedButton.Enabled);
nbxSeedButton.Click();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
@ -297,8 +357,7 @@ namespace BTCPayServer.Tests
.GetAttribute("value")));
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).Clear();
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).SendKeys("2");
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
var txId = await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
@ -308,7 +367,7 @@ namespace BTCPayServer.Tests
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await invoiceRepository.GetInvoice(invoiceId);
var payments = invoice.GetPayments();
var payments = invoice.GetPayments(false);
Assert.Equal(2, payments.Count);
var originalPayment = payments[0];
var coinjoinPayment = payments[1];
@ -365,10 +424,15 @@ namespace BTCPayServer.Tests
var alice = tester.NewAccount();
await alice.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
await notifications.ListenDerivationSchemesAsync(new[] { alice.DerivationScheme });
var address = (await nbx.GetUnusedAsync(alice.DerivationScheme, DerivationFeature.Deposit)).Address;
await tester.ExplorerNode.GenerateAsync(1);
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.0m));
await notifications.NextEventAsync();
BitcoinAddress address = null;
for (int i = 0; i < 5; i++)
{
address = (await nbx.GetUnusedAsync(alice.DerivationScheme, DerivationFeature.Deposit)).Address;
await tester.ExplorerNode.GenerateAsync(1);
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.0m));
await notifications.NextEventAsync();
}
var paymentAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest);
var otherAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest);
var psbt = (await nbx.CreatePSBTAsync(alice.DerivationScheme, new CreatePSBTRequest()
@ -410,7 +474,7 @@ namespace BTCPayServer.Tests
using var fakeServer = new FakeServer();
await fakeServer.Start();
var bip21 = new BitcoinUrlBuilder($"bitcoin:{paymentAddress}?pj={fakeServer.ServerUri}", Network.RegTest);
var requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
var requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
var request = await fakeServer.GetNextRequest();
Assert.Equal("1", request.Request.Query["v"][0]);
Assert.Equal(changeIndex.ToString(), request.Request.Query["additionalfeeoutputindex"][0]);
@ -426,7 +490,7 @@ namespace BTCPayServer.Tests
Assert.Contains("contribution is more than maxadditionalfeecontribution", ex.Message);
Logs.Tester.LogInformation("The payjoin receiver tries to change one of our output");
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -435,9 +499,8 @@ namespace BTCPayServer.Tests
fakeServer.Done();
ex = await Assert.ThrowsAsync<PayjoinSenderException>(async () => await requesting);
Assert.Contains("The receiver decreased the value of one", ex.Message);
Logs.Tester.LogInformation("The payjoin receiver tries to pocket the fee");
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -448,7 +511,7 @@ namespace BTCPayServer.Tests
Assert.Contains("The receiver decreased absolute fee", ex.Message);
Logs.Tester.LogInformation("The payjoin receiver tries to remove one of our output");
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -460,7 +523,7 @@ namespace BTCPayServer.Tests
Assert.Contains("Some of our outputs are not included in the proposal", ex.Message);
Logs.Tester.LogInformation("The payjoin receiver tries to change their own output");
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -472,7 +535,7 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation("The payjoin receiver tries to send money to himself");
pjClient.MaxFeeBumpContribution = Money.Satoshis(1);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -483,10 +546,9 @@ namespace BTCPayServer.Tests
ex = await Assert.ThrowsAsync<PayjoinSenderException>(async () => await requesting);
Assert.Contains("is not only paying fee", ex.Message);
pjClient.MaxFeeBumpContribution = null;
Logs.Tester.LogInformation("The payjoin receiver can't use additional fee without adding inputs");
pjClient.MinimumFeeRate = new FeeRate(50m);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -501,15 +563,19 @@ namespace BTCPayServer.Tests
var bob = tester.NewAccount();
await bob.GrantAccessAsync();
await bob.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
await notifications.DisposeAsync();
notifications = await nbx.CreateWebsocketNotificationSessionAsync();
await notifications.ListenDerivationSchemesAsync(new[] { bob.DerivationScheme });
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
await notifications.NextEventAsync();
bob.ModifyStore(s => s.PayJoinEnabled = true);
await bob.ModifyStore(s => s.PayJoinEnabled = true);
var invoice = bob.BitPay.CreateInvoice(
new Invoice() { Price = 0.1m, Currency = "BTC", FullNotifications = true });
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
tester.ExplorerClient.Network.NBitcoinNetwork);
psbt = (await nbx.CreatePSBTAsync(alice.DerivationScheme, new CreatePSBTRequest()
{
Destinations =
@ -528,7 +594,7 @@ namespace BTCPayServer.Tests
psbt.SignAll(derivationSchemeSettings.AccountDerivation, alice.GenerateWalletResponseV.AccountHDKey, signingAccount.GetRootedKeyPath());
var endpoint = TestAccount.GetPayjoinBitcoinUrl(invoice, Network.RegTest);
pjClient.MaxFeeBumpContribution = Money.Satoshis(50);
var proposal = await pjClient.RequestPayjoin(endpoint, derivationSchemeSettings, psbt, default);
var proposal = await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(derivationSchemeSettings), psbt, default);
Assert.True(proposal.TryGetFee(out var newFee));
Assert.Equal(Money.Satoshis(3001 + 50), newFee);
proposal = proposal.SignAll(derivationSchemeSettings.AccountDerivation, alice.GenerateWalletResponseV.AccountHDKey, signingAccount.GetRootedKeyPath());
@ -559,7 +625,7 @@ namespace BTCPayServer.Tests
psbt.SignAll(derivationSchemeSettings.AccountDerivation, alice.GenerateWalletResponseV.AccountHDKey, signingAccount.GetRootedKeyPath());
endpoint = TestAccount.GetPayjoinBitcoinUrl(invoice, Network.RegTest);
pjClient.MinimumFeeRate = new FeeRate(100_000_000.2m);
var ex2 = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, derivationSchemeSettings, psbt, default));
var ex2 = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(derivationSchemeSettings), psbt, default));
Assert.Equal(PayjoinReceiverWellknownErrors.NotEnoughMoney, ex2.WellknownError);
}
}
@ -801,16 +867,32 @@ retry:
//give the cow some cash
await cashCow.GenerateAsync(1);
//let's get some more utxos first
await receiverUser.ReceiveUTXO(Money.Coins(0.011m), btcPayNetwork);
await receiverUser.ReceiveUTXO(Money.Coins(0.012m), btcPayNetwork);
await receiverUser.ReceiveUTXO(Money.Coins(0.013m), btcPayNetwork);
await receiverUser.ReceiveUTXO(Money.Coins(0.014m), btcPayNetwork);
await senderUser.ReceiveUTXO(Money.Coins(0.021m), btcPayNetwork);
await senderUser.ReceiveUTXO(Money.Coins(0.022m), btcPayNetwork);
await senderUser.ReceiveUTXO(Money.Coins(0.023m), btcPayNetwork);
await senderUser.ReceiveUTXO(Money.Coins(0.024m), btcPayNetwork);
await senderUser.ReceiveUTXO(Money.Coins(0.025m), btcPayNetwork);
await senderUser.ReceiveUTXO(Money.Coins(0.026m), btcPayNetwork);
foreach (var m in new []
{
Money.Coins(0.011m),
Money.Coins(0.012m),
Money.Coins(0.013m),
Money.Coins(0.014m),
Money.Coins(0.015m),
Money.Coins(0.016m)
})
{
await receiverUser.ReceiveUTXO(m, btcPayNetwork);
}
foreach (var m in new[]
{
Money.Coins(0.021m),
Money.Coins(0.022m),
Money.Coins(0.023m),
Money.Coins(0.024m),
Money.Coins(0.025m),
Money.Coins(0.026m)
})
{
await senderUser.ReceiveUTXO(m, btcPayNetwork);
}
var senderChange = await senderUser.GetNewAddress(btcPayNetwork);
//Let's start the harassment
@ -841,17 +923,24 @@ retry:
settings.PaymentId == paymentMethodId);
ReceivedCoin[] senderCoins = null;
ReceivedCoin coin = null;
ReceivedCoin coin2 = null;
ReceivedCoin coin3 = null;
ReceivedCoin coin4 = null;
ReceivedCoin coin5 = null;
ReceivedCoin coin6 = null;
await TestUtils.EventuallyAsync(async () =>
{
senderCoins = await btcPayWallet.GetUnspentCoins(senderUser.DerivationScheme);
Assert.Contains(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
coin = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.021m);
coin2 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.022m);
coin3 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.023m);
coin4 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.024m);
coin5 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.025m);
coin6 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
});
var coin = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.021m);
var coin2 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.022m);
var coin3 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.023m);
var coin4 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.024m);
var coin5 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.025m);
var coin6 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
signingKeySettings.RootFingerprint =
@ -895,10 +984,6 @@ retry:
.SendEstimatedFees(new FeeRate(100m))
.BuildTransaction(true);
//Attempt 1: Send a signed tx to invoice 1 that does not pay the invoice at all
//Result: reject
// Assert.False((await tester.PayTester.HttpClient.PostAsync(endpoint,
// new StringContent(Invoice2Coin1.ToHex(), Encoding.UTF8, "text/plain"))).IsSuccessStatusCode);
//Attempt 2: Create two transactions using different inputs and send them to the same invoice.
//Result: Second Tx should be rejected.
@ -1050,7 +1135,7 @@ retry:
{
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
Assert.Contains(invoiceEntity.GetPayments(false), p => p.Accounted &&
((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
});
////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id && item.TxSeen);
@ -1079,8 +1164,8 @@ retry:
{
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(false).First().PayjoinInformation.ContributedOutPoints[0];
});
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
// The outpoint should now be available for next pj selection

View File

@ -189,10 +189,20 @@ namespace BTCPayServer.Tests
var response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.First();
var invoiceId = response.Value.ToString();
await paymentRequestController.PayPaymentRequest(invoiceId, false);
Assert.IsType<BadRequestObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(invoiceId, false));
request.AllowCustomPaymentAmounts = true;
response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.First();
var paymentRequestId = response.Value.ToString();
var invoiceId = Assert
invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
.Value
.ToString();
@ -224,12 +234,12 @@ namespace BTCPayServer.Tests
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
//a hack to generate invoices for the payment request is to manually create an invocie with an order id that matches:
//a hack to generate invoices for the payment request is to manually create an invoice with an order id that matches:
user.BitPay.CreateInvoice(new Invoice(1, "USD")
{
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(paymentRequestId)
});
//shouldnt crash
//shouldn't crash
await paymentRequestController.ViewPaymentRequest(paymentRequestId);
await paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId);
}

View File

@ -26,6 +26,21 @@ You can also generate blocks:
.\docker-bitcoin-generate.ps1 3
```
### Using Polar to test Lightning payments
- Install and run [Polar](https://lightningpolar.com/). Setup a small network of nodes.
- Go to your store's General Settings and enable Lightning.
- Build your connection string using the Connect infomation in the Polar app.
LND Connection string example:
type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to admin.macaroon on your computer, without these quotes";allowinsecure=true
Now you can create a Lightning invoice on BTCPay Server regtest and make a payment through Polar.
PLEASE NOTE: You may get an exception break in Visual Studio. You must quickly click "Continue" in VS so the invoice is generated.
Or, uncheck the box that says, "Break when this exceptiontype is thrown".
### Using the test litecoin-cli
Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` and `.\docker-litecoin-cli.sh` instead.
@ -55,6 +70,20 @@ Please, run the test `CanSetLightningServer`, this will establish a channel betw
Alternatively you can run the `./docker-lightning-channel-setup.sh` script to establish the channel connection.
The `./docker-lightning-channel-teardown.sh` script closes any existing lightning channels.
### Alternative Lightning testing: Using Polar to test Lightning payments
- Install and run [Polar](https://lightningpolar.com/). Setup a small network of nodes.
- Go to your store's General Settings and enable Lightning.
- Build your connection string using the Connect information in the Polar app.
LND Connection string example:
type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to admin.macaroon on your computer, without these quotes";allowinsecure=true
Now you can create a lightning invoice on BTCPay Server regtest and make a payment through Polar.
PLEASE NOTE: You may get an exception break in Visual Studio. You must quickly click "Continue" in VS so the invoice is generated.
Or, uncheck the box that says, "Break when this exception type is thrown".
## FAQ
### `docker-compose up dev` failed or tests are not passing, what should I do?

View File

@ -15,8 +15,10 @@ using BTCPayServer.Views.Stores;
using BTCPayServer.Views.Wallets;
using Microsoft.Extensions.Configuration;
using NBitcoin;
using BTCPayServer.BIP78.Sender;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.Extensions;
using Xunit;
namespace BTCPayServer.Tests
@ -32,9 +34,11 @@ namespace BTCPayServer.Tests
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) =>
new SeleniumTester { Server = ServerTester.Create(scope, newDb) };
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5);
public async Task StartAsync()
{
Server.PayTester.NoCSP = true;
await Server.StartAsync();
var windowSize = (Width: 1200, Height: 1000);
@ -46,50 +50,61 @@ 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" : Directory.GetCurrentDirectory());
var options = new ChromeOptions();
if (Server.PayTester.InContainer)
{
// this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789
options.AddArgument("no-sandbox");
}
if (!runInBrowser)
{
options.AddArguments("headless");
}
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
options.AddArgument("shm-size=2g");
Driver = new ChromeDriver(chromeDriverPath, options);
if (runInBrowser)
options.AddArgument("start-maximized");
if (Server.PayTester.InContainer)
{
// ensure maximized window size
Driver.Manage().Window.Maximize();
Driver = new OpenQA.Selenium.Remote.RemoteWebDriver(new Uri("http://selenium:4444/wd/hub"), new RemoteSessionSettings(options));
var containerIp = File.ReadAllText("/etc/hosts").Split('\n', StringSplitOptions.RemoveEmptyEntries).Last()
.Split('\t', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
Logs.Tester.LogInformation($"Selenium: Container's IP {containerIp}");
ServerUri = new Uri(Server.PayTester.ServerUri.AbsoluteUri.Replace($"http://{Server.PayTester.HostName}", $"http://{containerIp}", StringComparison.OrdinalIgnoreCase), UriKind.Absolute);
}
else
{
var cds = ChromeDriverService.CreateDefaultService(chromeDriverPath);
cds.EnableVerboseLogging = true;
cds.Port = Utils.FreeTcpPort();
cds.HostName = "127.0.0.1";
cds.Start();
Driver = new ChromeDriver(cds, options,
// A bit less than test timeout
TimeSpan.FromSeconds(50));
ServerUri = Server.PayTester.ServerUri;
}
Driver.Manage().Window.Maximize();
Logs.Tester.LogInformation($"Selenium: Using {Driver.GetType()}");
Logs.Tester.LogInformation($"Selenium: Browsing to {Server.PayTester.ServerUri}");
Logs.Tester.LogInformation($"Selenium: Browsing to {ServerUri}");
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
GoToRegister();
Driver.AssertNoError();
}
/// <summary>
/// Use this ServerUri when trying to browse with selenium
/// 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)
{
var el = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).FirstOrDefault(e => e.Displayed);
var className = $"alert-{StatusMessageModel.ToString(severity)}";
var el = Driver.FindElement(By.ClassName(className)) ?? Driver.WaitForElement(By.ClassName(className));
if (el is null)
throw new NoSuchElementException($"Unable to find alert-{StatusMessageModel.ToString(severity)}");
throw new NoSuchElementException($"Unable to find {className}");
return el;
}
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5);
public string Link(string relativeLink)
{
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
return ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
}
public void GoToRegister()
@ -113,59 +128,70 @@ namespace BTCPayServer.Tests
public (string storeName, string storeId) CreateNewStore()
{
Driver.FindElement(By.Id("Stores")).Click();
Driver.FindElement(By.Id("CreateStore")).Click();
Driver.WaitForElement(By.Id("Stores")).Click();
Driver.WaitForElement(By.Id("CreateStore")).Click();
var name = "Store" + RandomUtils.GetUInt64();
Driver.FindElement(By.Id("Name")).SendKeys(name);
Driver.FindElement(By.Id("Create")).Click();
StoreId = Driver.FindElement(By.Id("Id")).GetAttribute("value");
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
Driver.WaitForElement(By.Id("Create")).Click();
StoreId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
return (name, StoreId);
}
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
{
var isImport = !string.IsNullOrEmpty(seed);
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
// Modify case
if (Driver.PageSource.Contains("id=\"change-wallet-link\""))
// Replace previous wallet case
if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
{
Driver.FindElement(By.Id("change-wallet-link")).Click();
Driver.FindElement(By.Id("ChangeWalletLink")).Click();
Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REPLACE");
Driver.FindElement(By.Id("ConfirmContinue")).Click();
}
if (string.IsNullOrEmpty(seed))
if (isImport)
{
var option = privkeys ? "hotwallet" : "watchonly";
Logs.Tester.LogInformation($"Generating new seed ({option})");
Driver.FindElement(By.Id("generate-wallet-link")).Click();
Driver.FindElement(By.Id($"generate-{option}-link")).Click();
Logs.Tester.LogInformation("Progressing with existing seed");
Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
Driver.FindElement(By.Id("ImportSeedLink")).Click();
Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
Driver.SetCheckbox(By.Id("SavePrivateKeys"), privkeys);
}
else
{
Logs.Tester.LogInformation("Progressing with existing seed");
Driver.FindElement(By.Id("import-wallet-options-link")).Click();
Driver.FindElement(By.Id("import-seed-link")).Click();
Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys);
var option = privkeys ? "Hotwallet" : "Watchonly";
Logs.Tester.LogInformation($"Generating new seed ({option})");
Driver.FindElement(By.Id("GenerateWalletLink")).Click();
Driver.FindElement(By.Id($"Generate{option}Link")).Click();
}
Driver.FindElement(By.Id("ScriptPubKeyType")).Click();
Driver.FindElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
Driver.FindElement(By.Id("advanced-settings-button")).Click();
SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys);
Driver.FindElement(By.Id("advanced-settings-button")).Click(); // close settings again , otherwise the button might not be clickable for Selenium
Logs.Tester.LogInformation("Trying to click Continue button");
Driver.ToggleCollapse("AdvancedSettings");
Driver.SetCheckbox(By.Id("ImportKeysToRPC"), importkeys);
Driver.FindElement(By.Id("Continue")).Click();
// Seed backup page
FindAlertMessage();
if (string.IsNullOrEmpty(seed))
if (isImport)
{
seed = Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
// Confirm addresses
Driver.FindElement(By.Id("Confirm")).Click();
}
else
{
// Seed backup
FindAlertMessage();
if (string.IsNullOrEmpty(seed))
{
seed = Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
}
// Confirm seed backup
Driver.FindElement(By.Id("confirm")).Click();
Driver.FindElement(By.Id("submit")).Click();
// Confirm seed backup
Driver.FindElement(By.Id("confirm")).Click();
Driver.FindElement(By.Id("submit")).Click();
}
WalletId = new WalletId(StoreId, cryptoCode);
return new Mnemonic(seed);
}
@ -173,43 +199,58 @@ namespace BTCPayServer.Tests
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
{
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
Driver.FindElement(By.Id("import-wallet-options-link")).Click();
Driver.FindElement(By.Id("import-xpub-link")).Click();
Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
Driver.FindElement(By.Id("ImportXpubLink")).Click();
Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme);
Driver.FindElement(By.Id("Continue")).Click();
Driver.FindElement(By.Id("Confirm")).Click();
FindAlertMessage();
}
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
public void AddLightningNode(string cryptoCode = "BTC", LightningConnectionType? connectionType = null)
{
string connectionString;
if (connectionType == LightningConnectionType.Charge)
connectionString = $"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
else if (connectionType == LightningConnectionType.CLightning)
connectionString = "type=clightning;server=" + ((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri;
else if (connectionType == LightningConnectionType.LndREST)
connectionString = $"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
var connectionString = connectionType switch
{
LightningConnectionType.Charge =>
$"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
LightningConnectionType.CLightning =>
$"type=clightning;server={((CLightningClient) Server.MerchantLightningD).Address.AbsoluteUri}",
LightningConnectionType.LndREST =>
$"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true",
_ => null
};
if (connectionString == null)
{
Assert.True(Driver.FindElement(By.Id("LightningNodeType-Internal")).Enabled, "Usage of the internal Lightning node is disabled.");
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Internal\"]")).Click();
}
else
throw new NotSupportedException(connectionType.ToString());
{
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
Driver.FindElement(By.Id("ConnectionString")).SendKeys(connectionString);
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
Driver.FindElement(By.Id($"save")).Click();
}
Driver.FindElement(By.Id("test")).Click();
Assert.Contains("Connection to the Lightning node successful.", FindAlertMessage().Text);
}
public void AddInternalLightningNode(string cryptoCode)
{
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
Driver.FindElement(By.Id($"internal-ln-node-setter")).Click();
Driver.FindElement(By.Id($"save")).Click();
Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning node updated.", FindAlertMessage().Text);
var enabled = Driver.FindElement(By.Id($"{cryptoCode}LightningEnabled"));
if (enabled.Text == "Enable")
{
enabled.Click();
Assert.Contains($"{cryptoCode} Lightning payments are now enabled for this store.", FindAlertMessage().Text);
}
}
public void ClickOnAllSideMenus()
{
var links = Driver.FindElements(By.CssSelector(".nav-pills .nav-link")).Select(c => c.GetAttribute("href")).ToList();
var links = Driver.FindElements(By.CssSelector(".nav .nav-link")).Select(c => c.GetAttribute("href")).ToList();
Driver.AssertNoError();
Assert.NotEmpty(links);
foreach (var l in links)
{
Logs.Tester.LogInformation($"Checking no error on {l}");
@ -244,7 +285,7 @@ namespace BTCPayServer.Tests
public void GoToHome()
{
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
Driver.Navigate().GoToUrl(ServerUri);
}
public void Logout()
@ -282,26 +323,6 @@ namespace BTCPayServer.Tests
CheckForJSErrors();
}
public void SetCheckbox(IWebElement element, bool value)
{
if ((value && !element.Selected) || (!value && element.Selected))
{
element.Click();
}
if (value != element.Selected)
{
Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again");
SetCheckbox(element, value);
}
}
public void SetCheckbox(SeleniumTester s, string checkboxId, bool value)
{
SetCheckbox(s.Driver.FindElement(By.Id(checkboxId)), value);
}
public void GoToInvoices()
{
Driver.FindElement(By.Id("Invoices")).Click();
@ -318,23 +339,30 @@ namespace BTCPayServer.Tests
public void GoToLogin()
{
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "/login"));
Driver.Navigate().GoToUrl(new Uri(ServerUri, "/login"));
}
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
public string CreateInvoice(
string storeName,
decimal? amount = 100,
string currency = "USD",
string refundEmail = "",
string defaultPaymentMethod = "BTC"
)
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
if (amount is decimal v)
Driver.FindElement(By.Id("Amount")).SendKeys(v.ToString(CultureInfo.InvariantCulture));
var currencyEl = Driver.FindElement(By.Id("Currency"));
currencyEl.Clear();
currencyEl.SendKeys(currency);
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
Driver.FindElement(By.Name("DefaultPaymentMethod")).SendKeys(defaultPaymentMethod);
Driver.FindElement(By.Id("Create")).Click();
FindAlertMessage();
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
var statusElement = FindAlertMessage();
var id = statusElement.Text.Split(" ")[1];
return id;
}
@ -363,22 +391,22 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("bip21parse")).Click();
Driver.SwitchTo().Alert().SendKeys(bip21);
Driver.SwitchTo().Alert().Accept();
Driver.FindElement(By.Id("SendMenu")).Click();
Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
Driver.FindElement(By.Id("SignTransaction")).Click();
Driver.FindElement(By.Id("SignWithSeed")).Click();
Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
}
private void CheckForJSErrors()
{
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
// var errorStrings = new List<string>
// {
// "SyntaxError",
// "EvalError",
// "ReferenceError",
// "RangeError",
// "TypeError",
// "URIError"
// var errorStrings = new List<string>
// {
// "SyntaxError",
// "EvalError",
// "ReferenceError",
// "RangeError",
// "TypeError",
// "URIError"
// };
//
// var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
@ -394,7 +422,7 @@ namespace BTCPayServer.Tests
public void GoToWallet(WalletId walletId = null, WalletsNavPages navPages = WalletsNavPages.Send)
{
walletId ??= WalletId;
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}"));
Driver.Navigate().GoToUrl(new Uri(ServerUri, $"wallets/{walletId}"));
if (navPages != WalletsNavPages.Transactions)
{
Driver.FindElement(By.Id($"Wallet{navPages}")).Click();
@ -403,9 +431,9 @@ namespace BTCPayServer.Tests
public void GoToUrl(string relativeUrl)
{
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, relativeUrl));
Driver.Navigate().GoToUrl(new Uri(ServerUri, relativeUrl));
}
public void GoToServer(ServerNavPages navPages = ServerNavPages.Index)
{
Driver.FindElement(By.Id("ServerSettings")).Click();

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
@ -7,10 +8,12 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Services;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Manage;
using BTCPayServer.Views.Server;
using BTCPayServer.Views.Stores;
using BTCPayServer.Views.Wallets;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
@ -75,16 +78,17 @@ namespace BTCPayServer.Tests
Assert.Contains("server/services/lndseedbackup/BTC", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/server/services/lndseedbackup/BTC"));
s.Driver.FindElement(By.Id("details")).Click();
var seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
var seedEl = s.Driver.FindElement(By.Id("Seed"));
Assert.True(seedEl.Displayed);
Assert.Contains("about over million", seedEl.Text, StringComparison.OrdinalIgnoreCase);
var passEl = s.Driver.FindElement(By.Id("PasswordInput"));
var passEl = s.Driver.FindElement(By.Id("WalletPassword"));
Assert.True(passEl.Displayed);
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
s.Driver.FindElement(By.Id("delete")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
s.FindAlertMessage();
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
seedEl = s.Driver.FindElement(By.Id("Seed"));
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
}
}
@ -113,7 +117,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Email")).SendKeys(u2.RegisterDetails.Email);
s.Driver.FindElement(By.Id("save")).Click();
s.FindAlertMessage(Abstractions.Models.StatusMessageModel.StatusSeverity.Error);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
s.GoToProfile(ManageNavPages.Index);
s.Driver.FindElement(By.Id("Email")).Clear();
@ -202,6 +206,15 @@ namespace BTCPayServer.Tests
// We should be logged in now
s.Driver.FindElement(By.Id("mainNav"));
//let's test delete user quickly while we're at it
s.GoToProfile();
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
s.Driver.FindElement(By.Id("delete-user")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
Assert.Contains("/login", s.Driver.Url);
}
}
@ -211,6 +224,10 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var settings = s.Server.PayTester.GetService<SettingsRepository>();
var policies = await settings.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
policies.DisableSSHService = false;
await settings.UpdateSetting(policies);
s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("server/services/ssh", s.Driver.PageSource);
@ -239,6 +256,19 @@ namespace BTCPayServer.Tests
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
Assert.DoesNotContain("test2", text);
// Let's try to disable it now
s.Driver.FindElement(By.Id("disable")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DISABLE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
policies = await settings.GetSettingAsync<PoliciesSettings>();
Assert.True(policies.DisableSSHService);
policies.DisableSSHService = false;
await settings.UpdateSetting(policies);
}
}
@ -278,7 +308,7 @@ namespace BTCPayServer.Tests
{
// Cleanup old test run
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
}
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
s.Driver.AssertNoError();
@ -305,7 +335,7 @@ namespace BTCPayServer.Tests
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
Assert.Contains("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
s.Driver.AssertNoError();
Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
@ -313,13 +343,15 @@ namespace BTCPayServer.Tests
}
[Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")]
public async Task CanCreateStores()
{
using (var s = SeleniumTester.Create())
{
s.Server.ActivateLightning();
await s.StartAsync();
var alice = s.RegisterNewUser();
var storeData = s.CreateNewStore();
var alice = s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore();
var onchainHint = "Set up your wallet to receive payments at your store.";
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
@ -328,23 +360,31 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint not present");
s.GoToStores();
Assert.True(s.Driver.PageSource.Contains("warninghint_" + storeData.storeId),
"Warning hint on list not present");
Assert.True(s.Driver.PageSource.Contains($"warninghint_{storeId}"), "Warning hint on list not present");
s.GoToStore(storeData.storeId);
s.GoToStore(storeId);
Assert.Contains(storeName, s.Driver.PageSource);
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint should be present at this point");
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be present at this point");
s.AddDerivationScheme(); // wallet hint should be dismissed
// setup onchain wallet
s.GoToStore(storeId);
s.AddDerivationScheme();
s.Driver.AssertNoError();
Assert.False(s.Driver.PageSource.Contains(onchainHint),
"Wallet hint not dismissed on derivation scheme add");// dismiss lightning hint
Assert.False(s.Driver.PageSource.Contains(onchainHint), "Wallet hint not dismissed on derivation scheme add");
// setup offchain wallet
s.GoToStore(storeId);
s.AddLightningNode();
s.Driver.AssertNoError();
var successAlert = s.FindAlertMessage();
Assert.Contains("BTC Lightning node updated.", successAlert.Text);
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
Assert.Contains(storeData.storeName, s.Driver.PageSource);
var storeUrl = s.Driver.Url;
s.ClickOnAllSideMenus();
s.GoToInvoices();
var invoiceId = s.CreateInvoice(storeData.storeName);
var invoiceId = s.CreateInvoice(storeName);
s.FindAlertMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
@ -399,12 +439,9 @@ namespace BTCPayServer.Tests
s.Logout();
s.LogIn(alice);
s.Driver.FindElement(By.Id("Stores")).Click();
// there shouldn't be any hints now
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
s.Driver.FindElement(By.LinkText("Remove")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.FindElement(By.LinkText("Delete")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
s.Driver.FindElement(By.Id("Stores")).Click();
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
@ -433,7 +470,7 @@ namespace BTCPayServer.Tests
s.FindAlertMessage();
Assert.Contains(pairingCode, s.Driver.PageSource);
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
{
@ -442,10 +479,10 @@ namespace BTCPayServer.Tests
FullNotifications = true
}, NBitpayClient.Facade.Merchant);
client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
var code = await client.RequestClientAuthorizationAsync("hehe", NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(code.CreateLink(s.Server.PayTester.ServerUri));
s.Driver.Navigate().GoToUrl(code.CreateLink(s.ServerUri));
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
@ -474,16 +511,25 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Apps")).Click();
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale");
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Point of Sale");
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart");
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click();
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
s.Driver.FindElement(By.Id("SaveItemChanges")).Click();
s.Driver.FindElement(By.Id("ToggleRawEditor")).Click();
var template = s.Driver.FindElement(By.Id("Template")).GetAttribute("value");
Assert.Contains("buyButtonText: Take my money", template);
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Item list and cart");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
s.Driver.FindElement(By.Id("ViewApp")).Click();
var posBaseUrl = s.Driver.Url.Replace("/Cart", "");
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
Assert.True(s.Driver.PageSource.Contains("Take my money"), "PoS not showing correct default view");
s.Driver.Url = posBaseUrl + "/static";
Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view");
@ -515,7 +561,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
s.Driver.FindElement(By.Id("ViewApp")).Click();
Assert.Equal("Currently Active!", s.Driver.FindElement(By.CssSelector(".h6.text-muted")).Text);
Assert.Equal("currently active!", s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
}
}
@ -538,10 +584,27 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
// expire
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
s.Driver.Navigate().Refresh();
Assert.Equal("Expired", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
// unexpire
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
s.Driver.FindElement(By.Id("ClearExpiryDate")).Click();
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseCoinSelection()
{
@ -570,16 +633,16 @@ namespace BTCPayServer.Tests
var x = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
.OfType<DerivationSchemeSettings>()
.Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
var wallet = s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode);
wallet.InvalidateCache(x.AccountDerivation);
Assert.Contains(
await s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode)
.GetUnspentCoins(x.AccountDerivation),
await wallet.GetUnspentCoins(x.AccountDerivation),
coin => coin.OutPoint == spentOutpoint);
});
await s.Server.ExplorerNode.GenerateAsync(1);
s.GoToWallet(walletId);
s.Driver.FindElement(By.Id("advancedSettings")).Click();
s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
s.Driver.WaitForAndClick(By.Id("toggleInputSelection"));
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
@ -588,8 +651,7 @@ namespace BTCPayServer.Tests
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, bob, 0.3m);
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
var happyElement = s.FindAlertMessage();
var happyText = happyElement.Text;
@ -609,15 +671,14 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore();
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
s.GoToStore(storeId, StoreNavPages.Webhooks);
Logs.Tester.LogInformation("Let's create two webhooks");
for (var i = 0; i < 2; i++)
{
s.Driver.FindElement(By.Id("CreateWebhook")).Click();
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys($"http://127.0.0.1/callback{i}");
new SelectElement(s.Driver.FindElement(By.Name("Everything")))
.SelectByValue("false");
new SelectElement(s.Driver.FindElement(By.Id("Everything"))).SelectByValue("false");
s.Driver.FindElement(By.Id("InvoiceCreated")).Click();
s.Driver.FindElement(By.Id("InvoiceProcessing")).Click();
s.Driver.FindElement(By.Name("add")).Click();
@ -627,7 +688,8 @@ namespace BTCPayServer.Tests
var deletes = s.Driver.FindElements(By.LinkText("Delete"));
Assert.Equal(2, deletes.Count);
deletes[0].Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
deletes = s.Driver.FindElements(By.LinkText("Delete"));
Assert.Single(deletes);
s.FindAlertMessage();
@ -711,14 +773,34 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside");
s.GoToStore(storeId);
s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);");
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
s.Driver.ToggleCollapse("danger-zone");
s.Driver.FindElement(By.Id("delete-store")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.WaitForElement(By.Id("ConfirmContinue")).Click();
s.FindAlertMessage();
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanImportMnemonic()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(true);
foreach (var isHotwallet in new[] { false, true })
{
var (storeName, storeId) = s.CreateNewStore();
s.GenerateWallet(privkeys: isHotwallet, seed: "melody lizard phrase voice unique car opinion merge degree evil swift cargo");
s.GoToWallet(s.WalletId, WalletsNavPages.Settings);
if (isHotwallet)
Assert.Contains("View seed", s.Driver.PageSource);
else
Assert.DoesNotContain("View seed", s.Driver.PageSource);
}
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanManageWallet()
{
@ -736,7 +818,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
s.Driver.FindElement(By.Id("WalletSend")).Click();
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
//you cannot use the Sign with NBX option without saving private keys when generating the wallet.
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
@ -800,6 +882,11 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.LinkText("Manage")).Click();
s.ClickOnAllSideMenus();
// Make sure wallet info is correct
s.Driver.FindElement(By.Id("WalletSettings")).Click();
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
Assert.Contains("m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
// Make sure we can rescan, because we are admin!
s.Driver.FindElement(By.Id("WalletRescan")).Click();
@ -807,36 +894,24 @@ namespace BTCPayServer.Tests
// We setup the fingerprint and the account key path
s.Driver.FindElement(By.Id("WalletSettings")).Click();
// s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
// s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);
// Check the tx sent earlier arrived
s.Driver.FindElement(By.Id("WalletTransactions")).Click();
var walletTransactionLink = s.Driver.Url;
Assert.Contains(tx.ToString(), s.Driver.PageSource);
// Send to bob
s.Driver.FindElement(By.Id("WalletSend")).Click();
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, bob, 1);
s.Driver.FindElement(By.Id("SignTransaction")).Click();
void SignWith(Mnemonic signingSource)
{
// Send to bob
s.Driver.FindElement(By.Id("WalletSend")).Click();
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, bob, 1);
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();
// Input the seed
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);
// Broadcast
Assert.Contains(bob.ToString(), s.Driver.PageSource);
Assert.Contains("1.00000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.Equal(walletTransactionLink, s.Driver.Url);
}
SignWith(mnemonic);
// Broadcast
Assert.Contains(bob.ToString(), s.Driver.PageSource);
Assert.Contains("1.00000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.Equal(walletTransactionLink, s.Driver.Url);
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
@ -844,15 +919,10 @@ namespace BTCPayServer.Tests
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, jack, 0.01m);
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
Assert.Contains(jack.ToString(), s.Driver.PageSource);
Assert.Contains("0.01000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).Click();
Assert.EndsWith("psbt", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("#OtherActions")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.EndsWith("psbt/ready", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.Equal(walletTransactionLink, s.Driver.Url);
@ -868,17 +938,16 @@ namespace BTCPayServer.Tests
s.Driver.SwitchTo().Alert().SendKeys(bip21);
s.Driver.SwitchTo().Alert().Accept();
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value"));
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id("Outputs_0__Amount")).GetAttribute("value"));
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value"));
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
var walletUrl = s.Driver.Url;
s.Driver.FindElement(By.Id("SettingsMenu")).Click();
s.Driver.FindElement(By.Id("OtherActionsDropdownToggle")).Click();
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
// Seed backup page
var recoveryPhrase = s.Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
Assert.Equal(mnemonic.ToString(), recoveryPhrase);
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource);
@ -889,116 +958,179 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanUsePullPaymentsViaUI()
[Fact(Timeout = TestTimeout)]
public async Task CanImportWallet()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(true);
s.CreateNewStore();
s.GenerateWallet("BTC", "", true, true);
var (_, storeId) = s.CreateNewStore();
var mnemonic = s.GenerateWallet("BTC", "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
// Make sure wallet info is correct
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
Assert.Contains( "m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
}
}
await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(denomination: 50.0m);
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
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.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.LinkText("View")).Click();
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanUsePullPaymentsViaUI()
{
using var s = SeleniumTester.Create();
await s.StartAsync();
s.RegisterNewUser(true);
s.CreateNewStore();
s.GenerateWallet("BTC", "", true, true);
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(denomination: 50.0m);
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
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.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.LinkText("View")).Click();
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP2");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0");
s.Driver.FindElement(By.Id("Create")).Click();
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
// This should select the first View, ie, the last one PP2
s.Driver.FindElement(By.LinkText("View")).Click();
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter);
s.FindAlertMessage();
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP2");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0");
s.Driver.FindElement(By.Id("Create")).Click();
// We should not be able to use an address already used
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
// This should select the first View, ie, the last one PP2
s.Driver.FindElement(By.LinkText("View")).Click();
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter);
s.FindAlertMessage();
address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).Clear();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.FindAlertMessage();
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
// We should not be able to use an address already used
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
var viewPullPaymentUrl = s.Driver.Url;
// This one should have nothing
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
Assert.Equal(2, payouts.Count);
payouts[1].Click();
Assert.Contains("No payout waiting for approval", s.Driver.PageSource);
address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).Clear();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.FindAlertMessage();
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
// PP2 should have payouts
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
payouts[0].Click();
Assert.DoesNotContain("No payout waiting for approval", s.Driver.PageSource);
s.Driver.FindElement(By.Id("selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id("payCommand")).Click();
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
var viewPullPaymentUrl = s.Driver.Url;
// This one should have nothing
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
Assert.Equal(2, payouts.Count);
payouts[1].Click();
Assert.Empty(s.Driver.FindElements(By.ClassName("payout")));
// PP2 should have payouts
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
payouts[0].Click();
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
s.FindAlertMessage();
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
Assert.Contains("badge transactionLabel", s.Driver.PageSource);
});
Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text);
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
Assert.Contains("badge transactionLabel", s.Driver.PageSource);
});
Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text);
s.GoToWallet(navPages: WalletsNavPages.Payouts);
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
Assert.Contains("No payout waiting for approval", s.Driver.PageSource);
});
var txs = s.Driver.FindElements(By.ClassName("transaction-link"));
Assert.Equal(2, txs.Count);
s.Driver.Navigate().GoToUrl(viewPullPaymentUrl);
s.GoToWallet(navPages: WalletsNavPages.Payouts);
var x = s.Driver.PageSource;
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
ReadOnlyCollection<IWebElement> txs;
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
Assert.Equal(2, txs.Count);
Assert.Contains("In Progress", s.Driver.PageSource);
});
await s.Server.ExplorerNode.GenerateAsync(1);
s.Driver.Navigate().GoToUrl(viewPullPaymentUrl);
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
Assert.Equal(2, txs.Count);
Assert.Contains(PayoutState.InProgress.GetStateString(), s.Driver.PageSource);
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
Assert.Contains("Completed", s.Driver.PageSource);
});
await s.Server.ExplorerNode.GenerateAsync(10);
var pullPaymentId = viewPullPaymentUrl.Split('/').Last();
await s.Server.ExplorerNode.GenerateAsync(1);
await TestUtils.EventuallyAsync(async () =>
{
using var ctx = s.Server.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
var payoutsData = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId).ToListAsync();
Assert.True(payoutsData.All(p => p.State == Data.PayoutState.Completed));
});
}
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
Assert.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
});
await s.Server.ExplorerNode.GenerateAsync(10);
var pullPaymentId = viewPullPaymentUrl.Split('/').Last();
await TestUtils.EventuallyAsync(async () =>
{
using var ctx = s.Server.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
var payoutsData = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId).ToListAsync();
Assert.True(payoutsData.All(p => p.State == PayoutState.Completed));
});
s.GoToHome();
//offline/external payout test
s.Driver.FindElement(By.Id("NotificationsDropdownToggle")).Click();
s.Driver.FindElement(By.CssSelector("#notificationsForm button")).Click();
var newStore = s.CreateNewStore();
s.GenerateWallet("BTC", "", true, true);
var newWalletId = new WalletId(newStore.storeId, "BTC");
s.GoToWallet(newWalletId, WalletsNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("External Test");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.001");
s.Driver.FindElement(By.Id("Currency")).Clear();
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.LinkText("View")).Click();
address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
s.FindAlertMessage();
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
s.FindAlertMessage();
var tx =await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("InProgress-view")).Click();
Assert.Contains(tx.ToString(), s.Driver.PageSource);
}
private static void CanBrowseContent(SeleniumTester s)
@ -1014,8 +1146,9 @@ namespace BTCPayServer.Tests
private static void CanSetupEmailCore(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
s.Driver.FindElement(By.Id("QuickFillDropdownToggle")).Click();
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
s.FindAlertMessage();

View File

@ -7,6 +7,8 @@ using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Tests.Lnd;
using BTCPayServer.Tests.Logging;
using NBitcoin;
@ -86,6 +88,10 @@ namespace BTCPayServer.Tests
#endif
public void ActivateLightning()
{
ActivateLightning(LightningConnectionType.Charge);
}
public void ActivateLightning(LightningConnectionType internalNode)
{
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
@ -93,7 +99,39 @@ namespace BTCPayServer.Tests
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify;allowinsecure=true", "merchant_lightningd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
PayTester.IntegratedLightning = GetLightningConnectionString(internalNode, true);
}
public string GetLightningConnectionString(LightningConnectionType? connectionType, bool isMerchant)
{
string connectionString = null;
if (connectionType is null)
return LightningSupportedPaymentMethod.InternalNode;
if (connectionType == LightningConnectionType.Charge)
{
if (isMerchant)
connectionString = $"type=charge;server={MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
else
throw new NotSupportedException();
}
else if (connectionType == LightningConnectionType.CLightning)
{
if (isMerchant)
connectionString = "type=clightning;server=" +
((CLightningClient)MerchantLightningD).Address.AbsoluteUri;
else
connectionString = "type=clightning;server=" +
((CLightningClient)CustomerLightningD).Address.AbsoluteUri;
}
else if (connectionType == LightningConnectionType.LndREST)
{
if (isMerchant)
connectionString = $"type=lnd-rest;server={MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
else
throw new NotSupportedException();
}
else
throw new NotSupportedException(connectionType.ToString());
return connectionString;
}
public bool Dockerized

View File

@ -10,8 +10,10 @@ using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
using BTCPayServer.Storage.ViewModels;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
@ -180,23 +182,27 @@ namespace BTCPayServer.Tests
private async Task CanUploadRemoveFiles(ServerController controller)
{
var fileContent = "content";
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFile(TestUtils.GetFormFile("uploadtestfile.txt", fileContent)));
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileId"));
var fileId = uploadFormFileResult.RouteValues["fileId"].ToString();
List<IFormFile> fileList = new List<IFormFile>();
fileList.Add(TestUtils.GetFormFile("uploadtestfile1.txt", fileContent));
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFiles(fileList));
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileIds"));
string[] uploadFileList = (string[])uploadFormFileResult.RouteValues["fileIds"];
var fileId = uploadFileList[0];
Assert.Equal("Files", uploadFormFileResult.ActionName);
//check if file was uploaded and saved in db
var viewFilesViewModel =
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(fileId)).Model);
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
Assert.NotEmpty(viewFilesViewModel.Files);
Assert.Equal(fileId, viewFilesViewModel.SelectedFileId);
Assert.NotEmpty(viewFilesViewModel.DirectFileUrl);
Assert.True(viewFilesViewModel.DirectUrlByFiles.ContainsKey(fileId));
Assert.NotEmpty(viewFilesViewModel.DirectUrlByFiles[fileId]);
//verify file is available and the same
var net = new System.Net.WebClient();
var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectFileUrl));
var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId]));
Assert.Equal(fileContent, data);
//create a temporary link to file
@ -228,10 +234,8 @@ namespace BTCPayServer.Tests
//attempt to fetch deleted file
viewFilesViewModel =
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(fileId)).Model);
Assert.Null(viewFilesViewModel.DirectFileUrl);
Assert.Null(viewFilesViewModel.SelectedFileId);
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
Assert.Null(viewFilesViewModel.DirectUrlByFiles);
}

View File

@ -14,6 +14,7 @@ using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.PayJoin.Sender;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
@ -22,6 +23,8 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Operations;
using NBitcoin;
using BTCPayServer.BIP78.Sender;
using BTCPayServer.Payments.Lightning;
using NBitcoin.Payment;
using NBitpayClient;
using NBXplorer.DerivationStrategy;
@ -122,28 +125,22 @@ namespace BTCPayServer.Tests
CreateStoreAsync().GetAwaiter().GetResult();
}
public void SetNetworkFeeMode(NetworkFeeMode mode)
public async Task SetNetworkFeeMode(NetworkFeeMode mode)
{
ModifyStore((store) =>
await ModifyStore(store =>
{
store.NetworkFeeMode = mode;
});
}
public void ModifyStore(Action<StoreViewModel> modify)
public async Task ModifyStore(Action<StoreViewModel> modify)
{
var storeController = GetController<StoresController>();
StoreViewModel store = (StoreViewModel)((ViewResult)storeController.UpdateStore()).Model;
var response = await storeController.UpdateStore();
StoreViewModel store = (StoreViewModel)((ViewResult)response).Model;
modify(store);
storeController.UpdateStore(store).GetAwaiter().GetResult();
}
public Task ModifyStoreAsync(Action<StoreViewModel> modify)
{
var storeController = GetController<StoresController>();
StoreViewModel store = (StoreViewModel)((ViewResult)storeController.UpdateStore()).Model;
modify(store);
return storeController.UpdateStore(store);
}
public T GetController<T>(bool setImplicitStore = true) where T : Controller
{
@ -171,38 +168,29 @@ namespace BTCPayServer.Tests
}
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy,
bool importKeysToNBX = false)
bool importKeysToNBX = false, bool importsKeysToBitcoinCore = false)
{
if (StoreId is null)
await CreateStoreAsync();
SupportedNetwork = parent.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
GenerateWalletResponseV = await parent.ExplorerClient.GenerateWalletAsync(new GenerateWalletRequest()
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId, true);
var generateRequest = new WalletSetupRequest
{
ScriptPubKeyType = segwit,
SavePrivateKeys = importKeysToNBX,
});
await store.AddDerivationScheme(StoreId,
new DerivationSchemeViewModel()
{
Enabled = true,
CryptoCode = cryptoCode,
Network = SupportedNetwork,
RootFingerprint = GenerateWalletResponseV.AccountKeyPath.MasterFingerprint.ToString(),
RootKeyPath = SupportedNetwork.GetRootKeyPath(),
Source = "NBXplorer",
AccountKey = GenerateWalletResponseV.AccountHDKey.Neuter().ToWif(),
DerivationSchemeFormat = "BTCPay",
KeyPath = GenerateWalletResponseV.AccountKeyPath.KeyPath.ToString(),
DerivationScheme = DerivationScheme.ToString(),
Confirmation = true
}, cryptoCode);
ImportKeysToRPC = importsKeysToBitcoinCore
};
await store.GenerateWallet(StoreId, cryptoCode, WalletSetupMethod.HotWallet, generateRequest);
Assert.NotNull(store.GenerateWalletResponse);
GenerateWalletResponseV = store.GenerateWalletResponse;
return new WalletId(StoreId, cryptoCode);
}
public Task EnablePayJoin()
{
return ModifyStoreAsync(s => s.PayJoinEnabled = true);
return ModifyStore(s => s.PayJoinEnabled = true);
}
public GenerateWalletResponse GenerateWalletResponseV { get; set; }
@ -256,40 +244,19 @@ namespace BTCPayServer.Tests
{
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
}
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
public Task RegisterLightningNodeAsync(string cryptoCode, bool isMerchant = true, string storeId = null)
{
var storeController = this.GetController<StoresController>();
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
}
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
{
var storeController = GetController<StoresController>();
string connectionString = null;
if (connectionType == LightningConnectionType.Charge)
{
if (isMerchant)
connectionString = $"type=charge;server={parent.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
else
throw new NotSupportedException();
}
else if (connectionType == LightningConnectionType.CLightning)
{
if (isMerchant)
connectionString = "type=clightning;server=" +
((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri;
else
connectionString = "type=clightning;server=" +
((CLightningClient)parent.CustomerLightningD).Address.AbsoluteUri;
}
else if (connectionType == LightningConnectionType.LndREST)
{
if (isMerchant)
connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
else
throw new NotSupportedException();
}
else
throw new NotSupportedException(connectionType.ToString());
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
await storeController.AddLightningNode(StoreId,
new LightningNodeViewModel() { ConnectionString = connectionString, SkipPortTest = true }, "save", "BTC");
await storeController.SetupLightningNode(storeId ?? StoreId,
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", cryptoCode);
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
@ -358,7 +325,7 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
if (expectedError is null && !senderError)
{
var proposed = await pjClient.RequestPayjoin(endpoint, settings, psbt, default);
var proposed = await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default);
Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}");
Assert.NotNull(proposed);
return proposed;
@ -367,11 +334,11 @@ namespace BTCPayServer.Tests
{
if (senderError)
{
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default));
}
else
{
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default));
var split = expectedError.Split('|');
Assert.Equal(split[0], ex.ErrorCode);
if (split.Length > 1)

Some files were not shown because too many files have changed in this diff Show More