Compare commits

..

296 Commits

Author SHA1 Message Date
Kukks
8290a1606f Fix posdata with primitive array
fixes #4952
2023-05-05 11:29:53 +02:00
Kukks
eddd458744 Fix missing shopify link
fixes #4945
2023-05-05 09:51:45 +02:00
rockstardev
439ea20a89 Resolving weird Firefox form autocomplete behavior on our POS (#4950) 2023-05-04 18:49:35 +02:00
d11n
cec223c8e7 Fix missing closing div (#4944) 2023-05-03 10:55:20 +02:00
rockstardev
25cb188d00 Bumping LND to 0.16.2-beta 2023-04-29 09:10:07 -05:00
Pavlenex
31007a8d96 Merge pull request #4929 from dennisreimann/esc
Supporters: Add ESC
2023-04-27 18:30:09 +02:00
Dennis Reimann
a4fa8db69b Supporters: Add ESC 2023-04-27 18:28:29 +02:00
nicolas.dorier
6193835ea1 Make LightningListener nullable, fix some NRE 2023-04-27 13:37:32 +09:00
rockstardev
0c78e9e4ac Bumping LND to 0.16.1-beta (#4921) 2023-04-27 13:17:57 +09:00
nicolas.dorier
58c409e7fa Fix tests 2023-04-27 13:00:25 +09:00
Nicolas Dorier
9577eed524 Validate input in greenfield for payout processors (#4922) 2023-04-27 10:59:19 +09:00
Kukks
76f32cd064 Fix payment request overriding user provided email 2023-04-26 14:06:42 +02:00
Kukks
92d9c17095 Fix payment request merge conflict marker in UI 2023-04-26 14:06:23 +02:00
nicolas.dorier
b0396df33f Update changelog 2023-04-26 18:27:06 +09:00
nicolas.dorier
c17572c76f Clip configuration values for payout processors 2023-04-26 18:24:46 +09:00
nicolas.dorier
5c91e072a6 Prevent payout processors from stalling restart 2023-04-26 18:09:56 +09:00
nicolas.dorier
4991d0f965 Update Changelog and bump version 2023-04-26 17:57:25 +09:00
Andrew Camilleri
45b74e1ce5 Fix cart receipt + fix pos email form forwarding (#4917)
* Show correct array regardless of size

fixes #4890

* Email provided to pos form was not forwarded to form

fixes #4810

* Make invoice receipt url redirect to the invoice redirect url if receipt is not enabled

When setting up a default email rule upon invoice settlement, you would link to the receipt page naturally. However, if using the payment requests, receipts are disabled as the payment request itself is the receipt.  This commit makes the receipt url redirect to the invoice redirect url if available, and in the case of payment requests, it would mean the receipt url is the payment request url. fixes #4895

* Set the email address in the form when configured in the payment request

* fix pay request email copy

* fix payouts nav link

fixes #4788

* Update BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml

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

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-04-26 16:45:35 +09:00
Nicolas Dorier
ccb4b9a9ba Merge pull request #4916 from dennisreimann/receipt-print
Receipt: Optimize print view
2023-04-26 15:41:41 +09:00
Nicolas Dorier
dd635071d6 Merge pull request #4909 from dennisreimann/fix-4889
POS: Fix choiceKey case
2023-04-26 15:16:54 +09:00
Nicolas Dorier
fe1448dfae Merge pull request #4911 from NicolasDorier/qiponbtq
Remove LNURLStandardInvoiceEnabled
2023-04-26 15:15:20 +09:00
Ikko Eltociear Ashimine
56855bc54d Fix typo in PayJoinEndpointController.cs (#4918) 2023-04-25 22:29:06 +02:00
nicolas.dorier
b51fa8df5a Fix indent 2023-04-25 21:03:12 +09:00
Dennis Reimann
3aa979cb11 Test updates 2023-04-25 13:07:01 +02:00
Dennis Reimann
c95f75bc6c Remove disable BOLT11 option 2023-04-25 12:21:27 +02:00
Dennis Reimann
b13a636f89 Fix syntax error 2023-04-25 12:11:08 +02:00
Dennis Reimann
8de55cef31 Unify checkout v1 behaviour with v2 2023-04-25 11:36:45 +02:00
Dennis Reimann
cb781f42e3 POS: Fix choiceKey case
I came across this while debugging #4889. This does not actually fix it, but it fixes an inconsistence in the casing of the parameter name.

However, I think the original issue is a caching problem in the browser. I was able to reproduce it on first load, after reloading the page once more it works as intended. The weird thing is: even though the values are correct on first load (verified via debugger), the `choiceKey` for the first item is set incorrectly to an integer value.
2023-04-25 09:16:09 +02:00
Dennis Reimann
b59292dc24 Receipt: Optimize print view
Fixes #4902
2023-04-25 07:30:15 +02:00
nicolas.dorier
03b793d7e2 Fix tests 2023-04-25 10:28:36 +09:00
nicolas.dorier
bee18d1cfb Can set LazyPaymentMethod at the invoice creation level 2023-04-25 08:38:42 +09:00
nicolas.dorier
d8698181f4 Remove LNURLStandardInvoiceEnabled 2023-04-24 23:52:40 +09:00
d11n
47f5d97eaf Prevent an NRE in LNURL (#4908)
* Prevent an NRE in LNURL

In addition to f05a7f9f14. Fixes #4904.

* Revert "Prevent an NRE in LNURL"

This reverts commit 0b241d61ab45b79297211e04da0e05c2cb10dada.

* Fix NRE

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-04-24 21:50:31 +09:00
Nicolas Dorier
cb3c5e56fd Fix: Poll of Lightning Invoice status might fail on LND if LNURL is used (#4910) 2023-04-24 18:15:39 +09:00
Nicolas Dorier
39b76c08de Fix: Form in Payment Requests was not setting its values to the invoices metadata (#4907) 2023-04-24 18:04:46 +09:00
Zaxounette
c3c8cc21ff Security Page Refactor (#4815)
* Update SECURITY.md

* typo

* project vs product

* Suggestion Update - Docker Deployment

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

* Suggestion - Email highlight

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

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2023-04-24 15:04:56 +09:00
nicolas.dorier
6dba1b6d8b Nullable on LanguageService 2023-04-20 09:44:24 +09:00
nicolas.dorier
381fe70a79 Add UserAgent to IPN and webhook HTTP requests (Fix #4883) 2023-04-19 21:13:31 +09:00
ndeet
feb927c2e4 Add missing param, fix response code. (#4891) 2023-04-19 13:07:36 +09:00
d11n
e77bd4c188 Swagger: FIx typos in pull payments docs (#4893)
Fixes #4892.
2023-04-19 13:06:51 +09:00
d11n
43436fc49e Remove BTCPayServer.Plugins.Test (#4875)
Remove the plugin, which is superseded with the plugin template and move the PluginPacker to the root of th solution (which matches its location in the file system).
2023-04-17 11:38:03 +09:00
nicolas.dorier
d738f797ec bump 2023-04-17 11:11:00 +09:00
nicolas.dorier
b5de97f785 Update changelog 2023-04-17 10:54:00 +09:00
Nicolas Dorier
b0c1b0895d Fix crash if auto detect language on checkout page, and the language couldn't be detected (Fix #4881) (#4888) 2023-04-17 10:53:45 +09:00
nicolas.dorier
8e60932f81 Migrate reference to AppType in server settings (Fix #4882) 2023-04-17 10:34:41 +09:00
d11n
7d14cd74f2 LightningLikePaymentData: Null-check preimage (#4886)
Fixes #4884.
2023-04-17 00:08:40 +02:00
nicolas.dorier
717f1610f5 Update changelog 2023-04-13 16:28:30 +09:00
ndeet
f1abe6497f Fix wrong data mapping to store data instead of store user data. (#4874) 2023-04-13 16:27:40 +09:00
rockstardev
046129a57d Bumping LND to 0.16.0-beta (#4873) 2023-04-13 14:48:47 +09:00
nicolas.dorier
90d300a490 Remove superflous category in API doc 2023-04-13 08:41:46 +09:00
d11n
a2d506c0db Checkout v2: Confetti for processing payments (#4872)
Let's not spoil the fun for those paying on-chain.
2023-04-13 08:40:21 +09:00
d11n
58748a24da What's New entry for v1.9 (#4871)
Copy is just a proposal from my side. Link check expected to fail, because the blog post isn't up yet.
2023-04-12 20:40:20 +09:00
Nicolas Dorier
639e8a4a1d Add links to invoice metadata in the docs (#4869) 2023-04-12 16:30:34 +09:00
Nicolas Dorier
48ebaf5c5a Do not put payRequest in the metadata (#4870) 2023-04-12 16:30:22 +09:00
nicolas.dorier
1aaccb1e6b Do not show empty JObject in AddtionalData of views 2023-04-11 13:21:02 +09:00
nicolas.dorier
f05a7f9f14 Prevent an NRE in LNURL 2023-04-11 13:08:14 +09:00
nicolas.dorier
98ddb348b0 Update changelog 2023-04-10 20:21:13 +09:00
Nicolas Dorier
a4aa85ebab Fix: If connection to Lightning node was interrupted, payments would be missed (#4865) 2023-04-10 19:35:01 +09:00
Nicolas Dorier
516efe56f4 Refactor: Dot not make LNURLPaymentMethodDetails depends on BTCPayInvoiceId (#4864)
* Refactor: Dot not make LNURLPaymentMethodDetails depends on BTCPayInvoiceId

* Abstract PaymentProof

* fix bug

* Make the selenium container resolves the btcpay host name
2023-04-10 16:38:49 +09:00
nicolas.dorier
a4d72d5bbc Fix tests 2023-04-10 15:06:59 +09:00
nicolas.dorier
24b8ec16f1 Fix tests 2023-04-10 13:37:09 +09:00
nicolas.dorier
ac25fef555 Remove warning 2023-04-10 12:01:11 +09:00
nicolas.dorier
8302f082a2 Do not render the TruncateCenter if text null or empty 2023-04-10 11:17:29 +09:00
nicolas.dorier
7546ef7a8e Dotnet reformat 2023-04-10 11:07:03 +09:00
nicolas.dorier
f598c70a4f Add comment TODO for improving automatic translations efficiency. 2023-04-10 11:03:36 +09:00
nicolas.dorier
422da21de5 Update Changelog 2023-04-07 17:52:48 +09:00
nicolas.dorier
f530fb3241 Update changelog 2023-04-07 17:50:57 +09:00
Andrew Camilleri
5d39bb7466 Allow LN Address to customize invoice metadata, and various bug fixes on LNUrl (#4855)
* Allow LN Address to customize invoice metadata

solves https://github.com/OpenSats/website/issues/8

* Refactor GetLNUrl

* Fix lightningAddresssettings.Max being ignored

* Fix: The payRequest generated by the callback wasn't the same as the original

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-04-07 17:48:58 +09:00
Wouter Samaey
041cba72b6 Made Fake Custodian config field non-required (#4861) 2023-04-07 15:59:44 +09:00
Andrew Camilleri
892b3e273f Improve Labeling further (#4849)
* If loading addresses into the send wallet page using bip21 or address,  (or clicking on "Send selected payouts"  from the payotus page), existing labels will be pre-populated.
*  Add the payout label to the address when the payoutis created instead of to the transaction when it is paid.
*  Add the label attachments when adding labels from an address to the transaction.
2023-04-07 15:58:41 +09:00
nicolas.dorier
91faf5756d With core lightning, getting payment by paymenthash wouldn't return the successful payment if the first one failed. 2023-04-07 15:54:56 +09:00
Andrew Camilleri
e239390ebf Allow any bolt11 invoice for pullpayments/payouts (#4857)
closes #4830

If users want to deal with expired payout destinations, then they should be able to conifugre it that way. Some wallets simply do not allow customizing the bolt11 expiry and the defaults are much less than a day. I think we should merge #3857 if we introduce this as an automated payotu processor for lightning running every few minutes would work together with this and solve it.
2023-04-06 15:54:19 +09:00
nicolas.dorier
b24764d679 bump 2023-04-05 23:38:39 +09:00
Nicolas Dorier
4d5a568fd7 Changelog 1.9 (#4852) 2023-04-05 23:37:01 +09:00
Andrew Camilleri
5ab55e71e0 Add cart items to receipt (#4851) 2023-04-05 15:42:23 +02:00
Wouter Samaey
929d63ecf8 Fixed nav menu active states + Better Fake Custodian (#4854) 2023-04-05 15:11:46 +02:00
Kukks
0ef7f3715f remove obsolete reference 2023-04-05 15:09:42 +02:00
Nicolas Dorier
2298f3901a Update langs (#4853) 2023-04-05 13:32:24 +09:00
nicolas.dorier
3005f1937a Replace GetUriByAction by GetPathByAction 2023-04-05 10:58:56 +09:00
nicolas.dorier
f48eec2e93 Fix: Redirecting to local absolute URL in wizard windows should work 2023-04-05 10:55:35 +09:00
nicolas.dorier
754d304e54 Fix tx link in Recent Transactions 2023-04-05 08:53:34 +09:00
Nicolas Dorier
9b8d08a668 Make Checkout V2 the default (#4850)
* Make Checkout V2 the default

* Fix align
2023-04-05 08:35:50 +09:00
Nicolas Dorier
1b672a1ace Allow multi-step settings in custodian (#4838)
* Allow multi-step settings in custodian

* Fix CustodianAccount.Name not saved

* Reuse TradeQuantity for SimulateTrade

* TradeQuantityJsonConverter accepts numerics

* Fix build
2023-04-04 14:48:29 +09:00
Andrew Camilleri
60d6e98c67 Form System Flexibility improvements (#4774)
* Introduce very flexible form input system

* Refactorings after rebase

* Test fix

* Update BTCPayServer/Forms/FormDataService.cs

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-04-04 11:01:34 +09:00
d11n
11f05285a1 Invoice Details: Improve payments list and print view (#4817)
Closes #4729.

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-04-04 10:59:14 +09:00
d11n
2bd1842da1 Improve pagination (#4828)
Based on the design repo updates by @benalleng in btcpayserver/btcpayserver-design#61. Closes #3835.
2023-04-04 10:54:04 +09:00
Nisaba
57544068e9 Store rule emails in HTML + Test rule emails (#4843)
* Store rule emails in HTML + Test rule emails

* Store rule emails in HTML + Test rule emails

* Update BTCPayServer/Controllers/UIStoresController.Email.cs

Thanks !

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

* change in StoreEmails() to work properly with add commande

* CanSetupEmailServer() : Remove Bosy sendkeys that doesn't work with new HTML control

* Update test for new HTML control

* better command treatment

---------

Co-authored-by: nisaba <infos@nisaba.solutions>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2023-04-04 10:52:42 +09:00
Nicolas Dorier
60cfea9f94 Add presets in the checkout appearance (#4756) 2023-04-04 10:45:40 +09:00
Andrew Camilleri
eece001376 Add labels for recent txs dashboard widget (#4831)
* Add labels for recent txs dashboard widget

It is not with the rich data for now, but a good start.

* Turn labels into links

* Add rich info to dashboard labels

* Use truncate-center component for recent transactions

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-03-30 19:54:11 +02:00
d11n
98d8ef8e1a Checkout Appearance: Move language settings into the general section (#4833)
The language settings work for both versions of the checkout, so let's make them available for both. Request by @petzsch.
2023-03-30 08:18:13 +02:00
Nicolas Dorier
0e43042217 Remove FakeCustodian as a separate project (#4839) 2023-03-30 12:38:30 +09:00
Andrew Camilleri
c8e6714207 Merge pull request #4784 from dennisreimann/unify-plugins-apps
Apps become plugins
2023-03-29 20:03:18 +02:00
Andrew Camilleri
824e779eb2 Hooks for Zaps (#4826) 2023-03-29 12:27:04 +02:00
d11n
83ea898780 Checkout: Update wording (#4829)
We make it "Payment Received" instead of "Payment Sent", because in other places we are also talking from the merchant perspective.
2023-03-29 17:24:49 +09:00
nicolas.dorier
5af3233fd6 Add link to forms doc 2023-03-28 22:02:58 +09:00
d11n
08ff2f3173 Checkout: Fix language select width in modal (#4823) 2023-03-28 13:12:47 +02:00
d11n
22657b66d7 NFC finetuning (#4822) 2023-03-28 12:36:32 +02:00
d11n
8b6c7a6061 Pull Payments: FIx column order in list (#4818)
Date and name were swapped. Fixes #4814.
2023-03-28 19:10:50 +09:00
Andrew Camilleri
1f197f6688 Merge pull request #4819 from dennisreimann/nfc 2023-03-28 09:19:58 +02:00
Dennis Reimann
1055e61bb4 NFC improvements
Two changes which fix #4807:

- Once permissions are granted we start scanning immediately, no need to ask for permissions or have the user click the button again
- We don't abort the scan, which gets rid of the cases in which the OS took over after the scan, because the user left the card on the device

Also adds feedback for the NFC states scanning and submitting.
2023-03-27 18:28:53 +02:00
Dennis Reimann
7ad0aa82fc Apps become plugins
Unified navigation for apps that are now plugins. Part of #4744.
2023-03-27 16:43:44 +02:00
Andrew Camilleri
d3f5576570 Remove store integrations list page (#4816)
Co-authored-by: d11n <mail@dennisreimann.de>
2023-03-27 16:40:50 +02:00
d11n
45141d1391 Checkout v2: Payment processing state (#4778) 2023-03-27 12:12:11 +02:00
d11n
de9ac9fd43 Receipt: Add payment proof (#4782)
* Receipt: Add payment proof

Closes #4685.

* shice

* Add truncate-center component

* Improve view

* Hide button and link when printed

* Describe component

* Remove transaction ID from UI

* Remove modification to interface

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-27 14:07:12 +09:00
d11n
c53d5272d6 Wallet Transactions Export: Add BIP-329 support (#4799)
* Wallet Transactions Export: Add BIP-329 support

* Adjust wording

* Export one line per label

* Join labels, fix type

* Rewrite the ProcessBip329 function to be more performant

* Add nullable on all TransactionsExport

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-27 13:59:33 +09:00
Pavlenex
18c78192ec Reconstruct issue template (#4803)
* reconstruct issue template

* provide a direct link for filing a tech question
2023-03-27 13:59:07 +09:00
nicolas.dorier
632d67eef4 Fix casing in template example for forms 2023-03-27 12:54:12 +09:00
d11n
c23aa48688 Optimize invoice print view (#4783)
Closes #4729.
2023-03-26 20:44:05 +09:00
d11n
95f3e429b4 Wallet transactions: Add label manager (#4796)
* Wallet transactions: Add label manager

* Update BTCPayServer/Views/UIWallets/WalletTransactions.cshtml

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

* Add rich label info

* Fixes

* support labels in wallet send

* add labels to tx info page

* Remove noscript parts

* Allow click on transaction label info

* update psbt info labelstyling

* revert red pixel fix as it broke all

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2023-03-26 20:42:38 +09:00
Chukwuleta Tobechi
8635fcfe84 UI: Redesign Recovery Seed view (#4793)
* Improve recovery seed backup page

* Fix errors from Selenium tests (Sequence contains no elements)

* Revert previous commit

* Improve recovery seed backup page

* Recovery phrase UI update

* recovery seed UI format

* Improve word order

* One column version

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-03-24 16:09:53 +01:00
Andrew Camilleri
d861537d9a Merge pull request #4809 from dennisreimann/gf-ln-array-fix
Greenfield: Fix Lightning transaction list return types
2023-03-23 18:59:36 +01:00
Dennis Reimann
631ee99f60 Greenfield: Fix Lightning transaction list return types
The LocalBTCPayServerClient deserializes the results as arrays (`LightningPaymentData[]` and `LightningInvoiceData[]`) — if they are `IEnumerable` the `GetFromActionResult` does not return the data but null.
2023-03-23 17:42:10 +01:00
nicolas.dorier
ffa1441ccd Delete code detecting whether the running version of nbx fixed a bug
The reason to delete this is that any version of NBX with this bug
wouldn't be able to run nowadays because of another bug which would
prevent NBXplorer from synching (Array size too big)
2023-03-23 13:45:40 +09:00
Andrew Camilleri
2f3e947027 Merge pull request #4795 from Kukks/lnurl-disable-if-no-node 2023-03-22 09:02:52 +01:00
Andrew Camilleri
a62aecfdfe Merge pull request #4798 from dennisreimann/fix-4794 2023-03-22 09:02:35 +01:00
Andrew Camilleri
5f829c68f2 Merge pull request #4797 from dennisreimann/fix-4790 2023-03-22 09:02:10 +01:00
Dennis Reimann
0290d74aeb POS: Fix escaped HTML entities in item title
Properly escapes and the sanitized values. Fixes #4794.
2023-03-21 15:31:54 +01:00
Dennis Reimann
f6bc16007d Label tooltips: Use plain text instead of HTML
Fixes #4790.
2023-03-21 15:21:24 +01:00
Dennis Reimann
ad5752f09b Reuse LightningTimeout constant 2023-03-21 14:22:10 +01:00
Kukks
55565f1718 Do not provide lnurl method if ln node is dead
fixes #3541
2023-03-21 13:48:25 +01:00
nicolas.dorier
5f96d17b8c Update lang 2023-03-20 19:30:56 +09:00
nicolas.dorier
fd22406e0a Fix PullTransifexTranslationsCore 2023-03-20 19:21:35 +09:00
nicolas.dorier
64fe542c1e Update lang 2023-03-20 19:20:46 +09:00
Andrew Camilleri
fae1dc8dbb Adapt cookie auth to work with same API permission system (#4595)
* Adapt cookie auth to work with same API permission system

* Handle unscoped store permission case

* Do not consider Unscoped as a valid policy

* Add tests

* Refactor permissions scopes

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-20 10:46:46 +09:00
Wouter Samaey
6f2b673021 Custodian withdrawal support + Some refactoring and cleanup (#4085)
* Renamed "WithdrawAsync" to "WithdrawToStoreWalletAsync"

* WIP

* WIP withdrawal + Refactored Form saving to JObject

* WIP

* Form to fix bad values during withdrawing appears correctly

* WIP

* Lots of cleanup and refactoring + Password field and toggle password view

* Cleanup + Finishing touches on withdrawals

* Added "Destination" dummy text as this is always the destination.

* Fixed broken test

* Added support for withdrawing using qty as a percentage if it ends with "%". Needs more testing.

* Fixed broken build

* Fixed broken build (2)

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

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

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

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

* Improved unit tests

* Fixed swagger bug

* Test improvements

Make string conversion of quantity explicitely.

* Fix build warnings

* Swagger: Add missing operationId

* Made change Dennis requested

* Removed unused file

* Removed incorrect comment

* Extra contructor

* Renamed client methods

* Cleanup config before saving

* Fixed broken controller

* Refactor custodian

* Fix build

* Make decimal fields strings to match the rest of Greenfield

* Improve parsing of % quantities

---------

Co-authored-by: d11n <mail@dennisreimann.de>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-20 10:45:32 +09:00
Nicolas Dorier
b26679ca14 Prevent people from starting with --sqlitefile or --mysql (#4772) 2023-03-20 10:40:48 +09:00
Nicolas Dorier
04ba1430ca Refactor plugin apps (#4780)
* Refactor plugins

* Add missing names to view models

* Cleanups

* Replace SalesAppBaseType by two interfaces

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-03-20 10:39:26 +09:00
d11n
53f3758abc Replace text in copy buttons with icon (#4764)
Closes #4699.
2023-03-19 21:43:38 +01:00
d11n
c6742f5533 Language selector: Ensure correct font-size (#4761)
* Language selector: Ensure correct font-size

Fixes the cut-off text on iOS, because somehow iOS uses a larger font-size by default.

* Fix select background color

Webkit-based browsers displayed transparent in a weird way.
2023-03-19 08:44:23 +01:00
d11n
cb44591a47 Derivation scheme parsing incorporates fingerprint and key path (#4781) 2023-03-17 14:35:30 +01:00
Wouter Samaey
e02abb509f Allow plugins to do something before and after automatic payouts (#4224) 2023-03-17 13:50:37 +01:00
Nicolas Dorier
eff6be9643 Remove mention of LNUrl-Withdraw when paying by NFC (#4779) 2023-03-17 12:24:27 +01:00
Andrew Camilleri
348dbd7107 Support Form Select option (#4726)
* Support Form Select option

* Add country select
2023-03-17 14:37:37 +09:00
Andrew Camilleri
f74ea14d8b Plugins can now build apps (#4608)
* Plugins can now build apps

* fix tests

* fixup

* pluginize existing apps

* Test fixes part 1

* Test fixes part 2

* Fix Crowdfund namespace

* Syntax

* More namespace fixes

* Markup

* Test fix

* upstream fixes

* Add plugin icon

* Fix nullable build warnings

* allow pre popualting app creation

* Fixes after merge

* Make link methods async

* Use AppData as parameter for ConfigureLink

* GetApps by AppType

* Use ConfigureLink on dashboard

* Rename method

* Add properties to indicate stats support

* Property updates

* Test fixes

* Clean up imports

* Fixes after merge

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-03-17 11:56:32 +09:00
d11n
a671632fde Dashboard: Fix app stats tiles (#4775)
* Dashboard: Fix app stats tiles

They broke with #4747, because they contain script blocks that are loaded asynchronuosly and need to get run once the chart data is added to the page.

* Refactor PoS dashboard component

* Collocate the component JS files in separate files

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-16 15:51:24 +09:00
Andrew Camilleri
e344622c9e Form quick fixes (#4759) 2023-03-15 10:23:33 +01:00
Nicolas Dorier
06d7483ca3 Remove obsolete cli argument 'plugin-remote' (#4773) 2023-03-15 09:06:06 +01:00
nicolas.dorier
7fe041fc2c Changelog 1.8.4 2023-03-15 10:45:49 +09:00
nicolas.dorier
3f18e5476a Error when indexing invoices with some field that are too long (Fix #4771) 2023-03-15 09:31:38 +09:00
d11n
2a31613fe8 Fix invoice paid after expiration icon 2023-03-14 14:54:01 +01:00
d11n
ded0c8a3bc Update price display (#4736)
* Update price display

As proposed by @dstrukt in #4364.

* Update format

* Unify price display across the app

* Add DisplayFormatter

* Replace DisplayFormatCurrency method

* Use symbol currency format for invoice

* Unify currency formats on backend pages

* Revert recent changes

* Do not show exchange rate and fiat order amount for crypto denominations

* Fix test and add test cases
2023-03-13 10:12:58 +09:00
d11n
f3d9e07c5e Checkout v2: Celebrate payment with confetti (#4727)
* Checkout v2: Celebrate payment with confetti

Have a colorful celebration for successful payments.

* Make it default and add test
2023-03-13 10:09:56 +09:00
d11n
eb3ba95114 Make CanUsePullPaymentsViaUI more robust (#4762)
Fixes this nasty flaky test failure:

```
Failed CanUsePullPaymentsViaUI [17 s]
  Error Message:
   Assert.Equal() Failure
           ↓ (pos 1)
Expected: payout
Actual:   pull-payment
           ↑ (pos 1)
  Stack Trace:
     at BTCPayServer.Tests.ChromeTests.CanUsePullPaymentsViaUI() in /source/BTCPayServer.Tests/SeleniumTests.cs:line 1622
```

Because there are actually two labels, the previous selector was dependent on the correct ordern, because it always chose the first one …
2023-03-13 10:02:07 +09:00
Andrew Camilleri
7951dcada6 make sure we have cors for all of greenfield (#4760)
fixes #4758
2023-03-10 15:20:11 +01:00
Andrew Camilleri
06951a39c6 fix API breaking changefor payout processors (#4757)
fixes #4752
2023-03-10 17:57:33 +09:00
d11n
abe29f21f0 Checkout v2: Option to display amount in Sats in BIP21 case (#4730) 2023-03-09 21:36:11 +01:00
d11n
f57eab3008 Store branding: Add complementing text and accent colors (#4746) 2023-03-09 21:34:15 +01:00
nicolas.dorier
6d4b2348ac Update changelog 2023-03-08 21:56:40 +09:00
d11n
397ca6ef0c Checkout v2: Minor UI updates (#4734)
* Checkout v2: Minor copy change

* Allow copying of invoice ID and order ID on results page

* Add copy icons for payment details on results view

* Add missing powered by class to store footers
2023-03-08 21:39:03 +09:00
d11n
d6e5ee2851 UI: Decrease content padding top on small screens (#4749)
If the viewport height is less than 800px, decrease the content padding top for breakpoints L and on.
2023-03-08 21:37:25 +09:00
Nicolas Dorier
98d62e826b Do not through missing-permission error when no store on /api/v1/stores (Close #4735) (#4748) 2023-03-08 21:36:51 +09:00
Nicolas Dorier
7b5ce8f70c CSP: Remove unsafe-eval when vue isn't used (#4747)
* CSP: Remove unsafe-eval when vue isn't used

* Prevent XSS injection via VueJS
2023-03-08 17:57:36 +09:00
nicolas.dorier
2010a9a458 bump 2023-03-07 10:29:18 +09:00
nicolas.dorier
f787058c17 Fix: Impossible to create invoice after migration from Sqlite (Close #4743) 2023-03-07 10:27:04 +09:00
Kukks
87ccae0d90 add missing docs of store payment method criteria 2023-03-05 14:40:18 +01:00
nicolas.dorier
07d95c6ed7 bump clightning 2023-03-05 11:08:01 +09:00
nicolas.dorier
514823f7d2 bump clightning 2023-03-04 21:39:49 +09:00
d11n
fb4feb24f3 Minor updates from design repo (#4733) 2023-03-04 09:36:30 +01:00
Nicolas Dorier
5caa0e0722 [Greenfield] Allow passing email instead of user id in API (#4732) 2023-03-03 21:24:27 +09:00
nicolas.dorier
0406b420c8 Do not create if create API key is called on a non-existant user (Fix #4731) 2023-03-03 20:30:54 +09:00
nicolas.dorier
9d72b9779e Update Changelog 2023-03-03 20:20:59 +09:00
nicolas.dorier
fdc47e4a38 Avoid crash when some plugins are installed (#4725) 2023-03-03 20:18:09 +09:00
nicolas.dorier
0566e964c0 Fix incorrect punctuation in translations 2023-03-03 17:32:46 +09:00
nicolas.dorier
896fbf9a5c Lang update 2023-03-03 16:38:39 +09:00
d11n
126c8c101e Re-add language selector (#4723)
Fixed version
2023-03-02 16:34:15 +09:00
nicolas.dorier
3cb7cc01e4 Update db-migration.md 2023-03-02 13:20:47 +09:00
nicolas.dorier
2b3d15bf45 Update Changelog.md 2023-03-01 23:50:35 +09:00
d11n
4049bdadcb Changelog v1.8 (#4670)
* Changelog v1.8

* Update Changelog.md

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

* Add fix

* Update changlelog

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-01 23:41:43 +09:00
d11n
2042ba37d8 Update What's New (#4713)
Links to https://blog.btcpayserver.org/btcpay-server-1-8-0/ which doesn't exist yet, that's why the linkcheck is expected to fail.
2023-03-01 23:41:27 +09:00
nicolas.dorier
41a4ba62b0 Remove lang popup in checkout v2 2023-03-01 17:10:54 +09:00
nicolas.dorier
21558d25b1 Do not show product information if there are no product information 2023-03-01 16:19:10 +09:00
Nicolas Dorier
06622bfbfd Translate Checkout v2 (#4710) 2023-03-01 15:49:21 +09:00
d11n
16fd2e3938 Greenfield: Show detailed Lightning routing error (#4722)
The implementations have more detailed messages for LN routing errors, which e.g. allow me to detect self-payemnts in LNbank. We pass those from the LN lib, so if we have it, we should display it to provide a better insight on what's going on when a rout cannot be found.
2023-03-01 15:46:48 +09:00
Umar Bolatov
040d7670ec Add currency code to payment request list (#4709)
https://github.com/btcpayserver/btcpayserver/discussions/4619
2023-03-01 15:46:13 +09:00
d11n
23761eacc1 Unset X-Frame-Options header correctly (#4721)
* Unset X-Frame-Options header correctly

According to the [spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) there are onlye the `DENY` and `SAMEORIGIN` options, `ALLOW-FROM` being deprecated. Hence we have to actively unset the header, as we made `DENY` the default.

This also unsets the X-Frame-Options header for the public form pages, which fixes #4666.

* Ignore anti forgery token in Forms

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-03-01 15:27:18 +09:00
nicolas.dorier
5790bed766 Do not crash sqlite migration if the db is create but without tables 2023-03-01 15:04:32 +09:00
d11n
2f88da67e8 Fix build warnings and indentation (#4720) 2023-03-01 10:32:48 +09:00
Andrew Camilleri
21091cbf1a show all plugins regardless of version (#4717) 2023-02-28 12:16:33 +01:00
nicolas.dorier
808949a884 Update langs 2023-02-26 21:07:23 +09:00
nicolas.dorier
06334273dc Fix crashing on unserialize of old data 2023-02-26 11:18:54 +09:00
nicolas.dorier
5399c04dff Fix crashing on unserialize of old data 2023-02-26 11:05:23 +09:00
d11n
cd051d4093 Update transaction label display (#4700)
* Update transaction label display

* Fix test
2023-02-26 11:01:46 +09:00
nicolas.dorier
0ca6e8ccfb bump 2023-02-26 00:20:55 +09:00
nicolas.dorier
bd075919f3 Improve publish docker script 2023-02-26 00:19:59 +09:00
Nicolas Dorier
c229425534 Remove JSON in strings from JObjects (#4703) 2023-02-25 23:34:49 +09:00
Andrew Camilleri
e89b1826ce add invoicemetadata as a tab (#4693)
* add invocie metadata as a tab

* Allow forms to add to posdata too in pos app

* Cleanup view

* Display additional information directly

* Update BTCPayServer/Views/Shared/PosData.cshtml

* Update BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-02-25 22:38:28 +09:00
d11n
4ef19e19cc Checkout v2 fixes (#4705)
* Prevent duplicate titles on invoice view

* Fix text display of escaped values

Fixes #4696.

* Fix payment details re-rendering

Closes #4683. Closes #4684.

* Cleanup
2023-02-25 22:28:02 +09:00
Kukks
ff58301729 do not require docker for plugin restart
we now have a more graceful restart mechanism specifically for plugins and most installs can handle this mechanism
2023-02-24 13:52:46 +01:00
Nicolas Dorier
4ae05272c3 Greenfield: Admins can create/delete API keys of any user (#4680)
* Greenfield: Admins can create/delete API keys of any user

* Greenfield: Improve doc for scoped apikey (Close #4673)

* Fix permissions hierarchy

* Update BTCPayServer.Client/Permissions.cs

* Fix tests

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2023-02-24 16:19:03 +09:00
d11n
d14dafc871 Apply branding to custom forms (#4697) 2023-02-23 14:35:29 +01:00
nicolas.dorier
022a077726 Use new transifex API on PullTransifexTranslations 2023-02-23 20:59:16 +09:00
d11n
d5bd86b07a POS: Align Keypad centered vertically (#4690) 2023-02-23 10:30:16 +01:00
d11n
66e1eee010 POS improvements (#4668) 2023-02-23 09:52:37 +01:00
Nicolas Dorier
ddb125f458 Fix: HTML injection in payment request/posData/receiptData (Close #4678) (#4679)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-02-22 16:35:34 +01:00
Andrew Camilleri
e6a157a101 Merge pull request #4686 from dennisreimann/noscript-fix 2023-02-22 14:01:13 +01:00
Dennis Reimann
0a437fba6a Do not show Vue components when there is no JavaScript enabled
Small fix, came across this while testing the noscript checkout version.
2023-02-22 13:34:51 +01:00
Andrew Camilleri
39f2e80dc1 Merge pull request #4677 from dennisreimann/fix-4663 2023-02-22 12:30:16 +01:00
Dennis Reimann
13f9eb0d18 Cleanups and unified wording 2023-02-22 11:20:50 +01:00
Dennis Reimann
575b829799 Fix LNURL-Withdraw payments
Fixes comparisons of `long` and `LightMoney`, which did not work, because the `amount` provided was in sats and Lightmoney compares to millisats.

Closes #4663.
2023-02-22 11:18:26 +01:00
nicolas.dorier
02e50fadae Fix: Crash during migration on some SQLite instances (Close #4623) 2023-02-22 17:07:27 +09:00
nicolas.dorier
a02f191034 Fix: Crash during migration on some SQLite instances (Close #4623) 2023-02-22 16:55:19 +09:00
d11n
d73d0f178f Checkout: Allow NFC/LNURL-W whenever LNURL is available (#4671)
* Checkout: Allow NFC/LNURL-W whenever LNURL is available

With what we have in master right now, we display NFC only for top-up invoices. With these changes, we display NFC in all cases, where LNURL is available.

Note that this hides LNURL from the list of selectable payment methods, it's only available to use the NFC — and explicitely selectable only for the edge case of top-up invoice + non-unified QR (as before).

Rationale: Now that we got NFC tightly integrated, it doesn't make sense to support the NFC experience only for top-up invoices. With this we bring back LNURL for regular invoices as well, but don't make it selectable and use it only for the NFC functionality.

* Fix LNURL condition

* Improve and test NFC/LNURL display condition

Restores what was fixed in #4660.

* Fix and test Lightning-only case

* Add cache busting for locales
2023-02-22 15:53:14 +09:00
Andrew Camilleri
d542a61f5a Fix missing walletchanged event and add storeremoved event (#4676) 2023-02-22 13:13:58 +09:00
Andrew Camilleri
e0486aaa24 Label Manager component (#4594)
* Label Manager component

closes #4464

* UI updates

* Test fix

* add test

* fix warnings

* fix select update bug

* add test

* fix test

* Increase payment box max-width

* add labels from address to tx on detection

* Exclude well known label from the dropdown

* Add test on transaction label attachement, tighten UpdateLabels method to only update address labels

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-02-22 11:47:02 +09:00
Andrew Camilleri
02bf76fb3c Merge pull request #4554 from Kukks/gf/pmcriteria 2023-02-21 16:03:29 +01:00
Kukks
8a3ece4a70 add test 2023-02-21 15:31:11 +01:00
Kukks
c553dc02a9 Greenfield: Expose Payment method criteria 2023-02-21 15:23:51 +01:00
d11n
ff71caa47e Upgrade Lightning lib (#4674)
To include the fix from btcpayserver/BTCPayServer.Lightning#122
2023-02-21 16:00:10 +09:00
Nicolas Dorier
2bd8227e20 Start using JSONB column instead of app side compressed data (#4574) 2023-02-21 15:06:34 +09:00
d11n
5c61de3ae9 Different icons for notifications (#4632)
* Different icons for notifications

Closes #2510.

* Fix version appendix for SVG use attributes

* Fix SVGUse TagHelper

* Update icons
2023-02-21 11:06:27 +09:00
d11n
cff46f2d59 UI: Remove highlight for valid fields (#4669) 2023-02-20 19:23:09 +01:00
Andrew Camilleri
bbbaacc350 Generic Forms (#4561)
* Custom Forms

* Update BTCPayServer.Data/Migrations/20230125085242_AddForms.cs

* Cleanups

* Explain public form

* Add store branding

* Add form name to POS form

* add tests

* fix migration

* Minor cleanups

* Code improvements

* Add form validation

Closes #4317.

* Adapt form validation for Bootstrap 5

* update logic for forms

* pr changes

* Minor code cleanup

* Remove unused parameters

* Refactor Form data handling to avoid O(n3) issues

* Rename Hidden to Constant

* Pre-populate FormView from the query string params

* Fix test

---------

Co-authored-by: d11n <mail@dennisreimann.de>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-02-20 19:35:54 +09:00
d11n
60f84d5e30 Display "Pay by LNURL" only when appropriate (#4660)
Closes #4657.
2023-02-20 08:49:10 +09:00
d11n
5218aa3c43 Fix missing style tag around embedded CSS (#4659) 2023-02-18 20:38:02 +09:00
nicolas.dorier
4b2ea0c0c3 Fix: Should not crash with command line arg --help 2023-02-16 18:32:26 +09:00
Nicolas Dorier
9b865ef849 Fix build and run scripts (#4655) 2023-02-16 18:31:33 +09:00
nicolas.dorier
9344113ae4 Version bump 2023-02-16 17:46:27 +09:00
Nicolas Dorier
4448ac9d2a Changelog 1.7.12 (#4653) 2023-02-16 17:45:51 +09:00
Dennis Reimann
9aff143d40 UI: Fix standalone confirmation modal 2023-02-15 16:17:22 +01:00
Nicolas Dorier
fc14f418cb Fix: Setting the password of a new created user via API shouldn't be required (Close #4534) (#4647) 2023-02-15 17:11:39 +09:00
Nicolas Dorier
b99253ff47 Revert "Fix: Setting the password of a new created user via API shouldn't be required (Close #4534) (#4645)" (#4646)
This reverts commit 9cb844cbbb.
2023-02-15 16:32:36 +09:00
Nicolas Dorier
9cb844cbbb Fix: Setting the password of a new created user via API shouldn't be required (Close #4534) (#4645) 2023-02-15 16:32:03 +09:00
Nicolas Dorier
285aedef2f Fix: If user get locked out, unlocking or deleting user fails (Fix #4641) (#4644)
This is due to the fact our UserService is a singleton, and it had a
reference on UserManager which is scoped.

UserManager is caching user entities at the scope level.
UserService then had a view completely unsynchronized with the database.
2023-02-15 16:00:52 +09:00
nicolas.dorier
5121d64022 Fix: Migrating from SQLite was crashing in some conditions (Close #4623) 2023-02-15 15:59:45 +09:00
nicolas.dorier
8b80910d70 Fix: Unable to Edit amount when cloning paid Payment Request (Close #4639) 2023-02-15 15:33:26 +09:00
nicolas.dorier
a5ff655eed Fix: If user get locked out, unlocking or deleting user fails
This is due to the fact our UserService is a singleton, and it had a
reference on UserManager which is scoped.

UserManager is caching user entities at the scope level.
UserService then had a view completely unsynchronized with the database.
2023-02-15 14:28:34 +09:00
d11n
cc9c63c33e Add list count to user preferences cookie (#4637)
I think it's fair to assume that the user wants to set this as a preference and it fixes #4592.
2023-02-15 11:04:17 +09:00
Ikko Eltociear Ashimine
87eef72289 fix typo in vaultbridge.ui.js (#4640)
targetting -> targeting
2023-02-14 19:20:03 +01:00
d11n
8e8ba3d052 Webhook: Add missing model validation (#4636)
Fixes #4628.
2023-02-14 22:37:35 +09:00
Nicolas Dorier
fea27b900c Harden file type inputs (#4635) 2023-02-14 17:03:12 +09:00
d11n
7ad91a76cd Checkout v2: FIx automatic redirect after paid (#4633) 2023-02-14 08:56:00 +09:00
nicolas.dorier
a62b674722 bump 2023-02-13 23:40:32 +09:00
nicolas.dorier
350f35b08d Add code comment 2023-02-13 23:39:55 +09:00
Nicolas Dorier
f405321abc Changelog 1.7.11 (#4631) 2023-02-13 23:35:33 +09:00
Andrew Camilleri
0d077f6ce5 Fix lnurl for pull paymentdescription + fix authorize redirect form issue (#4630)
fixes #4627
fixes #4624
2023-02-13 23:34:43 +09:00
Nicolas Dorier
dffa6accb0 Fix XSS: Stenghten CSP rules on static file uploads (#4629) 2023-02-13 23:04:15 +09:00
Andrew Camilleri
b5abcd5ae5 Merge pull request #4622 from dennisreimann/domain-mapping 2023-02-13 13:41:34 +01:00
dstrukt
72a9e676c1 Feature Descriptor (#3831) 2023-02-13 09:25:24 +01:00
Nicolas Dorier
3658b396d3 Update db-migration.md 2023-02-11 21:04:43 +09:00
Nicolas Dorier
537acab16d Update db-migration.md 2023-02-11 21:04:28 +09:00
nicolas.dorier
8c6fe91c71 After successful migration from SQLite or MySql, there is an error after a restart 2023-02-11 21:01:36 +09:00
Dennis Reimann
3c344331af Improve domain mapping constraint
- Fix potential double assignment to appId, leading to an [exception](https://pastebin.com/j8dhtcTE)
- Add port to redirect, which makes it work in dev env
2023-02-10 18:15:54 +01:00
d11n
d14ce2a37f POS: Improve Keypad view (#4596)
* UI updates

* Updates modes and calculation

* Unify tip buttons

* White caret

* Add top margin to calculation

* Add space between mode buttons and keypad

* Discount updates
2023-02-10 16:26:38 +01:00
d11n
33d272d4b0 Crowdfund: View updates (#4588)
* Crowdfund: View updates

Improve store branding and remove the card styles, because they had borders which seemed like visual clutter. Other than that I made some changes to the header section and cleaned up the markup and indentation.

* adds column spacing + details header

* Move the Featured Image input

* Center align the Last Updated

* Remove store header, update header section

* Bump description font size

* Improve perk display

* Improve details section

* Fix main image display

---------

Co-authored-by: dstrukt <gfxdsign@gmail.com>
2023-02-10 16:26:09 +01:00
Andrew Camilleri
57f5c15670 Merge pull request #4620 from btcpayserver/Kukks-patch-1 2023-02-10 09:45:30 +01:00
Andrew Camilleri
487faa69c6 Bump version 2023-02-10 09:40:03 +01:00
Kukks
9148a1e564 fix #4570 2023-02-10 09:10:10 +01:00
Kukks
4c3f5e1e1a fix #4618 2023-02-10 08:59:18 +01:00
nicolas.dorier
b0bf0824dd Update db migration doc 2023-02-10 15:48:43 +09:00
nicolas.dorier
dea5991e01 bump 2023-02-10 11:53:17 +09:00
nicolas.dorier
739932a280 Changelog 2023-02-10 11:52:37 +09:00
Nicolas Dorier
1f8bc5b490 Add ability to migrate from MySQL and SQLite with EF (#4614) 2023-02-10 11:43:46 +09:00
d11n
753ffd401b BIP21 + LNURL fix (#4616)
In case of the unified invoice, the LNURL wasn't correct — with this change we are simply reusing th one that was issued on invoice creation instead of generating it anew on the fly.

Also fixes missing uppercasing for the QR code in case of non-unified QR.
And removes the `lightning:` scheme from the LNURL that's displayed to the user (unifies it with what we do for Onchain and Lightning)
2023-02-10 11:23:48 +09:00
nicolas.dorier
0d1bab45a0 Fix MySQL migrations 2023-02-10 11:17:52 +09:00
Andrew Camilleri
17cc439de3 Merge pull request #4617 from dennisreimann/lnurl-cors
LNURL: Add missing CORS
2023-02-09 20:34:44 +01:00
Dennis Reimann
5d03e300fb LNURL: Add missing CORS
In addition to #4587. Closes #4615.
2023-02-09 17:45:09 +01:00
nicolas.dorier
a20408bed1 Fix crash for installations supporting zcash or monero (#4610) 2023-02-09 09:49:08 +09:00
nicolas.dorier
ed0ccd6f13 Bump lightning lib, fix warnings 2023-02-08 21:29:20 +09:00
nicolas.dorier
bb1138efb5 Catch exception when creating new invoice with GetLNURL 2023-02-08 20:45:05 +09:00
nicolas.dorier
82b36aaca7 bump 2023-02-08 20:20:56 +09:00
Nicolas Dorier
688044429e Changelog 1.7.6 (#4606) 2023-02-08 20:14:51 +09:00
Nicolas Dorier
7bbfc8e6d4 Greenfield: Currency rate should be strings (#4607) 2023-02-08 19:18:37 +09:00
Andrew Camilleri
85513aa5c3 Make NFC built in (#4541)
* Make NFC built int

* support checkout v2

* uninstall old plugin

* fix lnurl in unified checkout

* fix tests

* fix tests

* fix old checkout unified qr

* clean up and make nfc submission more sturdy

* support topup invoices for lnurlw

* fix test

* Payment URI fixes

* Fix LNURL exclusion cases

* UI updates

* Adapt test

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-02-08 15:47:38 +09:00
ndeet
67254cc30c Website is nullable. (#4604) 2023-02-07 12:04:13 +01:00
Umar Bolatov
b055844973 Greenfield get app details (#4102)
* Add Greenfield API endpoints for retrieving app details

* add app items details

* Add GetPosApp and GetCrowdfundApp to LocalBTCPayServerClient

* Simplify POS app data items example

* Document app type enum

* make "RequiresRefundEmail" nullable

* remove "Template" and "PerksTemplate" fields
2023-02-07 17:01:53 +09:00
nicolas.dorier
9ba03848f2 Small perf improvement when fetching exactly 1 payout by id 2023-02-07 16:53:44 +09:00
Andrew Camilleri
5b96ab89fd Fix Payout Mark Paid in UI (#4585)
fixes #4579
2023-02-07 16:51:20 +09:00
Nicolas Dorier
6a4d8f7404 Fix: Payjoin wasn't always properly choosing utxo for optimal change (#4600)
* Fix: Payjoin wasn't always properly choosing utxo for optimal change

* Update with timeout
2023-02-07 16:43:31 +09:00
Nicolas Dorier
219d03b8dd Fix: If PoS item code contains a /, LNUrl would not work (fix #4601) (#4602)
This is caused by a weird buggy behavior from ASP.NET Core concerning
path value decoding. (More information on
https://github.com/dotnet/aspnetcore/issues/14170#issuecomment-533342396)

This hasn't been fixed for a while, and the dotnet team keeps reporting
it over and over.
2023-02-06 18:17:17 +09:00
d11n
523654f2eb Upgrade Lightning lib (#4589)
Includes the updates from btcpayserver/BTCPayServer.Lightning#121.
2023-02-06 13:26:11 +09:00
nicolas.dorier
b9b8cb9f63 Fix test 2023-02-06 12:38:18 +09:00
Dennis Reimann
94f2cd4257 Server Theme: Allow to unset CustomThemeCssUri
It is necessary to have a way to unset the deprecated variable, to get access to the new way of uploading a theme file.
2023-02-04 17:24:19 +01:00
d11n
26248774c2 App domain redirect (#4391)
* Fix duplicates in GetAllApps with allowNoUser

* Use domain mapping as canonical reference and redirect to it

* Revert domain mapping to hostname instead of URL
2023-02-02 20:53:42 +09:00
Kukks
99299ba06f bump nbx 2023-02-02 10:15:04 +01:00
Kukks
6e42eaa26c fix plugin packer when relative path provided 2023-02-02 10:15:03 +01:00
Nicolas Dorier
ae7b621e3d Change Stores.DerivationStrategies to JSONB (#4573)
Gradually changing column from blobs to JSONB allow us to
later take advantages of more powerful queries on postgres.
2023-02-02 09:43:39 +09:00
Nicolas Dorier
f2ced20c42 Fix a bunch of open redirect (#4575) 2023-02-02 09:42:58 +09:00
d11n
e4f256d5cd Lightning Address: Fix availability check and refactor option view (#4584)
As @petzsch rightfully assumed in #4578, the check took only the last available LN payment method into account, which in this case was LN on LTC. We now pass the crypto code as well and I refactored the checks as well as the option view.

Fixes #4578.
2023-02-02 09:42:41 +09:00
Nicolas Dorier
ca1dac4cc3 Add missing CORS to ln address of BTCPay (Compatibility Beach Wallet) (#4587) 2023-02-02 09:40:31 +09:00
Andrew Camilleri
8fc2729fab Merge pull request #4580 from dennisreimann/disqus-csp
Fixes https://github.com/btcpayserver/btcpayserver/issues/4572
2023-02-01 13:12:11 +01:00
Dennis Reimann
24c19efd52 Crowdfund: Add CSP rules for Disqus
Fixes #4572.
2023-01-31 22:13:45 +01:00
Kukks
a3edd829a6 Add onchain wallet send ui extension 2023-01-31 16:49:41 +01:00
nicolas.dorier
9ec475fa40 Fix PluginPacker crash 2023-01-31 23:24:20 +09:00
Andrew Camilleri
aad06c583e Greenfield: Add store rates api (#4550)
* Add store rates api

* Improve doc

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-01-31 14:42:25 +09:00
d11n
f821e35cb0 Store branding: Unify public pages (#4568)
Closes #3842.
2023-01-30 09:23:49 +01:00
nicolas.dorier
14313291d5 Make test less flaky by ordering GetApps query 2023-01-30 17:17:35 +09:00
Nicolas Dorier
b818352a04 Register rate providers in DI, so it can be accessed by plugins (#4551) 2023-01-30 09:46:12 +09:00
Umar Bolatov
c0c34fbb41 Add GreenField endpoint for fetching all apps (#4462)
Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-01-30 09:42:24 +09:00
Kukks
b372dc21d6 handle non built in tag attachments better 2023-01-27 15:44:21 +01:00
Nicolas Dorier
3d576cd06b Fix XSS on uploaded files to the file storage (#4567) 2023-01-26 19:12:06 +09:00
Umar Bolatov
438dcc4c6f Add Greenfield API endpoint for pull payment LNURL items (#4472)
* Add Greenfield API endpoint for pull payment LNURL items

close #4365

* Rename GetLNURLs to GetPullPaymentLNURL

* update "ln-url-not-supported" to "lnurl-not-supported"

* remove hardcoding of "BTC"

* update "PullPayments_LNURL" to "PullPayments_GetPullPaymentLNURL"

* update description of 400 status code response

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-01-26 14:43:07 +09:00
d11n
de4ac2c830 Greenfield: Add payments list endpoint (#4407)
Based on btcpayserver/BTCPayServer.Lightning#109
2023-01-26 13:22:49 +09:00
Andrew Camilleri
f46443a5e3 Add additional permission for pull payments (#4539)
* Add additional permission for pull payments

* Apply suggestions from code review
2023-01-26 09:46:05 +09:00
d11n
69e90b7ff1 Point of Sale: Improve merchant view (#4560)
* Point of Sale: Improve merchant view

Closes #3843.

* Trim bottom section

* Display App Name and Display Title next to each other

* Update views
2023-01-26 09:27:31 +09:00
Andrew Camilleri
5089ec9826 Merge pull request #4527 from dennisreimann/branding-new 2023-01-25 15:11:08 +01:00
Dennis Reimann
4df2f1f756 Store Branding: Add custom CSS option 2023-01-24 13:24:17 +01:00
672 changed files with 20428 additions and 8141 deletions

View File

@@ -1,8 +1,14 @@
blank_issues_enabled: true
contact_links:
- name: 🚀 Discussions
url: https://github.com/btcpayserver/btcpayserver/discussions
about: Technical discussions, questions and feature requests
- name: 💡 Request a feature
url: https://github.com/btcpayserver/btcpayserver/discussions/categories/ideas-feature-requests
about: Submit a feature request or vote on ideas posted by others. Features with most upvotes become roadmap candidates
- name: 🧑‍💻 Ask a technical question
url: https://github.com/btcpayserver/btcpayserver/discussions/new?category=technical-support
about: If you're experiencing a technical problem post it to our community support forum
- name: 🔌 Report a problem with a plugin
url: https://github.com/btcpayserver/btcpayserver/discussions/new?category=plugins-integrations
about: Experiencing a problem with a third-party plugin? Post it here and we will tag their developers to assist
- name: 📝 Official Documentation
url: https://docs.btcpayserver.org
about: Check our documentation for answers to common questions

View File

@@ -1,21 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Pack Test Plugin" type="DotNetProject" factoryName=".NET Project" singleton="false">
<option name="EXE_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1/BTCPayServer.PluginPacker.dll" />
<option name="PROGRAM_PARAMETERS" value="../../../../BTCPayServer.Plugins.Test\bin\Debug\netcoreapp3.1 BTCPayServer.Plugins.Test &quot;../../../../Packed Plugins&quot;" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<method v="2">
<option name="Build" default="false" projectName="BTCPayServer.Plugins.Test" projectPath="C:\Git\btcpayserver\Plugins\BTCPayServer.Plugins.Test\BTCPayServer.Plugins.Test.csproj" />
<option name="Build" />
</method>
</configuration>
</component>

View File

@@ -1,3 +1,5 @@
using System.IO;
namespace BTCPayServer.Configuration
{
public class DataDirectories
@@ -7,5 +9,12 @@ namespace BTCPayServer.Configuration
public string TempStorageDir { get; set; }
public string StorageDir { get; set; }
public string TempDir { get; set; }
public string ToDatadirFullPath(string path)
{
if (Path.IsPathRooted(path))
return path;
return Path.Combine(DataDir, path);
}
}
}

View File

@@ -19,6 +19,8 @@ namespace BTCPayServer.Abstractions.Contracts
public class NotificationViewModel
{
public string Id { get; set; }
public string Identifier { get; set; }
public string Type { get; set; }
public DateTimeOffset Created { get; set; }
public string Body { get; set; }
public string ActionLink { get; set; }

View File

@@ -5,4 +5,8 @@ public class AssetBalancesUnavailableException : CustodianApiException
public AssetBalancesUnavailableException(System.Exception e) : base(500, "asset-balances-unavailable", $"Cannot fetch the asset balances: {e.Message}", e)
{
}
public AssetBalancesUnavailableException(string errorMsg) : base(500, "asset-balances-unavailable", $"Cannot fetch the asset balances: {errorMsg}")
{
}
}

View File

@@ -10,7 +10,7 @@ public class CustodianApiException : Exception
HttpStatus = httpStatus;
Code = code;
}
public CustodianApiException(int httpStatus, string code, string message) : this(httpStatus, code, message, null)
{
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using BTCPayServer.Client.Models;
using BTCPayServer.JsonConverters;
namespace BTCPayServer.Abstractions.Custodians.Client;
public class SimulateWithdrawalResult
{
public string PaymentMethod { get; }
public string Asset { get; }
public decimal MinQty { get; }
public decimal MaxQty { get; }
public List<LedgerEntryData> LedgerEntries { get; }
// Fee can be NULL if unknown.
public decimal? Fee { get; }
public SimulateWithdrawalResult(string paymentMethod, string asset, List<LedgerEntryData> ledgerEntries,
decimal minQty, decimal maxQty)
{
PaymentMethod = paymentMethod;
Asset = asset;
LedgerEntries = ledgerEntries;
MinQty = minQty;
MaxQty = maxQty;
}
}

View File

@@ -5,9 +5,14 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Abstractions.Custodians;
/// <summary>
/// Interface for custodians that can move funds to the store wallet.
/// </summary>
public interface ICanWithdraw
{
public Task<WithdrawResult> WithdrawAsync(string paymentMethod, decimal amount, JObject config, CancellationToken cancellationToken);
public Task<WithdrawResult> WithdrawToStoreWalletAsync(string paymentMethod, decimal amount, JObject config, CancellationToken cancellationToken);
public Task<SimulateWithdrawalResult> SimulateWithdrawalAsync(string paymentMethod, decimal qty, JObject config, CancellationToken cancellationToken);
public Task<WithdrawResult> GetWithdrawalInfoAsync(string paymentMethod, string withdrawalId, JObject config, CancellationToken cancellationToken);

View File

@@ -1,3 +1,4 @@
#nullable enable
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -20,7 +21,6 @@ public interface ICustodian
*/
Task<Dictionary<string, decimal>> GetAssetBalancesAsync(JObject config, CancellationToken cancellationToken);
public Task<Form.Form> GetConfigForm(JObject config, string locale,
CancellationToken cancellationToken = default);
public Task<Form.Form> GetConfigForm(JObject config, CancellationToken cancellationToken = default);
}

View File

@@ -7,6 +7,10 @@ namespace BTCPayServer.Abstractions.Extensions;
public static class GreenfieldExtensions
{
public static IActionResult UserNotFound(this ControllerBase ctrl)
{
return ctrl.CreateAPIError(404, "user-not-found", "The user was not found");
}
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
{
return controller.UnprocessableEntity(modelState.ToGreenfieldValidationError());

View File

@@ -12,7 +12,7 @@ public class Field
{
public static Field Create(string label, string name, string value, bool required, string helpText, string type = "text")
{
return new Field()
return new Field
{
Label = label,
Name = name,
@@ -26,14 +26,14 @@ public class Field
// The name of the HTML5 node. Should be used as the key for the posted data.
public string Name;
public bool Hidden;
public bool Constant;
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
// HTML5 compatible type string like "text", "textarea", "email", "password", etc.
public string Type;
public static Field CreateFieldset()
{
return new Field() { Type = "fieldset" };
return new Field { Type = "fieldset" };
}
// The value field is what is currently in the DB or what the user entered, but possibly not saved yet due to validation errors.
@@ -55,7 +55,7 @@ public class Field
public List<Field> Fields { get; set; } = new();
// The field is considered "valid" if there are no validation errors
public List<string> ValidationErrors = new List<string>();
public List<string> ValidationErrors = new();
public virtual bool IsValid()
{

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;
using Npgsql.Internal.TypeHandlers.GeometricHandlers;
namespace BTCPayServer.Abstractions.Form;
@@ -20,6 +22,7 @@ public class Form
return JObject.FromObject(this, CamelCaseSerializerSettings.Serializer).ToString(Newtonsoft.Json.Formatting.Indented);
}
#nullable restore
// Messages to be shown at the top of the form indicating user feedback like "Saved successfully" or "Please change X because of Y." or a warning, etc...
public List<AlertMessage> TopMessages { get; set; } = new();
@@ -29,129 +32,110 @@ public class Form
// Are all the fields valid in the form?
public bool IsValid()
{
if (TopMessages?.Any(t => t.Type == AlertMessage.AlertMessageType.Danger) is true)
return false;
return Fields.Select(f => f.IsValid()).All(o => o);
}
public Field GetFieldByName(string name)
public Field GetFieldByFullName(string fullName)
{
return GetFieldByName(name, Fields, null);
}
private static Field GetFieldByName(string name, List<Field> fields, string prefix)
{
prefix ??= string.Empty;
foreach (var field in fields)
foreach (var f in GetAllFields())
{
var currentPrefix = prefix;
if (!string.IsNullOrEmpty(field.Name))
{
currentPrefix = $"{prefix}{field.Name}";
if (currentPrefix.Equals(name, StringComparison.InvariantCultureIgnoreCase))
{
return field;
}
currentPrefix += "_";
}
var subFieldResult = GetFieldByName(name, field.Fields, currentPrefix);
if (subFieldResult is not null)
{
return subFieldResult;
}
if (f.FullName == fullName)
return f.Field;
}
return null;
}
public List<string> GetAllNames()
public IEnumerable<(string FullName, List<string> Path, Field Field)> GetAllFields()
{
return GetAllNames(Fields);
}
private static List<string> GetAllNames(List<Field> fields)
{
var names = new List<string>();
foreach (var field in fields)
HashSet<string> nameReturned = new();
foreach (var f in GetAllFieldsCore(new List<string>(), Fields))
{
string prefix = string.Empty;
if (!string.IsNullOrEmpty(field.Name))
{
names.Add(field.Name);
prefix = $"{field.Name}_";
}
if (field.Fields.Any())
{
names.AddRange(GetAllNames(field.Fields).Select(s => $"{prefix}{s}"));
}
}
return names;
}
public void ApplyValuesFromOtherForm(Form form)
{
foreach (var fieldset in Fields)
{
foreach (var field in fieldset.Fields)
{
field.Value = form
.GetFieldByName(
$"{(string.IsNullOrEmpty(fieldset.Name) ? string.Empty : fieldset.Name + "_")}{field.Name}")
?.Value;
}
var fullName = string.Join('_', f.Path.Where(s => !string.IsNullOrEmpty(s)));
if (!nameReturned.Add(fullName))
continue;
yield return (fullName, f.Path, f.Field);
}
}
public void ApplyValuesFromForm(IFormCollection form)
public bool ValidateFieldNames(out List<string> errors)
{
var names = GetAllNames();
foreach (var name in names)
errors = new List<string>();
HashSet<string> nameReturned = new();
foreach (var f in GetAllFieldsCore(new List<string>(), Fields))
{
var field = GetFieldByName(name);
if (field is null || !form.TryGetValue(name, out var val))
var fullName = string.Join('_', f.Path.Where(s => !string.IsNullOrEmpty(s)));
if (!nameReturned.Add(fullName))
{
errors.Add($"Form contains duplicate field names '{fullName}'");
continue;
}
field.Value = val;
}
return errors.Count == 0;
}
public Dictionary<string, object> GetValues()
IEnumerable<(List<string> Path, Field Field)> GetAllFieldsCore(List<string> path, List<Field> fields)
{
return GetValues(Fields);
}
private static Dictionary<string, object> GetValues(List<Field> fields)
{
var result = new Dictionary<string, object>();
foreach (Field field in fields)
foreach (var field in fields)
{
var name = field.Name ?? string.Empty;
if (field.Fields.Any())
List<string> thisPath = new(path.Count + 1);
thisPath.AddRange(path);
if (!string.IsNullOrEmpty(field.Name))
{
var values = GetValues(fields);
values.Remove(string.Empty, out var keylessValue);
thisPath.Add(field.Name);
yield return (thisPath, field);
}
result.TryAdd(name, values);
if (keylessValue is not Dictionary<string, object> dict)
continue;
foreach (KeyValuePair<string, object> keyValuePair in dict)
foreach (var child in field.Fields)
{
if (field.Constant)
child.Constant = true;
foreach (var descendant in GetAllFieldsCore(thisPath, field.Fields))
{
result.TryAdd(keyValuePair.Key, keyValuePair.Value);
yield return descendant;
}
}
else
}
}
public void ApplyValuesFromForm(IEnumerable<KeyValuePair<string, StringValues>> form)
{
var values = form.GroupBy(f => f.Key, f => f.Value).ToDictionary(g => g.Key, g => g.First());
foreach (var f in GetAllFields())
{
if (f.Field.Constant || !values.TryGetValue(f.FullName, out var val))
continue;
f.Field.Value = val;
}
}
public void SetValues(JObject values)
{
var fields = GetAllFields().ToDictionary(k => k.FullName, k => k.Field);
SetValues(fields, new List<string>(), values);
}
private void SetValues(Dictionary<string, Field> fields, List<string> path, JObject values)
{
foreach (var prop in values.Properties())
{
List<string> propPath = new List<string>(path.Count + 1);
propPath.AddRange(path);
propPath.Add(prop.Name);
if (prop.Value.Type == JTokenType.Object)
{
result.TryAdd(name, field.Value);
SetValues(fields, propPath, (JObject)prop.Value);
}
else if (prop.Value.Type == JTokenType.String)
{
var fullName = string.Join('_', propPath.Where(s => !string.IsNullOrEmpty(s)));
if (fields.TryGetValue(fullName, out var f) && !f.Constant)
f.Value = prop.Value.Value<string>();
}
}
return result;
}
}

View File

@@ -114,6 +114,11 @@ namespace BTCPayServer.Security
_Policies.Add(policy);
}
public void UnsafeEval()
{
Add("script-src", "'unsafe-eval'");
}
public IEnumerable<ConsentSecurityPolicy> Rules => _Policies;
public bool HasRules => _Policies.Count != 0;

View File

@@ -23,10 +23,19 @@ public class SVGUse : UrlResolutionTagHelper2
{
_fileVersionProvider = fileVersionProvider;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var attr = output.Attributes["href"].Value.ToString();
attr = _fileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, attr);
var symbolIndex = attr!.IndexOf("#", StringComparison.InvariantCulture);
var start = attr.IndexOf("~", StringComparison.InvariantCulture) + 1;
var length = (symbolIndex != -1 ? symbolIndex : attr.Length) - start;
var filePath = attr.Substring(start, length);
if (!string.IsNullOrEmpty(filePath))
{
var versioned = _fileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, filePath);
attr = attr.Replace(filePath, versioned);
}
output.Attributes.SetAttribute("href", attr);
base.Process(context, output);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -22,6 +23,15 @@ namespace BTCPayServer.Client
return await HandleResponse<ApiKeyData>(response);
}
public virtual async Task<ApiKeyData> CreateAPIKey(string userId, CreateApiKeyRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{userId}/api-keys",
bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<ApiKeyData>(response);
}
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
@@ -35,5 +45,14 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task RevokeAPIKey(string userId, string apikey, CancellationToken token = default)
{
if (apikey == null)
throw new ArgumentNullException(nameof(apikey));
if (userId is null)
throw new ArgumentNullException(nameof(userId));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{userId}/api-keys/{apikey}", null, HttpMethod.Delete), token);
await HandleResponse(response);
}
}
}

View File

@@ -52,6 +52,44 @@ namespace BTCPayServer.Client
return await HandleResponse<AppDataBase>(response);
}
public virtual async Task<AppDataBase[]> GetAllApps(string storeId, CancellationToken token = default)
{
if (storeId == null)
throw new ArgumentNullException(nameof(storeId));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/apps",
method: HttpMethod.Get), token);
return await HandleResponse<AppDataBase[]>(response);
}
public virtual async Task<AppDataBase[]> GetAllApps(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/apps",
method: HttpMethod.Get), token);
return await HandleResponse<AppDataBase[]>(response);
}
public virtual async Task<PointOfSaleAppData> GetPosApp(string appId, CancellationToken token = default)
{
if (appId == null)
throw new ArgumentNullException(nameof(appId));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/apps/pos/{appId}",
method: HttpMethod.Get), token);
return await HandleResponse<PointOfSaleAppData>(response);
}
public virtual async Task<CrowdfundAppData> GetCrowdfundApp(string appId, CancellationToken token = default)
{
if (appId == null)
throw new ArgumentNullException(nameof(appId));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/apps/crowdfund/{appId}",
method: HttpMethod.Get), token);
return await HandleResponse<CrowdfundAppData>(response);
}
public virtual async Task DeleteApp(string appId, CancellationToken token = default)
{
if (appId == null)

View File

@@ -50,7 +50,7 @@ namespace BTCPayServer.Client
await HandleResponse(response);
}
public virtual async Task<DepositAddressData> GetDepositAddress(string storeId, string accountId, string paymentMethod, CancellationToken token = default)
public virtual async Task<DepositAddressData> GetCustodianAccountDepositAddress(string storeId, string accountId, string paymentMethod, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/addresses/{paymentMethod}"), token);
return await HandleResponse<DepositAddressData>(response);
@@ -58,7 +58,6 @@ namespace BTCPayServer.Client
public virtual async Task<MarketTradeResponseData> MarketTradeCustodianAccountAsset(string storeId, string accountId, TradeRequestData request, CancellationToken token = default)
{
//var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
//return await HandleResponse<ApplicationUserData>(response);
var internalRequest = CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/market", null,
@@ -67,13 +66,13 @@ namespace BTCPayServer.Client
return await HandleResponse<MarketTradeResponseData>(response);
}
public virtual async Task<MarketTradeResponseData> GetTradeInfo(string storeId, string accountId, string tradeId, CancellationToken token = default)
public virtual async Task<MarketTradeResponseData> GetCustodianAccountTradeInfo(string storeId, string accountId, string tradeId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/{tradeId}", method: HttpMethod.Get), token);
return await HandleResponse<MarketTradeResponseData>(response);
}
public virtual async Task<TradeQuoteResponseData> GetTradeQuote(string storeId, string accountId, string fromAsset, string toAsset, CancellationToken token = default)
public virtual async Task<TradeQuoteResponseData> GetCustodianAccountTradeQuote(string storeId, string accountId, string fromAsset, string toAsset, CancellationToken token = default)
{
var queryPayload = new Dictionary<string, object>();
queryPayload.Add("fromAsset", fromAsset);
@@ -82,13 +81,19 @@ namespace BTCPayServer.Client
return await HandleResponse<TradeQuoteResponseData>(response);
}
public virtual async Task<WithdrawalResponseData> CreateWithdrawal(string storeId, string accountId, WithdrawRequestData request, CancellationToken token = default)
public virtual async Task<WithdrawalResponseData> CreateCustodianAccountWithdrawal(string storeId, string accountId, WithdrawRequestData request, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals", bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<WithdrawalResponseData>(response);
}
public virtual async Task<WithdrawalResponseData> GetWithdrawalInfo(string storeId, string accountId, string paymentMethod, string withdrawalId, CancellationToken token = default)
public virtual async Task<WithdrawalSimulationResponseData> SimulateCustodianAccountWithdrawal(string storeId, string accountId, WithdrawRequestData request, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals/simulation", bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<WithdrawalSimulationResponseData>(response);
}
public virtual async Task<WithdrawalResponseData> GetCustodianAccountWithdrawalInfo(string storeId, string accountId, string paymentMethod, string withdrawalId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals/{paymentMethod}/{withdrawalId}", method: HttpMethod.Get), token);
return await HandleResponse<WithdrawalResponseData>(response);

View File

@@ -114,6 +114,24 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningInvoiceData[]>(response);
}
public virtual async Task<LightningPaymentData[]> GetLightningPayments(string cryptoCode,
bool? includePending = null, long? offsetIndex = null, CancellationToken token = default)
{
var queryPayload = new Dictionary<string, object>();
if (includePending is bool v)
{
queryPayload.Add("includePending", v.ToString());
}
if (offsetIndex is > 0)
{
queryPayload.Add("offsetIndex", offsetIndex);
}
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/payments", queryPayload), token);
return await HandleResponse<LightningPaymentData[]>(response);
}
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
CancellationToken token = default)
{

View File

@@ -116,6 +116,24 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningInvoiceData[]>(response);
}
public virtual async Task<LightningPaymentData[]> GetLightningPayments(string storeId, string cryptoCode,
bool? includePending = null, long? offsetIndex = null, CancellationToken token = default)
{
var queryPayload = new Dictionary<string, object>();
if (includePending is bool v)
{
queryPayload.Add("includePending", v.ToString());
}
if (offsetIndex is > 0)
{
queryPayload.Add("offsetIndex", offsetIndex);
}
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/payments", queryPayload), token);
return await HandleResponse<LightningPaymentData[]>(response);
}
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
CreateLightningInvoiceRequest request, CancellationToken token = default)
{

View File

@@ -97,5 +97,15 @@ namespace BTCPayServer.Client
method: HttpMethod.Post, bodyPayload: request), cancellationToken);
await HandleResponse(response);
}
public virtual async Task<PullPaymentLNURL> GetPullPaymentLNURL(string pullPaymentId,
CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest(
$"/api/v1/pull-payments/{pullPaymentId}/lnurl",
method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PullPaymentLNURL>(response);
}
}
}

View File

@@ -37,7 +37,7 @@ namespace BTCPayServer.Client
return await HandleResponse<StoreRateConfiguration>(response);
}
public virtual async Task<List<StoreRatePreviewResult>> PreviewUpdateStoreRateConfiguration(string storeId,
public virtual async Task<List<StoreRateResult>> PreviewUpdateStoreRateConfiguration(string storeId,
StoreRateConfiguration request,
string[] currencyPair,
CancellationToken token = default)
@@ -47,7 +47,18 @@ namespace BTCPayServer.Client
queryPayload: new Dictionary<string, object>() { { "currencyPair", currencyPair } },
method: HttpMethod.Post),
token);
return await HandleResponse<List<StoreRatePreviewResult>>(response);
return await HandleResponse<List<StoreRateResult>>(response);
}
public virtual async Task<List<StoreRateResult>> GetStoreRates(string storeId, string[] currencyPair,
CancellationToken token = default)
{
using var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/rates",
queryPayload: new Dictionary<string, object>() { { "currencyPair", currencyPair } },
method: HttpMethod.Get),
token);
return await HandleResponse<List<StoreRateResult>>(response);
}
}
}

View File

@@ -30,9 +30,9 @@ namespace BTCPayServer.JsonConverters
case JTokenType.Integer:
case JTokenType.String:
if (objectType == typeof(decimal) || objectType == typeof(decimal?))
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
return decimal.Parse(token.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture);
if (objectType == typeof(double) || objectType == typeof(double?))
return double.Parse(token.ToString(), CultureInfo.InvariantCulture);
return double.Parse(token.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture);
throw new JsonSerializationException("Unexpected object type: " + objectType);
case JTokenType.Null when objectType == typeof(decimal?) || objectType == typeof(double?):
return null;

View File

@@ -0,0 +1,36 @@
using System;
using System.Globalization;
using BTCPayServer.Client.Models;
using BTCPayServer.Lightning;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.JsonConverters
{
public class TradeQuantityJsonConverter : JsonConverter<TradeQuantity>
{
public override TradeQuantity ReadJson(JsonReader reader, Type objectType, TradeQuantity existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
switch (token.Type)
{
case JTokenType.Float:
case JTokenType.Integer:
case JTokenType.String:
if (TradeQuantity.TryParse(token.ToString(), out var q))
return q;
break;
case JTokenType.Null:
return null;
}
throw new JsonObjectException("Invalid TradeQuantity, expected string. Expected: \"1.50\" or \"50%\"", reader);
}
public override void WriteJson(JsonWriter writer, TradeQuantity value, JsonSerializer serializer)
{
if (value is not null)
writer.WriteValue(value.ToString());
}
}
}

View File

@@ -86,6 +86,7 @@ namespace BTCPayServer.Client.Models
public bool? RequiresRefundEmail { get; set; } = null;
public string DefaultLanguage { get; set; }
public CheckoutType? CheckoutType { get; set; }
public bool? LazyPaymentMethods { get; set; }
}
}
public class InvoiceData : InvoiceDataBase

View File

@@ -3,7 +3,6 @@ namespace BTCPayServer.Client.Models
public class LNURLPayPaymentMethodBaseData
{
public bool UseBech32Scheme { get; set; }
public bool EnableForStandardInvoices { get; set; }
public bool LUD12Enabled { get; set; }
public LNURLPayPaymentMethodBaseData()

View File

@@ -16,12 +16,11 @@ namespace BTCPayServer.Client.Models
{
}
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme)
{
Enabled = enabled;
CryptoCode = cryptoCode;
UseBech32Scheme = useBech32Scheme;
EnableForStandardInvoices = enableForStandardInvoices;
}
}
}

View File

@@ -1,3 +1,4 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
@@ -6,6 +7,7 @@ namespace BTCPayServer.Client.Models;
public class LedgerEntryData
{
public string Asset { get; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Qty { get; }
[JsonConverter(typeof(StringEnumConverter))]

View File

@@ -3,8 +3,8 @@ namespace BTCPayServer.Client.Models;
public class LightningAddressData
{
public string Username { get; set; }
public string? CurrencyCode { get; set; }
public string CurrencyCode { get; set; }
public decimal? Min { get; set; }
public decimal? Max { get; set; }
}

View File

@@ -16,7 +16,7 @@ namespace BTCPayServer.Client.Models
[JsonProperty("BOLT11")]
public string BOLT11 { get; set; }
public string PaymentHash { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
@@ -24,7 +24,7 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? PaidAt { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset ExpiresAt { get; set; }

View File

@@ -4,7 +4,6 @@ namespace BTCPayServer.Client.Models
{
public string ConnectionString { get; set; }
public bool DisableBOLT11PaymentOption { get; set; }
public LightningNetworkPaymentMethodBaseData()
{

View File

@@ -16,13 +16,12 @@ namespace BTCPayServer.Client.Models
{
}
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod, bool disableBOLT11PaymentOption)
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
{
Enabled = enabled;
CryptoCode = cryptoCode;
ConnectionString = connectionString;
PaymentMethod = paymentMethod;
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
}
public string PaymentMethod { get; set; }

View File

@@ -6,6 +6,8 @@ namespace BTCPayServer.Client.Models
public class NotificationData
{
public string Id { get; set; }
public string Identifier { get; set; }
public string Type { get; set; }
public string Body { get; set; }
public bool Seen { get; set; }
public Uri Link { get; set; }

View File

@@ -0,0 +1,13 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class PaymentMethodCriteriaData
{
public string PaymentMethod { get; set; }
public string CurrencyCode { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public bool Above { get; set; }
}

View File

@@ -15,11 +15,53 @@ namespace BTCPayServer.Client.Models
public class PointOfSaleAppData : AppDataBase
{
// We can add POS specific things here later
public string Title { get; set; }
public string DefaultView { get; set; }
public bool ShowCustomAmount { get; set; }
public bool ShowDiscount { get; set; }
public bool EnableTips { get; set; }
public string Currency { get; set; }
public object Items { get; set; }
public string FixedAmountPayButtonText { get; set; }
public string CustomAmountPayButtonText { get; set; }
public string TipText { get; set; }
public string CustomCSSLink { get; set; }
public string NotificationUrl { get; set; }
public string RedirectUrl { get; set; }
public string Description { get; set; }
public string EmbeddedCSS { get; set; }
public bool? RedirectAutomatically { get; set; }
public bool? RequiresRefundEmail { get; set; }
}
public class CrowdfundAppData : AppDataBase
{
// We can add Crowdfund specific things here later
public string Title { get; set; }
public bool Enabled { get; set; }
public bool EnforceTargetAmount { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? StartDate { get; set; }
public string TargetCurrency { get; set; }
public string Description { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? EndDate { get; set; }
public decimal? TargetAmount { get; set; }
public string CustomCSSLink { get; set; }
public string MainImageUrl { get; set; }
public string EmbeddedCSS { get; set; }
public string NotificationUrl { get; set; }
public string Tagline { get; set; }
public object Perks { get; set; }
public bool DisqusEnabled { get; set; }
public string DisqusShortname { get; set; }
public bool SoundsEnabled { get; set; }
public bool AnimationsEnabled { get; set; }
public int ResetEveryAmount { get; set; }
public string ResetEvery { get; set; }
public bool DisplayPerksValue { get; set; }
public bool DisplayPerksRanking { get; set; }
public bool SortPerksByPopularity { get; set; }
public string[] Sounds { get; set; }
public string[] AnimationColors { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace BTCPayServer.Client.Models
{
public class PullPaymentLNURL
{
public string LNURLBech32 { get; set; }
public string LNURLUri { get; set; }
}
}

View File

@@ -63,6 +63,9 @@ namespace BTCPayServer.Client.Models
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<PaymentMethodCriteriaData> PaymentMethodCriteria { get; set; }
public bool PayJoinEnabled { get; set; }
public InvoiceData.ReceiptOptions Receipt { get; set; }

View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace BTCPayServer.Client.Models;
public class StoreRatePreviewResult
{
public string CurrencyPair { get; set; }
public decimal? Rate { get; set; }
public List<string> Errors { get; set; }
}

View File

@@ -1,7 +1,13 @@
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class StoreRateResult
{
public string CurrencyPair { get; set; }
public decimal Rate { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Rate { get; set; }
public List<string> Errors { get; set; }
}

View File

@@ -1,8 +1,13 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class TradeQuoteResponseData
{
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Bid { get; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Ask { get; }
public string ToAsset { get; }
public string FromAsset { get; }

View File

@@ -1,8 +1,11 @@
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class TradeRequestData
{
public string FromAsset { set; get; }
public string ToAsset { set; get; }
public string Qty { set; get; }
[JsonConverter(typeof(JsonConverters.TradeQuantityJsonConverter))]
public TradeQuantity Qty { set; get; }
}

View File

@@ -1,13 +1,85 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Http.Headers;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class WithdrawRequestData
{
public string PaymentMethod { set; get; }
public decimal Qty { set; get; }
[JsonConverter(typeof(JsonConverters.TradeQuantityJsonConverter))]
public TradeQuantity Qty { set; get; }
public WithdrawRequestData(string paymentMethod, decimal qty)
public WithdrawRequestData()
{
}
public WithdrawRequestData(string paymentMethod, TradeQuantity qty)
{
PaymentMethod = paymentMethod;
Qty = qty;
}
}
#nullable enable
public record TradeQuantity
{
public TradeQuantity(decimal value, ValueType type)
{
Type = type;
Value = value;
}
public enum ValueType
{
Exact,
Percent
}
public ValueType Type { get; }
public decimal Value { get; set; }
public override string ToString()
{
if (Type == ValueType.Exact)
return Value.ToString(CultureInfo.InvariantCulture);
else
return Value.ToString(CultureInfo.InvariantCulture) + "%";
}
public static TradeQuantity Parse(string str)
{
if (!TryParse(str, out var r))
throw new FormatException("Invalid TradeQuantity");
return r;
}
public static bool TryParse(string str, [MaybeNullWhen(false)] out TradeQuantity quantity)
{
if (str is null)
throw new ArgumentNullException(nameof(str));
quantity = null;
str = str.Trim();
str = str.Replace(" ", "");
if (str.Length == 0)
return false;
if (str[^1] == '%')
{
if (!decimal.TryParse(str[..^1], NumberStyles.Any, CultureInfo.InvariantCulture, out var r))
return false;
if (r < 0.0m)
return false;
quantity = new TradeQuantity(r, TradeQuantity.ValueType.Percent);
}
else
{
if (!decimal.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out var r))
return false;
if (r < 0.0m)
return false;
quantity = new TradeQuantity(r, TradeQuantity.ValueType.Exact);
}
return true;
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace BTCPayServer.Client.Models;
public abstract class WithdrawalBaseResponseData
{
public string Asset { get; }
public string PaymentMethod { get; }
public List<LedgerEntryData> LedgerEntries { get; }
public string AccountId { get; }
public string CustodianCode { get; }
public WithdrawalBaseResponseData(string paymentMethod, string asset, List<LedgerEntryData> ledgerEntries, string accountId,
string custodianCode)
{
PaymentMethod = paymentMethod;
Asset = asset;
LedgerEntries = ledgerEntries;
AccountId = accountId;
CustodianCode = custodianCode;
}
}

View File

@@ -5,18 +5,13 @@ using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client.Models;
public class WithdrawalResponseData
public class WithdrawalResponseData : WithdrawalBaseResponseData
{
public string Asset { get; }
public string PaymentMethod { get; }
public List<LedgerEntryData> LedgerEntries { get; }
public string WithdrawalId { get; }
public string AccountId { get; }
public string CustodianCode { get; }
[JsonConverter(typeof(StringEnumConverter))]
public WithdrawalStatus Status { get; }
public string WithdrawalId { get; }
public DateTimeOffset CreatedTime { get; }
public string TransactionId { get; }
@@ -24,14 +19,10 @@ public class WithdrawalResponseData
public string TargetAddress { get; }
public WithdrawalResponseData(string paymentMethod, string asset, List<LedgerEntryData> ledgerEntries, string withdrawalId, string accountId,
string custodianCode, WithdrawalStatus status, DateTimeOffset createdTime, string targetAddress, string transactionId)
string custodianCode, WithdrawalStatus status, DateTimeOffset createdTime, string targetAddress, string transactionId) : base(paymentMethod, asset, ledgerEntries, accountId,
custodianCode)
{
PaymentMethod = paymentMethod;
Asset = asset;
LedgerEntries = ledgerEntries;
WithdrawalId = withdrawalId;
AccountId = accountId;
CustodianCode = custodianCode;
TargetAddress = targetAddress;
TransactionId = transactionId;
Status = status;

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
public class WithdrawalSimulationResponseData : WithdrawalBaseResponseData
{
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? MinQty { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? MaxQty { get; set; }
public WithdrawalSimulationResponseData(string paymentMethod, string asset, string accountId,
string custodianCode, List<LedgerEntryData> ledgerEntries, decimal? minQty, decimal? maxQty) : base(paymentMethod,
asset, ledgerEntries, accountId, custodianCode)
{
MinQty = minQty;
MaxQty = maxQty;
}
}

View File

@@ -28,8 +28,11 @@ namespace BTCPayServer.Client
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
public const string CanViewUsers = "btcpay.server.canviewusers";
public const string CanCreateUser = "btcpay.server.cancreateuser";
public const string CanManageUsers = "btcpay.server.canmanageusers";
public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts";
public const string CanDepositToCustodianAccounts = "btcpay.store.candeposittocustodianaccount";
@@ -64,11 +67,14 @@ namespace BTCPayServer.Client
yield return CanViewLightningInvoiceInStore;
yield return CanCreateLightningInvoiceInStore;
yield return CanManagePullPayments;
yield return CanCreatePullPayments;
yield return CanCreateNonApprovedPullPayments;
yield return CanViewCustodianAccounts;
yield return CanManageCustodianAccounts;
yield return CanDepositToCustodianAccounts;
yield return CanWithdrawFromCustodianAccounts;
yield return CanTradeCustodianAccount;
yield return CanManageUsers;
}
}
public static bool IsValidPolicy(string policy)
@@ -92,9 +98,45 @@ namespace BTCPayServer.Client
{
return policy.StartsWith("btcpay.plugin", StringComparison.OrdinalIgnoreCase);
}
public static bool IsUserPolicy(string policy)
{
return policy.StartsWith("btcpay.user", StringComparison.OrdinalIgnoreCase);
}
}
public class PermissionSet
{
public PermissionSet() : this(Array.Empty<Permission>())
{
}
public PermissionSet(Permission[] permissions)
{
Permissions = permissions;
}
public Permission[] Permissions { get; }
public bool Contains(Permission requestedPermission)
{
return Permissions.Any(p => p.Contains(requestedPermission));
}
public bool Contains(string permission, string store)
{
if (permission is null)
throw new ArgumentNullException(nameof(permission));
if (store is null)
throw new ArgumentNullException(nameof(store));
return Contains(Permission.Create(permission, store));
}
}
public class Permission
{
static Permission()
{
Init();
}
public static Permission Create(string policy, string scope = null)
{
if (TryCreatePermission(policy, scope, out var r))
@@ -110,7 +152,7 @@ namespace BTCPayServer.Client
policy = policy.Trim().ToLowerInvariant();
if (!Policies.IsValidPolicy(policy))
return false;
if (scope != null && !Policies.IsStorePolicy(policy))
if (!string.IsNullOrEmpty(scope) && !Policies.IsStorePolicy(policy))
return false;
permission = new Permission(policy, scope);
return true;
@@ -163,7 +205,7 @@ namespace BTCPayServer.Client
}
if (!Policies.IsStorePolicy(subpermission.Policy))
return true;
return Scope == null || subpermission.Scope == this.Scope;
return Scope == null || subpermission.Scope == Scope;
}
public static IEnumerable<Permission> ToPermissions(string[] permissions)
@@ -179,37 +221,61 @@ namespace BTCPayServer.Client
private bool ContainsPolicy(string subpolicy)
{
if (this.Policy == Policies.Unrestricted)
return ContainsPolicy(Policy, subpolicy);
}
private static bool ContainsPolicy(string policy, string subpolicy)
{
if (policy == Policies.Unrestricted)
return true;
if (this.Policy == subpolicy)
if (policy == subpolicy)
return true;
switch (subpolicy)
if (!PolicyMap.TryGetValue(policy, out var subPolicies))
return false;
return subPolicies.Contains(subpolicy) || subPolicies.Any(s => ContainsPolicy(s, subpolicy));
}
private static Dictionary<string, HashSet<string>> PolicyMap = new();
private static void Init()
{
PolicyHasChild(Policies.CanModifyStoreSettings,
Policies.CanManageCustodianAccounts,
Policies.CanManagePullPayments,
Policies.CanModifyInvoices,
Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks,
Policies.CanModifyPaymentRequests,
Policies.CanUseLightningNodeInStore);
PolicyHasChild(Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments);
PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile);
PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore);
PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser);
PolicyHasChild(Policies.CanModifyServerSettings,
Policies.CanUseInternalLightningNode,
Policies.CanManageUsers);
PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore);
PolicyHasChild(Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests);
}
private static void PolicyHasChild(string policy, params string[] subPolicies)
{
if (PolicyMap.TryGetValue(policy, out var existingSubPolicies))
{
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyInvoices:
case Policies.CanModifyStoreWebhooks when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewInvoices when this.Policy == Policies.CanViewStoreSettings:
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanModifyInvoices when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewProfile when this.Policy == Policies.CanModifyProfile:
case Policies.CanModifyPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanManagePullPayments when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyPaymentRequests:
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
case Policies.CanViewLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
case Policies.CanViewLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
case Policies.CanViewNotificationsForUser when this.Policy == Policies.CanManageNotificationsForUser:
case Policies.CanUseInternalLightningNode when this.Policy == Policies.CanModifyServerSettings:
case Policies.CanViewCustodianAccounts when this.Policy == Policies.CanManageCustodianAccounts:
case Policies.CanViewCustodianAccounts when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanManageCustodianAccounts when this.Policy == Policies.CanModifyStoreSettings:
return true;
default:
return false;
foreach (string subPolicy in subPolicies)
{
existingSubPolicies.Add(subPolicy);
}
}
else
{
PolicyMap.Add(policy, subPolicies.ToHashSet());
}
}
@@ -218,23 +284,17 @@ namespace BTCPayServer.Client
public override string ToString()
{
if (Scope != null)
{
return $"{Policy}:{Scope}";
}
return Policy;
return Scope != null ? $"{Policy}:{Scope}" : Policy;
}
public override bool Equals(object obj)
{
Permission item = obj as Permission;
if (item == null)
return false;
return ToString().Equals(item.ToString());
return item != null && ToString().Equals(item.ToString());
}
public static bool operator ==(Permission a, Permission b)
{
if (System.Object.ReferenceEquals(a, b))
if (ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;

View File

@@ -1,6 +1,6 @@
using System;
using System.Linq;
using BTCPayServer.Data.Data;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
@@ -30,7 +30,12 @@ namespace BTCPayServer.Data
{
_designTime = designTime;
}
#nullable enable
public async Task<string?> GetMigrationState()
{
return (await Settings.FromSqlRaw("SELECT \"Id\", \"Value\" FROM \"Settings\" WHERE \"Id\"='MigrationData'").AsNoTracking().FirstOrDefaultAsync())?.Value;
}
#nullable restore
public DbSet<AddressInvoiceData> AddressInvoices { get; set; }
public DbSet<APIKeyData> ApiKeys { get; set; }
public DbSet<AppData> Apps { get; set; }
@@ -69,6 +74,7 @@ namespace BTCPayServer.Data
public DbSet<WebhookData> Webhooks { get; set; }
public DbSet<LightningAddressData> LightningAddresses { get; set; }
public DbSet<PayoutProcessorData> PayoutProcessors { get; set; }
public DbSet<FormData> Forms { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
@@ -83,23 +89,23 @@ namespace BTCPayServer.Data
// some of the data models don't have OnModelCreating for now, commenting them
ApplicationUser.OnModelCreating(builder);
ApplicationUser.OnModelCreating(builder, Database);
AddressInvoiceData.OnModelCreating(builder);
APIKeyData.OnModelCreating(builder);
APIKeyData.OnModelCreating(builder, Database);
AppData.OnModelCreating(builder);
CustodianAccountData.OnModelCreating(builder);
CustodianAccountData.OnModelCreating(builder, Database);
//StoredFile.OnModelCreating(builder);
InvoiceEventData.OnModelCreating(builder);
InvoiceSearchData.OnModelCreating(builder);
InvoiceWebhookDeliveryData.OnModelCreating(builder);
InvoiceData.OnModelCreating(builder);
NotificationData.OnModelCreating(builder);
InvoiceData.OnModelCreating(builder, Database);
NotificationData.OnModelCreating(builder, Database);
//OffchainTransactionData.OnModelCreating(builder);
BTCPayServer.Data.PairedSINData.OnModelCreating(builder);
PairingCodeData.OnModelCreating(builder);
//PayjoinLock.OnModelCreating(builder);
PaymentRequestData.OnModelCreating(builder);
PaymentData.OnModelCreating(builder);
PaymentRequestData.OnModelCreating(builder, Database);
PaymentData.OnModelCreating(builder, Database);
PayoutData.OnModelCreating(builder);
PendingInvoiceData.OnModelCreating(builder);
//PlannedTransaction.OnModelCreating(builder);
@@ -110,7 +116,7 @@ namespace BTCPayServer.Data
StoreWebhookData.OnModelCreating(builder);
StoreData.OnModelCreating(builder, Database);
U2FDevice.OnModelCreating(builder);
Fido2Credential.OnModelCreating(builder);
Fido2Credential.OnModelCreating(builder, Database);
BTCPayServer.Data.UserStore.OnModelCreating(builder);
//WalletData.OnModelCreating(builder);
WalletObjectData.OnModelCreating(builder, Database);
@@ -118,10 +124,11 @@ namespace BTCPayServer.Data
#pragma warning disable CS0612 // Type or member is obsolete
WalletTransactionData.OnModelCreating(builder);
#pragma warning restore CS0612 // Type or member is obsolete
WebhookDeliveryData.OnModelCreating(builder);
LightningAddressData.OnModelCreating(builder);
PayoutProcessorData.OnModelCreating(builder);
//WebhookData.OnModelCreating(builder);
WebhookDeliveryData.OnModelCreating(builder, Database);
LightningAddressData.OnModelCreating(builder, Database);
PayoutProcessorData.OnModelCreating(builder, Database);
WebhookData.OnModelCreating(builder, Database);
FormData.OnModelCreating(builder, Database);
if (Database.IsSqlite() && !_designTime)

View File

@@ -1,9 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class APIKeyData
public class APIKeyData : IHasBlob<APIKeyBlob>
{
[MaxLength(50)]
public string Id { get; set; }
@@ -16,13 +18,15 @@ namespace BTCPayServer.Data
public APIKeyType Type { get; set; } = APIKeyType.Legacy;
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public StoreData StoreData { get; set; }
public ApplicationUser User { get; set; }
public string Label { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<APIKeyData>()
.HasOne(o => o.StoreData)
@@ -36,6 +40,13 @@ namespace BTCPayServer.Data
builder.Entity<APIKeyData>()
.HasIndex(o => o.StoreId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<APIKeyData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}

View File

@@ -2,11 +2,13 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
public class ApplicationUser : IdentityUser, IHasBlob<UserBlob>
{
public bool RequiresEmailConfirmation { get; set; }
public List<StoredFile> StoredFiles { get; set; }
@@ -20,15 +22,28 @@ namespace BTCPayServer.Data
public List<UserStore> UserStores { get; set; }
public List<Fido2Credential> Fido2Credentials { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public List<IdentityUserRole<string>> UserRoles { get; set; }
public static void OnModelCreating(ModelBuilder builder)
public static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<ApplicationUser>()
.HasMany<IdentityUserRole<string>>(user => user.UserRoles)
.WithOne().HasForeignKey(role => role.UserId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<ApplicationUser>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
public class UserBlob
{
public bool ShowInvoiceStatusChangeHint { get; set; }
}
}

View File

@@ -1,11 +1,13 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data;
public class CustodianAccountData
public class CustodianAccountData : IHasBlob<JObject>
{
[Required]
[MaxLength(50)]
@@ -24,19 +26,29 @@ public class CustodianAccountData
public string Name { get; set; }
[JsonIgnore]
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
[JsonIgnore]
public string Blob2 { get; set; }
[JsonIgnore]
public StoreData StoreData { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<CustodianAccountData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.CustodianAccounts)
.HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade);
builder.Entity<APIKeyData>()
builder.Entity<CustodianAccountData>()
.HasIndex(o => o.StoreId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<CustodianAccountData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}

View File

@@ -2,10 +2,11 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class Fido2Credential
public class Fido2Credential : IHasBlobUntyped
{
public string Name { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -14,6 +15,7 @@ namespace BTCPayServer.Data
public string ApplicationUserId { get; set; }
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public CredentialType Type { get; set; }
public enum CredentialType
{
@@ -22,12 +24,18 @@ namespace BTCPayServer.Data
[Display(Name = "Lightning node (LNURL Auth)")]
LNURLAuth
}
public static void OnModelCreating(ModelBuilder builder)
public static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<Fido2Credential>()
.HasOne(o => o.ApplicationUser)
.WithMany(i => i.Fido2Credentials)
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
if (databaseFacade.IsNpgsql())
{
builder.Entity<Fido2Credential>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
public ApplicationUser ApplicationUser { get; set; }

View File

@@ -2,11 +2,30 @@ using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data.Data;
namespace BTCPayServer.Data;
public class FormData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string Name { get; set; }
public string StoreId { get; set; }
public StoreData Store { get; set; }
public string Config { get; set; }
public bool Public { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<FormData>()
.HasOne(o => o.Store)
.WithMany(o => o.Forms).OnDelete(DeleteBehavior.Cascade);
builder.Entity<FormData>().HasIndex(o => o.StoreId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<FormData>()
.Property(o => o.Config)
.HasColumnType("JSONB");
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public interface IHasBlob<T>
{
[Obsolete("Use Blob2 instead")]
byte[] Blob { get; set; }
string Blob2 { get; set; }
}
public interface IHasBlob
{
[Obsolete("Use Blob2 instead")]
byte[] Blob { get; set; }
string Blob2 { get; set; }
public Type Type { get; set; }
}
public interface IHasBlobUntyped
{
[Obsolete("Use Blob2 instead")]
byte[] Blob { get; set; }
string Blob2 { get; set; }
}
}

View File

@@ -2,10 +2,11 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class InvoiceData
public class InvoiceData : IHasBlobUntyped
{
public string Id { get; set; }
@@ -16,7 +17,9 @@ namespace BTCPayServer.Data
public List<PaymentData> Payments { get; set; }
public List<InvoiceEventData> Events { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public string ItemCode { get; set; }
public string OrderId { get; set; }
public string Status { get; set; }
@@ -32,7 +35,7 @@ namespace BTCPayServer.Data
public RefundData CurrentRefund { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<InvoiceData>()
.HasOne(o => o.StoreData)
@@ -42,6 +45,13 @@ namespace BTCPayServer.Data
builder.Entity<InvoiceData>()
.HasOne(o => o.CurrentRefund);
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
if (databaseFacade.IsNpgsql())
{
builder.Entity<InvoiceData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
}

View File

@@ -1,17 +1,22 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data;
public class LightningAddressData
public class LightningAddressData : IHasBlob<LightningAddressDataBlob>
{
public string Username { get; set; }
public string StoreDataId { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public StoreData Store { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<LightningAddressData>()
.HasOne(o => o.Store)
@@ -20,6 +25,12 @@ public class LightningAddressData
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<LightningAddressData>().HasKey(o => o.Username);
if (databaseFacade.IsNpgsql())
{
builder.Entity<LightningAddressData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
@@ -28,4 +39,6 @@ public class LightningAddressDataBlob
public string CurrencyCode { get; set; }
public decimal? Min { get; set; }
public decimal? Max { get; set; }
public JObject InvoiceMetadata { get; set; }
}

View File

@@ -1,10 +1,12 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
namespace BTCPayServer.Data
{
public class NotificationData
public class NotificationData : IHasBlobUntyped
{
[MaxLength(36)]
public string Id { get; set; }
@@ -17,15 +19,23 @@ namespace BTCPayServer.Data
[Required]
public string NotificationType { get; set; }
public bool Seen { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<NotificationData>()
.HasOne(o => o.ApplicationUser)
.WithMany(n => n.Notifications)
.HasForeignKey(k => k.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
if (databaseFacade.IsNpgsql())
{
builder.Entity<NotificationData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
}

View File

@@ -1,24 +1,34 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class PaymentData
public class PaymentData : IHasBlobUntyped
{
public string Id { get; set; }
public string InvoiceDataId { get; set; }
public InvoiceData InvoiceData { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public string Type { get; set; }
public bool Accounted { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<PaymentData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PaymentData>()
.HasIndex(o => o.InvoiceDataId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<PaymentData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
}

View File

@@ -1,9 +1,10 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class PaymentRequestData
public class PaymentRequestData : IHasBlobUntyped
{
public string Id { get; set; }
public DateTimeOffset Created { get; set; }
@@ -14,10 +15,12 @@ namespace BTCPayServer.Data
public Client.Models.PaymentRequestData.PaymentRequestStatus Status { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<PaymentRequestData>()
.HasOne(o => o.StoreData)
@@ -28,6 +31,13 @@ namespace BTCPayServer.Data
.HasDefaultValue(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero));
builder.Entity<PaymentRequestData>()
.HasIndex(o => o.Status);
if (databaseFacade.IsNpgsql())
{
builder.Entity<PaymentRequestData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
}

View File

@@ -1,9 +1,15 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data.Data;
namespace BTCPayServer.Data;
public class PayoutProcessorData
public class AutomatedPayoutBlob
{
public TimeSpan Interval { get; set; } = TimeSpan.FromHours(1);
}
public class PayoutProcessorData : IHasBlobUntyped
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
@@ -12,14 +18,22 @@ public class PayoutProcessorData
public string PaymentMethod { get; set; }
public string Processor { get; set; }
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<PayoutProcessorData>()
.HasOne(o => o.Store)
.WithMany(data => data.PayoutProcessors).OnDelete(DeleteBehavior.Cascade);
if (databaseFacade.IsNpgsql())
{
builder.Entity<PayoutProcessorData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
public override string ToString()

View File

@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using PayoutProcessorData = BTCPayServer.Data.PayoutProcessorData;
namespace BTCPayServer.Data
{
@@ -25,7 +27,6 @@ namespace BTCPayServer.Data
[Obsolete("Use GetDerivationStrategies instead")]
public string DerivationStrategy { get; set; }
[Obsolete("Use GetDerivationStrategies instead")]
public string DerivationStrategies { get; set; }
public string StoreName { get; set; }
@@ -50,6 +51,7 @@ namespace BTCPayServer.Data
public IEnumerable<PayoutData> Payouts { get; set; }
public IEnumerable<CustodianAccountData> CustodianAccounts { get; set; }
public IEnumerable<StoreSettingData> Settings { get; set; }
public IEnumerable<FormData> Forms { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
@@ -58,6 +60,20 @@ namespace BTCPayServer.Data
builder.Entity<StoreData>()
.Property(o => o.StoreBlob)
.HasColumnType("JSONB");
builder.Entity<StoreData>()
.Property(o => o.DerivationStrategies)
.HasColumnType("JSONB");
}
else if (databaseFacade.IsMySql())
{
builder.Entity<StoreData>()
.Property(o => o.StoreBlob)
.HasConversion(new ValueConverter<string, byte[]>
(
convertToProviderExpression: (str) => Encoding.UTF8.GetBytes(str),
convertFromProviderExpression: (bytes) => Encoding.UTF8.GetString(bytes)
));
}
}
}

View File

@@ -12,6 +12,13 @@ namespace BTCPayServer.Data
{
public class Types
{
public static readonly HashSet<string> AllTypes;
static Types()
{
AllTypes = typeof(Types).GetFields()
.Where(f => f.FieldType == typeof(string))
.Select(f => (string)f.GetValue(null)).ToHashSet(StringComparer.OrdinalIgnoreCase);
}
public const string Label = "label";
public const string Tx = "tx";
public const string Payjoin = "payjoin";

View File

@@ -1,15 +1,29 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class WebhookData
public class WebhookData : IHasBlobUntyped
{
[Key]
[MaxLength(25)]
public string Id { get; set; }
[Required]
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
public List<WebhookDeliveryData> Deliveries { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
if (databaseFacade.IsNpgsql())
{
builder.Entity<WebhookData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
}

View File

@@ -1,10 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class WebhookDeliveryData
public class WebhookDeliveryData : IHasBlobUntyped
{
[Key]
[MaxLength(25)]
@@ -16,17 +17,24 @@ namespace BTCPayServer.Data
[Required]
public DateTimeOffset Timestamp { get; set; }
[Required]
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get; set; }
public string Blob2 { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<WebhookDeliveryData>()
.HasOne(o => o.Webhook)
.WithMany(a => a.Deliveries).OnDelete(DeleteBehavior.Cascade);
builder.Entity<WebhookDeliveryData>().HasIndex(o => o.WebhookId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<WebhookDeliveryData>()
.Property(o => o.Blob2)
.HasColumnType("JSONB");
}
}
}
}

View File

@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
@@ -17,20 +17,21 @@ namespace BTCPayServer.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.AddColumn<string>(
name: "StoreDataId",
table: "Payouts",
type: "TEXT",
nullable: true);
nullable: true,
maxLength: maxLength);
migrationBuilder.CreateTable(
name: "PayoutProcessors",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
StoreId = table.Column<string>(type: "TEXT", nullable: true),
PaymentMethod = table.Column<string>(type: "TEXT", nullable: true),
Processor = table.Column<string>(type: "TEXT", nullable: true),
Id = table.Column<string>(nullable: false, maxLength: maxLength),
StoreId = table.Column<string>(nullable: true, maxLength: maxLength),
PaymentMethod = table.Column<string>(nullable: true),
Processor = table.Column<string>(nullable: true),
Blob = table.Column<byte[]>(nullable: true)
},
constraints: table =>

View File

@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
@@ -17,12 +17,13 @@ namespace BTCPayServer.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.CreateTable(
name: "LightningAddresses",
columns: table => new
{
Username = table.Column<string>(type: "TEXT", nullable: false),
StoreDataId = table.Column<string>(type: "TEXT", nullable: false),
Username = table.Column<string>(nullable: false, maxLength: maxLength),
StoreDataId = table.Column<string>(nullable: false, maxLength: maxLength),
Blob = table.Column<byte[]>( nullable: true)
},
constraints: table =>

View File

@@ -14,12 +14,13 @@ namespace BTCPayServer.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxlength = migrationBuilder.IsMySql() ? 255 : null;
migrationBuilder.CreateTable(
name: "StoreSettings",
columns: table => new
{
Name = table.Column<string>(type: "TEXT", nullable: false),
StoreId = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(nullable: false, maxLength: maxlength),
StoreId = table.Column<string>(nullable: false, maxLength: maxlength),
Value = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true)
},
constraints: table =>

View File

@@ -17,13 +17,15 @@ namespace BTCPayServer.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxlength = migrationBuilder.IsMySql() ? 255 : null;
migrationBuilder.CreateTable(
name: "WalletObjects",
columns: table => new
{
WalletId = table.Column<string>(type: "TEXT", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: false),
Id = table.Column<string>(type: "TEXT", nullable: false),
WalletId = table.Column<string>(nullable: false, maxLength: maxlength),
Type = table.Column<string>(nullable: false, maxLength: maxlength),
Id = table.Column<string>(nullable: false, maxLength: maxlength),
Data = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true)
},
constraints: table =>
@@ -35,15 +37,17 @@ namespace BTCPayServer.Migrations
table: "WalletObjects",
columns: new[] { "Type", "Id" });
maxlength = migrationBuilder.IsMySql() ? 100 : null;
migrationBuilder.CreateTable(
name: "WalletObjectLinks",
columns: table => new
{
WalletId = table.Column<string>(type: "TEXT", nullable: false),
AType = table.Column<string>(type: "TEXT", nullable: false),
AId = table.Column<string>(type: "TEXT", nullable: false),
BType = table.Column<string>(type: "TEXT", nullable: false),
BId = table.Column<string>(type: "TEXT", nullable: false),
WalletId = table.Column<string>(nullable: false, maxLength: maxlength),
AType = table.Column<string>(nullable: false, maxLength: maxlength),
AId = table.Column<string>(nullable: false, maxLength: maxlength),
BType = table.Column<string>(nullable: false, maxLength: maxlength),
BId = table.Column<string>(nullable: false, maxLength: maxlength),
Data = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true)
},
constraints: table =>

View File

@@ -0,0 +1,30 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20230123062447_migrateoldratesource")]
public partial class migrateoldratesource : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql("UPDATE \"Stores\" SET \"StoreBlob\"=jsonb_set(\"StoreBlob\", \'{preferredExchange}\', \'{\"oasis_trade\": \"oasisdev\", \"gdax\":\"coinbasepro\", \"coinaverage\":\"coingecko\"}\'::jsonb->(\"StoreBlob\"->>\'preferredExchange\')) WHERE \"StoreBlob\"->>\'preferredExchange\' = ANY (ARRAY[\'oasis_trade\', \'gdax\', \'coinaverage\']);");
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Not supported
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20230125085242_AddForms")]
public partial class AddForms : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxlength = migrationBuilder.IsMySql() ? 255 : null;
migrationBuilder.CreateTable(
name: "Forms",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false, maxLength: maxlength),
Name = table.Column<string>(type: "TEXT", nullable: true, maxLength: maxlength),
StoreId = table.Column<string>(type: "TEXT", nullable: true, maxLength: maxlength),
Config = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true),
Public = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Forms", x => x.Id);
table.ForeignKey(
name: "FK_Forms_Stores_StoreId",
column: x => x.StoreId,
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Forms_StoreId",
table: "Forms",
column: "StoreId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Forms");
}
}
}

View File

@@ -0,0 +1,150 @@
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("20230130040047_blob2")]
public partial class blob2 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
var type = migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT";
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "Webhooks",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "WebhookDeliveries",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "PaymentRequests",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "Notifications",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "LightningAddresses",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "Fido2Credentials",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "AspNetUsers",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "ApiKeys",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "Invoices",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "Payments",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "PayoutProcessors",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Blob2",
table: "CustodianAccount",
type: type,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Type",
table: "Payments",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Blob2",
table: "Webhooks");
migrationBuilder.DropColumn(
name: "Blob2",
table: "WebhookDeliveries");
migrationBuilder.DropColumn(
name: "Blob2",
table: "PaymentRequests");
migrationBuilder.DropColumn(
name: "Blob2",
table: "Notifications");
migrationBuilder.DropColumn(
name: "Blob2",
table: "LightningAddresses");
migrationBuilder.DropColumn(
name: "Blob2",
table: "Fido2Credentials");
migrationBuilder.DropColumn(
name: "Blob2",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "Blob2",
table: "ApiKeys");
migrationBuilder.DropColumn(
name: "Blob2",
table: "Invoices");
migrationBuilder.DropColumn(
name: "Blob2",
table: "Payments");
migrationBuilder.DropColumn(
name: "Blob2",
table: "PayoutProcessors");
migrationBuilder.DropColumn(
name: "Blob2",
table: "CustodianAccount");
migrationBuilder.DropColumn(
name: "Type",
table: "Payments");
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20230130062447_jsonb2")]
public partial class jsonb2 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql("ALTER TABLE \"Stores\" ALTER COLUMN \"DerivationStrategies\" TYPE JSONB USING \"DerivationStrategies\"::JSONB");
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Not supported
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20230315062447_fixmaxlength")]
public partial class fixmaxlength : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql("ALTER TABLE \"InvoiceSearches\" ALTER COLUMN \"Value\" TYPE TEXT USING \"Value\"::TEXT;");
migrationBuilder.Sql("ALTER TABLE \"Invoices\" ALTER COLUMN \"OrderId\" TYPE TEXT USING \"OrderId\"::TEXT;");
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Not supported
}
}
}

View File

@@ -45,6 +45,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<string>("Label")
.HasColumnType("TEXT");
@@ -109,6 +112,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
@@ -183,6 +189,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<string>("CustodianCode")
.IsRequired()
.HasMaxLength(50)
@@ -205,7 +214,32 @@ namespace BTCPayServer.Migrations
b.ToTable("CustodianAccount");
});
modelBuilder.Entity("BTCPayServer.Data.Data.PayoutProcessorData", b =>
modelBuilder.Entity("BTCPayServer.Data.FormData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Config")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<bool>("Public")
.HasColumnType("INTEGER");
b.Property<string>("StoreId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("StoreId");
b.ToTable("Forms");
});
modelBuilder.Entity("BTCPayServer.Data.PayoutProcessorData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
@@ -242,6 +276,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
@@ -266,6 +303,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
@@ -376,6 +416,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<string>("StoreDataId")
.IsRequired()
.HasColumnType("TEXT");
@@ -401,6 +444,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
@@ -512,9 +558,15 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<string>("InvoiceDataId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
@@ -533,6 +585,9 @@ namespace BTCPayServer.Migrations
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Created")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
@@ -705,8 +760,8 @@ namespace BTCPayServer.Migrations
b.Property<int>("SpeedPolicy")
.HasColumnType("INTEGER");
b.Property<byte[]>("StoreBlob")
.HasColumnType("BLOB");
b.Property<string>("StoreBlob")
.HasColumnType("TEXT");
b.Property<byte[]>("StoreCertificate")
.HasColumnType("BLOB");
@@ -920,9 +975,11 @@ namespace BTCPayServer.Migrations
.HasColumnType("TEXT");
b.Property<byte[]>("Blob")
.IsRequired()
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Webhooks");
@@ -935,9 +992,11 @@ namespace BTCPayServer.Migrations
.HasColumnType("TEXT");
b.Property<byte[]>("Blob")
.IsRequired()
.HasColumnType("BLOB");
b.Property<string>("Blob2")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("TEXT");
@@ -1129,7 +1188,17 @@ namespace BTCPayServer.Migrations
b.Navigation("StoreData");
});
modelBuilder.Entity("BTCPayServer.Data.Data.PayoutProcessorData", b =>
modelBuilder.Entity("BTCPayServer.Data.FormData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "Store")
.WithMany("Forms")
.HasForeignKey("StoreId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Store");
});
modelBuilder.Entity("BTCPayServer.Data.PayoutProcessorData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "Store")
.WithMany("PayoutProcessors")
@@ -1519,6 +1588,8 @@ namespace BTCPayServer.Migrations
b.Navigation("CustodianAccounts");
b.Navigation("Forms");
b.Navigation("Invoices");
b.Navigation("LightningAddresses");

View File

@@ -28,7 +28,7 @@ namespace BTCPayServer.PluginPacker
var name = args[1];
var outputDir = Path.Combine(args[2], name);
var outputFile = Path.Combine(outputDir, name);
var rootDLLPath = Path.Combine(directory, name + ".dll");
var rootDLLPath = Path.GetFullPath(Path.Combine(directory, name + ".dll"));
if (!File.Exists(rootDLLPath))
{
throw new Exception($"{rootDLLPath} could not be found");

View File

@@ -12,17 +12,15 @@ namespace BTCPayServer.Rating
public string Name { get; }
public string Url { get; }
public string Id { get; }
public string SourceId { get; }
public RateSource Source { get; }
public AvailableRateProvider(string id, string name, string url) : this(id, id, name, url, RateSource.Direct)
public AvailableRateProvider(string id, string name, string url) : this(id, name, url, RateSource.Direct)
{
}
public AvailableRateProvider(string id, string sourceId, string name, string url, RateSource source)
public AvailableRateProvider(string id, string name, string url, RateSource source)
{
Id = id;
SourceId = sourceId;
Name = name;
Url = url;
Source = source;

View File

@@ -1305,7 +1305,7 @@
"name":"Satoshis",
"code":"SATS",
"divisibility":0,
"symbol":"Sats",
"symbol":"sats",
"crypto":true
},
{

View File

@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BTCPayServer.Rating;
using NBitcoin;
using Newtonsoft.Json;
@@ -28,14 +27,6 @@ namespace BTCPayServer.Services.Rates
}
static readonly Dictionary<string, IFormatProvider> _CurrencyProviders = new Dictionary<string, IFormatProvider>();
public string FormatCurrency(string price, string currency)
{
return FormatCurrency(decimal.Parse(price, CultureInfo.InvariantCulture), currency);
}
public string FormatCurrency(decimal price, string currency)
{
return price.ToString("C", GetCurrencyProvider(currency));
}
public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback)
{
@@ -56,6 +47,7 @@ namespace BTCPayServer.Services.Rates
currencyInfo.CurrencySymbol = currency;
return currencyInfo;
}
public NumberFormatInfo GetNumberFormatInfo(string currency)
{
var curr = GetCurrencyProvider(currency);
@@ -65,6 +57,7 @@ namespace BTCPayServer.Services.Rates
return ni;
return null;
}
public IFormatProvider GetCurrencyProvider(string currency)
{
lock (_CurrencyProviders)
@@ -104,30 +97,6 @@ namespace BTCPayServer.Services.Rates
currencyProviders.TryAdd(code, number);
}
/// <summary>
/// Format a currency like "0.004 $ (USD)", round to significant divisibility
/// </summary>
/// <param name="value">The value</param>
/// <param name="currency">Currency code</param>
/// <returns></returns>
public string DisplayFormatCurrency(decimal value, string currency)
{
var provider = GetNumberFormatInfo(currency, true);
var currencyData = GetCurrencyData(currency, true);
var divisibility = currencyData.Divisibility;
value = value.RoundToSignificant(ref divisibility);
if (divisibility != provider.CurrencyDecimalDigits)
{
provider = (NumberFormatInfo)provider.Clone();
provider.CurrencyDecimalDigits = divisibility;
}
if (currencyData.Crypto)
return value.ToString("C", provider);
else
return value.ToString("C", provider) + $" ({currency})";
}
readonly Dictionary<string, CurrencyData> _Currencies;
static CurrencyData[] LoadCurrency()

View File

@@ -15,6 +15,8 @@ namespace BTCPayServer.Services.Rates
_httpClient = httpClient ?? new HttpClient();
}
public RateSourceInfo RateSourceInfo => new("argoneum", "Argoneum", "https://rates.argoneum.net/rates");
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
// Example result: AGM to BTC rate: {"agm":5000000.000000}

View File

@@ -19,6 +19,9 @@ namespace BTCPayServer.Rating.Providers
public decimal? ask { get; set; }
}
private readonly HttpClient _httpClient;
public RateSourceInfo RateSourceInfo => new RateSourceInfo("btcturk", "BtcTurk", "https://api.btcturk.com/api/v2/ticker");
public BtcTurkRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();

View File

@@ -222,6 +222,8 @@ namespace BTCPayServer.Services.Rates
}
}
public RateSourceInfo RateSourceInfo => _Inner.RateSourceInfo;
private async Task<LatestFetch> Fetch(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

View File

@@ -11,6 +11,9 @@ namespace BTCPayServer.Services.Rates
public class BitbankRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public RateSourceInfo RateSourceInfo => new("bitbank", "Bitbank", "https://public.bitbank.cc/tickers");
public BitbankRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();

View File

@@ -15,6 +15,8 @@ namespace BTCPayServer.Services.Rates
_httpClient = httpClient ?? new HttpClient();
}
public RateSourceInfo RateSourceInfo => new RateSourceInfo("bitflyer", "Bitflyer", "https://api.bitflyer.com/v1/ticker");
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.bitflyer.jp/v1/ticker", cancellationToken);

View File

@@ -15,6 +15,8 @@ namespace BTCPayServer.Services.Rates
_httpClient = httpClient ?? new HttpClient();
}
public RateSourceInfo RateSourceInfo => new("bitpay", "Bitpay", "https://bitpay.com/rates");
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://bitpay.com/rates", cancellationToken);

View File

@@ -14,6 +14,8 @@ public class BudaRateProvider : IRateProvider
_httpClient = httpClient ?? new HttpClient();
}
public RateSourceInfo RateSourceInfo => new RateSourceInfo("buda", "Buda", "https://www.buda.com/api/v2/markets/btc-clp/ticker");
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://www.buda.com/api/v2/markets/btc-clp/ticker", cancellationToken);

View File

@@ -14,6 +14,8 @@ namespace BTCPayServer.Services.Rates
_httpClient = httpClient ?? new HttpClient();
}
public RateSourceInfo RateSourceInfo => new RateSourceInfo("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD");
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", cancellationToken);

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@ namespace BTCPayServer.Services.Rates
{
public class CryptoMarketExchangeRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
private readonly HttpClient _httpClient;
public CryptoMarketExchangeRateProvider(HttpClient httpClient)
{

View File

@@ -40,6 +40,9 @@ namespace BTCPayServer.Services.Rates
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
readonly ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>();
public RateSourceInfo RateSourceInfo { get; set; }
private async Task<PairRate> CreateExchangeRate(T exchangeAPI, KeyValuePair<string, ExchangeTicker> ticker)
{
if (notFoundSymbols.TryGetValue(ticker.Key, out _))

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
namespace BTCPayServer.Services.Rates
{
public class FallbackRateProvider : IRateProvider
{
readonly IRateProvider[] _Providers;
public FallbackRateProvider(IRateProvider[] providers)
{
ArgumentNullException.ThrowIfNull(providers);
_Providers = providers;
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
foreach (var p in _Providers)
{
try
{
return await p.GetRatesAsync(cancellationToken).ConfigureAwait(false);
}
catch when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex) { Exceptions.Add(ex); }
}
return Array.Empty<PairRate>();
}
public List<Exception> Exceptions { get; set; } = new List<Exception>();
}
}

View File

@@ -12,6 +12,7 @@ namespace BTCPayServer.Rating
{
public class HitBTCRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker");
private readonly HttpClient _httpClient;
public HitBTCRateProvider(HttpClient httpClient)
{

View File

@@ -6,6 +6,7 @@ namespace BTCPayServer.Services.Rates
{
public interface IRateProvider
{
RateSourceInfo RateSourceInfo { get; }
Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken);
}
}

View File

@@ -16,7 +16,7 @@ namespace BTCPayServer.Services.Rates
// Make sure that only one request is sent to kraken in general
public class KrakenExchangeRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("kraken", "Kraken", "https://api.kraken.com/0/public/Ticker?pair=ATOMETH,ATOMEUR,ATOMUSD,ATOMXBT,BATETH,BATEUR,BATUSD,BATXBT,BCHEUR,BCHUSD,BCHXBT,DAIEUR,DAIUSD,DAIUSDT,DASHEUR,DASHUSD,DASHXBT,EOSETH,EOSXBT,ETHCHF,ETHDAI,ETHUSDC,ETHUSDT,GNOETH,GNOXBT,ICXETH,ICXEUR,ICXUSD,ICXXBT,LINKETH,LINKEUR,LINKUSD,LINKXBT,LSKETH,LSKEUR,LSKUSD,LSKXBT,NANOETH,NANOEUR,NANOUSD,NANOXBT,OMGETH,OMGEUR,OMGUSD,OMGXBT,PAXGETH,PAXGEUR,PAXGUSD,PAXGXBT,SCETH,SCEUR,SCUSD,SCXBT,USDCEUR,USDCUSD,USDCUSDT,USDTCAD,USDTEUR,USDTGBP,USDTZUSD,WAVESETH,WAVESEUR,WAVESUSD,WAVESXBT,XBTCHF,XBTDAI,XBTUSDC,XBTUSDT,XDGEUR,XDGUSD,XETCXETH,XETCXXBT,XETCZEUR,XETCZUSD,XETHXXBT,XETHZCAD,XETHZEUR,XETHZGBP,XETHZJPY,XETHZUSD,XLTCXXBT,XLTCZEUR,XLTCZUSD,XMLNXETH,XMLNXXBT,XMLNZEUR,XMLNZUSD,XREPXETH,XREPXXBT,XREPZEUR,XXBTZCAD,XXBTZEUR,XXBTZGBP,XXBTZJPY,XXBTZUSD,XXDGXXBT,XXLMXXBT,XXMRXXBT,XXMRZEUR,XXMRZUSD,XXRPXXBT,XXRPZEUR,XXRPZUSD,XZECXXBT,XZECZEUR,XZECZUSD");
public HttpClient HttpClient
{
get

View File

@@ -19,6 +19,9 @@ namespace BTCPayServer.Services.Rates
return _Instance;
}
}
public RateSourceInfo RateSourceInfo => new RateSourceInfo("NULL", "NULL", "https://NULL.NULL");
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Array.Empty<PairRate>());

View File

@@ -13,6 +13,7 @@ namespace BTCPayServer.Services.Rates
{
public class RipioExchangeProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
private readonly HttpClient _httpClient;
public RipioExchangeProvider(HttpClient httpClient)
{

View File

@@ -14,6 +14,7 @@ namespace BTCPayServer.Services.Rates
{
public class YadioRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("yadio", "Yadio", "https://api.yadio.io/exrates/BTC");
private readonly HttpClient _httpClient;
public YadioRateProvider(HttpClient httpClient)
{

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BTCPayServer.Rating
{
public class RateSourceInfo
{
public RateSourceInfo(string id, string displayName, string url)
{
Id = id;
DisplayName = displayName;
Url = url;
}
public string Id { get; set; }
public string DisplayName { get; set; }
public string Url { get; set; }
}
}

View File

@@ -2,11 +2,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using BTCPayServer.Rating.Providers;
using ExchangeSharp;
using NBitcoin;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
@@ -15,6 +17,7 @@ namespace BTCPayServer.Services.Rates
{
class WrapperRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => _inner.RateSourceInfo;
private readonly IRateProvider _inner;
public Exception Exception { get; private set; }
public TimeSpan Latency { get; set; }
@@ -47,148 +50,49 @@ namespace BTCPayServer.Services.Rates
public ExchangeException Exception { get; internal set; }
public string Exchange { get; internal set; }
}
public RateProviderFactory(IHttpClientFactory httpClientFactory)
public RateProviderFactory(IHttpClientFactory httpClientFactory, IEnumerable<IRateProvider> rateProviders)
{
_httpClientFactory = httpClientFactory;
foreach (var prov in rateProviders)
{
Providers.Add(prov.RateSourceInfo.Id, prov);
}
InitExchanges();
}
private readonly IHttpClientFactory _httpClientFactory;
private readonly Dictionary<string, IRateProvider> _DirectProviders = new Dictionary<string, IRateProvider>();
public Dictionary<string, IRateProvider> Providers
{
get
{
return _DirectProviders;
}
}
internal IEnumerable<AvailableRateProvider> GetDirectlySupportedExchanges()
{
yield return new AvailableRateProvider("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr");
yield return new AvailableRateProvider("bittrex", "Bittrex", "https://bittrex.com/api/v1.1/public/getmarketsummaries");
yield return new AvailableRateProvider("poloniex", "Poloniex", "https://poloniex.com/public?command=returnTicker");
yield return new AvailableRateProvider("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker");
yield return new AvailableRateProvider("ndax", "NDAX", "https://ndax.io/api/returnTicker");
yield return new AvailableRateProvider("coingecko", "CoinGecko", "https://api.coingecko.com/api/v3/exchange_rates");
yield return new AvailableRateProvider("kraken", "Kraken", "https://api.kraken.com/0/public/Ticker?pair=ATOMETH,ATOMEUR,ATOMUSD,ATOMXBT,BATETH,BATEUR,BATUSD,BATXBT,BCHEUR,BCHUSD,BCHXBT,DAIEUR,DAIUSD,DAIUSDT,DASHEUR,DASHUSD,DASHXBT,EOSETH,EOSXBT,ETHCHF,ETHDAI,ETHUSDC,ETHUSDT,GNOETH,GNOXBT,ICXETH,ICXEUR,ICXUSD,ICXXBT,LINKETH,LINKEUR,LINKUSD,LINKXBT,LSKETH,LSKEUR,LSKUSD,LSKXBT,NANOETH,NANOEUR,NANOUSD,NANOXBT,OMGETH,OMGEUR,OMGUSD,OMGXBT,PAXGETH,PAXGEUR,PAXGUSD,PAXGXBT,SCETH,SCEUR,SCUSD,SCXBT,USDCEUR,USDCUSD,USDCUSDT,USDTCAD,USDTEUR,USDTGBP,USDTZUSD,WAVESETH,WAVESEUR,WAVESUSD,WAVESXBT,XBTCHF,XBTDAI,XBTUSDC,XBTUSDT,XDGEUR,XDGUSD,XETCXETH,XETCXXBT,XETCZEUR,XETCZUSD,XETHXXBT,XETHZCAD,XETHZEUR,XETHZGBP,XETHZJPY,XETHZUSD,XLTCXXBT,XLTCZEUR,XLTCZUSD,XMLNXETH,XMLNXXBT,XMLNZEUR,XMLNZUSD,XREPXETH,XREPXXBT,XREPZEUR,XXBTZCAD,XXBTZEUR,XXBTZGBP,XXBTZJPY,XXBTZUSD,XXDGXXBT,XXLMXXBT,XXMRXXBT,XXMRZEUR,XXMRZUSD,XXRPXXBT,XXRPZEUR,XXRPZUSD,XZECXXBT,XZECZEUR,XZECZUSD");
yield return new AvailableRateProvider("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD");
yield return new AvailableRateProvider("buda", "Buda", "https://www.buda.com/api/v2/markets/btc-clp/ticker");
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/tickers");
yield return new AvailableRateProvider("bitflyer", "Bitflyer", "https://api.bitflyer.com/v1/ticker");
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates");
yield return new AvailableRateProvider("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
yield return new AvailableRateProvider("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
yield return new AvailableRateProvider("btcturk", "BtcTurk", "https://api.btcturk.com/api/v2/ticker");
yield return new AvailableRateProvider("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,tWPRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0");
yield return new AvailableRateProvider("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker");
yield return new AvailableRateProvider("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products");
yield return new AvailableRateProvider("argoneum", "Argoneum", "https://rates.argoneum.net/rates");
yield return new AvailableRateProvider("yadio", "Yadio", "https://api.yadio.io/exrates/BTC");
}
public Dictionary<string, IRateProvider> Providers { get; } = new Dictionary<string, IRateProvider>();
void InitExchanges()
{
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
AddExchangeSharpProviders<ExchangeBinanceAPI>("binance");
AddExchangeSharpProviders<ExchangeBittrexAPI>("bittrex");
AddExchangeSharpProviders<ExchangePoloniexAPI>("poloniex");
AddExchangeSharpProviders<ExchangeNDAXAPI>("ndax");
// Handmade providers
Providers.Add("hitbtc", new HitBTCRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_HITBTC")));
Providers.Add("coingecko", new CoinGeckoRateProvider(_httpClientFactory));
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
Providers.Add("buda", new BudaRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BUDA")));
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
Providers.Add("ripio", new RipioExchangeProvider(_httpClientFactory?.CreateClient("EXCHANGE_RIPIO")));
Providers.Add("cryptomarket", new CryptoMarketExchangeRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_CRYPTOMARKET")));
Providers.Add("bitflyer", new BitflyerRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITFLYER")));
Providers.Add("yadio", new YadioRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_YADIO")));
Providers.Add("btcturk", new BtcTurkRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BTCTURK")));
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
// Backward compatibility: coinaverage should be using coingecko to prevent stores from breaking
Providers.Add("coinaverage", new CoinGeckoRateProvider(_httpClientFactory));
AddExchangeSharpProviders<ExchangeBitfinexAPI>("bitfinex");
AddExchangeSharpProviders<ExchangeOKExAPI>("okex");
AddExchangeSharpProviders<ExchangeCoinbaseAPI>("coinbasepro");
// Those exchanges make too many requests, exchange sharp do not parallelize so it is too slow...
//AddExchangeSharpProviders<ExchangeGeminiAPI>("gemini");
//AddExchangeSharpProviders<ExchangeBitstampAPI>("bitstamp");
//AddExchangeSharpProviders<ExchangeBitMEXAPI>("bitmex");
foreach (var provider in Providers.ToArray())
{
var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]);
prov.RefreshRate = TimeSpan.FromMinutes(1.0);
prov.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers[provider.Key] = prov;
var rsi = provider.Value.RateSourceInfo;
AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url));
}
Providers["gdax"] = Providers["coinbasepro"];
foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
foreach (var supportedExchange in CoinGeckoRateProvider.SupportedExchanges.Values)
{
if (!Providers.ContainsKey(supportedExchange.Id) && supportedExchange.Id != CoinGeckoRateProvider.CoinGeckoName)
{
var coingecko = new CoinGeckoRateProvider(_httpClientFactory)
{
UnderlyingExchange = supportedExchange.SourceId
UnderlyingExchange = supportedExchange.Id
};
var bgFetcher = new BackgroundFetcherRateProvider(coingecko);
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers.Add(supportedExchange.Id, bgFetcher);
var rsi = coingecko.RateSourceInfo;
AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url, RateSource.Coingecko));
}
}
AvailableRateProviders.Sort((a, b) => StringComparer.Ordinal.Compare(a.DisplayName, b.DisplayName));
}
private IRateProvider AddExchangeSharpProviders<T>(string providerName) where T : ExchangeAPI
{
var provider = new ExchangeSharpRateProvider<T>(_httpClientFactory.CreateClient($"EXCHANGE_{providerName}".ToUpperInvariant()));
Providers.Add(providerName, provider);
return provider;
}
IEnumerable<AvailableRateProvider> _AvailableRateProviders = null;
public IEnumerable<AvailableRateProvider> GetSupportedExchanges()
{
if (_AvailableRateProviders == null)
{
var availableProviders = new Dictionary<string, AvailableRateProvider>();
foreach (var exchange in GetDirectlySupportedExchanges())
{
availableProviders.Add(exchange.Id, exchange);
}
foreach (var exchange in GetCoinGeckoSupportedExchanges())
{
availableProviders.TryAdd(exchange.Id, exchange);
}
_AvailableRateProviders = availableProviders.Values.OrderBy(o => o.Name).ToArray();
}
return _AvailableRateProviders;
}
internal IEnumerable<AvailableRateProvider> GetCoinGeckoSupportedExchanges()
{
return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>
new AvailableRateProvider(Normalize(token["id"].ToString().ToLowerInvariant()), token["id"].ToString().ToLowerInvariant(), token["name"].ToString(),
$"https://api.coingecko.com/api/v3/exchanges/{token["id"]}/tickers", RateSource.Coingecko));
}
private string Normalize(string name)
{
if (name == "oasis_trade")
return "oasisdex";
if (name == "gdax")
return "coinbasepro";
return name;
}
public List<AvailableRateProvider> AvailableRateProviders { get; } = new List<AvailableRateProvider>();
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
{

View File

@@ -11,6 +11,7 @@ using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Plugins.PointOfSale;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
@@ -50,6 +51,7 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme(cryptoCode);
user.RegisterDerivationScheme("LTC");
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
user.SetLNUrl("BTC", false);
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
@@ -386,7 +388,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("BOLT11Expiration")).SendKeys("5" + Keys.Enter);
s.GoToInvoice(invoice.Id);
s.Driver.FindElement(By.Id("IssueRefund")).Click();
if (multiCurrency)
{
s.Driver.WaitUntilAvailable(By.Id("RefundForm"), TimeSpan.FromSeconds(1));
@@ -396,21 +398,21 @@ namespace BTCPayServer.Tests
}
s.Driver.WaitUntilAvailable(By.Id("RefundForm"), TimeSpan.FromSeconds(1));
Assert.Contains("$5,500.00", s.Driver.PageSource); // Should propose reimburse in fiat
Assert.Contains("1.10000000 ", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before
Assert.Contains("2.20000000 ", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate
Assert.Contains("5,500.00 USD", s.Driver.PageSource); // Should propose reimburse in fiat
Assert.Contains("1.10000000 BTC", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before
Assert.Contains("2.20000000 BTC", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate
s.Driver.WaitForAndClick(By.Id(rateSelection));
s.Driver.FindElement(By.Id("ok")).Click();
s.Driver.WaitUntilAvailable(By.Id("Destination"), TimeSpan.FromSeconds(1));
Assert.Contains("pull-payments", s.Driver.Url);
if (rateSelection == "FiatOption")
Assert.Contains("$5,500.00", s.Driver.PageSource);
Assert.Contains("5,500.00 USD", s.Driver.PageSource);
if (rateSelection == "CurrentOption")
Assert.Contains("2.20000000 ", s.Driver.PageSource);
Assert.Contains("2.20000000 BTC", s.Driver.PageSource);
if (rateSelection == "RateThenOption")
Assert.Contains("1.10000000 ", s.Driver.PageSource);
Assert.Contains("1.10000000 BTC", s.Driver.PageSource);
s.GoToInvoice(invoice.Id);
s.Driver.FindElement(By.Id("IssueRefund")).Click();
s.Driver.WaitUntilAvailable(By.Id("Destination"), TimeSpan.FromSeconds(1));
@@ -584,7 +586,7 @@ namespace BTCPayServer.Tests
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
@@ -622,10 +624,11 @@ namespace BTCPayServer.Tests
var apps = user.GetController<UIAppsController>();
var pos = user.GetController<UIPointOfSaleController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString();
var appType = PointOfSaleAppType.AppType;
vm.AppName = "test";
vm.SelectedAppType = appType;
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var redirect = Assert.IsType<RedirectResult>(apps.CreateApp(user.StoreId, vm).Result);
Assert.EndsWith("/settings/pos", redirect.Url);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
@@ -680,7 +683,7 @@ donation:
var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple"));
Assert.NotNull(appleInvoice);
Assert.Equal("good apple", appleInvoice.ItemDesc);
// testing custom amount
var action = Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result);
@@ -735,7 +738,7 @@ donation:
Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility);
Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace);
}
//test inventory related features
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
@@ -756,7 +759,7 @@ noninventoryitem:
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result);
return Task.CompletedTask;
});
//we already bought all available stock so this should fail
await Task.Delay(100);
Assert.IsType<RedirectToActionResult>(publicApps
@@ -819,13 +822,13 @@ normal:
normalInvoice.CryptoInfo,
s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
s.CryptoCode));
//test topup option
vmpos.Template = @"
a:
price: 1000.0
title: good apple
b:
price: 10.0
custom: false
@@ -843,7 +846,7 @@ f:
g:
custom: topup
";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.DoesNotContain("custom", vmpos.Template);
@@ -855,7 +858,7 @@ g:
Assert.Contains(items, item => item.Id == "e" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);
Assert.Contains(items, item => item.Id == "f" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup);
Assert.Contains(items, item => item.Id == "g" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Static, null, null, null, null, null, "g").Result);
invoices = user.BitPay.GetInvoices();

View File

@@ -286,7 +286,7 @@ namespace BTCPayServer.Tests
if (permissions.Contains(canModifyAllStores) || storePermissions.Any())
{
var resultStores =
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
await TestApiAgainstAccessToken<Client.Models.StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
tester.PayTester.HttpClient);
foreach (var selectiveStorePermission in storePermissions)

View File

@@ -23,7 +23,7 @@
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Include="Selenium.Support" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="108.0.5359.7100" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="112.0.5615.4900" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Payments;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Stores;
@@ -27,6 +28,7 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
s.AddDerivationScheme();
s.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
@@ -72,6 +74,7 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
s.AddDerivationScheme();
// Now create an invoice that requires a refund email
@@ -124,6 +127,7 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice();
@@ -154,13 +158,13 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser(true);
s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
s.AddLightningNode();
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId);
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
Assert.Equal("Bitcoin (Lightning)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
s.Driver.Quit();
}
@@ -174,6 +178,7 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser(true);
s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
s.AddLightningNode();
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
@@ -182,7 +187,7 @@ namespace BTCPayServer.Tests
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("buyerTotalLine")).Text);
}
[Fact(Timeout = TestTimeout)]
@@ -193,6 +198,7 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
s.GoToStore();
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");

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