Compare commits

...

314 Commits

Author SHA1 Message Date
6f9e0dca51 Add translations 2024-12-10 23:59:20 +09:00
d2c3c37f4d Add changelog for 2.0.4 (#6472) 2024-12-10 23:56:13 +09:00
b797cc9af8 Multisig/watchonly wallet transaction creation flow proof of concept (#5743) 2024-12-10 21:56:52 +09:00
cc915df10e Merge pull request #6469 from NicolasDorier/validatinglightningclient
Plugins: Add a way for LightningClient to be more customizable (Fix #6467)
2024-12-10 19:01:09 +09:00
fe53424710 Merge pull request #6470 from NicolasDorier/globalcheckoutmodelext
Plugin: Add IGlobalCheckoutModelExtension
2024-12-10 18:59:46 +09:00
6cfd4cb633 Merge pull request #6471 from dennisreimann/fix-throttle
POS: Fix throttling for unauthenticated users
2024-12-10 18:54:40 +09:00
4d38f91bd5 POS: Fix throttling for unauthenticated users
Fixes a regression introduced with d24adda70084f0f807d2a98d204b16b509ae2397: The negation for the `_rateLimitService.Throttle` result was removed with that commit, which lead to all unauthenticated request getting throttled. (It was correctly implemented in #6415.

Fixes btcpayserver/app#131.
2024-12-09 18:01:09 +01:00
4f63f08aeb Plugin: Add IGlobalCheckoutModelExtension 2024-12-09 21:29:04 +09:00
e7e7fab1a9 Plugins: Add a way for LightningClient to validate the connection string asynchronously 2024-12-09 21:25:22 +09:00
1214367503 bump nbx image 2024-12-09 16:31:56 +09:00
d24adda700 Fix throttling on PoS 2024-12-09 16:18:07 +09:00
eb89f4636b Merge pull request #6415 from Kukks/rate-limit-pos
Do not throttle pos when logged in
2024-12-09 12:42:51 +09:00
7da247ffd6 Fix test 2024-12-09 09:44:00 +09:00
cadcf27ebc Merge pull request #6433 from dennisreimann/mwb-integration-docker
App: Docker Compose additions
2024-12-09 09:35:14 +09:00
61b882d426 fix: truncate center css for icons (#6465)
currently, the info icon used for displaying a link is way too big since
its not matched by the current css
2024-12-06 18:11:54 +01:00
e4d780417d Merge pull request #6464 from NicolasDorier/refactormails
Access email settings through the factory
2024-12-06 16:35:55 +09:00
4d01e3a16a Access email settings through the factory 2024-12-06 16:19:17 +09:00
52a1627d81 Merge pull request #6438 from dennisreimann/mwb-integration-email-qr
Add QR Code with link to invitation email
2024-12-06 13:39:32 +09:00
d5b9d91a75 Merge pull request #6462 from dennisreimann/fix-flaky-tests
Tests: Fix flakyness of two tests
2024-12-05 19:51:40 +09:00
cd507b10e0 Tests: Fix flakyness 2024-12-05 10:19:08 +01:00
f7a96272c1 Merge pull request #6461 from NicolasDorier/fix6437
Fix: Combination of status filters on invoices page causes 500 fatal server error (Fix #6437)
2024-12-04 23:39:53 +09:00
a8d1f55544 Editor: Use offcanvas for all breakpoints (#6441)
Avoids scrolling the editor out of the viewport. Closes #6436.

Done for the POS/Crowdfund editor, as well as the Forms one.
2024-12-04 15:18:27 +01:00
fb8ca19327 Fix: Combination of status filters on invoices page causes 500 fatal server error (Fix #6437) 2024-12-04 22:07:32 +09:00
341bea48f8 UI: Fix close icon in wizard across views 2024-12-04 12:12:54 +01:00
aa336e7d8a Update and fix docker-compose test 2024-12-04 12:10:45 +01:00
77f9b6a88c Merge branch 'master' into mwb-integration-email-qr 2024-12-04 11:53:10 +01:00
3b94c2798c Updates from code review 2024-12-04 11:48:47 +01:00
70f97382a4 Merge pull request #6412 from NicolasDorier/payreqfix
Fix: Payment Requests should show payments of invalid invoices
2024-12-04 19:18:43 +09:00
e7b98fbae3 Merge pull request #6442 from dennisreimann/mwb-integration-signin
Account: Sign in user after accepting invitation or resetting password
2024-12-04 19:16:59 +09:00
ad3a635f59 Merge pull request #6457 from NicolasDorier/updatefido
Update Dependencies
2024-12-04 19:16:24 +09:00
eea9ba10fd Merge pull request #6452 from schjonhaug/rates-from-norwegian-exchanges
Add Rate Providers for BTC/NOK
2024-12-04 19:14:58 +09:00
f2dc033067 Bugfix: Removed unnecessary references to javascript files (#6459) (#6460) 2024-12-04 11:13:56 +01:00
bf37ee9cf6 Explicitely reference some dotnet packages 2024-12-03 23:14:20 +09:00
9b4fe5930e Bump TwentyTwenty.Storage 2024-12-03 22:37:10 +09:00
a4e950343a Bump NTag424 2024-12-03 22:30:46 +09:00
af54e7ebc2 Merge pull request #6429 from dennisreimann/app-invoices-link
UI: Move app's invoices link to the top
2024-12-03 22:22:06 +09:00
8edb4eae3b Bump MimeKit and DigitalRuby 2024-12-03 21:23:07 +09:00
694b8e111c Update FIDO library 2024-12-03 21:13:23 +09:00
cccaf0e72c Added Bare Bitcoin as exchange recommendation for NOK 2024-12-03 09:06:27 +01:00
7d65817acd Merge pull request #6449 from NicolasDorier/bumpnet
bump dotnet
2024-12-03 15:42:25 +09:00
43f159cd81 Bump dotnet 2024-12-03 14:42:43 +09:00
6e2f355aa6 Merge pull request #6456 from NicolasDorier/razorbuild
Do not use razor runtime compilation when views have been compiled on build
2024-12-03 14:17:09 +09:00
e2f868df52 Do not use razor runtime compilation when views have been compiled on build 2024-12-03 13:30:05 +09:00
898f0f4481 Greenfield: Improve store users API (#6427)
* Greenfield: Improve store users API

- Adds an endpoint to update store users (before they had to be removed ad re-added)
- Checks for the existance of a user and responds with 404 in that case (fixes #6423)
- Allows retrieval of user by user id or email for add and update (consistent with the other endpoints)
- Improves the API docs for the store users endpoints

* Swagger: Reuse UserIdOrEmail parameter component

* Add details to store user data
2024-12-02 15:35:33 +01:00
615b7842db Added tests 2024-12-02 15:01:58 +01:00
2bc6499f1b Added support for Bitmynt and Bare Bitcoin 2024-12-02 15:01:58 +01:00
9175af4abe Cleanup old checkout related settings code 2024-12-02 11:53:21 +01:00
3046fe0f0b Replace PayjoinLocks handling with Dapper (avoid logs being written) 2024-12-02 10:16:07 +09:00
6822608c14 Replace .GetAwaiter().GetResult() to await in tests 2024-12-02 09:36:31 +09:00
177ddb4117 Remove possible NRE in the LightningListener 2024-12-02 09:28:11 +09:00
3b876413c1 Merge pull request #6443 from btcpayserver/feat/nuget-update
Updating NuGet dependencies to new version
2024-12-02 09:03:02 +09:00
6397c8e07c Resolving xunit warnings after update 2024-11-29 20:53:29 -06:00
9fce9fcb7b Bumping Lightning.Common and NBitcoin 2024-11-29 20:47:26 -06:00
0eec3eefd6 Bumping more entity framework dependencies, razor, newtonsoft.json 2024-11-29 20:44:33 -06:00
83ce5a2107 Bumping xunit, EntityFrameworkCore and PostgreSQL 2024-11-29 20:35:34 -06:00
4ea6a03b4b Bumping Nbxplorer.Client and BtcPayServer.Lightning dependencies 2024-11-29 20:27:43 -06:00
bdf12aab0f App: Sales stats should only include paid invoices (#6444)
Fixes btcpayserver/app#110
2024-11-29 08:23:53 +01:00
299527fe16 bump nbx 2024-11-29 14:30:03 +09:00
e14c086fed bump nbx 2024-11-28 19:36:59 +09:00
fbf707cde2 Account: Sign in user after accepting invitation or resetting password
UX improvements, which we are porting from the app to unify the experience.
2024-11-26 11:42:13 +01:00
45a6bc8ae2 Add QR Code with link to invitation email
This will help onboarding via the app.
2024-11-26 11:23:39 +01:00
71c578f866 App: Docker Compose additions
For this one, @kukks is the owner and can procide more detail.
2024-11-26 08:25:59 +01:00
5cf3819743 Show Invoices link regardless of archival status 2024-11-26 06:52:51 +01:00
d3315c2fa6 Integrate mobile-working-branch part 1 (#6428) 2024-11-26 14:17:40 +09:00
f7f3244b3d UI: Move app's invoices link to the top 2024-11-25 18:02:07 +01:00
d6211327db Greenfield: Users API fixes (#6425)
* Do not crash when creatuing users without password

* More precise error message for user approval toggling
2024-11-25 09:38:44 +09:00
76f87011a2 Handle undefined AudioContext, resolves #6422 (#6424) 2024-11-23 10:52:23 +01:00
c6fc0302d2 fix: correct < plugin dependency implementation (#6420)
was wrongly implemented using >=
2024-11-22 10:17:16 +01:00
843c813f86 Replace entity upsert in PlannedTransaction by plain SQL (#6416)
* Replace entity upsert in PlannedTransaction by plain SQL

* Fix flaky test from #6411
2024-11-21 15:47:03 +09:00
57742ad871 Do not throttle pos when logged in 2024-11-20 15:50:46 +01:00
7db510b5ca Swagger: Fix errors, warnings and formatting (#6410)
* Fix linting errors

* Fix linting warnings

* Fix inconsistend indentation and unify formatting

* Extract StoreId schemas

* Extract CryptoCode parameter

* Extract AppId parameter

* More StoreId parameter references

* Extract WebhookId and DeliveryId parameters

* Extract InvoiceId parameter

* Formatting
2024-11-20 22:46:55 +09:00
1490fbb721 Fix: Payment Requests should show payments of invalid invoices 2024-11-19 11:02:59 +09:00
dd4400f8dd Refactoring: Use pattern matching for CanRefund/Mark invoices 2024-11-19 10:51:36 +09:00
4279ee1962 Add 1.13.7 changelog 2024-11-14 22:47:15 +09:00
558f45a48c Bump btcpay lib 2024-11-14 22:21:06 +09:00
6a76167637 Bump NBitcoin libraries 2024-11-14 22:19:10 +09:00
f56ad3408c Remove \r\n in the translations 2024-11-14 22:10:03 +09:00
8e688b7f28 Fix: Getting notifications via API would crash 2024-11-14 21:53:37 +09:00
6d737ce119 Bump Libraries 2024-11-14 21:46:15 +09:00
2075e16767 bump libs 2024-11-14 21:17:55 +09:00
2c4b1798ad Bump and changelog (#6402) 2024-11-14 21:12:54 +09:00
b040f78f70 Update checkout translations 2024-11-14 16:10:49 +09:00
c221cb7ccb Update Default Translations 2024-11-14 16:09:29 +09:00
8a51e21310 Add test 2024-11-14 14:32:53 +09:00
31b73f0d82 Fix: Boltcard would get bricked during reset from the balance view with wrong card (#6400) 2024-11-14 13:14:36 +09:00
777748d79e UI: Fix escaped HTML tags in UTXO rescan message (#6399)
Fixes #6398.
2024-11-13 21:07:30 +09:00
d0f97e85d2 UI: Allow text break in labels to avoid horizontal scrolling (#6366) 2024-11-13 21:05:40 +09:00
2eff7523c3 UI: Fix mising navigation links for store managers (#6368) 2024-11-13 21:05:23 +09:00
12d4803c5c Fix: Incorrect calculation for crowdfund and payment request status (#6381) 2024-11-13 20:59:52 +09:00
fdbee350b8 Greenfield: Create Pull Request payoutMethods is now optional (#6396) 2024-11-13 18:31:55 +09:00
78f47516b8 Fix: Pay button shouldn't throw exception if currency isn't specific (#6324) (#6395) 2024-11-13 16:25:23 +09:00
088c0c7f85 UI: Do not escape apostrophe in custom server name (#6391)
Fixes #6352.
2024-11-13 16:24:47 +09:00
809e475e29 UI: Fix close icon in create store wizard 2024-11-13 08:12:44 +01:00
8b776ed9e5 POS: Update button icons (#6390)
Adjusts the POS buttons to match what we defined for the app in btcpayserver/app#93.
2024-11-13 07:50:15 +01:00
984475b9d0 Fix: Pull payment could get stuck in Pending mode (#6259) (#6394) 2024-11-13 15:30:41 +09:00
894f68f2ae Improve error messages for on-chain related greenfield operations (#6393)
* Improve error messages for on-chain related greenfield operations

* Fix test
2024-11-13 10:11:04 +09:00
796d8d4406 Fix: Activating the automated payout processor in the UI would crash 2024-11-12 17:57:34 +09:00
7ac6cad18c Be more flexible on derivation scheme parsing 2024-11-12 15:26:30 +09:00
b3abc8f161 Fix: Newline during import of multisig xpub results in different addresses for wallet (#6328) (#6386) 2024-11-12 12:15:42 +09:00
e80296fa85 UI: Escape translated strings in JS (#6373)
Fixes #6370.
2024-11-12 11:16:56 +09:00
d0779b88e0 Fix: InvoiceCurrencyAmount and Rate columns in reports displays 0.00 (#6385) 2024-11-12 11:15:02 +09:00
acd7765159 fix: center qr code (#6362) 2024-11-12 10:43:00 +09:00
0a51031334 Docs: Improve invoice payemnt tolerance API docs (#6383)
Fixes #6378.
2024-11-12 09:59:17 +09:00
0f7c0341c5 Fix: Do not allow retry of payouts if they are non interactive (Boltcard) (#6382) 2024-11-12 09:58:10 +09:00
d40669c7bd Fix flakyness in CanUseLightningAPI test 2024-11-11 18:20:45 +01:00
fe8360e870 Remove old Altcoins folder 2024-11-11 08:01:15 +01:00
72fd2ec424 Add additional error message if payout fail with no route 2024-11-11 12:09:17 +09:00
7e7d4086cd Fix: The lightning symbol was missing in the payment stats (#6376) 2024-11-09 23:09:31 +09:00
f5d26a1555 Store: Fix missing invitation email when adding new user (#6372)
Fixes #6369, a regression introduced with#6188: Sending invite emails required a flag to be set, which defaulted to false. This fixes the code to explicitely set the flag and also defaults it to true (sending an invite email unless declared otherwise).
2024-11-09 13:55:34 +01:00
540fb6c9f6 UI: Improve brand color adjustment (#6351)
Uses a more finegrained method of deciding whether or not to adjust the brand color: Instead of simply using the brightness of the color, we calculate the contrast ratio and adjust the color by increasing the contrast until it is sufficient.

The thresholds also try to preserve the brand color in its original form, so that the color only gets adjusted if the contrast is very low.
2024-11-08 09:01:28 +01:00
12681eda36 bump 2024-11-08 16:36:27 +09:00
a05dbef337 Fix: Payouts were incorrectly marked as canceled even after successful (#6365)
completion
2024-11-08 16:13:13 +09:00
a8581510e5 improve UX description (#6129)
* Include sparrow 2fa wallet import plus improve UX description

* Add test

* Remove 2FA inclusion

* udate the btcpay network

* 2FA clean up

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2024-11-07 17:00:28 +09:00
bbba7551b7 Minor forgotten dispose in tests 2024-11-07 10:47:02 +09:00
a129114603 Greenfield: Add image upload for app items (#6226)
Upload endpoints for app item images. Follow-up to #6075.
Tested to work with the app item editor.

Uses UploadImage consistently in API and UI.
2024-11-07 10:43:22 +09:00
392ec623c0 Refactoring: Remove StoreData object from view models (#6363) 2024-11-07 08:58:47 +09:00
641bdcff31 Histograms: Add Lightning data and API endpoints (#6217)
* Histograms: Add Lightning data and API endpoints

Ported over from the mobile-working-branch.

Adds histogram data for Lightning and exposes the wallet/lightning histogram data via the API. It also add a dashboard graph for the Lightning balance.

Caveat: The Lightning histogram is calculated by using the current channel balance and going backwards through as much invoices and transactions as we have. The "start" of the LN graph data might not be accurate though. That's because we don't track (and not even have) the LN onchain data. It is calculated by using the current channel balance and going backwards through as much invoices and transactions as we have. So the historic graph data for LN is basically a best effort of trying to reconstruct it with what we have: The LN channel transactions.

* More timeframes

* Refactoring: Remove redundant WalletHistogram types

* Remove store property from dashboard tile view models

* JS error fixes
2024-11-05 21:40:37 +09:00
b3945d758a chore: add rider .run folder to .gitignore (#6358)
custom run configurations in rider are stored inside .run folder
2024-11-05 19:00:29 +09:00
4272ea97db refactor: use PaymentHash instead of Id when checking pending ln payout (#6360)
Does the same, but the `GetPayment` call explicitly wants a payment
hash, which is clearer this way (thought there might be a bug here when
I first read the code)
2024-11-05 19:00:15 +09:00
5e438b84e1 chore: rm unnecessary try catch block (#6359)
this one is redundant since the calls to `Pay` and `GetPayment` are
inside their own try catch blocks now
2024-11-05 17:54:58 +09:00
ff79a31066 Refactoring: Move AppItem to Client lib and use the class for item list (#6258)
* Refactoring: Move AppItem to Client lib and use the class for item list

This makes it available for the app, which would otherwise have to replicate the model. Also uses the proper class for the item/perk list of the app models.

* Remove unused app item payment methods property

* Do not ignore nullable values in JSON

* Revert to use Newtonsoft types
2024-11-05 11:49:30 +09:00
225264a283 Reports: Fix export (#6357)
Regression from the translation PRs, in which the proper button ID was replaced. Fixes #6356.
2024-11-04 11:50:15 +01:00
8a5a160645 bump 2024-11-04 13:12:35 +09:00
5cbadc09f9 Changelog 2.0.1 2024-11-04 13:08:57 +09:00
7aa87d397e Fix: Wrong manifest downloaded when installing plugin on old btcpay (Fix #6344) (#6354) 2024-11-04 13:05:10 +09:00
693eceb80f Reolve pull payment timezone (#6348) 2024-11-01 08:28:43 +09:00
7d8fc14159 fix: save proof blob if payout is in progress (#6343)
the payout cant be tracked later otherwise and will be marked as
cancelled
2024-11-01 08:24:21 +09:00
4687bb95cb Fix: Incorrect percentage accounting of raised money in crowdfunding (#6347) 2024-11-01 08:23:10 +09:00
e3ec07da76 Fix: Crowdfund page was crashing from 2.0.0 (#6342) (#6346) 2024-10-31 23:42:18 +09:00
910801d305 Replace font-awesome icon on Policies page 2024-10-31 12:23:30 +01:00
5ad0b128aa Dummy commit 2024-10-30 23:39:11 +09:00
5cbeea4fb3 Changelog 2.0 (#6313)
* Changelog 2.0

* Update Changelog.md

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2024-10-30 15:35:00 +01:00
a6e18736d6 Keypad updates (#6338)
* Add keypad icons

Closes #6195.

* Keypad JS fixes
2024-10-29 23:44:37 +09:00
373b90e3b5 Liquid fixes (#6340)
make sure link provider is per payment method of liquid assets. Also remove ETB as it has been unused. Also hide the send button as it is not supported thrrough BTCPay
2024-10-29 23:43:37 +09:00
92f9b226fe Prevent additional concurrency issues with LightnignPendingPayoutListener 2024-10-28 22:12:29 +09:00
0ac6553840 Add download icon 2024-10-28 08:25:30 +01:00
41a2241ae1 feat: log download button (#6330)
* feat: add download button to logs view

* fix: add using block for `fileStream` if it isnt downloaded
2024-10-27 21:43:47 +09:00
9bb1a5b80a Prevent concurrency race on lightning payout update 2024-10-27 19:55:30 +09:00
0e59107eee Fix tests with LightningPendingPayoutListener overriding automated payouts state changes 2024-10-27 19:34:20 +09:00
c9fe68b812 fix: pass current offset to log route (#6329)
the current offset is lost otherwise and will cause a 404 if it was
greater than 0
2024-10-27 19:12:39 +09:00
e7b9688602 refactor: make BitcoinCheckoutModelExtension support other payment handlers (#6311)
* refactor: make `BitcoinCheckoutModelExtension` support other payment handlers

The bitcoin checkout extension doesn't have to be tied to the native
bitcoin handler since it only really needs the payment details to be in
a specific format, which can be provided by other handlers aswell,
allowing for better code reuse.

* refactor: initialize payment methods in constructor
2024-10-25 22:50:46 +09:00
a962e60de9 More Translations (#6318)
* Store selector

* Footer

* Notifications

* Checkout Appearance

* Users list

* Forms

* Emails

* Pay Button

* Edit Dictionary

* Remove newlines, fix typos

* Forms

* Pull payments and payouts

* Various pages

* Use local docs link

* Fix

* Even more translations

* Fixes #6325

* Account pages

* Notifications

* Placeholders

* Various pages and components

* Add more
2024-10-25 22:48:53 +09:00
e5611f9165 Fix tests (#6333) 2024-10-25 22:23:27 +09:00
540ad13265 Paging improvements (#6332)
* Domain Mapping: Passthrough query params when redirecting

* Clean up Pager

* Use current URL when paging

* Refactor
2024-10-25 22:23:03 +09:00
2849426092 Checkout: Allow breaking long item description texts 2024-10-25 13:11:26 +02:00
c4a2b4e975 Merge pull request #6327 from btcpayserver/bugfix/vaulticons
Properly cleaning up old feedback in vault feedback items
2024-10-24 15:33:44 -05:00
d508f5dc09 Properly cleaning up old feedback in vault feedback items 2024-10-22 21:50:22 -05:00
81ce8b0469 Fix flaky test 2024-10-23 00:17:02 +09:00
5a3a661e91 Revert "Revert "Fix flaky test""
This reverts commit bb5c6bd68d15bab091e3835c2d51687da8a55def.
2024-10-22 23:44:16 +09:00
bb5c6bd68d Revert "Fix flaky test"
This reverts commit 9dfabeab52644ba863230be39eca7496f4377365.
2024-10-22 23:41:41 +09:00
9dfabeab52 Fix flaky test 2024-10-22 23:36:12 +09:00
ad07330bf1 Update CLN support to 24.08.2 (#6323) 2024-10-21 00:25:50 +09:00
74011e50e3 Do not translate checkout with the backend language 2024-10-20 11:49:36 +09:00
3dfdbf544a Automated processor get disabled after some repeated failures (#6320) 2024-10-20 00:08:28 +09:00
4bf0b79c2a Simple rename 2024-10-19 22:07:20 +09:00
cc0ea0b3f8 Refactor payouts processing (#6314) 2024-10-19 21:33:34 +09:00
62d765125d Toggle color fix 2024-10-18 15:14:30 +02:00
b5b45d9a27 Rename Transaction->Translation 2024-10-18 16:06:51 +09:00
8e098710c1 Require non interactivity for boltcard payments (#6289) 2024-10-18 14:09:41 +09:00
6dfb369b55 UI: Inactive toggle hover color fix (#6299) 2024-10-18 14:08:34 +09:00
817522ff97 refactor(checkout): displayed payment methods vue component (#6316)
The displayed payment methods can change with updates aswell, so it
should be rendered as a vue component instead
2024-10-18 14:05:00 +09:00
8b5b90d247 refactor: make BeforeFetchingRates function public (#6310)
This function is useful when a payment method wants to update its
payment prompt after receiving a partial payment, like ln does
2024-10-18 14:03:37 +09:00
b670097592 feat: provide store info to modify-lnurlp-request filter (#6312)
adds store data to the filter using a new `StoreLNURLPayRequest` class
which simply adds a `Store` member.

closes: https://github.com/btcpayserver/btcpayserver/issues/6301
2024-10-18 14:03:07 +09:00
7b6a115adc [Greenfield] Select default payoutMethodId if none are selected in the Refund route (#6315) 2024-10-17 22:54:59 +09:00
77fba4aee3 Add more translations (#6302)
* Newlines

* Dashboard

* Add more translations

* Moar

* Remove &nbsp; from translated texts

* Dictionary controller translations

* Batch 1 of controller updates

* Batch 2 of controller updates

* Component translations

* Batch 3 of controller updates

* Fixes
2024-10-17 22:51:40 +09:00
7e1712c8cd Merge pull request #6309 from NicolasDorier/fixmonero
Fix monero payments
2024-10-17 20:40:05 +09:00
b7affb1d34 Fix monero payments 2024-10-17 18:55:00 +09:00
d7fd90c4c3 Bump client lib 2024-10-17 16:41:36 +09:00
1d94782463 Merge pull request #6305 from NicolasDorier/fixelements
Fix elements payments
2024-10-16 22:34:32 +09:00
c7a05c3f09 Fix elements payments 2024-10-16 22:34:17 +09:00
2dc58a82b7 Item editor: Do not use only known props for diff (#6307)
Brooke the file seller plugin which adds a property and this did not detect the item change
2024-10-16 14:25:06 +02:00
755dbbab00 fix server nav ui extensin (#6306) 2024-10-16 14:24:56 +02:00
b470fe22f1 Remove potential NRE 2024-10-16 17:04:27 +09:00
5b2560ddf7 Merge pull request #6304 from NicolasDorier/cleanupetw
Cleanup useless code
2024-10-16 16:38:15 +09:00
be429c527c Cleanup useless code 2024-10-16 16:25:16 +09:00
65fd537200 Merge pull request #6303 from btcpayserver/feat/lnd-0.18.3
Bumping LND to 0.18.3-beta
2024-10-15 18:49:41 -05:00
6e43c7f06f Bumping LND to 0.18.3-beta 2024-10-15 18:21:26 -05:00
5867b5c000 Merge pull request #6297 from NicolasDorier/fioqnt
Pretty names of payment methods isn't provided by CheckoutExtensions
2024-10-15 23:28:42 +09:00
05887cf8b0 Fix potential stack overflow 2024-10-15 23:11:28 +09:00
c43721d489 Pretty names of payment methods isn't provided by CheckoutExtensions 2024-10-14 21:53:14 +09:00
0bf75d52d7 Merge pull request #6292 from NicolasDorier/addtranslations
Add translations to the Dashboard and more
2024-10-14 19:45:06 +09:00
c35af2dc69 Add translations to the Dashboard 2024-10-14 19:19:56 +09:00
73a9835a27 Fix build warning 2024-10-13 00:17:49 +09:00
87ab15f754 Fix crash on Monero/ZCash on invoices list 2024-10-13 00:10:49 +09:00
6bc608c081 Merge pull request #6287 from btcpayserver/feat/plugin-search
Support for searching plugins by name
2024-10-11 08:05:27 -05:00
cbea1d8691 Merge pull request #6291 from NicolasDorier/markedfordeletion
Improve UX for uninstalling disabled plugins
2024-10-11 21:41:01 +09:00
58f21a69aa Improve UX for uninstalling disabled plugins 2024-10-11 19:35:37 +09:00
426c5b9a24 Merge pull request #6290 from NicolasDorier/plugincrashdetect
Disable plugins crashing at startup
2024-10-11 10:56:46 +09:00
511e90efd1 Disable plugins crashing at startup 2024-10-11 10:50:49 +09:00
bc7b856654 Start sending BTCPay version string to help with filtering on plugin-builder side 2024-10-10 05:46:07 -05:00
d50d2f9ca0 Merge pull request #6288 from NicolasDorier/bettershowwarnings
Show warnings if NFC payment isn't complete
2024-10-10 19:23:42 +09:00
1b53defab3 Show warnings if NFC payment isn't complete 2024-10-10 19:16:09 +09:00
3e612921f3 Remove flaky test 2024-10-10 17:50:18 +09:00
ec51d43490 Improve error message if LNWithdraw fails 2024-10-10 17:24:19 +09:00
80dc5028f7 Fix migration crashes for instance having monero, zcash 2024-10-10 11:13:23 +09:00
2329c4a75f Support for searching plugins by name 2024-10-09 06:47:11 -05:00
ae76cc1ca2 Small UI improvements in the payout processors 2024-10-09 17:44:19 +09:00
e4f79f046a Remove unused field from automated payout settings 2024-10-09 13:13:10 +09:00
622d837ea1 Merge pull request #6286 from NicolasDorier/efwoiiqnf
Prevent double BOLT11 payment with LNUrlWithdraw
2024-10-09 13:10:17 +09:00
c0aa9a8bd4 Prevent double BOLT11 payment with LNUrlWithdraw 2024-10-09 13:10:04 +09:00
9b1052f023 Remove useless code 2024-10-08 21:25:37 +09:00
c77c2f8bd6 Merge pull request #6284 from btcpayserver/addfontawesome
Add fontawesome back
2024-10-08 21:19:43 +09:00
402eaa8f12 Add fontawesome back 2024-10-08 21:17:02 +09:00
212e8c3654 Fix potential crash in migration 2024-10-08 16:48:56 +09:00
7c77b16517 Fix potential crash on migration 2024-10-08 16:30:21 +09:00
e5bb0bcba3 Fix forgotten save 2024-10-08 16:25:11 +09:00
ca4a7d8771 Migrate excludedPaymentMethods from stores 2024-10-08 16:21:44 +09:00
663f97265a Merge pull request #6283 from NicolasDorier/activationbug
Fix: An unactivated payment method failing to activate would crash the checkout
2024-10-08 15:50:28 +09:00
b91f3048ef Fix: An unactivated payment method failing to activate would crash the checkout 2024-10-08 15:07:32 +09:00
4fe0bf1236 Merge pull request #6282 from NicolasDorier/refactorcheckout
Allow payment methods to modify all the payment model
2024-10-07 21:57:00 +09:00
dd35af3c55 Use AddUIExtension 2024-10-07 21:43:06 +09:00
68f24e47cd Rename more legacy fields 2024-10-07 21:22:03 +09:00
968223a953 Rename PaymentModel to CheckoutModel 2024-10-07 19:58:08 +09:00
2f287874e3 Rename legacy fields 2024-10-07 19:51:50 +09:00
c35e7406cd Cleanup AvailableCrypto from the model 2024-10-07 19:15:40 +09:00
34b2cca492 Simplify extension of payments extensions 2024-10-07 18:37:38 +09:00
e1bfc04451 Move checkout registration to the UI Extension 2024-10-07 17:38:02 +09:00
ef0ba7b0c4 Remove useless properties 2024-10-07 16:18:09 +09:00
0a2d8880ba Remove CheckoutBodyVueComponentName 2024-10-07 15:20:26 +09:00
8dcd7e6966 Remove no javascript for checkout 2024-10-07 15:18:41 +09:00
b744fd6167 Allow payment methods to modify all the payment model 2024-10-07 14:53:21 +09:00
5bcc5c919a Improve logging of rates in invoices (#6281) 2024-10-07 09:38:09 +09:00
01e12329e9 Remove additional cryptoCode from events (#6277) 2024-10-07 09:37:56 +09:00
471bf57835 Merge branch 'master' of github.com:btcpayserver/btcpayserver 2024-10-04 23:34:52 +09:00
b246beab3e Add the concept of RateDivisibility (#6278) 2024-10-04 23:34:31 +09:00
abc8161a08 Add the concept of RateDivisibility 2024-10-04 23:14:38 +09:00
64ba8248d2 Can inject currency data in CurrencyNameTable (#6276) 2024-10-04 22:24:44 +09:00
206d222455 Fix missing interpolation marker 2024-10-04 15:18:06 +02:00
2e114d7c29 Remove references to cryptoCode in SyncStatus (#6275) 2024-10-04 16:58:31 +09:00
5190c25be0 OnlyIfSupportAttribute should use PaymentMethodId (#6274) 2024-10-04 16:58:24 +09:00
5704919b3a BlockExplorer links should be using payment method ids (#6273) 2024-10-04 16:58:13 +09:00
c3e51f51b6 Fix warnings 2024-10-03 21:51:07 +09:00
8c35edb6e8 UI: Additional improvements to the User Invitation flow (#6233)
* UI: Additional improvements to the User Invitation flow

Closes #6224.

* Clear invitation token only after the user can sign in

Fixes "404 Error on Follow-Up Visits" of #6236.

* Minor spacing fix

* Update accordion button
2024-10-03 21:35:01 +09:00
2f2b4094f6 [UI] Do not show unabled payment methods in invoice creation (#6272) 2024-10-03 21:34:09 +09:00
413a9b4269 Add translation for store rate and wallet setup (#6271) 2024-10-03 19:21:19 +09:00
a698aa8a5b Do not crash if payment method disabled when store supports it 2024-10-03 19:21:01 +09:00
0f79526566 Do not make the test framework depends on CurrentDirectory 2024-10-03 16:04:16 +09:00
1ffbab7338 Small improvements to make development of plugins easier (#6270) 2024-10-03 15:16:21 +09:00
3a71c45a89 Add updated image upload support on Crowdfund plugin (#6254)
* Add updated image upload support on Crowdfund plugin

* Refactor crowdfund image upload fix

* update crowdfund url for greenfield api

* Resolve integration test assertion

* Remove superfluous and unused command argument

* Fix missing validation error

* Minor API controller update

* Property and usage fixes

* Fix test after merge

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2024-10-03 10:39:41 +09:00
8f062f918b Add comments 2024-10-03 10:35:47 +09:00
2f05d00219 V2 compatibility: Re-add deprecated navigation methods (#6267)
Gives the new methods a new name and re-adds the old ones in order to not break plugins. Simple enough backwartds compatible change, makred the old methods as obsolete to make plugin developers aware that new methods are available.
2024-10-02 08:25:43 +09:00
b48ca92675 Fix app stats sorting (#6265) 2024-10-02 08:23:25 +09:00
4a31cf0a09 Migrate payment requests (#6260) 2024-10-01 16:07:51 +09:00
82620ee327 Move wallet payment settings back to store settings (#6251)
Intermediate solution, until we implement these settings on the payment method level. Closes #6237.
2024-09-30 19:13:51 +09:00
6d284b4124 Give time for pollers to detect payments after server restart 2024-09-27 15:48:16 +09:00
83fa8cbf0f prevent app creation without wallet creation (#6255)
* prevent app creation without wallet creation

* resolve test failures

* resolve selenium test
2024-09-27 15:28:55 +09:00
9ba4b030ed Fix: Do not expose xpub without modify store permission (#6212) 2024-09-27 15:27:04 +09:00
272cc3d3c9 POS: Option for user sign in via the QR code (#6231)
* Login Code: Turn into Blazor component and extend with data for the app

* POS: Add login code for POS frontend

* Improve components, fix test
2024-09-26 19:10:14 +09:00
b5590a38fe Add better error message if v1 routes are used. 2024-09-26 19:09:27 +09:00
443a350bad App Service: Validate IDs when parsing items template (#6228)
Validates missing and duplicate IDs on the edit actions and when creating/updating apps via the API.
Fails gracefully by excluding existing items without ID or with duplicate ID for the rest of the cases.

Fixes #6227.
2024-09-26 15:52:16 +09:00
7013e618de Remove dead fields from swagger 2024-09-26 12:23:41 +09:00
363b60385b Renaming various properties in the Payouts API (#6246)
* Rename Payouts Currency/OriginalCurrency

* Rename Payout Processor PayoutMethodIds

* Rename paymentMethods to payoutMethodIds

* Rename payoutMethodIds to payoutMethods
2024-09-26 11:25:45 +09:00
90635ffc4e Remove BTCPAY_EXPERIMENTALV2_CONFIRM 2024-09-25 23:11:53 +09:00
056f850268 Optimize load time of StoreRoles related pages/routes (#6245) 2024-09-25 23:10:13 +09:00
336f2d88e9 Fix flaky CanManageWallet
Clicking on Sign Transaction in the Wallet Send page, will, when a hot
wallet is setup, use PostRedirect page to redirect to the
broadcast screen. The problem was that sometimes, s.Driver.PageSource
would return this PostRedirect page rather than the broadcast page.
Waiting for an element of the broadcast page fixes this issue.
2024-09-25 21:53:15 +09:00
e16b4062b5 Fix payout processor migration 2024-09-25 18:50:49 +09:00
747dacf3b1 Consolidate migrations from alpha (#6244) 2024-09-25 18:23:10 +09:00
f00a71922f Optimize queries from payout processor at startup 2024-09-24 23:39:05 +09:00
c97c9d4ece Add SQL test for GetMonitoredInvoices 2024-09-24 22:07:02 +09:00
9d3f8672d9 Fix GetMonitoredInvoices 2024-09-24 17:21:36 +09:00
fe48cd4236 fix InvoiceRepository.GetMonitoredInvoices (#6243) 2024-09-24 15:44:51 +09:00
587d3aa612 Fix query 2024-09-24 09:52:28 +09:00
8a951940fd Remove dead property 2024-09-24 09:47:46 +09:00
b726ef8a2e Migrate PayoutProcessors's PayoutMethodId in entity migration 2024-09-24 09:43:02 +09:00
25e360e175 Allow listeners to retrieve invoices with nonActivated prompts 2024-09-24 08:43:30 +09:00
1d9ec253fb Fix migration of Invoice's payment (#6241) 2024-09-23 23:59:18 +09:00
3cf1aa00fa Payments should use composite key (#6240)
* Payments should use composite key

* Invert PK for InvoiceAddress
2024-09-23 17:06:56 +09:00
36a5d0ee3f Fix: Monero and ZCash not tracking addresses 2024-09-22 11:13:09 +09:00
f5e5174045 Refactor: Add GetMonitoredInvoices to fetch pending invoices or those with pending payments (#6235) 2024-09-20 18:54:36 +09:00
ba2301ebfe Refactor the InvoiceAddresses table (#6232) 2024-09-19 22:15:02 +09:00
df651a2157 Fix GetInvoicesWithPendingPayments 2024-09-18 18:11:30 +09:00
2d2c1d5f2d fix: check lightning payment status (#6219) 2024-09-17 21:41:04 +09:00
0f93581ff5 Refactor confirmation count tracking (#6215) 2024-09-17 17:28:58 +09:00
397452a7fe Setting standard 150x100 size for Unbank logo 2024-09-16 16:38:04 -05:00
cd3157361a Adding Unbank as BTCPay Server Foundation Supporter (#6225) 2024-09-16 16:33:11 -05:00
29a89f185a Fix: Not able to change SpeedPolicy of a store 2024-09-13 22:59:14 +09:00
2f7a5c2967 Wallet: Generate receive address automatically (#6122)
* Wallet: Generate receive address automatically

This circumvents landing on a blank page with only the "generate address" button and automatically generates a new address, unless the Unreserve action was used.

* Fix close button leading to same page

* Fix tests

* Remove unreserve feature

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2024-09-13 22:03:45 +09:00
f07ed53f7e Handle password reset when SMTP isn't configured or validated (#6150)
* Handle password reset when SMTP isn't configured or the configuration cannot be validated

* include rel in external a tag

* Simplify it

* Test fix

* Simplify a bit

* selenium test to manage users

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2024-09-13 21:42:08 +09:00
7348a6a62f Store Branding: Apply brand color to backend as well (#5992)
* Store Branding: Apply brand color to backend as well

Closes #5990.

* Add adjustments for different theme scenarios

* Add description text

* Make it optional to apply the brand color to the backend

* Toggle color fixes
2024-09-13 21:39:21 +09:00
b7ba53eb60 UI: Fix for truncate center component (#6213) 2024-09-13 17:55:31 +09:00
e389d6a96b UI: Minor fix on policies page 2024-09-12 18:43:52 +02:00
0238dffc7a POS: Fix accounting for manually entered keypad amounts (#6178)
* POS: Fix accounting for manually entered keypad amounts

For keypad orders where there are products AND manual amount entries, we didn't account for the latter.

Fixes #6168.

* Adjust wording: "Manual entry" becomes "Custom Amount"
2024-09-12 21:36:35 +09:00
666445e8f7 Greenfield: App endpoints for sales statistics (#6103) 2024-09-12 16:17:16 +09:00
36bada8feb Uniformize Wallet API's path (#6209)
* Uniformize Wallet API's path

* Rewrite old API path to new API

* Rename routes
2024-09-12 15:19:10 +09:00
b4946f4db1 Fix divisibility in invoice details of lightning amounts (#6202)
* Fix divisibility in invoice details of lightning amounts

This PR will show 11 decimal in the invoice details for BTC amount
of lightning payment methods.

It also hacks around the fact that some
lightning clients don't create the requested amount of sats, which
resulted in over or under payments. (Blink not supporting msats, and
strike)

Now, In that case, a payment method fee (which can be negative) called tweak fee
will be added to the prompt.

We are also hiding this tweak fee from the user in the checkout page in
order to not disturb the UI with inconsequential fee of 0.000000001 sats.

* Only show 8 digits in checkout, even if amount is 11 digits
2024-09-12 12:43:08 +09:00
f3d485da53 Invitation process improvements (#6188)
* Server: Make sending email optional when adding user

Closes #6158.

* Generate custom invite token and store it in user blob

Closes btcpayserver/app/#46.

* QR code for user invite

Closes #6157.

* Text fix
2024-09-12 12:31:57 +09:00
3342122be2 Make Role Permissions more human legible (#6191)
Had to rename `CanModifyStoreWebhooks` to `CanModifyWebhooks` for this, but the value stayed the same, so I don't think it's a big deal.

Closes #6183.
2024-09-12 12:29:10 +09:00
f59751853a Fx pos data additional info (#6172)
* Resolve Additional Information from posData

* code formatting

* Minor adjustments

* Update ChromeDriver

* Revert and improve PosData partial

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2024-09-12 12:27:02 +09:00
a60c55c6df Transactions: Improve TX ID display (#6190)
* Transactions: Improve TX ID display

* Elastic fix

* Test and behaviour fix
2024-09-12 10:08:16 +09:00
3bad5883bb Permissions: Remove deprecated custodian account policies (#6193)
Updates the store owner role and removes these three deprecated policies:

- `btcpay.store.cantradecustodianaccount`
- `btcpay.store.canwithdrawfromcustodianaccount`
- `btcpay.store.candeposittocustodianaccount`
2024-09-12 10:02:37 +09:00
d4c30866b7 Fix timeout issues during migration (#6208) 2024-09-10 20:11:07 +09:00
7c92ce771f Reindex Invoices table if corrupt, fix migration timeout (#6207) 2024-09-10 17:34:02 +09:00
4601359ebe Do not block the Lightning PendingPayoutListener if a client is failing 2024-09-10 17:33:36 +09:00
04b1130837 Fix tests 2024-09-09 23:39:59 +09:00
c377617b5a Fix migration issue on invalid json char 2024-09-09 23:15:53 +09:00
222e8f66df Fix entity half migrated entities for v2 2024-09-09 21:21:04 +09:00
87e2f5f414 Add migration logs 2024-09-09 19:03:07 +09:00
7de05700e9 Edit dictionary should be in JSON format (#6203) 2024-09-09 11:25:36 +09:00
841f41da2f Invoice: Improve zero amount invoice handling (#6199)
This is for the checkout page to properly redirect paid invoices with no payment methods (e.g. free invoices with zero amount) to either the receipt page or redirect URL. Only fall back to 404 if there is neither.

Fixes #6123.
2024-09-09 11:05:03 +09:00
73dcde7780 UI: Create Store CTA should be full-width
Closes btcpayserver/app#92
2024-09-06 13:23:52 +02:00
377ff52222 Fix: Incorrect rounding for lightning amounts (#6201) 2024-09-06 18:48:20 +09:00
e4b5609d78 Fix doc 2024-09-06 13:27:12 +09:00
c93497af10 Rename PaymentMethod => PaymentMethodId (#6198)
* Rename PaymentMethod => PaymentMethodId

* Rename DB Columns
2024-09-06 13:24:33 +09:00
99dda66bbc Rename PayoutData.Destination (#6197) 2024-09-06 10:34:10 +09:00
4ce68f817d UI: Spacing fixes for Policies page 2024-09-02 12:06:34 +02:00
1c027be106 Greenfield: Can set the label when generating a wallet for store (#6186) 2024-09-02 18:54:15 +09:00
4a94074595 Add topups to payouts (#6187) 2024-09-02 18:37:39 +09:00
b49f6c3f86 Checkout fixes and improvements (#6181)
* Adjust to new payment method IDs

* Sound improvements

* NFC: Handle no data/empty tag case

Fixes #6154.
2024-09-02 18:20:29 +09:00
9c8fe9277d Fix route's param in doc 2024-09-02 16:27:49 +09:00
a7c70e6912 Add missing doc for scriptPubKeyType TaprootBIP86 2024-09-02 10:35:06 +09:00
28156a5a6b Include hover to display total sales per day (#6174)
* Include hover to display total sales per day

* Add label to top items as well

* Bring in recent Chart modifications from app

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2024-08-30 09:58:50 +02:00
a3cfb9e5e4 Removal of the Altcoins build (#6177)
* Remove some useless #if ALTCOINS

* Removal of the Altcoins build
2024-08-30 08:34:23 +09:00
25ccc6a9f9 Update notificaiton counter record (#6169) 2024-08-29 12:47:29 +02:00
8d1904c185 Sales report dashboard issue #6167 - querySelectorAll returns multiple elements that leads to not updateding the reuired element innerHtml directy, so i change querySelector that will directly update our .sales-count element innerHtml (#6176)
Co-authored-by: Umar Farooq <umar.u.farooq@constellationdealer.com>
2024-08-29 11:30:34 +02:00
798 changed files with 24375 additions and 10846 deletions

View File

@ -2,7 +2,7 @@ version: 2
jobs:
fast_tests:
machine:
image: ubuntu-2004:202111-02
image: ubuntu-2004:2024.11.1
steps:
- checkout
- run:
@ -10,7 +10,7 @@ jobs:
cd .circleci && ./run-tests.sh "Fast=Fast|ThirdParty=ThirdParty" && ./can-build.sh
selenium_tests:
machine:
image: ubuntu-2004:202111-02
image: ubuntu-2004:2024.11.1
steps:
- checkout
- run:
@ -18,7 +18,7 @@ jobs:
cd .circleci && ./run-tests.sh "Selenium=Selenium"
integration_tests:
machine:
image: ubuntu-2004:202111-02
image: ubuntu-2004:2024.11.1
steps:
- checkout
- run:
@ -26,7 +26,7 @@ jobs:
cd .circleci && ./run-tests.sh "Integration=Integration"
trigger_docs_build:
machine:
image: ubuntu-2004:202111-02
image: ubuntu-2004:2024.11.1
steps:
- run:
command: |

View File

@ -2,8 +2,8 @@
set -e
cd ../BTCPayServer.Tests
docker-compose -v
docker-compose -f "docker-compose.altcoins.yml" down --v
docker-compose --version
docker-compose -f "docker-compose.altcoins.yml" down -v
# For some reason, docker-compose pull fails time to time, so we try several times
n=0

1
.gitignore vendored
View File

@ -266,6 +266,7 @@ paket-files/
# JetBrains Rider
.idea/
*.sln.iml
.run
# CodeRush
.cr/

View File

@ -32,8 +32,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="8.0.838" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />

View File

@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using BTCPayServer.Abstractions.Models;
namespace BTCPayServer.Abstractions.Contracts;
@ -14,4 +15,5 @@ public interface IFileService
Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
bool isDownload);
Task RemoveFile(string fileId, string userId);
Task<UploadImageResultModel> UploadImage(IFormFile file, string userId, long maxFileSizeInBytes = 1_000_000);
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer.Abstractions.Contracts
public interface ISyncStatus
{
public string CryptoCode { get; set; }
public string PaymentMethodId { get; set; }
public bool Available { get; }
}
}

View File

@ -8,6 +8,14 @@ namespace BTCPayServer.Abstractions.Extensions;
public static class SetStatusMessageModelExtensions
{
public static void SetStatusSuccess(this ITempDataDictionary tempData, string statusMessage)
{
tempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = statusMessage
});
}
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
{
if (statusMessage == null)
@ -26,19 +34,14 @@ public static class SetStatusMessageModelExtensions
tempData.TryGetValue("StatusMessageModel", out var model);
if (successMessage != null || errorMessage != null)
{
var parsedModel = new StatusMessageModel();
parsedModel.Message = (string)successMessage ?? (string)errorMessage;
if (successMessage != null)
var parsedModel = new StatusMessageModel
{
parsedModel.Severity = StatusMessageModel.StatusSeverity.Success;
}
else
{
parsedModel.Severity = StatusMessageModel.StatusSeverity.Error;
}
Message = (string)successMessage ?? (string)errorMessage,
Severity = successMessage != null ? StatusMessageModel.StatusSeverity.Success : StatusMessageModel.StatusSeverity.Error
};
return parsedModel;
}
else if (model != null && model is string str)
if (model is string str)
{
return JObject.Parse(str).ToObject<StatusMessageModel>();
}

View File

@ -55,7 +55,7 @@ namespace BTCPayServer.Abstractions.Extensions
viewData[ACTIVE_CATEGORY_KEY] = activeCategory;
}
public static bool IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null)
public static bool IsCategoryActive(this ViewDataDictionary viewData, string category, object id = null)
{
if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY)) return false;
var activeId = viewData[ACTIVE_ID_KEY];
@ -65,12 +65,12 @@ namespace BTCPayServer.Abstractions.Extensions
return categoryMatch && idMatch;
}
public static bool IsActiveCategory<T>(this ViewDataDictionary viewData, T category, object id = null)
public static bool IsCategoryActive<T>(this ViewDataDictionary viewData, T category, object id = null)
{
return IsActiveCategory(viewData, category.ToString(), id);
return IsCategoryActive(viewData, category.ToString(), id);
}
public static bool IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null)
public static bool IsPageActive(this ViewDataDictionary viewData, string page, string category, object id = null)
{
if (!viewData.ContainsKey(ACTIVE_PAGE_KEY)) return false;
var activeId = viewData[ACTIVE_ID_KEY];
@ -82,7 +82,7 @@ namespace BTCPayServer.Abstractions.Extensions
return categoryAndPageMatch && idMatch;
}
public static bool IsActivePage<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null)
public static bool IsPageActive<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null)
where T : IConvertible
{
return pages.Any(page => ActivePageClass(viewData, page.ToString(), page.GetType().ToString(), id) == ACTIVE_CLASS);
@ -95,7 +95,7 @@ namespace BTCPayServer.Abstractions.Extensions
public static string ActiveCategoryClass(this ViewDataDictionary viewData, string category, object id = null)
{
return IsActiveCategory(viewData, category, id) ? ACTIVE_CLASS : null;
return IsCategoryActive(viewData, category, id) ? ACTIVE_CLASS : null;
}
public static string ActivePageClass<T>(this ViewDataDictionary viewData, T page, object id = null)
@ -106,12 +106,42 @@ namespace BTCPayServer.Abstractions.Extensions
public static string ActivePageClass(this ViewDataDictionary viewData, string page, string category, object id = null)
{
return IsActivePage(viewData, page, category, id) ? ACTIVE_CLASS : null;
return IsPageActive(viewData, page, category, id) ? ACTIVE_CLASS : null;
}
public static string ActivePageClass<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null) where T : IConvertible
{
return IsActivePage(viewData, pages, id) ? ACTIVE_CLASS : null;
return IsPageActive(viewData, pages, id) ? ACTIVE_CLASS : null;
}
[Obsolete("Use ActiveCategoryClass instead")]
public static string IsActiveCategory<T>(this ViewDataDictionary viewData, T category, object id = null)
{
return ActiveCategoryClass(viewData, category, id);
}
[Obsolete("Use ActiveCategoryClass instead")]
public static string IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null)
{
return ActiveCategoryClass(viewData, category, id);
}
[Obsolete("Use ActivePageClass instead")]
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null) where T : IConvertible
{
return ActivePageClass(viewData, page, id);
}
[Obsolete("Use ActivePageClass instead")]
public static string IsActivePage<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null) where T : IConvertible
{
return ActivePageClass(viewData, pages, id);
}
[Obsolete("Use ActivePageClass instead")]
public static string IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null)
{
return ActivePageClass(viewData, page, category, id);
}
public static HtmlString ToBrowserDate(this DateTimeOffset date, string netFormat, string jsDateFormat = "short", string jsTimeFormat = "short")

View File

@ -14,14 +14,6 @@ namespace BTCPayServer.Abstractions.Models
public string SeverityCSS => ToString(Severity);
private void ParseNonJsonStatus(string s)
{
Message = s;
Severity = s.StartsWith("Error", StringComparison.InvariantCultureIgnoreCase)
? StatusSeverity.Error
: StatusSeverity.Success;
}
public static string ToString(StatusSeverity severity)
{
switch (severity)

View File

@ -0,0 +1,16 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Abstractions.Models;
public class UploadImageResultModel
{
public bool Success { get; set; }
public string Response { get; set; } = string.Empty;
public IStoredFile? StoredFile { get; set; }
}

View File

@ -1,9 +1,11 @@
using System;
using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Abstractions.Services
{
public class UIExtension : IUIExtension
{
[Obsolete("Use extension method BTCPayServer.Extensions.AddUIExtension(this IServiceCollection services, string location, string partialViewName) instead")]
public UIExtension(string partial, string location)
{
Partial = partial;

View File

@ -0,0 +1,139 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Lightning;
using NBitcoin;
namespace BTCPayServer.Client.App;
//methods available on the hub in the client
public interface IBTCPayAppHubClient
{
Task NotifyServerEvent(ServerEvent ev);
Task NotifyNetwork(string network);
Task NotifyServerNode(string nodeInfo);
Task TransactionDetected(TransactionDetectedRequest request);
Task NewBlock(string block);
Task StartListen(string key);
Task<LightningInvoice> CreateInvoice(string key, CreateLightningInvoiceRequest createLightningInvoiceRequest);
Task<LightningInvoice?> GetLightningInvoice(string key, uint256 paymentHash);
Task<LightningPayment?> GetLightningPayment(string key, uint256 paymentHash);
Task CancelInvoice(string key, uint256 paymentHash);
Task<List<LightningPayment>> GetLightningPayments(string key, ListPaymentsParams request);
Task<List<LightningInvoice>> GetLightningInvoices(string key, ListInvoicesParams request);
Task<PayResponse> PayInvoice(string key, string bolt11, long? amountMilliSatoshi);
Task MasterUpdated(long? deviceIdentifier);
Task<LightningNodeInformation> GetLightningNodeInfo(string key);
Task<LightningNodeBalance> GetLightningBalance(string key);
}
//methods available on the hub in the server
public interface IBTCPayAppHubServer
{
Task<bool> DeviceMasterSignal(long deviceIdentifier, bool active);
Task<Dictionary<string,string>> Pair(PairRequest request);
Task<AppHandshakeResponse> Handshake(AppHandshake request);
Task<bool> BroadcastTransaction(string tx);
Task<decimal> GetFeeRate(int blockTarget);
Task<BestBlockResponse> GetBestBlock();
Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string identifier, string[] txIds, string[] outpoints);
Task<string> DeriveScript(string identifier);
Task TrackScripts(string identifier, string[] scripts);
Task<string> UpdatePsbt(string[] identifiers, string psbt);
Task<CoinResponse[]> GetUTXOs(string[] identifiers);
Task<Dictionary<string, TxResp[]>> GetTransactions(string[] identifiers);
Task SendInvoiceUpdate(LightningInvoice lightningInvoice);
Task<long?> GetCurrentMaster();
}
public class ServerEvent
{
public string? Type { get; set; }
public string? StoreId { get; set; }
public string? UserId { get; set; }
public string? AppId { get; set; }
public string? InvoiceId { get; set; }
public string? Detail { get; set; }
}
public record TxResp
{
public TxResp(long confirmations, long? height, decimal balanceChange, DateTimeOffset timestamp, string transactionId)
{
Confirmations = confirmations;
Height = height;
BalanceChange = balanceChange;
Timestamp = timestamp;
TransactionId = transactionId;
}
public long Confirmations { get; set; }
public long? Height { get; set; }
public decimal BalanceChange { get; set; }
public DateTimeOffset Timestamp { get; set; }
public string TransactionId { get; set; }
public override string ToString()
{
return $"{{ Confirmations = {Confirmations}, Height = {Height}, BalanceChange = {BalanceChange}, Timestamp = {Timestamp}, TransactionId = {TransactionId} }}";
}
}
public class TransactionDetectedRequest
{
public string? Identifier { get; set; }
public string? TxId { get; set; }
public string[]? SpentScripts { get; set; }
public string[]? ReceivedScripts { get; set; }
public bool Confirmed { get; set; }
}
public class CoinResponse
{
public string? Identifier{ get; set; }
public bool Confirmed { get; set; }
public string? Script { get; set; }
public string? Outpoint { get; set; }
public decimal Value { get; set; }
public string? Path { get; set; }
}
public class TxInfoResponse
{
public Dictionary<string,TransactionResponse>? Txs { get; set; }
public Dictionary<string,string>? BlockHeaders { get; set; }
public Dictionary<string,int>? BlockHeghts { get; set; }
}
public class TransactionResponse
{
public string? BlockHash { get; set; }
public string? Transaction { get; set; }
}
public class BestBlockResponse
{
public string? BlockHash { get; set; }
public int BlockHeight { get; set; }
public string? BlockHeader { get; set; }
}
public class AppHandshake
{
public string[]? Identifiers { get; set; }
}
public class AppHandshakeResponse
{
//response about identifiers being tracked successfully
public string[]? IdentifiersAcknowledged { get; set; }
}
public class PairRequest
{
public Dictionary<string, string?> Derivations { get; set; } = new();
}

View File

@ -0,0 +1,8 @@
#nullable enable
namespace BTCPayServer.Client.App.Models;
public class AcceptInviteRequest
{
public string? UserId { get; set; }
public string? Code { get; set; }
}

View File

@ -0,0 +1,10 @@
#nullable enable
namespace BTCPayServer.Client.App.Models;
public class AcceptInviteResult
{
public string? Email { get; set; }
public bool? RequiresUserApproval { get; set; }
public bool? EmailHasBeenConfirmed { get; set; }
public string? PasswordSetCode { get; set; }
}

View File

@ -0,0 +1,10 @@
using System;
namespace BTCPayServer.Client.App.Models;
public class AccessTokenResult
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTimeOffset Expiry { get; set; }
}

View File

@ -0,0 +1,13 @@
#nullable enable
namespace BTCPayServer.Client.App.Models;
public class AppInstanceInfo
{
public string BaseUrl { get; set; } = null!;
public string ServerName { get; set; } = null!;
public string? ContactUrl { get; set; }
public string? LogoUrl { get; set; }
public string? CustomThemeCssUrl { get; set; }
public string? CustomThemeExtension { get; set; }
public bool RegistrationEnabled { get; set; }
}

View File

@ -0,0 +1,44 @@
#nullable enable
using System.Collections.Generic;
namespace BTCPayServer.Client.App.Models;
public class AppUserInfo
{
public string? UserId { get; set; }
public string? Email { get; set; }
public string? Name { get; set; }
public string? ImageUrl { get; set; }
public IEnumerable<string>? Roles { get; set; }
public IEnumerable<AppUserStoreInfo>? Stores { get; set; }
public void SetInfo(string email, string? name, string? imageUrl)
{
Email = email;
Name = name;
ImageUrl = imageUrl;
}
public static bool Equals(AppUserInfo? x, AppUserInfo? y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return x.UserId == y.UserId && x.Email == y.Email &&
x.Name == y.Name && x.ImageUrl == y.ImageUrl &&
Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores);
}
}
public class AppUserStoreInfo
{
public string Id { get; set; } = null!;
public string? Name { get; set; }
public string? LogoUrl { get; set; }
public string? RoleId { get; set; }
public string? PosAppId { get; set; }
public string? DefaultCurrency { get; set; }
public bool Archived { get; set; }
public IEnumerable<string>? Permissions { get; set; }
}

View File

@ -0,0 +1,11 @@
#nullable enable
using System.Collections.Generic;
namespace BTCPayServer.Client.App.Models;
public class CreateStoreData
{
public string? DefaultCurrency { get; set; }
public string? RecommendedExchangeId { get; set; }
public Dictionary<string, string>? Exchanges { get; set; }
}

View File

@ -12,11 +12,11 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Configurations>Debug;Release;Altcoins-Debug;Altcoins-Release</Configurations>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(Version)' == '' ">1.7.4</Version>
<Version Condition=" '$(Version)' == '' ">2.0.1</Version>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@ -30,8 +30,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.1" />
<PackageReference Include="NBitcoin" Version="7.0.37" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.2" />
<PackageReference Include="NBitcoin" Version="7.0.46" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -58,9 +59,33 @@ public partial class BTCPayServerClient
return await SendHttpRequest<CrowdfundAppData>($"api/v1/apps/crowdfund/{appId}", null, HttpMethod.Get, token);
}
public virtual async Task<AppSalesStats> GetAppSales(string appId, int numberOfDays = 7, CancellationToken token = default)
{
if (appId == null) throw new ArgumentNullException(nameof(appId));
var queryPayload = new Dictionary<string, object> { { nameof(numberOfDays), numberOfDays } };
return await SendHttpRequest<AppSalesStats>($"api/v1/apps/{appId}/sales", queryPayload, HttpMethod.Get, token);
}
public virtual async Task<List<AppItemStats>> GetAppTopItems(string appId, int offset = 0, int count = 10, CancellationToken token = default)
{
if (appId == null) throw new ArgumentNullException(nameof(appId));
var queryPayload = new Dictionary<string, object> { { nameof(offset), offset }, { nameof(count), count } };
return await SendHttpRequest<List<AppItemStats>>($"api/v1/apps/{appId}/top-items", queryPayload, HttpMethod.Get, token);
}
public virtual async Task DeleteApp(string appId, CancellationToken token = default)
{
if (appId == null) throw new ArgumentNullException(nameof(appId));
await SendHttpRequest($"api/v1/apps/{appId}", null, HttpMethod.Delete, token);
}
public virtual async Task<FileData> UploadAppItemImage(string appId, string filePath, string mimeType, CancellationToken token = default)
{
return await UploadFileRequest<FileData>($"api/v1/apps/{appId}/image", filePath, mimeType, "file", HttpMethod.Post, token);
}
public virtual async Task DeleteAppItemImage(string appId, string fileId, CancellationToken token = default)
{
await SendHttpRequest($"api/v1/apps/{appId}/image/{fileId}", null, HttpMethod.Delete, token);
}
}

View File

@ -46,9 +46,15 @@ public partial class BTCPayServerClient
return await SendHttpRequest<InvoiceData>($"api/v1/stores/{storeId}/invoices/{invoiceId}", null, HttpMethod.Get, token);
}
public virtual async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId, string invoiceId,
bool onlyAccountedPayments = true, bool includeSensitive = false,
CancellationToken token = default)
{
return await SendHttpRequest<InvoicePaymentMethodDataModel[]>($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods", null, HttpMethod.Get, token);
var queryPayload = new Dictionary<string, object>
{
{ nameof(onlyAccountedPayments), onlyAccountedPayments },
{ nameof(includeSensitive), includeSensitive }
};
return await SendHttpRequest<InvoicePaymentMethodDataModel[]>($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods", queryPayload, HttpMethod.Get, token);
}
public virtual async Task ArchiveInvoice(string storeId, string invoiceId,

View File

@ -21,6 +21,13 @@ public partial class BTCPayServerClient
return await SendHttpRequest<LightningNodeBalanceData>($"api/v1/server/lightning/{cryptoCode}/balance", null, HttpMethod.Get, token);
}
public virtual async Task<HistogramData> GetLightningNodeHistogram(string cryptoCode, HistogramType? type = null,
CancellationToken token = default)
{
var queryPayload = type == null ? null : new Dictionary<string, object> { { "type", type.ToString() } };
return await SendHttpRequest<HistogramData>($"api/v1/server/lightning/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
}
public virtual async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
CancellationToken token = default)
{

View File

@ -21,6 +21,13 @@ public partial class BTCPayServerClient
return await SendHttpRequest<LightningNodeBalanceData>($"api/v1/stores/{storeId}/lightning/{cryptoCode}/balance", null, HttpMethod.Get, token);
}
public virtual async Task<HistogramData> GetLightningNodeHistogram(string storeId, string cryptoCode, HistogramType? type = null,
CancellationToken token = default)
{
var queryPayload = type == null ? null : new Dictionary<string, object> { { "type", type.ToString() } };
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/lightning/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
}
public virtual async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
CancellationToken token = default)
{

View File

@ -15,7 +15,7 @@ public partial class BTCPayServerClient
int amount = 10,
CancellationToken token = default)
{
return await SendHttpRequest<UpdatePaymentMethodRequest, OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview",
return await SendHttpRequest<UpdatePaymentMethodRequest, OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview",
new Dictionary<string, object> { { "offset", offset }, { "amount", amount } },
new UpdatePaymentMethodRequest { Config = JValue.CreateString(derivationScheme) },
HttpMethod.Post, token);
@ -25,7 +25,7 @@ public partial class BTCPayServerClient
string storeId, string paymentMethodId, int offset = 0, int amount = 10,
CancellationToken token = default)
{
return await SendHttpRequest<OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview",
return await SendHttpRequest<OnChainPaymentMethodPreviewResultData>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview",
new Dictionary<string, object> { { "offset", offset }, { "amount", amount } }, HttpMethod.Get, token);
}
@ -33,7 +33,7 @@ public partial class BTCPayServerClient
string paymentMethodId, GenerateOnChainWalletRequest request,
CancellationToken token = default)
{
return await SendHttpRequest<GenerateOnChainWalletResponse>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate", request, HttpMethod.Post, token);
return await SendHttpRequest<GenerateOnChainWalletResponse>($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/generate", request, HttpMethod.Post, token);
}
}

View File

@ -15,7 +15,7 @@ public partial class BTCPayServerClient
parameters.Add("includeNeighbourData", v);
try
{
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, HttpMethod.Get, token);
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, HttpMethod.Get, token);
}
catch (GreenfieldAPIException err) when (err.APIError.Code == "wallet-object-not-found")
{
@ -31,17 +31,17 @@ public partial class BTCPayServerClient
parameters.Add("ids", ids);
if (query?.IncludeNeighbourData is bool v)
parameters.Add("includeNeighbourData", v);
return await SendHttpRequest<OnChainWalletObjectData[]>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", parameters, HttpMethod.Get, token);
return await SendHttpRequest<OnChainWalletObjectData[]>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects", parameters, HttpMethod.Get, token);
}
public virtual async Task RemoveOnChainWalletObject(string storeId, string cryptoCode, OnChainWalletObjectId objectId,
CancellationToken token = default)
{
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", null, HttpMethod.Delete, token);
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}", null, HttpMethod.Delete, token);
}
public virtual async Task<OnChainWalletObjectData> AddOrUpdateOnChainWalletObject(string storeId, string cryptoCode, AddOnChainWalletObjectRequest request,
CancellationToken token = default)
{
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", request, HttpMethod.Post, token);
return await SendHttpRequest<OnChainWalletObjectData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects", request, HttpMethod.Post, token);
}
public virtual async Task AddOrUpdateOnChainWalletLink(string storeId, string cryptoCode,
@ -49,7 +49,7 @@ public partial class BTCPayServerClient
AddOnChainWalletObjectLinkRequest request = null,
CancellationToken token = default)
{
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links", request, HttpMethod.Post, token);
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}/links", request, HttpMethod.Post, token);
}
public virtual async Task RemoveOnChainWalletLinks(string storeId, string cryptoCode,
@ -57,6 +57,6 @@ public partial class BTCPayServerClient
OnChainWalletObjectId link,
CancellationToken token = default)
{
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", null, HttpMethod.Delete, token);
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", null, HttpMethod.Delete, token);
}
}

View File

@ -14,8 +14,16 @@ public partial class BTCPayServerClient
public virtual async Task<OnChainWalletOverviewData> ShowOnChainWalletOverview(string storeId, string cryptoCode,
CancellationToken token = default)
{
return await SendHttpRequest<OnChainWalletOverviewData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet", null, HttpMethod.Get, token);
return await SendHttpRequest<OnChainWalletOverviewData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet", null, HttpMethod.Get, token);
}
public virtual async Task<HistogramData> GetOnChainWalletHistogram(string storeId, string cryptoCode, HistogramType? type = null,
CancellationToken token = default)
{
var queryPayload = type == null ? null : new Dictionary<string, object> { { "type", type.ToString() } };
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/histogram", queryPayload, HttpMethod.Get, token);
}
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
CancellationToken token = default)
{
@ -24,13 +32,13 @@ public partial class BTCPayServerClient
{
queryParams.Add("blockTarget", blockTarget);
}
return await SendHttpRequest<OnChainWalletFeeRateData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feeRate", queryParams, HttpMethod.Get, token);
return await SendHttpRequest<OnChainWalletFeeRateData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/feerate", queryParams, HttpMethod.Get, token);
}
public virtual async Task<OnChainWalletAddressData> GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, bool forceGenerate = false,
CancellationToken token = default)
{
return await SendHttpRequest<OnChainWalletAddressData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>
return await SendHttpRequest<OnChainWalletAddressData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/address", new Dictionary<string, object>
{
{"forceGenerate", forceGenerate}
}, HttpMethod.Get, token);
@ -39,7 +47,7 @@ public partial class BTCPayServerClient
public virtual async Task UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode,
CancellationToken token = default)
{
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", null, HttpMethod.Delete, token);
await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/address", null, HttpMethod.Delete, token);
}
public virtual async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
@ -59,14 +67,14 @@ public partial class BTCPayServerClient
{
query.Add(nameof(skip), skip);
}
return await SendHttpRequest<IEnumerable<OnChainWalletTransactionData>>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query, HttpMethod.Get, token);
return await SendHttpRequest<IEnumerable<OnChainWalletTransactionData>>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", query, HttpMethod.Get, token);
}
public virtual async Task<OnChainWalletTransactionData> GetOnChainWalletTransaction(
string storeId, string cryptoCode, string transactionId,
CancellationToken token = default)
{
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}", null, HttpMethod.Get, token);
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions/{transactionId}", null, HttpMethod.Get, token);
}
public virtual async Task<OnChainWalletTransactionData> PatchOnChainWalletTransaction(
@ -74,7 +82,7 @@ public partial class BTCPayServerClient
PatchOnChainTransactionRequest request,
bool force = false, CancellationToken token = default)
{
return await SendHttpRequest<PatchOnChainTransactionRequest, OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}",
return await SendHttpRequest<PatchOnChainTransactionRequest, OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions/{transactionId}",
new Dictionary<string, object> { {"force", force} }, request, HttpMethod.Patch, token);
}
@ -82,7 +90,7 @@ public partial class BTCPayServerClient
string cryptoCode,
CancellationToken token = default)
{
return await SendHttpRequest<IEnumerable<OnChainWalletUTXOData>>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos", null, HttpMethod.Get, token);
return await SendHttpRequest<IEnumerable<OnChainWalletUTXOData>>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/utxos", null, HttpMethod.Get, token);
}
public virtual async Task<OnChainWalletTransactionData> CreateOnChainTransaction(string storeId,
@ -94,7 +102,7 @@ public partial class BTCPayServerClient
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
"Please use CreateOnChainTransactionButDoNotBroadcast when wanting to only create the transaction");
}
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", request, HttpMethod.Post, token);
return await SendHttpRequest<OnChainWalletTransactionData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", request, HttpMethod.Post, token);
}
public virtual async Task<Transaction> CreateOnChainTransactionButDoNotBroadcast(string storeId,
@ -106,6 +114,6 @@ public partial class BTCPayServerClient
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
"Please use CreateOnChainTransaction when wanting to also broadcast the transaction");
}
return Transaction.Parse(await SendHttpRequest<string>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", request, HttpMethod.Post, token), network);
return Transaction.Parse(await SendHttpRequest<string>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", request, HttpMethod.Post, token), network);
}
}

View File

@ -19,14 +19,14 @@ public partial class BTCPayServerClient
await SendHttpRequest($"api/v1/stores/{storeId}/payout-processors/{processor}/{paymentMethod}", null, HttpMethod.Delete, token);
}
public virtual async Task<IEnumerable<LightningAutomatedPayoutSettings>> GetStoreLightningAutomatedPayoutProcessors(string storeId, string? paymentMethod = null, CancellationToken token = default)
public virtual async Task<IEnumerable<LightningAutomatedPayoutSettings>> GetStoreLightningAutomatedPayoutProcessors(string storeId, string? payoutMethodId = null, CancellationToken token = default)
{
return await SendHttpRequest<IEnumerable<LightningAutomatedPayoutSettings>>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory{(paymentMethod is null ? string.Empty : $"/{paymentMethod}")}", null, HttpMethod.Get, token);
return await SendHttpRequest<IEnumerable<LightningAutomatedPayoutSettings>>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory{(payoutMethodId is null ? string.Empty : $"/{payoutMethodId}")}", null, HttpMethod.Get, token);
}
public virtual async Task<LightningAutomatedPayoutSettings> UpdateStoreLightningAutomatedPayoutProcessors(string storeId, string paymentMethod, LightningAutomatedPayoutSettings request, CancellationToken token = default)
public virtual async Task<LightningAutomatedPayoutSettings> UpdateStoreLightningAutomatedPayoutProcessors(string storeId, string payoutMethodId, LightningAutomatedPayoutSettings request, CancellationToken token = default)
{
return await SendHttpRequest<LightningAutomatedPayoutSettings>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{paymentMethod}", request, HttpMethod.Put, token);
return await SendHttpRequest<LightningAutomatedPayoutSettings>($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{payoutMethodId}", request, HttpMethod.Put, token);
}
public virtual async Task<OnChainAutomatedPayoutSettings> UpdateStoreOnChainAutomatedPayoutProcessors(string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request, CancellationToken token = default)

View File

@ -29,4 +29,10 @@ public partial class BTCPayServerClient
if (request == null) throw new ArgumentNullException(nameof(request));
await SendHttpRequest<StoreUserData>($"api/v1/stores/{storeId}/users", request, HttpMethod.Post, token);
}
public virtual async Task UpdateStoreUser(string storeId, string userId, StoreUserData request, CancellationToken token = default)
{
if (request == null) throw new ArgumentNullException(nameof(request));
await SendHttpRequest<StoreUserData>($"api/v1/stores/{storeId}/users/{userId}", request, HttpMethod.Put, token);
}
}

View File

@ -156,7 +156,7 @@ public partial class BTCPayServerClient
protected virtual async Task<T> UploadFileRequest<T>(string apiPath, string filePath, string mimeType, string formFieldName, HttpMethod method = null, CancellationToken token = default)
{
using MultipartFormDataContent multipartContent = new();
var fileContent = new StreamContent(File.OpenRead(filePath));
using var fileContent = new StreamContent(File.OpenRead(filePath));
var fileName = Path.GetFileName(filePath);
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType);
multipartContent.Add(fileContent, formFieldName, fileName);

View File

@ -0,0 +1,13 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class AppCartItem
{
public string Id { get; set; }
public string Title { get; set; }
public int Count { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Price { get; set; }
}

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models;
public enum AppItemPriceType
{
Fixed,
Topup,
Minimum
}
public class AppItem
{
public string Id { get; set; }
public string Title { get; set; }
public bool Disabled { get; set; }
public string Description { get; set; }
public string[] Categories { get; set; }
public string Image { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public AppItemPriceType PriceType { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Price { get; set; }
public string BuyButtonText { get; set; }
public int? Inventory { get; set; }
[JsonExtensionData]
public Dictionary<string, JToken> AdditionalData { get; set; }
}

View File

@ -0,0 +1,15 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class AppItemStats
{
public string ItemCode { get; set; }
public string Title { get; set; }
public int SalesCount { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Total { get; set; }
public string TotalFormatted { get; set; }
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class AppSalesStats
{
public int SalesCount { get; set; }
public IEnumerable<AppSalesStatsItem> Series { get; set; }
}
public class AppSalesStatsItem
{
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTime Date { get; set; }
public string Label { get; set; }
public int SalesCount { get; set; }
}

View File

@ -8,6 +8,6 @@ namespace BTCPayServer.Client.Models
public string Destination { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Amount { get; set; }
public string PaymentMethod { get; set; }
public string PayoutMethodId { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class CreatePosInvoiceRequest
{
public string AppId { get; set; }
public List<AppCartItem> Cart { get; set; }
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public List<decimal> Amounts { get; set; }
public int? DiscountPercent { get; set; }
public int? TipPercent { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? DiscountAmount { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Tip { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Subtotal { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Total { get; set; }
public string PosData { get; set; }
}

View File

@ -19,7 +19,7 @@ namespace BTCPayServer.Client.Models
public DateTimeOffset? ExpiresAt { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? StartsAt { get; set; }
public string[] PaymentMethods { get; set; }
public string[] PayoutMethods { get; set; }
public bool AutoApproveClaims { get; set; }
}
}

View File

@ -37,7 +37,7 @@ public abstract class CrowdfundBaseData : AppBaseData
public class CrowdfundAppData : CrowdfundBaseData
{
public object? Perks { get; set; }
public AppItem[]? Perks { get; set; }
}
public class CrowdfundAppRequest : CrowdfundBaseData, IAppRequest

View File

@ -10,6 +10,7 @@ namespace BTCPayServer.Client
{
public class GenerateOnChainWalletRequest
{
public string Label { get; set; }
public int AccountNumber { get; set; } = 0;
[JsonConverter(typeof(MnemonicJsonConverter))]
public Mnemonic ExistingMnemonic { get; set; }
@ -29,6 +30,7 @@ namespace BTCPayServer.Client
{
public class ConfigData
{
public string Label { get; set; }
public string AccountDerivation { get; set; }
[JsonExtensionData]
IDictionary<string, JToken> AdditionalData { get; set; }

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client.Models;
public enum HistogramType
{
Week,
Month,
YTD,
Year,
TwoYears,
Day
}
public class HistogramData
{
[JsonConverter(typeof(StringEnumConverter))]
public HistogramType Type { get; set; }
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public List<decimal> Series { get; set; }
[JsonProperty(ItemConverterType = typeof(DateTimeToUnixTimeConverter))]
public List<DateTimeOffset> Labels { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Balance { get; set; }
}

View File

@ -6,12 +6,11 @@ namespace BTCPayServer.Client.Models;
public class LightningAutomatedPayoutSettings
{
public string PaymentMethod { get; set; }
public string PayoutMethodId { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
public TimeSpan IntervalSeconds { get; set; }
public int? CancelPayoutAfterFailures { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool ProcessNewPayoutsInstantly { get; set; }

View File

@ -11,7 +11,7 @@ namespace BTCPayServer.Client.Models
public string Body { get; set; }
public string StoreId { get; set; }
public bool Seen { get; set; }
public Uri Link { get; set; }
public string Link { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset CreatedTime { get; set; }

View File

@ -6,7 +6,7 @@ namespace BTCPayServer.Client.Models;
public class OnChainAutomatedPayoutSettings
{
public string PaymentMethod { get; set; }
public string PayoutMethodId { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
public TimeSpan IntervalSeconds { get; set; }

View File

@ -5,7 +5,7 @@ namespace BTCPayServer.Client.Models;
public class PaymentMethodCriteriaData
{
public string PaymentMethod { get; set; }
public string PaymentMethodId { get; set; }
public string CurrencyCode { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }

View File

@ -21,12 +21,13 @@ namespace BTCPayServer.Client.Models
public string Id { get; set; }
public string PullPaymentId { get; set; }
public string Destination { get; set; }
public string PaymentMethod { get; set; }
public string CryptoCode { get; set; }
public string PayoutMethodId { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public decimal OriginalAmount { get; set; }
public string OriginalCurrency { get; set; }
public string PayoutCurrency { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? PaymentMethodAmount { get; set; }
public decimal? PayoutAmount { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public PayoutState State { get; set; }
public int Revision { get; set; }

View File

@ -4,6 +4,6 @@ namespace BTCPayServer.Client.Models
{
public string Name { get; set; }
public string FriendlyName { get; set; }
public string[] PaymentMethods { get; set; }
public string[] PayoutMethods { get; set; }
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
@ -11,17 +10,17 @@ namespace BTCPayServer.Client.Models
static PermissionMetadata()
{
Dictionary<string, PermissionMetadata> nodes = new Dictionary<string, PermissionMetadata>();
foreach (var policy in Client.Policies.AllPolicies)
foreach (var policy in Policies.AllPolicies)
{
nodes.Add(policy, new PermissionMetadata() { PermissionName = policy });
}
foreach (var n in nodes)
{
foreach (var policy in Client.Policies.AllPolicies)
foreach (var policy in Policies.AllPolicies)
{
if (policy.Equals(n.Key, StringComparison.OrdinalIgnoreCase))
continue;
if (Client.Permission.Create(n.Key).Contains(Client.Permission.Create(policy)))
if (Permission.Create(n.Key).Contains(Permission.Create(policy)))
n.Value.SubPermissions.Add(policy);
}
}

View File

@ -1,8 +1,6 @@
#nullable enable
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models;
@ -31,7 +29,7 @@ public abstract class PointOfSaleBaseData : AppBaseData
public class PointOfSaleAppData : PointOfSaleBaseData
{
public object? Items { get; set; }
public AppItem[]? Items { get; set; }
}
public class PointOfSaleAppRequest : PointOfSaleBaseData, IAppRequest

View File

@ -17,7 +17,7 @@ namespace BTCPayServer.Client.Models
public class RefundInvoiceRequest
{
public string? Name { get; set; } = null;
public string? PaymentMethod { get; set; }
public string? PayoutMethodId { get; set; }
public string? Description { get; set; } = null;
[JsonConverter(typeof(StringEnumConverter))]

View File

@ -32,7 +32,7 @@ namespace BTCPayServer.Client.Models
public class SyncStatus
{
public string CryptoCode { get; set; }
public string PaymentMethodId { get; set; }
public virtual bool Available { get; set; }
}

View File

@ -17,6 +17,7 @@ namespace BTCPayServer.Client.Models
public string Website { get; set; }
public string BrandColor { get; set; }
public bool ApplyBrandColorToBackend { get; set; }
public string LogoUrl { get; set; }
public string CssUrl { get; set; }
public string PaymentSoundUrl { get; set; }

View File

@ -17,7 +17,25 @@ namespace BTCPayServer.Client.Models
/// </summary>
public string UserId { get; set; }
/// <summary>
/// the store role of the user
/// </summary>
public string Role { get; set; }
/// <summary>
/// the email AND username of the user
/// </summary>
public string Email { get; set; }
/// <summary>
/// the name of the user
/// </summary>
public string Name { get; set; }
/// <summary>
/// the image url of the user
/// </summary>
public string ImageUrl { get; set; }
}
public class RoleData

View File

@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
namespace BTCPayServer.Client
{
@ -16,7 +17,7 @@ namespace BTCPayServer.Client
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
public const string CanModifyWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanViewReports = "btcpay.store.canviewreports";
@ -48,7 +49,7 @@ namespace BTCPayServer.Client
yield return CanViewInvoices;
yield return CanCreateInvoice;
yield return CanModifyInvoices;
yield return CanModifyStoreWebhooks;
yield return CanModifyWebhooks;
yield return CanModifyServerSettings;
yield return CanModifyStoreSettings;
yield return CanViewStoreSettings;
@ -104,6 +105,16 @@ namespace BTCPayServer.Client
{
return policy.StartsWith("btcpay.user", StringComparison.OrdinalIgnoreCase);
}
private static readonly CultureInfo _culture = new (CultureInfo.InvariantCulture.Name);
public static string DisplayName(string policy)
{
var p = policy.Split(".");
if (p.Length < 3 || p[0] != "btcpay") return policy;
var constName = typeof(Policies).GetFields().Select(f => f.Name).FirstOrDefault(f => f.Equals(p[^1], StringComparison.OrdinalIgnoreCase));
var perm = string.IsNullOrEmpty(constName) ? string.Join(' ', p[2..]) : Regex.Replace(constName, "([A-Z])", " $1", RegexOptions.Compiled).Trim();
return $"{_culture.TextInfo.ToTitleCase(p[1])}: {_culture.TextInfo.ToTitleCase(perm)}";
}
}
public class PermissionSet
@ -247,7 +258,7 @@ namespace BTCPayServer.Client
Policies.CanManagePullPayments,
Policies.CanModifyInvoices,
Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks,
Policies.CanModifyWebhooks,
Policies.CanModifyPaymentRequests,
Policies.CanManagePayouts,
Policies.CanUseLightningNodeInStore);

View File

@ -0,0 +1,50 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client;
public static class PosDataParser
{
public static Dictionary<string, object> ParsePosData(JToken? posData)
{
var result = new Dictionary<string, object>();
if (posData is JObject jobj)
{
foreach (var item in jobj)
{
ParsePosDataItem(item, ref result);
}
}
return result;
}
static void ParsePosDataItem(KeyValuePair<string, JToken?> item, ref Dictionary<string, object> result)
{
switch (item.Value?.Type)
{
case JTokenType.Array:
var items = item.Value.AsEnumerable().ToList();
var arrayResult = new List<object>();
for (var i = 0; i < items.Count; i++)
{
arrayResult.Add(items[i] is JObject
? ParsePosData(items[i])
: items[i].ToString());
}
result.TryAdd(item.Key, arrayResult);
break;
case JTokenType.Object:
result.TryAdd(item.Key, ParsePosData(item.Value));
break;
case null:
break;
default:
result.TryAdd(item.Key, item.Value.ToString());
break;
}
}
}

View File

@ -4,8 +4,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using BTCPayServer.Common;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
@ -133,7 +131,7 @@ namespace BTCPayServer
public string GetTrackedDestination(Script scriptPubKey)
{
return scriptPubKey.Hash.ToString() + "#" + CryptoCode.ToUpperInvariant();
return scriptPubKey.Hash.ToString();
}
}

View File

@ -3,13 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer;
using StandardConfiguration;
namespace BTCPayServer
{

View File

@ -1,16 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="4.3.1" />
<PackageReference Include="NBXplorer.Client" Version="4.3.6" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Altcoins\" />
</ItemGroup>
</Project>

View File

@ -1,97 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace BTCPayServer
{
public class CustomThreadPool : IDisposable
{
readonly CancellationTokenSource _Cancel = new CancellationTokenSource();
readonly TaskCompletionSource<bool> _Exited;
int _ExitedCount = 0;
readonly Thread[] _Threads;
Exception _UnhandledException;
readonly BlockingCollection<(Action, TaskCompletionSource<object>)> _Actions = new BlockingCollection<(Action, TaskCompletionSource<object>)>(new ConcurrentQueue<(Action, TaskCompletionSource<object>)>());
public CustomThreadPool(int threadCount, string threadName)
{
if (threadCount <= 0)
throw new ArgumentOutOfRangeException(nameof(threadCount));
_Exited = new TaskCompletionSource<bool>();
_Threads = Enumerable.Range(0, threadCount).Select(_ => new Thread(RunLoop) { Name = threadName }).ToArray();
foreach (var t in _Threads)
t.Start();
}
public void Do(Action act)
{
DoAsync(act).GetAwaiter().GetResult();
}
public T Do<T>(Func<T> act)
{
return DoAsync(act).GetAwaiter().GetResult();
}
public async Task<T> DoAsync<T>(Func<T> act)
{
TaskCompletionSource<object> done = new TaskCompletionSource<object>();
_Actions.Add((() =>
{
try
{
done.TrySetResult(act());
}
catch (Exception ex) { done.TrySetException(ex); }
}
, done));
return (T)(await done.Task.ConfigureAwait(false));
}
public Task DoAsync(Action act)
{
return DoAsync<object>(() =>
{
act();
return null;
});
}
void RunLoop()
{
try
{
foreach (var act in _Actions.GetConsumingEnumerable(_Cancel.Token))
{
act.Item1();
}
}
catch (OperationCanceledException) when (_Cancel.IsCancellationRequested) { }
catch (Exception ex)
{
_Cancel.Cancel();
_UnhandledException = ex;
}
if (Interlocked.Increment(ref _ExitedCount) == _Threads.Length)
{
foreach (var action in _Actions)
{
try
{
action.Item2.TrySetCanceled();
}
catch { }
}
_Exited.TrySetResult(true);
}
}
public void Dispose()
{
_Cancel.Cancel();
_Exited.Task.GetAwaiter().GetResult();
}
}
}

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

View File

@ -4,7 +4,6 @@ using System.Linq;
using BTCPayServer.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Bson;
namespace BTCPayServer
{

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
@ -39,7 +41,6 @@ namespace BTCPayServer.Data
public DbSet<PaymentRequestData> PaymentRequests { get; set; }
public DbSet<PaymentData> Payments { get; set; }
public DbSet<PayoutData> Payouts { get; set; }
public DbSet<PendingInvoiceData> PendingInvoices { get; set; }
public DbSet<PlannedTransaction> PlannedTransactions { get; set; }
public DbSet<PullPaymentData> PullPayments { get; set; }
public DbSet<RefundData> Refunds { get; set; }
@ -62,6 +63,7 @@ namespace BTCPayServer.Data
public DbSet<LightningAddressData> LightningAddresses { get; set; }
public DbSet<PayoutProcessorData> PayoutProcessors { get; set; }
public DbSet<FormData> Forms { get; set; }
public DbSet<PendingTransaction> PendingTransactions { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
@ -83,9 +85,8 @@ namespace BTCPayServer.Data
PairingCodeData.OnModelCreating(builder);
//PayjoinLock.OnModelCreating(builder);
PaymentRequestData.OnModelCreating(builder, Database);
PaymentData.OnModelCreating(builder, Database);
PaymentData.OnModelCreating(builder);
PayoutData.OnModelCreating(builder, Database);
PendingInvoiceData.OnModelCreating(builder);
//PlannedTransaction.OnModelCreating(builder);
PullPaymentData.OnModelCreating(builder, Database);
RefundData.OnModelCreating(builder);
@ -108,7 +109,7 @@ namespace BTCPayServer.Data
WebhookData.OnModelCreating(builder, Database);
FormData.OnModelCreating(builder, Database);
StoreRole.OnModelCreating(builder, Database);
PendingTransaction.OnModelCreating(builder, Database);
}
}
}

View File

@ -1,6 +1,4 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using Microsoft.EntityFrameworkCore;
@ -23,7 +21,7 @@ namespace BTCPayServer.Data
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseLoggerFactory(LoggerFactory);
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
builder.AddInterceptors(MigrationInterceptor.Instance);
ConfigureBuilder(builder, npgsqlOptionsAction);
return new ApplicationDbContext(builder.Options);
}

View File

@ -3,12 +3,12 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.24" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.31" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
@ -20,5 +20,7 @@
<ItemGroup>
<None Remove="DBScripts\001.InvoiceFunctions.sql" />
<None Remove="DBScripts\002.RefactorPayouts.sql" />
<None Remove="DBScripts\003.RefactorPendingInvoicesPayments.sql" />
<None Remove="DBScripts\004.MonitoredInvoices.sql" />
</ItemGroup>
</Project>

View File

@ -7,6 +7,7 @@ UPDATE "Payouts" SET
"Currency" = split_part("PayoutMethodId", '_', 1),
"PayoutMethodId"=
CASE
WHEN ("Blob"->>'Amount')::NUMERIC < 0 THEN 'TOPUP'
WHEN split_part("PayoutMethodId", '_', 2) = 'LightningLike' THEN split_part("PayoutMethodId", '_', 1) || '-LN'
ELSE split_part("PayoutMethodId", '_', 1) || '-CHAIN'
END;

View File

@ -0,0 +1,9 @@
CREATE OR REPLACE FUNCTION is_pending(status TEXT)
RETURNS BOOLEAN AS $$
SELECT status = 'Processing' OR status = 'New';
$$ LANGUAGE sql IMMUTABLE;
CREATE INDEX "IX_Invoices_Pending" ON "Invoices"((1)) WHERE is_pending("Status");
CREATE INDEX "IX_Payments_Pending" ON "Payments"((1)) WHERE is_pending("Status");
DROP TABLE "PendingInvoices";
ANALYZE "Invoices";

View File

@ -0,0 +1,23 @@
CREATE OR REPLACE FUNCTION get_prompt(invoice_blob JSONB, payment_method_id TEXT)
RETURNS JSONB AS $$
SELECT invoice_blob->'prompts'->payment_method_id
$$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION get_monitored_invoices(arg_payment_method_id TEXT, include_non_activated BOOLEAN)
RETURNS TABLE (invoice_id TEXT, payment_id TEXT, payment_method_id TEXT) AS $$
WITH cte AS (
-- Get all the invoices which are pending. Even if no payments.
SELECT i."Id" invoice_id, p."Id" payment_id, p."PaymentMethodId" payment_method_id FROM "Invoices" i LEFT JOIN "Payments" p ON i."Id" = p."InvoiceDataId"
WHERE is_pending(i."Status")
UNION ALL
-- For invoices not pending, take all of those which have pending payments
SELECT i."Id" invoice_id, p."Id" payment_id, p."PaymentMethodId" payment_method_id FROM "Invoices" i INNER JOIN "Payments" p ON i."Id" = p."InvoiceDataId"
WHERE is_pending(p."Status") AND NOT is_pending(i."Status"))
SELECT cte.* FROM cte
JOIN "Invoices" i ON cte.invoice_id=i."Id"
LEFT JOIN "Payments" p ON cte.payment_id=p."Id" AND cte.payment_method_id=p."PaymentMethodId"
WHERE (p."PaymentMethodId" IS NOT NULL AND p."PaymentMethodId" = arg_payment_method_id) OR
(p."PaymentMethodId" IS NULL AND get_prompt(i."Blob2", arg_payment_method_id) IS NOT NULL AND
(include_non_activated IS TRUE OR (get_prompt(i."Blob2", arg_payment_method_id)->'inactive')::BOOLEAN IS NOT TRUE));
$$ LANGUAGE SQL STABLE;

View File

@ -9,6 +9,7 @@ namespace BTCPayServer.Data
public string Address { get; set; }
public InvoiceData InvoiceData { get; set; }
public string InvoiceDataId { get; set; }
public string PaymentMethodId { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
@ -18,7 +19,7 @@ namespace BTCPayServer.Data
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AddressInvoiceData>()
#pragma warning disable CS0618
.HasKey(o => o.Address);
.HasKey(o => new { o.Address, o.PaymentMethodId });
#pragma warning restore CS0618
}
}

View File

@ -46,5 +46,6 @@ namespace BTCPayServer.Data
public bool ShowInvoiceStatusChangeHint { get; set; }
public string ImageUrl { get; set; }
public string Name { get; set; }
public string InvitationToken { get; set; }
}
}

View File

@ -15,32 +15,13 @@ using Microsoft.EntityFrameworkCore.Diagnostics;
using BTCPayServer.Migrations;
using Newtonsoft.Json.Serialization;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System.Threading.Tasks;
using System.Threading;
namespace BTCPayServer.Data
{
public partial class InvoiceData
public partial class InvoiceData : MigrationInterceptor.IHasMigration
{
/// <summary>
/// We have a migration running in the background that will migrate the data from the old blob to the new blob
/// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly.
/// </summary>
public class MigrationInterceptor : IMaterializationInterceptor
{
public static readonly MigrationInterceptor Instance = new MigrationInterceptor();
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
{
if (entity is InvoiceData invoiceData && invoiceData.Currency is null)
{
invoiceData.Migrate();
}
else if (entity is PaymentData paymentData && paymentData.Currency is null)
{
paymentData.Migrate();
}
return entity;
}
}
static HashSet<string> superflousProperties = new HashSet<string>()
{
"availableAddressHashes",
@ -74,13 +55,14 @@ namespace BTCPayServer.Data
};
#pragma warning disable CS0618 // Type or member is obsolete
public void Migrate()
public bool TryMigrate()
{
if (Currency is not null)
return;
return false;
if (Blob is not (null or { Length: 0 }))
{
Blob2 = MigrationExtensions.Unzip(Blob);
Blob2 = MigrationExtensions.SanitizeJSON(Blob2);
Blob = null;
}
var blob = JObject.Parse(Blob2);
@ -228,7 +210,7 @@ namespace BTCPayServer.Data
}
blob.ConvertNumberToString("price");
Currency = blob["currency"].Value<string>();
Currency = blob["currency"].Value<string>().ToUpperInvariant();
var isTopup = blob["type"]?.Value<string>() is "TopUp";
var amount = decimal.Parse(blob["price"].Value<string>(), CultureInfo.InvariantCulture);
Amount = isTopup && amount == 0 ? null : decimal.Parse(blob["price"].Value<string>(), CultureInfo.InvariantCulture);
@ -368,7 +350,11 @@ namespace BTCPayServer.Data
};
blob["version"] = 3;
Blob2 = blob.ToString(Formatting.None);
return true;
}
[NotMapped]
public bool Migrated { get; set; }
static string[] detailsRemoveDefault =
[
"paymentMethodFeeRate",

View File

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data
{
public partial class InvoiceData : IHasBlobUntyped
@ -25,14 +26,14 @@ namespace BTCPayServer.Data
public string ExceptionStatus { get; set; }
public List<AddressInvoiceData> AddressInvoices { get; set; }
public bool Archived { get; set; }
public List<PendingInvoiceData> PendingInvoices { get; set; }
public List<InvoiceSearchData> InvoiceSearchData { get; set; }
public List<RefundData> Refunds { get; set; }
public static string GetOrderId(string blob) => throw new NotSupportedException();
public static string GetItemCode(string blob) => throw new NotSupportedException();
public static bool IsPending(string status) => throw new NotSupportedException();
[Timestamp]
[Timestamp]
// With this, update of InvoiceData will fail if the row was modified by another process
public uint XMin { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
@ -50,7 +51,7 @@ namespace BTCPayServer.Data
.HasColumnType("NUMERIC");
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(GetOrderId), new[] { typeof(string) }), b => b.HasName("get_orderid"));
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(GetItemCode), new[] { typeof(string) }), b => b.HasName("get_itemcode"));
}
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(IsPending), new[] { typeof(string) }), b => b.HasName("is_pending"));
}
}
}

View File

@ -137,9 +137,10 @@ namespace BTCPayServer.Data
{
return paymentType switch
{
"BTCLike" => $"{cryptoCode}-CHAIN",
"BTCLike" or "MoneroLike" or "ZcashLike" => $"{cryptoCode}-CHAIN",
"LightningLike" or "LightningNetwork" => $"{cryptoCode}-LN",
"LNURLPAY" => $"{cryptoCode}-LNURL",
_ => throw new NotSupportedException("Unknown payment type " + paymentType)
};
}
@ -147,5 +148,25 @@ namespace BTCPayServer.Data
return $"{splitted[0]}-CHAIN";
throw new NotSupportedException("Unknown payment id " + paymentMethodId);
}
public static string TryMigratePaymentMethodId(string paymentMethodId)
{
var splitted = paymentMethodId.Split(new[] { '_', '-' });
if (splitted is [var cryptoCode, var paymentType])
{
return paymentType switch
{
"BTCLike" or "MoneroLike" or "ZcashLike" => $"{cryptoCode}-CHAIN",
"LightningLike" or "LightningNetwork" => $"{cryptoCode}-LN",
"LNURLPAY" => $"{cryptoCode}-LNURL",
_ => paymentMethodId
};
}
if (splitted.Length == 1)
return $"{splitted[0]}-CHAIN";
return paymentMethodId;
}
// Make postgres happy
public static string SanitizeJSON(string json) => json.Replace("\\u0000", string.Empty, StringComparison.OrdinalIgnoreCase);
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
/// <summary>
/// We have a migration running in the background that will migrate the data from the old blob to the new blob
/// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly.
/// </summary>
public class MigrationInterceptor : IMaterializationInterceptor, ISaveChangesInterceptor
{
public interface IHasMigration
{
bool TryMigrate();
bool Migrated { get; set; }
}
public static readonly MigrationInterceptor Instance = new MigrationInterceptor();
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
{
if (entity is IHasMigration hasMigration && hasMigration.TryMigrate())
{
hasMigration.Migrated = true;
}
return entity;
}
public ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default(CancellationToken))
{
foreach (var entry in eventData.Context.ChangeTracker.Entries())
{
if (entry is { Entity: IHasMigration { Migrated: true }, State: EntityState.Modified })
// It seems doing nothing, but this actually set all properties as modified
entry.State = EntityState.Modified;
}
return new ValueTask<InterceptionResult<int>>(result);
}
}
}

View File

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Migrations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.DataEncoders;
@ -13,16 +16,17 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data
{
public partial class PaymentData
public partial class PaymentData : MigrationInterceptor.IHasMigration
{
public void Migrate()
public bool TryMigrate()
{
#pragma warning disable CS0618 // Type or member is obsolete
if (Currency is not null)
return;
return false;
if (Blob is not (null or { Length: 0 }))
{
Blob2 = MigrationExtensions.Unzip(Blob);
Blob2 = MigrationExtensions.SanitizeJSON(Blob2);
Blob = null;
}
var blob = JObject.Parse(Blob2);
@ -42,9 +46,10 @@ namespace BTCPayServer.Data
}
var cryptoCode = blob["cryptoCode"].Value<string>();
Type = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
Type = MigrationExtensions.MigratePaymentMethodId(Type);
var divisibility = MigrationExtensions.GetDivisibility(Type);
MigratedPaymentMethodId = PaymentMethodId;
PaymentMethodId = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
PaymentMethodId = MigrationExtensions.MigratePaymentMethodId(PaymentMethodId);
var divisibility = MigrationExtensions.GetDivisibility(PaymentMethodId);
Currency = blob["cryptoCode"].Value<string>();
blob.Remove("cryptoCode");
blob.Remove("cryptoPaymentDataType");
@ -87,7 +92,7 @@ namespace BTCPayServer.Data
blob.Remove("output");
blob.Remove("outpoint");
// Convert from sats to btc
if (cryptoData["value"] is not (null or { Type: JTokenType.Null }))
if (cryptoData["value"] is not (null or { Type: JTokenType.Null } or { Type: JTokenType.Object }))
{
var v = cryptoData["value"].Value<long>();
Amount = (decimal)v / (decimal)Money.COIN;
@ -98,7 +103,22 @@ namespace BTCPayServer.Data
blob.ConvertNumberToString("paymentMethodFee");
blob.Remove("networkFee");
blob.RemoveIfNull("paymentMethodFee");
}
}
// Liquid
else if (cryptoData["value"] is { Type: JTokenType.Object })
{
var v = cryptoData["value"]["value"].Value<long>();
var assetId = cryptoData["value"]["assetId"].Value<string>();
divisibility = GetDivisibility(assetId) ?? 8;
Amount = (decimal)v / (decimal)Math.Pow(10.0, divisibility);
cryptoData.Remove("value");
cryptoData["assetId"] = assetId;
blob["paymentMethodFee"] = blob["networkFee"];
blob.RemoveIfValue<decimal>("paymentMethodFee", 0.0m);
blob.ConvertNumberToString("paymentMethodFee");
blob.Remove("networkFee");
blob.RemoveIfNull("paymentMethodFee");
}
// Convert from millisats to btc
else if (cryptoData["amount"] is not (null or { Type: JTokenType.Null }))
{
@ -157,8 +177,25 @@ namespace BTCPayServer.Data
Blob2 = blob.ToString(Formatting.None);
Accounted = null;
#pragma warning restore CS0618 // Type or member is obsolete
return true;
}
private int? GetDivisibility(string assetId) =>
assetId switch
{
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2" => 8,
"aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf" => 2,
"0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a" => 8,
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" => 8,
_ => null,
};
[NotMapped]
public bool Migrated { get; set; }
[NotMapped]
[EditorBrowsable(EditorBrowsableState.Never)]
public string MigratedPaymentMethodId { get; set; }
static readonly DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
public static long DateTimeToMilliUnixTime(in DateTime time)
{

View File

@ -1,4 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -26,13 +27,15 @@ namespace BTCPayServer.Data
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public string Type { get; set; }
public string PaymentMethodId { get; set; }
[Obsolete("Use Status instead")]
public bool? Accounted { get; set; }
public PaymentStatus? Status { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
public static bool IsPending(PaymentStatus? status) => throw new NotSupportedException();
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<PaymentData>()
.HasKey(o => new { o.Id, o.PaymentMethodId });
builder.Entity<PaymentData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
@ -47,6 +50,7 @@ namespace BTCPayServer.Data
builder.Entity<PaymentData>()
.Property(o => o.Amount)
.HasColumnType("NUMERIC");
builder.HasDbFunction(typeof(PaymentData).GetMethod(nameof(IsPending), new[] { typeof(PaymentStatus?) }), b => b.HasName("is_pending"));
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data
{
public partial class PaymentRequestData : MigrationInterceptor.IHasMigration
{
[NotMapped]
public bool Migrated { get; set; }
public bool TryMigrate()
{
#pragma warning disable CS0618 // Type or member is obsolete
if (Blob is null && Blob2 is not null)
return false;
if (Blob2 is null)
{
Blob2 = Blob is not (null or { Length: 0 }) ? MigrationExtensions.Unzip(Blob) : "{}";
Blob2 = MigrationExtensions.SanitizeJSON(Blob2);
}
Blob = null;
#pragma warning restore CS0618 // Type or member is obsolete
var jobj = JObject.Parse(Blob2);
// Fixup some legacy payment requests
if (jobj["expiryDate"].Type == JTokenType.Date)
{
jobj["expiryDate"] = new JValue(NBitcoin.Utils.DateTimeToUnixTime(jobj["expiryDate"].Value<DateTime>()));
Blob2 = jobj.ToString(Newtonsoft.Json.Formatting.None);
}
return true;
}
}
}

View File

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class PaymentRequestData : IHasBlobUntyped
public partial class PaymentRequestData : IHasBlobUntyped
{
public string Id { get; set; }
public DateTimeOffset Created { get; set; }

View File

@ -1,11 +1,9 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
using BTCPayServer.Client.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NBitcoin;
namespace BTCPayServer.Data
@ -44,7 +42,14 @@ namespace BTCPayServer.Data
public string Blob { get; set; }
public string Proof { get; set; }
#nullable enable
public string? Destination { get; set; }
/// <summary>
/// For example, BTC-CHAIN needs to ensure that only a single address is tied to an active payout.
/// If `PayoutBlob.Destination` is `bitcoin://1BvBMSeYstWetqTFn5Au4m4GFg7xJaNVN2?amount=0.1`
/// Then `DedupId` is `1BvBMSeYstWetqTFn5Au4m4GFg7xJaNVN2`
/// For Lightning, Destination could be the lightning address, BOLT11 or LNURL
/// But the `DedupId` would be the `PaymentHash`.
/// </summary>
public string? DedupId { get; set; }
#nullable restore
public StoreData StoreData { get; set; }
@ -62,11 +67,11 @@ namespace BTCPayServer.Data
builder.Entity<PayoutData>()
.HasIndex(o => o.State);
builder.Entity<PayoutData>()
.HasIndex(x => new { DestinationId = x.Destination, x.State });
.HasIndex(x => new { DestinationId = x.DedupId, x.State });
builder.Entity<PayoutData>()
.Property(o => o.Blob)
.HasColumnType("JSONB");
.HasColumnType("jsonb");
builder.Entity<PayoutData>()
.Property(o => o.Proof)
.HasColumnType("JSONB");

View File

@ -16,7 +16,7 @@ public class PayoutProcessorData : IHasBlobUntyped
public string Id { get; set; }
public string StoreId { get; set; }
public StoreData Store { get; set; }
public string PaymentMethod { get; set; }
public string PayoutMethodId { get; set; }
public string Processor { get; set; }
[Obsolete("Use Blob2 instead")]
@ -36,6 +36,6 @@ public class PayoutProcessorData : IHasBlobUntyped
public override string ToString()
{
return $"{Processor} {PaymentMethod} {StoreId}";
return $"{Processor} {PayoutMethodId} {StoreId}";
}
}

View File

@ -1,18 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
public class PendingInvoiceData
{
public string Id { get; set; }
public InvoiceData InvoiceData { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<PendingInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(o => o.PendingInvoices)
.HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade);
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data;
public class PendingTransaction: IHasBlob<PendingTransactionBlob>
{
public string TransactionId { get; set; }
public string CryptoCode { get; set; }
public string StoreId { get; set; }
public StoreData Store { get; set; }
public DateTimeOffset? Expiry { get; set; }
public PendingTransactionState State { get; set; }
public string[] OutpointsUsed { get; set; }
[NotMapped][Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<PendingTransaction>()
.HasOne(o => o.Store)
.WithMany(i => i.PendingTransactions)
.HasForeignKey(i => i.StoreId)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<PendingTransaction>().HasKey(transaction => new {transaction.CryptoCode, transaction.TransactionId});
builder.Entity<PendingTransaction>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
builder.Entity<PendingTransaction>()
.Property(o => o.OutpointsUsed)
.HasColumnType("text[]");
}
}
public enum PendingTransactionState
{
Pending,
Cancelled,
Expired,
Invalidated,
Signed,
Broadcast
}
public class PendingTransactionBlob
{
public string PSBT { get; set; }
public List<CollectedSignature> CollectedSignatures { get; set; } = new();
}
public class CollectedSignature
{
public DateTimeOffset Timestamp { get; set; }
public string ReceivedPSBT { get; set; }
}

View File

@ -49,6 +49,7 @@ namespace BTCPayServer.Data
public IEnumerable<FormData> Forms { get; set; }
public IEnumerable<StoreRole> StoreRoles { get; set; }
public bool Archived { get; set; }
public IEnumerable<PendingTransaction> PendingTransactions { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{

View File

@ -1,30 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240405052858_cleanup_address_invoices")]
public partial class cleanup_address_invoices : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
DELETE FROM ""AddressInvoices"" WHERE ""Address"" LIKE '%_LightningLike';
ALTER TABLE ""AddressInvoices"" DROP COLUMN IF EXISTS ""CreatedTime"";
");
migrationBuilder.Sql(@"VACUUM (FULL, ANALYZE) ""AddressInvoices"";", true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -11,5 +11,30 @@ namespace BTCPayServer.Migrations
[DBScript("002.RefactorPayouts.sql")]
public partial class migratepayouts : DBScriptsMigration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
base.Up(migrationBuilder);
migrationBuilder.RenameColumn(
name: "Destination",
table: "Payouts",
newName: "DedupId");
migrationBuilder.RenameIndex(
name: "IX_Payouts_Destination_State",
table: "Payouts",
newName: "IX_Payouts_DedupId_State");
migrationBuilder.RenameColumn(
name: "PaymentMethod",
table: "PayoutProcessors",
newName: "PayoutMethodId");
migrationBuilder.Sql("""
UPDATE "PayoutProcessors"
SET
"PayoutMethodId" = CASE WHEN STRPOS("PayoutMethodId", '_') = 0 THEN "PayoutMethodId" || '-CHAIN'
WHEN STRPOS("PayoutMethodId", '_LightningLike') > 0 THEN split_part("PayoutMethodId", '_LightningLike', 1) || '-LN'
WHEN STRPOS("PayoutMethodId", '_LNURLPAY') > 0 THEN split_part("PayoutMethodId",'_LNURLPAY', 1) || '-LN'
ELSE "PayoutMethodId" END
""");
}
}
}

View File

@ -0,0 +1,54 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Newtonsoft.Json;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240904092905_UpdateStoreOwnerRole")]
public partial class UpdateStoreOwnerRole : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
"StoreRoles",
keyColumns: new[] { "Id" },
keyColumnTypes: new[] { "TEXT" },
keyValues: new[] { "Owner" },
columns: new[] { "Permissions" },
columnTypes: new[] { "TEXT[]" },
values: new object[]
{
new[]
{
"btcpay.store.canmodifystoresettings"
}
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
"StoreRoles",
keyColumns: new[] { "Id" },
keyColumnTypes: new[] { "TEXT" },
keyValues: new[] { "Owner" },
columns: new[] { "Permissions" },
columnTypes: new[] { "TEXT[]" },
values: new object[]
{
new[]
{
"btcpay.store.canmodifystoresettings",
"btcpay.store.cantradecustodianaccount",
"btcpay.store.canwithdrawfromcustodianaccount",
"btcpay.store.candeposittocustodianaccount"
}
});
}
}
}

View File

@ -0,0 +1,15 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240913034505_refactorpendinginvoicespayments")]
[DBScript("003.RefactorPendingInvoicesPayments.sql")]
public partial class refactorpendinginvoicespayments : DBScriptsMigration
{
}
}

View File

@ -0,0 +1,80 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240919085726_refactorinvoiceaddress")]
public partial class refactorinvoiceaddress : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices");
migrationBuilder.AddColumn<string>(
name: "PaymentMethodId",
table: "AddressInvoices",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.Sql("""
UPDATE "AddressInvoices"
SET
"Address" = (string_to_array("Address", '#'))[1],
"PaymentMethodId" = CASE WHEN (string_to_array("Address", '#'))[2] IS NULL THEN 'BTC-CHAIN'
WHEN STRPOS((string_to_array("Address", '#'))[2], '_') = 0 THEN (string_to_array("Address", '#'))[2] || '-CHAIN'
WHEN STRPOS((string_to_array("Address", '#'))[2], '_MoneroLike') > 0 THEN replace((string_to_array("Address", '#'))[2],'_MoneroLike','-CHAIN')
WHEN STRPOS((string_to_array("Address", '#'))[2], '_ZcashLike') > 0 THEN replace((string_to_array("Address", '#'))[2],'_ZcashLike','-CHAIN')
ELSE '' END;
ALTER TABLE "AddressInvoices" DROP COLUMN IF EXISTS "CreatedTime";
DELETE FROM "AddressInvoices" WHERE "PaymentMethodId" = '';
""");
migrationBuilder.AddPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices",
columns: new[] { "Address", "PaymentMethodId" });
migrationBuilder.Sql("VACUUM (ANALYZE) \"AddressInvoices\";", true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices");
migrationBuilder.DropColumn(
name: "PaymentMethodId",
table: "AddressInvoices");
migrationBuilder.AddPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices",
column: "Address");
migrationBuilder.CreateTable(
name: "PendingInvoices",
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PendingInvoices", x => x.Id);
table.ForeignKey(
name: "FK_PendingInvoices_Invoices_Id",
column: x => x.Id,
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}
}
}

View File

@ -0,0 +1,38 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240923065254_refactorpayments")]
public partial class refactorpayments : DBScriptsMigration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Payments",
table: "Payments");
migrationBuilder.RenameColumn(
name: "Type",
table: "Payments",
newName: "PaymentMethodId");
migrationBuilder.Sql("UPDATE \"Payments\" SET \"PaymentMethodId\"='' WHERE \"PaymentMethodId\" IS NULL;");
migrationBuilder.AddPrimaryKey(
name: "PK_Payments",
table: "Payments",
columns: new[] { "Id", "PaymentMethodId" });
base.Up(migrationBuilder);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -0,0 +1,15 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240924065254_monitoredinvoices")]
[DBScript("004.MonitoredInvoices.sql")]
public partial class monitoredinvoices : DBScriptsMigration
{
}
}

View File

@ -0,0 +1,53 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20241029163147_AddingPendingTransactionsTable")]
public partial class AddingPendingTransactionsTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PendingTransactions",
columns: table => new
{
TransactionId = table.Column<string>(type: "text", nullable: false),
CryptoCode = table.Column<string>(type: "text", nullable: false),
StoreId = table.Column<string>(type: "text", nullable: true),
Expiry = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
State = table.Column<int>(type: "integer", nullable: false),
OutpointsUsed = table.Column<string[]>(type: "text[]", nullable: true),
Blob2 = table.Column<string>(type: "JSONB", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PendingTransactions", x => new { x.CryptoCode, x.TransactionId });
table.ForeignKey(
name: "FK_PendingTransactions_Stores_StoreId",
column: x => x.StoreId,
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_PendingTransactions_StoreId",
table: "PendingTransactions",
column: "StoreId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PendingTransactions");
}
}
}

View File

@ -63,10 +63,13 @@ namespace BTCPayServer.Migrations
b.Property<string>("Address")
.HasColumnType("text");
b.Property<string>("PaymentMethodId")
.HasColumnType("text");
b.Property<string>("InvoiceDataId")
.HasColumnType("text");
b.HasKey("Address");
b.HasKey("Address", "PaymentMethodId");
b.HasIndex("InvoiceDataId");
@ -479,6 +482,9 @@ namespace BTCPayServer.Migrations
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("PaymentMethodId")
.HasColumnType("text");
b.Property<bool?>("Accounted")
.HasColumnType("boolean");
@ -503,10 +509,7 @@ namespace BTCPayServer.Migrations
b.Property<string>("Status")
.HasColumnType("text");
b.Property<string>("Type")
.HasColumnType("text");
b.HasKey("Id");
b.HasKey("Id", "PaymentMethodId");
b.HasIndex("InvoiceDataId");
@ -557,7 +560,7 @@ namespace BTCPayServer.Migrations
.HasColumnType("numeric");
b.Property<string>("Blob")
.HasColumnType("JSONB");
.HasColumnType("jsonb");
b.Property<string>("Currency")
.HasColumnType("text");
@ -565,7 +568,7 @@ namespace BTCPayServer.Migrations
b.Property<DateTimeOffset>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Destination")
b.Property<string>("DedupId")
.HasColumnType("text");
b.Property<decimal>("OriginalAmount")
@ -601,7 +604,7 @@ namespace BTCPayServer.Migrations
b.HasIndex("StoreDataId");
b.HasIndex("Destination", "State");
b.HasIndex("DedupId", "State");
b.ToTable("Payouts");
});
@ -618,7 +621,7 @@ namespace BTCPayServer.Migrations
b.Property<string>("Blob2")
.HasColumnType("JSONB");
b.Property<string>("PaymentMethod")
b.Property<string>("PayoutMethodId")
.HasColumnType("text");
b.Property<string>("Processor")
@ -634,14 +637,34 @@ namespace BTCPayServer.Migrations
b.ToTable("PayoutProcessors");
});
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
modelBuilder.Entity("BTCPayServer.Data.PendingTransaction", b =>
{
b.Property<string>("Id")
b.Property<string>("CryptoCode")
.HasColumnType("text");
b.HasKey("Id");
b.Property<string>("TransactionId")
.HasColumnType("text");
b.ToTable("PendingInvoices");
b.Property<string>("Blob2")
.HasColumnType("JSONB");
b.Property<DateTimeOffset?>("Expiry")
.HasColumnType("timestamp with time zone");
b.Property<string[]>("OutpointsUsed")
.HasColumnType("text[]");
b.Property<int>("State")
.HasColumnType("integer");
b.Property<string>("StoreId")
.HasColumnType("text");
b.HasKey("CryptoCode", "TransactionId");
b.HasIndex("StoreId");
b.ToTable("PendingTransactions");
});
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
@ -1331,15 +1354,14 @@ namespace BTCPayServer.Migrations
b.Navigation("Store");
});
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
modelBuilder.Entity("BTCPayServer.Data.PendingTransaction", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("PendingInvoices")
.HasForeignKey("Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BTCPayServer.Data.StoreData", "Store")
.WithMany("PendingTransactions")
.HasForeignKey("StoreId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("InvoiceData");
b.Navigation("Store");
});
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
@ -1572,8 +1594,6 @@ namespace BTCPayServer.Migrations
b.Navigation("Payments");
b.Navigation("PendingInvoices");
b.Navigation("Refunds");
});
@ -1602,6 +1622,8 @@ namespace BTCPayServer.Migrations
b.Navigation("Payouts");
b.Navigation("PendingTransactions");
b.Navigation("PullPayments");
b.Navigation("Settings");

View File

@ -6,9 +6,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="NBitcoin" Version="7.0.37" />
<PackageReference Include="NBitcoin" Version="7.0.46" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.4" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1175,13 +1175,6 @@
"symbol":null,
"crypto":true
},
{
"name":"USDt",
"code":"USDT",
"divisibility":8,
"symbol":null,
"crypto":true
},
{
"name":"LCAD",
"code":"LCAD",
@ -1315,13 +1308,6 @@
"symbol": null,
"crypto": true
},
{
"name":"USDt",
"code":"USDT20",
"divisibility":6,
"symbol":null,
"crypto":true
},
{
"name":"FaucetToken",
"code":"FAU",

View File

@ -5,6 +5,9 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Newtonsoft.Json;
@ -18,14 +21,74 @@ namespace BTCPayServer.Services.Rates
public string Symbol { get; set; }
public bool Crypto { get; set; }
}
public class CurrencyNameTable
public interface CurrencyDataProvider
{
public static CurrencyNameTable Instance = new();
public CurrencyNameTable()
Task<CurrencyData[]> LoadCurrencyData(CancellationToken cancellationToken);
}
public class InMemoryCurrencyDataProvider : CurrencyDataProvider
{
private readonly CurrencyData[] _currencyData;
public InMemoryCurrencyDataProvider(CurrencyData[] currencyData)
{
_Currencies = LoadCurrency().ToDictionary(k => k.Code, StringComparer.InvariantCultureIgnoreCase);
_currencyData = currencyData;
}
public Task<CurrencyData[]> LoadCurrencyData(CancellationToken cancellationToken) => Task.FromResult(_currencyData);
}
public class AssemblyCurrencyDataProvider : CurrencyDataProvider
{
private readonly Assembly _assembly;
private readonly string _manifestResourceStream;
public AssemblyCurrencyDataProvider(Assembly assembly, string manifestResourceStream)
{
_assembly = assembly;
_manifestResourceStream = manifestResourceStream;
}
public Task<CurrencyData[]> LoadCurrencyData(CancellationToken cancellationToken)
{
var stream = _assembly.GetManifestResourceStream(_manifestResourceStream);
if (stream is null)
throw new InvalidOperationException("Unknown manifestResourceStream");
string content = null;
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
content = reader.ReadToEnd();
}
var currencies = JsonConvert.DeserializeObject<CurrencyData[]>(content);
return Task.FromResult(currencies.ToArray());
}
}
public class CurrencyNameTable
{
public CurrencyNameTable(IEnumerable<CurrencyDataProvider> currencyDataProviders, ILogger<CurrencyNameTable> logger)
{
_currencyDataProviders = currencyDataProviders;
_logger = logger;
}
public async Task ReloadCurrencyData(CancellationToken cancellationToken)
{
var currencies = new Dictionary<string, CurrencyData>(StringComparer.InvariantCultureIgnoreCase);
var loadings = _currencyDataProviders.Select(c => (Task: c.LoadCurrencyData(cancellationToken), Prov: c)).ToList();
foreach (var loading in loadings)
{
try
{
foreach (var curr in await loading.Task)
{
currencies.TryAdd(curr.Code, curr);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error loading currency data for " + loading.Prov.GetType().FullName);
}
}
_Currencies = currencies;
}
static readonly Dictionary<string, IFormatProvider> _CurrencyProviders = new();
public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback)
@ -123,20 +186,9 @@ namespace BTCPayServer.Services.Rates
currencyProviders.TryAdd(code, number);
}
readonly Dictionary<string, CurrencyData> _Currencies;
static CurrencyData[] LoadCurrency()
{
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Rating.Currencies.json");
string content = null;
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
content = reader.ReadToEnd();
}
var currencies = JsonConvert.DeserializeObject<CurrencyData[]>(content);
return currencies;
}
Dictionary<string, CurrencyData> _Currencies = new();
private readonly IEnumerable<CurrencyDataProvider> _currencyDataProviders;
private readonly ILogger<CurrencyNameTable> _logger;
public IEnumerable<CurrencyData> Currencies => _Currencies.Values;

View File

@ -1,11 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BTCPayServer.Services.Rates;
namespace BTCPayServer.Rating
{
public class CurrencyPair
{
private static readonly HashSet<string> _knownCurrencies;
static CurrencyPair()
{
var prov = new AssemblyCurrencyDataProvider(typeof(BTCPayServer.Rating.BidAsk).Assembly, "BTCPayServer.Rating.Currencies.json");
// It's OK this is sync function
_knownCurrencies = prov.LoadCurrencyData(default).GetAwaiter().GetResult()
.Select(c => c.Code).ToHashSet(StringComparer.OrdinalIgnoreCase);
}
public CurrencyPair(string left, string right)
{
ArgumentNullException.ThrowIfNull(right);
@ -49,10 +60,9 @@ namespace BTCPayServer.Rating
for (int i = 3; i < 5; i++)
{
var potentialCryptoName = currencyPair.Substring(0, i);
var currency = CurrencyNameTable.Instance.GetCurrencyData(potentialCryptoName, false);
if (currency != null)
if (_knownCurrencies.Contains(potentialCryptoName))
{
value = new CurrencyPair(currency.Code, currencyPair.Substring(i));
value = new CurrencyPair(potentialCryptoName, currencyPair.Substring(i));
return true;
}
}

View File

@ -0,0 +1,39 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
public class BareBitcoinRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public RateSourceInfo RateSourceInfo => new("barebitcoin", "Bare Bitcoin", "https://api.bb.no/price");
public BareBitcoinRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
// Extract market and otc prices
var market = jobj["market"].Value<decimal>();
var buy = jobj["buy"].Value<decimal>();
var sell = jobj["sell"].Value<decimal>();
// Create currency pair for BTC/NOK
var pair = new CurrencyPair("BTC", "NOK");
// Return single pair rate with sell/buy as bid/ask
return new[] { new PairRate(pair, new BidAsk(sell, buy)) };
}
}
}

View File

@ -0,0 +1,39 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
public class BitmyntRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public RateSourceInfo RateSourceInfo => new("bitmynt", "Bitmynt", "https://ny.bitmynt.no/data/rates.json");
public BitmyntRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
// Extract bid and ask prices from current_rate object
var currentRate = jobj["current_rate"];
var bid = currentRate["bid"].Value<decimal>();
var ask = currentRate["ask"].Value<decimal>();
// Create currency pair for BTC/NOK
var pair = new CurrencyPair("BTC", "NOK");
// Return single pair rate with bid/ask
return new[] { new PairRate(pair, new BidAsk(bid, ask)) };
}
}
}

View File

@ -3,10 +3,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Lightning;
using BTCPayServer.Models.AppViewModels;
@ -25,7 +24,7 @@ using Newtonsoft.Json.Linq;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
using WalletSettingsViewModel = BTCPayServer.Models.StoreViewModels.WalletSettingsViewModel;
namespace BTCPayServer.Tests
@ -94,16 +93,16 @@ namespace BTCPayServer.Tests
Assert.IsType<ViewResult>(response);
// Get enabled state from settings
response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
response = await controller.WalletSettings(user.StoreId, cryptoCode);
var onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.NotNull(onchainSettingsModel?.DerivationScheme);
Assert.True(onchainSettingsModel.Enabled);
// Disable wallet
onchainSettingsModel.Enabled = false;
response = controller.UpdateWalletSettings(onchainSettingsModel).GetAwaiter().GetResult();
response = await controller.UpdateWalletSettings(onchainSettingsModel);
Assert.IsType<RedirectToActionResult>(response);
response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
response = await controller.WalletSettings(user.StoreId, cryptoCode);
onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.NotNull(onchainSettingsModel?.DerivationScheme);
Assert.False(onchainSettingsModel.Enabled);
@ -125,7 +124,7 @@ namespace BTCPayServer.Tests
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
// Removing the derivation scheme, should redirect to store page
response = controller.ConfirmDeleteWallet(user.StoreId, cryptoCode).GetAwaiter().GetResult();
response = await controller.ConfirmDeleteWallet(user.StoreId, cryptoCode);
Assert.IsType<RedirectToActionResult>(response);
// Setting it again should show the confirmation page
@ -175,7 +174,7 @@ namespace BTCPayServer.Tests
Assert.Equal("ElectrumFile", settingsVm.Source);
// Now let's check that no data has been lost in the process
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
var store = await tester.PayTester.StoreRepository.FindStore(storeId);
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
var onchainBTC = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
@ -207,7 +206,7 @@ namespace BTCPayServer.Tests
Assert.Equal("paid", invoice.Status);
});
var wallet = tester.PayTester.GetController<UIWalletsController>();
var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC,
var psbt = await wallet.CreatePSBT(btcNetwork, onchainBTC,
new WalletSendModel()
{
Outputs = new List<WalletSendModel.TransactionOutput>
@ -220,7 +219,7 @@ namespace BTCPayServer.Tests
}
},
FeeSatoshiPerByte = 1
}, default).GetAwaiter().GetResult();
}, default);
Assert.NotNull(psbt);
@ -320,10 +319,10 @@ namespace BTCPayServer.Tests
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
var checkout =
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
(Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id)
.GetAwaiter().GetResult()).Value;
Assert.Single(checkout.AvailableCryptos);
Assert.Equal("LTC", checkout.CryptoCode);
Assert.Single(checkout.AvailablePaymentMethods);
Assert.Equal("LTC", checkout.PaymentMethodCurrency);
//////////////////////
@ -337,7 +336,7 @@ namespace BTCPayServer.Tests
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status);
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
checkout = (Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id)
.GetAwaiter().GetResult()).Value;
Assert.Equal("Processing", checkout.Status);
});
@ -441,136 +440,135 @@ namespace BTCPayServer.Tests
[Trait("Altcoins", "Altcoins")]
public async Task CanPayWithTwoCurrencies()
{
using (var tester = CreateServerTester())
using var tester = CreateServerTester();
tester.ActivateLTC();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
// First we try payment with a merchant having only BTC
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
var cashCow = tester.ExplorerNode;
await cashCow.GenerateAsync(2); // get some money in case
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
var firstPayment = Money.Coins(0.04m);
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
TestUtils.Eventually(() =>
{
tester.ActivateLTC();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
// First we try payment with a merchant having only BTC
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.True(invoice.BtcPaid == firstPayment);
});
var cashCow = tester.ExplorerNode;
await cashCow.GenerateAsync(2); // get some money in case
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
var firstPayment = Money.Coins(0.04m);
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.True(invoice.BtcPaid == firstPayment);
});
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
var checkout =
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null)
.GetAwaiter().GetResult()).Value;
Assert.Single(checkout.AvailableCryptos);
Assert.Equal("BTC", checkout.CryptoCode);
Assert.Single(invoice.PaymentCodes);
Assert.Single(invoice.SupportedTransactionCurrencies);
Assert.Single(invoice.SupportedTransactionCurrencies);
Assert.Single(invoice.PaymentSubtotals);
Assert.Single(invoice.PaymentTotals);
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
//////////////////////
// Retry now with LTC enabled
user.RegisterDerivationScheme("LTC");
invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
cashCow = tester.ExplorerNode;
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
firstPayment = Money.Coins(0.04m);
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.True(invoice.BtcPaid == firstPayment);
});
cashCow = tester.LTCExplorerNode;
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
Assert.NotNull(ltcCryptoInfo);
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(Money.Zero, invoice.BtcDue);
var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC");
Assert.Equal(Money.Zero, ltcPaid.Due);
Assert.Equal(secondPayment, ltcPaid.CryptoPaid);
Assert.Equal("paid", invoice.Status);
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
});
controller = tester.PayTester.GetController<UIInvoiceController>(null);
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC")
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
var checkout =
(Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, null)
.GetAwaiter().GetResult()).Value;
Assert.Equal(2, checkout.AvailableCryptos.Count);
Assert.Equal("LTC", checkout.CryptoCode);
Assert.Single(checkout.AvailablePaymentMethods);
Assert.Equal("BTC", checkout.PaymentMethodCurrency);
Assert.Equal(2, invoice.PaymentCodes.Count());
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
Assert.Equal(2, invoice.PaymentSubtotals.Count());
Assert.Equal(2, invoice.PaymentTotals.Count());
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
Assert.Single(invoice.PaymentCodes);
Assert.Single(invoice.SupportedTransactionCurrencies);
Assert.Single(invoice.SupportedTransactionCurrencies);
Assert.Single(invoice.PaymentSubtotals);
Assert.Single(invoice.PaymentTotals);
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
//////////////////////
// Check if we can disable LTC
invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
// Retry now with LTC enabled
user.RegisterDerivationScheme("LTC");
invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
cashCow = tester.ExplorerNode;
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
firstPayment = Money.Coins(0.04m);
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.True(invoice.BtcPaid == firstPayment);
});
cashCow = tester.LTCExplorerNode;
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
Assert.NotNull(ltcCryptoInfo);
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(Money.Zero, invoice.BtcDue);
var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC");
Assert.Equal(Money.Zero, ltcPaid.Due);
Assert.Equal(secondPayment, ltcPaid.CryptoPaid);
Assert.Equal("paid", invoice.Status);
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
});
controller = tester.PayTester.GetController<UIInvoiceController>(null);
checkout = (Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC")
.GetAwaiter().GetResult()).Value;
Assert.Equal(2, checkout.AvailablePaymentMethods.Count);
Assert.Equal("LTC", checkout.PaymentMethodCurrency);
Assert.Equal(2, invoice.PaymentCodes.Count());
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
Assert.Equal(2, invoice.PaymentSubtotals.Count());
Assert.Equal(2, invoice.PaymentTotals.Count());
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
// Check if we can disable LTC
invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true,
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true,
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
{
{"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}}
}
}, Facade.Merchant);
{"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}}
}
}, Facade.Merchant);
Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC"));
Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC"));
}
Assert.Single(invoice.CryptoInfo, c => c.CryptoCode == "BTC");
Assert.DoesNotContain(invoice.CryptoInfo, c => c.CryptoCode == "LTC");
}
[Fact]
@ -745,7 +743,7 @@ noninventoryitem:
invoices = user.BitPay.GetInvoices();
Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem")));
var inventoryItemInvoice =
Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem")));
Assert.Single(invoices, invoice => invoice.ItemCode.Equals("inventoryitem"));
Assert.NotNull(inventoryItemInvoice);
//let's mark the inventoryitem invoice as invalid, this should return the item to back in stock
@ -759,39 +757,6 @@ noninventoryitem:
AppService.Parse(vmpos.Template).Single(item => item.Id == "inventoryitem").Inventory);
}, 10000);
//test payment methods option
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = "BTC";
vmpos.Template = @"
btconly:
price: 1.0
title: good apple
payment_methods:
- BTC
normal:
price: 1.0";
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "btconly").Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "normal").Result);
invoices = user.BitPay.GetInvoices();
var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal");
var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly");
Assert.Single(btcOnlyInvoice.CryptoInfo);
Assert.Equal("BTC",
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
Assert.Equal("BTC-CHAIN",
btcOnlyInvoice.CryptoInfo.First().PaymentType);
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
Assert.Contains(
normalInvoice.CryptoInfo,
s => "BTC-CHAIN" == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
s.CryptoCode));
//test topup option
vmpos.Template = @"
a:
@ -821,13 +786,13 @@ g:
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.DoesNotContain("custom", vmpos.Template);
var items = AppService.Parse(vmpos.Template);
Assert.Contains(items, item => item.Id == "a" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
Assert.Contains(items, item => item.Id == "b" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
Assert.Contains(items, item => item.Id == "c" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
Assert.Contains(items, item => item.Id == "d" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
Assert.Contains(items, item => item.Id == "e" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
Assert.Contains(items, item => item.Id == "f" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
Assert.Contains(items, item => item.Id == "g" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
Assert.Contains(items, item => item.Id == "a" && item.PriceType == AppItemPriceType.Fixed);
Assert.Contains(items, item => item.Id == "b" && item.PriceType == AppItemPriceType.Fixed);
Assert.Contains(items, item => item.Id == "c" && item.PriceType == AppItemPriceType.Minimum);
Assert.Contains(items, item => item.Id == "d" && item.PriceType == AppItemPriceType.Fixed);
Assert.Contains(items, item => item.Id == "e" && item.PriceType == AppItemPriceType.Minimum);
Assert.Contains(items, item => item.Id == "f" && item.PriceType == AppItemPriceType.Topup);
Assert.Contains(items, item => item.Id == "g" && item.PriceType == AppItemPriceType.Topup);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Static, choiceKey: "g").Result);

View File

@ -63,7 +63,6 @@ namespace BTCPayServer.Tests
//no tether on our regtest, lets create it and set it
var tether = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT");
var lbtc = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("LBTC");
var etb = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("ETB");
var issueAssetResult = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
tether.AssetId = uint256.Parse(issueAssetResult.Result["asset"].ToString());
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network)
@ -71,15 +70,10 @@ namespace BTCPayServer.Tests
Assert.Equal(tether.AssetId, tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT").AssetId);
Assert.Equal(tether.AssetId, ((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network).AssetId);
var issueAssetResult2 = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
etb.AssetId = uint256.Parse(issueAssetResult2.Result["asset"].ToString());
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("ETB").Network)
.AssetId = etb.AssetId;
user.RegisterDerivationScheme("LBTC");
user.RegisterDerivationScheme("USDT");
user.RegisterDerivationScheme("ETB");
//test: register 2 assets on the same elements network and make sure paying an invoice on one does not affect the other in any way
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
@ -109,11 +103,7 @@ namespace BTCPayServer.Tests
Assert.Equal("paid", localInvoice.Status);
Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)).Payments);
});
//test precision based on https://github.com/ElementsProject/elements/issues/805#issuecomment-601277606
var etbBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "ETB").PaymentUrls.BIP21, etb.NBitcoinNetwork);
//precision = 2, 1ETB = 0.00000100
Assert.Equal(100, etbBip21.Amount.Satoshi);
var lbtcBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "LBTC").PaymentUrls.BIP21, lbtc.NBitcoinNetwork);
//precision = 8, 0.1 = 0.1

View File

@ -7,6 +7,15 @@
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="BTCPayServer.Tests.OutputPathAttribute">
<!-- _Parameter1, _Parameter2, etc. correspond to the
matching parameter of a constructor of that .NET attribute type -->
<_Parameter1>$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(OutputPath)'))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
<Target Name="CopyAditionalFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
<ItemGroup>
@ -30,20 +39,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.16" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
<PackageReference Include="Selenium.Support" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver" Version="4.22.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="125.0.6422.14100" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="128.0.6613.11900" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="AltcoinTests\**\*.cs"></Compile>
</ItemGroup>
<ItemGroup>
<None Update=".dockerignore">
<DependentUpon>Dockerfile</DependentUpon>
@ -64,4 +71,7 @@
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="obj\Debug\net8.0\" />
</ItemGroup>
</Project>

View File

@ -7,12 +7,14 @@ using System.Security.Claims;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Configuration;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Rating;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Tests.Logging;
@ -27,16 +29,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer;
using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes;
namespace BTCPayServer.Tests
{
public enum TestDatabases
{
Postgres,
MySQL,
}
public class BTCPayServerTester : IDisposable
{
internal readonly string _Directory;
@ -69,11 +64,6 @@ namespace BTCPayServer.Tests
set;
}
public string MySQL
{
get; set;
}
public string Postgres
{
get; set;
@ -89,11 +79,6 @@ namespace BTCPayServer.Tests
get; set;
}
public TestDatabases TestDatabase
{
get; set;
}
public async Task RestartStartupTask<T>()
{
var startupTask = GetService<IServiceProvider>().GetServices<Abstractions.Contracts.IStartupTask>()
@ -164,9 +149,7 @@ namespace BTCPayServer.Tests
if (!string.IsNullOrEmpty(SSHConnection))
config.AppendLine($"sshconnection={SSHConnection}");
if (TestDatabase == TestDatabases.MySQL && !String.IsNullOrEmpty(MySQL))
config.AppendLine($"mysql=" + MySQL);
else if (!String.IsNullOrEmpty(Postgres))
if (!String.IsNullOrEmpty(Postgres))
config.AppendLine($"postgres=" + Postgres);
if (!string.IsNullOrEmpty(ExplorerPostgres))
@ -180,6 +163,8 @@ namespace BTCPayServer.Tests
HttpClient.BaseAddress = ServerUri;
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
var confBuilder = new DefaultConfiguration() { Logger = LoggerProvider.CreateLogger("Console") }.CreateConfigurationBuilder(new[] { "--datadir", _Directory, "--conf", confPath, "--disable-registration", DisableRegistration ? "true" : "false" });
// This make sure that tests work outside of this assembly (ie, test project it a plugin)
confBuilder.SetBasePath(TestUtils.TestDirectory);
#if DEBUG
confBuilder.AddJsonFile("appsettings.dev.json", true, false);
#endif
@ -195,7 +180,7 @@ namespace BTCPayServer.Tests
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
l.SetMinimumLevel(LogLevel.Information)
.AddFilter("Microsoft", LogLevel.Error)
.AddFilter("Hangfire", LogLevel.Error)
.AddFilter("Microsoft.EntityFrameworkCore.Migrations", LogLevel.Information)
.AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error)
.AddProvider(LoggerProvider);
});
@ -283,7 +268,7 @@ namespace BTCPayServer.Tests
private string FindBTCPayServerDirectory()
{
var solutionDirectory = TestUtils.TryGetSolutionDirectoryInfo(Directory.GetCurrentDirectory());
var solutionDirectory = TestUtils.TryGetSolutionDirectoryInfo();
return Path.Combine(solutionDirectory.FullName, "BTCPayServer");
}

View File

@ -58,8 +58,8 @@ namespace BTCPayServer.Tests
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
var clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text);
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
Assert.StartsWith("bcrt", address);
Assert.DoesNotContain("lightning=", payUrl);
Assert.Equal($"bitcoin:{address}", payUrl);
Assert.Equal($"bitcoin:{address}", clipboard);
@ -80,7 +80,7 @@ namespace BTCPayServer.Tests
{
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
Assert.StartsWith("lightning:lnurl", payUrl);
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text);
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center")).GetAttribute("data-text"));
s.Driver.ElementDoesNotExist(By.Id("Address_BTC-CHAIN"));
});
@ -94,7 +94,7 @@ namespace BTCPayServer.Tests
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center-start")).Text;
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center")).GetAttribute("data-text");
Assert.Equal($"lightning:{address}", payUrl);
Assert.Equal($"lightning:{address}", clipboard);
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
@ -147,7 +147,7 @@ namespace BTCPayServer.Tests
invoiceId = s.CreateInvoice(2100, "EUR");
s.GoToInvoiceCheckout(invoiceId);
await Task.Delay(200);
address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
var amountFraction = "0.00001";
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
Money.Parse(amountFraction));
@ -261,8 +261,8 @@ namespace BTCPayServer.Tests
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
Assert.StartsWith($"bitcoin:{copyAddressOnchain}?amount=", payUrl);
Assert.Contains("?amount=", payUrl);
Assert.Contains("&lightning=", payUrl);
@ -327,8 +327,8 @@ namespace BTCPayServer.Tests
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center")).GetAttribute("data-text");
Assert.StartsWith($"bitcoin:{copyAddressOnchain}", payUrl);
Assert.Contains("?lightning=lnurl", payUrl);
Assert.DoesNotContain("amount=", payUrl);

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