From bc8978182e73979c8becc7bf5a8c800b66b9bd37 Mon Sep 17 00:00:00 2001 From: Cronos Date: Tue, 17 Apr 2018 11:13:50 -0500 Subject: [PATCH 01/43] Add Polis --- BTCPayServer/BTCPayNetworkProvider.Polis.cs | 32 ++++++++++++++++++++ BTCPayServer/BTCPayNetworkProvider.cs | 1 + BTCPayServer/BTCPayServer.csproj | 15 ++++----- BTCPayServer/wwwroot/imlegacy/polis.png | Bin 0 -> 10819 bytes Nuget.Config | 2 +- 5 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 BTCPayServer/BTCPayNetworkProvider.Polis.cs create mode 100644 BTCPayServer/wwwroot/imlegacy/polis.png diff --git a/BTCPayServer/BTCPayNetworkProvider.Polis.cs b/BTCPayServer/BTCPayNetworkProvider.Polis.cs new file mode 100644 index 000000000..7dc0e9303 --- /dev/null +++ b/BTCPayServer/BTCPayNetworkProvider.Polis.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Services.Rates; +using NBitcoin; +using NBXplorer; + +namespace BTCPayServer +{ + public partial class BTCPayNetworkProvider + { + public void InitPolis() + { + NBitcoin.Altcoins.Polis.EnsureRegistered(); + + var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("POLIS"); + Add(new BTCPayNetwork() + { + CryptoCode = nbxplorerNetwork.CryptoCode, + BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/f{0}", + NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, + NBXplorerNetwork = nbxplorerNetwork, + UriScheme = "polis", + DefaultRateProvider = new CoinAverageRateProviderDescription("POLIS"), + CryptoImagePath = "imlegacy/polis.png", + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType), + CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("1'") + }); + } + } +} diff --git a/BTCPayServer/BTCPayNetworkProvider.cs b/BTCPayServer/BTCPayNetworkProvider.cs index faef1e19f..523eebd84 100644 --- a/BTCPayServer/BTCPayNetworkProvider.cs +++ b/BTCPayServer/BTCPayNetworkProvider.cs @@ -48,6 +48,7 @@ namespace BTCPayServer InitBitcoin(); InitLitecoin(); InitDogecoin(); + InitPolis(); } /// diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 767120e1e..301d507c4 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -35,25 +35,26 @@ - - - + - - - + + + + + + + - diff --git a/BTCPayServer/wwwroot/imlegacy/polis.png b/BTCPayServer/wwwroot/imlegacy/polis.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae0ca7c13cad30b20e3a1674199709b378b1732 GIT binary patch literal 10819 zcmWk!cRX9)8@>r4R>j^#)u>goRYgMW+Ix$lMQc-=5NeMWHCkUOT6?s1t*EWkD2m#< zs%D9q{QUkp_jB)i&$;h?&-?@k zZrJ?PEc}c;pZEnh_&5W~PM(jQ;actvPo0gN9h`!^2b>iE;P$$fs^<&IK;wei7a|NBMqI6(eHRs;N=l~&c!2-?NEsK^YwRGN7} zQV?Ka(RtKsv5%HWsu8LIB(^CQ*+kt1;=agf&zY zaOMvSsQDTY@{`0b$weojPePQGm#j;HQxs4WxJp3(t5mqDo=w@kB#{WBLK+TTB?Ch3exe>uRD>M0-HXVF+jY`3A?{s=iY5cZJLF<)~*K zU&Rlm`(&>N45I5WRyZbI^Atv|#Y8RnJ{dXTdA;Mi+xmm3_$c(n@7MSl7JHqkj`pkPvnjWZqPBVaHs#%z6*i~hsq=j_CyE$l1R>pQ=<_VbguHoj%Gee&Jh4(CBc{P%C+ zBxLW{>gjMUp>4JOVmtgNL^NjX)2C-6Dln9!6Q>9|M52H9+Ig#$Mo4*>fU;u68vK$W zk>z4Ue&Zq=ezdko+)WSO{!@Lm82O4;sozgWKH{|NSpo2>>F-^8(b;yqA~AsL9$006 zH%FS_t6tS*vcwH{ksGMZf#9M~r>;Vpzf=ePGaRR+eT|ue%bWRWg|hI*0L&!O@A+Wl z2{B$vTVt*wyoGX|02PP|n4DOA^7bG6GPOrLzitq-WYMD^AN$SeCsWLauRiGGG^g7* z`9KW|GFUWqHwtY_!i@fSdJ31{wjh7= zm@J+9DNUIOpgg%C@?Q+ieT(JB3fn*F9fk=voDibiU41E;;hFl5Tz+@#cPn8kqngyn z@Hkw`)rGqygm2|jY1w2JL&naxZq&L`S~BmP&SHAO*m<~D9|I= zq4THyiE?Q3Y${#(4(kh`5oraver&QtPyHES{P`1+@nq6dHT3RL;xe}dJ=lUoJx7>p}J@gfYsz z;dy)#9Zn2+K3aoy5v?KXwE(nHhkRMYKgWMOCGd2U_V|^=oz*P3dJX13)OZtVJ$)LHi6Nb*THixoDb0VMQm3j;3@N6TtOGOb?|4}l{h z7KH|B$1#^~GgbeNE&}$RG-R+|aonAQ@c?JM;FnXlIP)b@T>Jw=^{HhW}|T=iG8tG^(*|{O=R4|M^m35X(VTj|NaGH!ovIoh)aYkrL$&i z?AT~Li|^`jBbr!L{AvT!LEJd`>tES0v*xKoNPqaypE$)|As`;E{eoh&^t80n+8@sV7n)vw zT#0d0Q3Q`!w7vP)f(;0O4sIH z!|YBG2yf>PtOPd2qmeQa`LfdwO@`ukeAz70nD-PrJaV^fbY&l+7cfHXfHWAiC6)a}CF;cyeP z7svEmLGEqZdvZ$^>PM&(u7FcwgBKyx_7PTkuZ=|7=T<~A|!8OmSnFQ`2mpHoJBoYv^{+7>zLX~a+xdD74Qv(rc zbz_bEH{vj^_~2#Xl}>nA|3ewCDZ7a=D}QOjB$4k@Yh8Ndi2x~bRVT`(3~~emDlYSM z2s?BENz9PYIm;hvC{-xZ+!cZ>jubQ!*;?va`}uHOIagpQJ#9Q5kf(NiF6iM_IA+B# zkowqn+$c)^S$!)@`YuTFea>ZaBxOCfr21#0WbNnoT}zQ;qiHY_k3!}My-|iXx*?=-a)!@^N%4?qV4C?^d|6G!zg1yn$f`%};5)$Nd)UVnR zw+J$Fn$=bD8q%UB6;fv6<>rKKv@R9pR4y|7a962$KxN)FCNqB^Vs*{UPs(F_n`Zp( z;02%A5;>J@1r)8X=@FW*WV9k!1E45i)=9GbLetbB_{G)!*(8_QH1;c~L#(`Q- z+HdR3lZ4VP3YP?q!pzXypIx2EmcT>9gG~KKfaV#&YqWasw^C4Dz37;~caN^)E9^&X znMOtI4@|Cd=g>BZ8`~=$C4_7GNY1^5>>K9}kKtS9vbBa&>wn0O*>ldu{p@+op8c=_ zLhs(CbXZUM!t-%AvTh$vX8J2S+FQlIV1a=Y!h*PxIsW@L8cHpH2Z*|KU#K@)RQFbuYL)dG2tgM*9>%~&a3)&^hk^?kPh2_Ia+L~OZY=>8upO*v=mh@x}yW3q$ z30qqB>I|@S9%V%>1uiKx)WSPLQex@Kvf{?w?~_n;Snk_>hyzYztFkS>Ziobk2;H z|GCy-Z$nk3)tB&Um$Vn=m?Q9V`R3(m?8@tyAiAfhAa=2TxsO4l^@Izdj|lBBXAV!D zl+UME5+V7PZL=Xq_c{))9?W=~3$ixooxb#J!`nR(;npmo|2+}Q^BNrQ5*@$VH38?T z!q>b+F6ZtCbM_^Vd_C>|n5%_*_Q`4mlz)6#od0IU>8XX5TfoM8B6Cnj#3t9^m?JHv z$Wl!WSp37)jRsM`Xi#6V@+ZPeS?9EXB%D*{l0veXZf;EWXc^<^uj)1IY|K*L=vI2n z-uv7uxyj5%y2@6NDehAbt7C=ahCKgR6__&(&oyUoVL!cOvjK+5oa{`X-tndlrs722xD(A@g?x%@yCj7}FZs{;4DPO7 zPG(leorO1puZ%+@Dv7}%2K2ch? z+BSP!K)Xnd$K5Gp#;cRj_$R*h34ml2&fu8pk>Vb@hj9z8%MD>Y5r_H9mM_@ zuVqOljWA<;C=xDRbtgFl_F~3{mi;8wXr+1#998LB0q}vK+gA)>5g8Xsx46r4R=3^m zV6>%5il4D(^$ewUC8RM;3=J!|&XK@FBW&v_O;XSg9x)Pbb%SSf1pw|(FlAlWrI*bO z7b3b=r4>)Y`QXzZQ)qXc?U?o0@o?SQ21{?|OhIl5_WK)SlXiTfn54TG#ywy|UnNW{G&x=|$X)a?D zH4k&UU51^ktgM)%5rf()d{q7)NM~$5Z;XE-cl&*JLvvvl)KuW{(x}-9^R^cZQ{|{P zir~t9UASI~b&*orD0vqkL_nv-S!w6SBS&56PQ$bSRq% z;rg3=?kCupkQ@6~Uwf2q&5us=d8PN_wPdck+KKQse6!-@tvAJoV`e`aNXmEF%+%}1 zL@omRcR-|ieTp@<=Q<#{I>~;M0Slm$5-`VG)&kK5e!9-2LJMRiVZ5BG}k&cO&hz$DmuUdCH2WF86%o^oLN4bi&0r*%iG` zI}UJ~g>BI}|9;_VwhLL;XC@K354}QDh_j-nm=-aL4Mc8OLr)u7-lGX-br=3&l*G=% zb>`2Y=qhD*P;9aoS``91n^eztMvO*w_EWieNwx0h`p9VkU(%IqD-E902T@`py#R02xuSZ zj}1l4EF^S}=15nyfIb_R`;jQMd-0yU#{K;!sF%cV3FK)-glcJMyuY^lliVp(&H?BN zK+$V2@__4~RzDt5JU!$j;S)Ki_`?sCSOr1TtIBTohx}nndN&8ege={1@I*eX01Upw zl?+2|l9+v$5)Ct(nZzjzWGE?oF9mFgL<;`%y_+-pd84BsHE~0vePhYHfVp~A|5rUY z{je+9vyk86WaEL$H}jfI{!1P~R@~yejH)jE!TAs`t+T7ajc0stV3W6ZINLcK@crF; z3lLv2DO{3RU6xc%K-|@lnaz&oh>$9$|NRcw|;&v#AKLH7x48)yJO(%$MoCp zK4CA|4Bfkw=fthZ*$YP`nt2?sqqjr-at0Lr|911aaboXm^(jARYLF2pgFOSm=dmA& zWzRh}UkOlV9U7VS){S0K&+l1Gge(_W({Bj!-xw`8CFA-+h%hZ9j#K6hkk(5!+#L*Y z4}CdMU&QZk$}G6pHl9l2r)mkr6FS(h?rZ~_-V1LBoVZ6yDy?j&dCSpN1NCd4$~J?T zv0|V2cBo2LzX${I!r6!^#gocz#|hpYL4WsW~fIe#mLsJPUn&G!Axo6x{Xh!QMMUP%94e z%L6t^5dP5NB9&8nn0yom%C>oA1(Ilf`WNDnP~c;APc=HFc5Sbh}Y|S_?{gt1?qEprvD~G?XJ9Yx3k9VCy8bj0sP#t z9IlQENk!UF4u<_A-f8_7_e(9P`FPZ~Ie;fMo`aeHRC}6jx{UIAs-4-7|I3dLa*p~X zmiuO6QalxoO&)i&b~O7alOGQi)oB2Ses45LftmeDOl&`YTU0vEoSa=@U`YzP{qvPt zWYTqnY$tWA3ZbrMWgN)+imY><2T&05xT}}S17va^s%gn|WO4sK)P>wX2beur=E_*@ z{1Zj#Ol_A_)!-^HWIm@pKWLG?fRXZGHfq+QCOwx#fA3Z{15=tNftsA8zStc1PQKc; z$gCVtMj~z?KLc?wkPm9JO|ipEteH-C6Rk?}osEI<6w66zQydW`9ct`q3o+@n_mys7vrjLTfdAjx!qF!V9>Y8meV^h&xBkrZgIWn;I~! z8PB|2>#L@F3mQ~eu;PHlIL?&&m8bZ}!2r=gD?zMJ20b(y=$-;iKmZX57y14|%+TK( z#bt-59v|^o%0xC^m}WT%s;Ty|ahT)VG4JiC%|@@kO)*_fHJJQ%`$1Z(BWTJl()j`= zkvgL7OY_k&OC3nCqb^EZV?}?iui1K%{^-7=w%5>$PcVLrffR}8L*eHuQNr!ymMDcf5s7*rgUi({U*Se)jirgn%e^-n++O-*%mCjH zVRME|n9H}DS-NLB=22+l){!U(d^}H!Zi#K=eZlYtw+4kWAzcjG)n@Y7eW-=q+9wsY zoSq4$!58u(sc`r)dxDe0Cm;iA6SG0q#GVd;v;654@Sg_IYxVVYjbytxJ;A<>+ zb!$;Bl@)16{>-e>D;5G5%s%h^%SkFjc>B@OJhQOV5)iF^S5;|nH_RljfaNVjN=@XO z*f!mA4h7D9G1Spn)|V-WH0n?;L#23&KNXy#&|8$GjLa;|xBZe~2PhHN(v2>xT?LgV z^*;_X1o_kWunB8aLV+$tkfxMztJySj3YT-CZD9&U@*W@vv_B~{iX-+Hbd)o&v8<+3TH?HqaS~l11nC`<$d`#9o z_<{xd0C5{3e`r{yKG&{mJwa#_nf=)lY&vVXnV3Pi#8cBC+G}Px>%v&w#6`Y^^(foFX2ms zqh9a7{YOKY5pS@BFvUZv=L0kDAOl@Ff+AV>3=HxXnx5ad2V?{|nL;-ixG%t~Lvrk$XQ| ze4OVyDg#fK`+pvLPN%u`#9t(-i*JUQ>$md09Y+4o8c>kS! zIi;2%n-EfTbgxi}&#h|b8p%Do&$IFD_0JgM#D2czw>QU$#qQqKN}qj;@dhC+#3OA< zTvuP}g9k94__sZIk69ZW8ENCxdQ9oIm<#+}@E*j?AFHbit8ecKwmBo4fU9wcCN=4Q zrVWsmRzW}nagF@Oh|o6(=SdNn^vZ0KkKXL_+y>IMs?DgVX78VVMaji{p$00UEX?|a1l%kMlZ*wp?Wh%@68w{7&>F~Z2{<7*i_U1;1 zk_qMB8y#|eumv$fJk{p0pP9Y++rHT4I6v)S%OY_oC zr@<%*&N{%vgWVGNdQX!P?Zt_1B$sAA1LlmcA`&35jlf5*RiYzp)!pu5A3WY<#B}y^ z3*&KGUvJO1W)$v+SFGk>v!IiWc)~c8%FLaooLxZ5}gGrM5R(KV*ay@|qXB*C{b~IAE!|jA3 z=tJIpq(HtKFn9`SC5Gp1OR^^Iy!X*v$5Ldik|IU8`qh-F)lZ--#O#m6*N=gA4vA+o zQca-BWIK{0l3%pWjVPfX*Vufn6inVW{1m78QrvGtN4kZ)v>ef-F@5=C^fHwR=<(aq z{R}PwaC@$A0Iw__^*5tAeZxE4MZL^%f(H%z(C`!D-^sKUOk~1CMZH%{hY`Q81nT${>YYzYhFjXAyBFt;6fV@f|e{k^At4 zEo345S8b|0Zh{!yz7qaXV4LK@?miJoG6~x@m>VL{D0sXl2;MwlZYiDwtK%@SzTkUbHB4y z^EvZpwa~NswNEeBBAmZ5d(J1`vEqSi2QZTrvN_c!yN@V6t2;L~is%JnvsuF!MV1;z z5PQTVp$%MlwbkFWDCIu5B&#zcBc>lwqmTFe{ROB23*3&-Oojj<_QCOcq8MfLAd}w( zZx!JJ?D0ed0BO3uv~RsU{5LWY?nbSiNw+aT_0>n`eyAtNQEl%Z}pny2jh!!2Q2<(&KSG8}%9u{LM?tFBFp^;1WH#nO-fmH)@Wwom!Eef=>Jr4TqQGW?)U?6<#l zaPVaX+N4j^T0ojrO||J*5ggI2iy(=*y!sf?$iI$P|1LT9Z`yYsW$O(iCuK|G(2bD& zOa9$}`i8#ro~J1QuH}Qjb%>!iLpQy zAixY7e{Z(BcaiD+l@uoL$grLHM!%s(os<|Zl?W301pE`zt3WqhO<7;f0tzJkIZC)q z3JGD-qDv?I^K3%@83Rd#$g1A?Ko7*gCi3Vj(^fQ&zZp#Q-+kA(F{W{~_;k@b_Gj=f z_l2<^#X(Obv}t!zOlwi)c7dH2p3CeEXxt+6+rgdr&=!RVg zd^)F{CFogN0CxLo8bZ}!Zhxk}Of0>hZ)wKB3jLrX?n7tAE@$B=IjX~_0)Ui-^7W`E zB^yd~tdXhRsEpw~^VFfPcY^=zrpN!jBgt1z+b%D8@OP-$mna-=llRXG0T`7BJ&JPd z^RtceH+SIkS;erBD<;n1lRi?J{CD9=+S)kgt$2B6W>TL{hY))c+xgZXits9Gx;VDN+*q|A#VGV`X`wc6lcBiTe1v_ zeY`&%%M(nTos<;=Pis>Xsl7orZb*#-9|@ip7k^HUzRePT1aDfNwp@$~c76C$-YmBz zZym=EV-5AaIQdf9?FBKTT;JfvP&1(DIzMvDj?*SGw4BS_JpuR@>R&7vSJ|#ovygKO z-{!9V(@lyUl;|5ZP`%5f{1OQNWskU;cz}@Xl+INqv(Y-ea4WL^?-#yv z^w?0=yx7!~6rVTP6=ViacPvmMfY94HyWJL(mEa$gg@5xM0&OO#0gt+VGTjV|qquaq zIFZi+CNY1atM7$g%1OVEO5+NdEK|Cs`gtZBVRXwHMG-}sjs_wt$qYBqSojKw}*>=6nYzLy`E0X3)>)HQ$ig5C1^6Hzo?x4oOu zlicO)>}!vjw@EN+6epQx*IjPiu!%C8$EyJ0ZQ*V9TKyi%{zG9Vhix@aQ-7D;u@0nR z2@7>|ZXWyS4RWQqCbtHE6-u8Y2=;CGfHwFQO$^nB)fSEkOp+*gIZSR_;`hNpwhP6P2NFWN?_)rGzM5#Gvmr zL_vk-qI03LiTW;+&mc(J&rVH4dl7}}zX1=`55UtHh9!1qT|<|IfzPQ=E49-F-Jhdx zlJ0QJ{2Xf3xg0+~7X^+#fmU@QDM_iW1qgF#vT0~*fR|P2MH1#^E-==Tbq60`?J;5+ zSwcv8|6Zn3k?BL?el5^V1=@z#Y5idaYDoVN`+BmKtoI1lEN|5W*0A165AW%sz2FKe zK+h?fohy2FdA8n-76oK^tZ+ZTy5^vf||9 z7%(;PrOg6ssKv6f59Kod?@xo6e_Q>IbULBTz-L$;e!FhXzEwCGl6SOy?Ffuy{07+5}2v6#!cR~GV@xW zr+x)<+LC=L3ViQ@viHz1Q< zSpezb_Z1zD5Q4(n>&WV|V6G)tQ)sXS8P&!Jeq%1EIO3MP>U+a7Nlz3%`OHZ8VSI$w zpT3o#x}faDnMp*75H=rZqDY>mMw8$T+^5e?#0&| zBiP|)M^-BC)z+`IfW(m6dodUZ^kF$>3T$>*ir#{s-Ad1cpkGx=)Cn_3F-Etv{fX=~ vqhjv1xE2~1DToV4 - \ No newline at end of file + From daf1a0a4bc1e1df78110def0e43c14f7782d34fc Mon Sep 17 00:00:00 2001 From: Cronos Date: Tue, 17 Apr 2018 11:19:28 -0500 Subject: [PATCH 02/43] Revert to origin csproj --- BTCPayServer/BTCPayServer.csproj | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 301d507c4..767120e1e 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -35,26 +35,25 @@ + + - + + + + - - - - - - - + From f7989541b9b4749596bab0347c458a0d34bff54b Mon Sep 17 00:00:00 2001 From: cronos Date: Fri, 11 May 2018 10:26:08 -0500 Subject: [PATCH 03/43] Change Polis Rates - Add Cryptopia --- BTCPayServer/BTCPayNetworkProvider.Polis.cs | 16 +++++++++------- .../Services/Rates/BTCPayRateProviderFactory.cs | 1 + .../Services/Rates/CoinAverageSettings.cs | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/BTCPayServer/BTCPayNetworkProvider.Polis.cs b/BTCPayServer/BTCPayNetworkProvider.Polis.cs index 7dc0e9303..e6de92d95 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Polis.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Polis.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -12,20 +12,22 @@ namespace BTCPayServer { public void InitPolis() { - NBitcoin.Altcoins.Polis.EnsureRegistered(); - var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("POLIS"); Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, - BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/f{0}", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/api/tx/{0}" : "https://insight.polispay.org/apitx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "polis", - DefaultRateProvider = new CoinAverageRateProviderDescription("POLIS"), + DefaultRateRules = new[] + { + "POLIS_X = POLIS_BTC * BTC_X", + "POLIS_BTC = cryptopia(POLIS_BTC)" + }, CryptoImagePath = "imlegacy/polis.png", - DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType), - CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("1'") + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'") }); } } diff --git a/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs b/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs index b115d1d30..6c03bdfee 100644 --- a/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs +++ b/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs @@ -70,6 +70,7 @@ namespace BTCPayServer.Services.Rates DirectProviders.Add("bittrex", new ExchangeSharpRateProvider("bittrex", new ExchangeBittrexAPI(), true)); DirectProviders.Add("poloniex", new ExchangeSharpRateProvider("poloniex", new ExchangePoloniexAPI(), false)); DirectProviders.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitbtcAPI(), false)); + DirectProviders.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false)); // Handmade providers DirectProviders.Add("bitpay", new BitpayRateProvider(new NBitpayClient.Bitpay(new NBitcoin.Key(), new Uri("https://bitpay.com/")))); diff --git a/BTCPayServer/Services/Rates/CoinAverageSettings.cs b/BTCPayServer/Services/Rates/CoinAverageSettings.cs index f3da666f6..bd9acefad 100644 --- a/BTCPayServer/Services/Rates/CoinAverageSettings.cs +++ b/BTCPayServer/Services/Rates/CoinAverageSettings.cs @@ -121,6 +121,7 @@ namespace BTCPayServer.Services.Rates (DisplayName: "Quoine", Name: "quoine"), (DisplayName: "BTC Markets", Name: "btcmarkets"), (DisplayName: "Bitso", Name: "bitso"), + (DisplayName: "Cryptopia", Name: "cryptopia"), }) { AvailableExchanges.TryAdd(item.Name, new CoinAverageExchange(item.Name, item.DisplayName)); From d102c142b9b5ae931b320ca9e12c31beaf86a828 Mon Sep 17 00:00:00 2001 From: cronos Date: Fri, 11 May 2018 10:46:49 -0500 Subject: [PATCH 04/43] Typo --- BTCPayServer/BTCPayNetworkProvider.Polis.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayNetworkProvider.Polis.cs b/BTCPayServer/BTCPayNetworkProvider.Polis.cs index e6de92d95..98b1fc920 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Polis.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Polis.cs @@ -16,7 +16,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, - BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/api/tx/{0}" : "https://insight.polispay.org/apitx/{0}", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "polis", From 7fdf19ca220bd59c2910275fabe244d27b638ca5 Mon Sep 17 00:00:00 2001 From: cronos Date: Fri, 11 May 2018 11:07:42 -0500 Subject: [PATCH 05/43] Remove cryptopia from CoinAverage --- BTCPayServer/Services/Rates/CoinAverageSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/BTCPayServer/Services/Rates/CoinAverageSettings.cs b/BTCPayServer/Services/Rates/CoinAverageSettings.cs index bf4669d80..9d0c06df2 100644 --- a/BTCPayServer/Services/Rates/CoinAverageSettings.cs +++ b/BTCPayServer/Services/Rates/CoinAverageSettings.cs @@ -120,7 +120,6 @@ namespace BTCPayServer.Services.Rates (DisplayName: "Quoine", Name: "quoine"), (DisplayName: "BTC Markets", Name: "btcmarkets"), (DisplayName: "Bitso", Name: "bitso"), - (DisplayName: "Cryptopia", Name: "cryptopia"), }) { AvailableExchanges.TryAdd(item.Name, new CoinAverageExchange(item.Name, item.DisplayName)); From 0a83f21af59393ec2b107026e8c428293fd50c42 Mon Sep 17 00:00:00 2001 From: zeusthealmighty <37151557+zeusthealmighty@users.noreply.github.com> Date: Mon, 14 May 2018 10:04:12 -0500 Subject: [PATCH 06/43] KeyPath Updated --- BTCPayServer/BTCPayNetworkProvider.Polis.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayNetworkProvider.Polis.cs b/BTCPayServer/BTCPayNetworkProvider.Polis.cs index 98b1fc920..69640dab1 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Polis.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Polis.cs @@ -27,7 +27,7 @@ namespace BTCPayServer }, CryptoImagePath = "imlegacy/polis.png", DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), - CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'") + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1997'") : new KeyPath("1'") }); } } From 20635ea3d623cdd15da2b44c7da96e273952b810 Mon Sep 17 00:00:00 2001 From: rockstardev Date: Wed, 16 May 2018 05:46:11 -0500 Subject: [PATCH 07/43] Showing exchange rate for cryptos Ref: https://github.com/btcpayserver/btcpayserver/pull/170#issuecomment-389462155 --- .../Controllers/InvoiceController.UI.cs | 32 +++++++++++-------- .../Views/Invoice/Checkout-Body.cshtml | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 7515c18da..08d9c7118 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -85,7 +85,7 @@ namespace BTCPayServer.Controllers { cryptoPayment.Address = onchainMethod.DepositAddress; } - cryptoPayment.Rate = FormatCurrency(data); + cryptoPayment.Rate = ExchangeRate(data); cryptoPayment.PaymentUrl = cryptoInfo.PaymentUrls.BIP21; model.CryptoPayments.Add(cryptoPayment); } @@ -244,14 +244,14 @@ namespace BTCPayServer.Controllers BtcAddress = paymentMethodDetails.GetPaymentDestination(), BtcDue = accounting.Due.ToString(), OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(), - OrderAmountFiat = OrderAmountFiat(invoice.ProductInformation), + OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation), CustomerEmail = invoice.RefundMail, RequiresRefundEmail = storeBlob.RequiresRefundEmail, ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds), MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds, MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes, ItemDesc = invoice.ProductInformation.ItemDesc, - Rate = FormatCurrency(paymentMethod), + Rate = ExchangeRate(paymentMethod), MerchantRefLink = invoice.RedirectURL ?? "/", StoreName = store.StoreName, InvoiceBitcoinUrl = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? cryptoInfo.PaymentUrls.BIP21 : @@ -289,11 +289,20 @@ namespace BTCPayServer.Controllers return (paymentMethodId.PaymentType == PaymentTypes.BTCLike ? Url.Content(network.CryptoImagePath) : Url.Content(network.LightningImagePath)); } - private string FormatCurrency(PaymentMethod paymentMethod) + private string OrderAmountFromInvoice(string cryptoCode, ProductInformation productInformation) + { + // if invoice source currency is the same as currently display currency, no need for "order amount from invoice" + if (cryptoCode == productInformation.Currency) + return null; + + return FormatCurrency(productInformation.Price, productInformation.Currency, _CurrencyNameTable); + } + private string ExchangeRate(PaymentMethod paymentMethod) { string currency = paymentMethod.ParentEntity.ProductInformation.Currency; return FormatCurrency(paymentMethod.Rate, currency, _CurrencyNameTable); } + public static string FormatCurrency(decimal price, string currency, CurrencyNameTable currencies) { var provider = currencies.GetNumberFormatInfo(currency); @@ -314,18 +323,13 @@ namespace BTCPayServer.Controllers provider = (NumberFormatInfo)provider.Clone(); provider.CurrencyDecimalDigits = divisibility; } - return price.ToString("C", provider) + $" ({currency})"; - } - private string OrderAmountFiat(ProductInformation productInformation) - { - // check if invoice source currency is crypto... if it is there is no "order amount in fiat" - if (_NetworkProvider.GetNetwork(productInformation.Currency) != null) - { - return null; - } - return FormatCurrency(productInformation.Price, productInformation.Currency, _CurrencyNameTable); + if (_specialCryptoFormat.Contains(currency)) + return price.ToString("C", provider); + else + return price.ToString("C", provider) + $" ({currency})"; } + private static readonly string[] _specialCryptoFormat = new[] { "BTC", "LTC" }; [HttpGet] [Route("i/{invoiceId}/status")] diff --git a/BTCPayServer/Views/Invoice/Checkout-Body.cshtml b/BTCPayServer/Views/Invoice/Checkout-Body.cshtml index e00aa4547..aa596e9fc 100644 --- a/BTCPayServer/Views/Invoice/Checkout-Body.cshtml +++ b/BTCPayServer/Views/Invoice/Checkout-Body.cshtml @@ -73,7 +73,7 @@ {{ srvModel.btcDue }} {{ srvModel.cryptoCode }} -
+
1 {{ srvModel.cryptoCode }} = {{ srvModel.rate }}
From eeb522fe7d852e610e1c4b0ded7fc56ffa229ce1 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 16 May 2018 21:19:48 +0900 Subject: [PATCH 08/43] Remove special case for showing crypto currency --- BTCPayServer/Controllers/InvoiceController.UI.cs | 5 ++--- BTCPayServer/Services/Rates/CurrencyNameTable.cs | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 08d9c7118..699fcd564 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -323,13 +323,12 @@ namespace BTCPayServer.Controllers provider = (NumberFormatInfo)provider.Clone(); provider.CurrencyDecimalDigits = divisibility; } - - if (_specialCryptoFormat.Contains(currency)) + + if (currencyData.Crypto) return price.ToString("C", provider); else return price.ToString("C", provider) + $" ({currency})"; } - private static readonly string[] _specialCryptoFormat = new[] { "BTC", "LTC" }; [HttpGet] [Route("i/{invoiceId}/status")] diff --git a/BTCPayServer/Services/Rates/CurrencyNameTable.cs b/BTCPayServer/Services/Rates/CurrencyNameTable.cs index 97e748722..d826ed746 100644 --- a/BTCPayServer/Services/Rates/CurrencyNameTable.cs +++ b/BTCPayServer/Services/Rates/CurrencyNameTable.cs @@ -31,6 +31,7 @@ namespace BTCPayServer.Services.Rates get; internal set; } + public bool Crypto { get; set; } } public class CurrencyNameTable { @@ -125,7 +126,8 @@ namespace BTCPayServer.Services.Rates { Code = network.CryptoCode, Divisibility = 8, - Name = network.CryptoCode + Name = network.CryptoCode, + Crypto = true }); } From fe5347aa861656520ce19b6360ccf2d38e9c4c3a Mon Sep 17 00:00:00 2001 From: rockstardev Date: Wed, 16 May 2018 10:40:22 -0500 Subject: [PATCH 09/43] Maintaining BitPay compatibility Ref: https://github.com/btcpayserver/btcpayserver/issues/180 --- BTCPayServer/Models/InvoiceResponse.cs | 8 ++++---- BTCPayServer/Services/Invoices/InvoiceEntity.cs | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/BTCPayServer/Models/InvoiceResponse.cs b/BTCPayServer/Models/InvoiceResponse.cs index c7ad99dff..116252c96 100644 --- a/BTCPayServer/Models/InvoiceResponse.cs +++ b/BTCPayServer/Models/InvoiceResponse.cs @@ -224,21 +224,21 @@ namespace BTCPayServer.Models { get; set; } - + [JsonProperty("paymentSubtotals")] public Dictionary PaymentSubtotals { get; set; } [JsonProperty("paymentTotals")] public Dictionary PaymentTotals { get; set; } - [JsonProperty("amountPaid")] + [JsonProperty("amountPaid", DefaultValueHandling = DefaultValueHandling.Include)] public long AmountPaid { get; set; } [JsonProperty("minerFees")] public long MinerFees { get; set; } [JsonProperty("exchangeRates")] - public Dictionary> ExchangeRates{ get; set; } + public Dictionary> ExchangeRates { get; set; } [JsonProperty("supportedTransactionCurrencies")] public Dictionary SupportedTransactionCurrencies { get; set; } @@ -246,7 +246,7 @@ namespace BTCPayServer.Models [JsonProperty("addresses")] public Dictionary Addresses { get; set; } [JsonProperty("paymentCodes")] - public Dictionary PaymentCodes{get; set;} + public Dictionary PaymentCodes { get; set; } } public class Flags { diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 27204ed0f..de174c628 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -338,9 +338,8 @@ namespace BTCPayServer.Services.Invoices Status = Status, Currency = ProductInformation.Currency, Flags = new Flags() { Refundable = Refundable }, - PaymentSubtotals = new Dictionary(), - PaymentTotals= new Dictionary(), + PaymentTotals = new Dictionary(), SupportedTransactionCurrencies = new Dictionary(), Addresses = new Dictionary(), PaymentCodes = new Dictionary(), @@ -351,7 +350,6 @@ namespace BTCPayServer.Services.Invoices dto.CryptoInfo = new List(); foreach (var info in this.GetPaymentMethods(networkProvider)) { - var accounting = info.Calculate(); var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo(); var subtotalPrice = accounting.TotalDue - accounting.NetworkFee; @@ -375,7 +373,7 @@ namespace BTCPayServer.Services.Invoices cryptoInfo.CryptoPaid = accounting.CryptoPaid.ToString(); cryptoInfo.Address = address; - + cryptoInfo.ExRates = exrates; var paymentId = info.GetId(); var scheme = info.Network.UriScheme; @@ -392,7 +390,7 @@ namespace BTCPayServer.Services.Invoices BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}", }; } - + if (paymentId.PaymentType == PaymentTypes.LightningLike) { cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls() From 365911286b95f60ebfca547309a32b7a7b196a5a Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 20 May 2018 17:04:03 +0900 Subject: [PATCH 10/43] bump --- BTCPayServer/BTCPayServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index ca3aa0595..2760fa082 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.21 + 1.0.2.22 NU1701,CA1816,CA1308,CA1810,CA2208 From 011dd5574f6332de677a81d82bb5f5a27ac32cd5 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 20 May 2018 23:22:20 +0900 Subject: [PATCH 11/43] Add a fallback currency format info --- BTCPayServer/Services/Rates/CurrencyNameTable.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/Services/Rates/CurrencyNameTable.cs b/BTCPayServer/Services/Rates/CurrencyNameTable.cs index d826ed746..4478d9c61 100644 --- a/BTCPayServer/Services/Rates/CurrencyNameTable.cs +++ b/BTCPayServer/Services/Rates/CurrencyNameTable.cs @@ -47,8 +47,19 @@ namespace BTCPayServer.Services.Rates var data = GetCurrencyProvider(currency); if (data is NumberFormatInfo nfi) return nfi; - return ((CultureInfo)data).NumberFormat; + if (data is CultureInfo ci) + return ci.NumberFormat; + return CreateFallbackCurrencyFormatInfo(currency); } + + private NumberFormatInfo CreateFallbackCurrencyFormatInfo(string currency) + { + var usd = GetNumberFormatInfo("USD"); + var currencyInfo = (NumberFormatInfo)usd.Clone(); + currencyInfo.CurrencySymbol = currency; + return currencyInfo; + } + public IFormatProvider GetCurrencyProvider(string currency) { lock (_CurrencyProviders) From 3cf3aa63f645e7caf998a5798d556707d2e1cb05 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 20 May 2018 23:37:18 +0900 Subject: [PATCH 12/43] CurrencyNameTable can use fallback --- .../Controllers/AppsController.PointOfSale.cs | 4 ++-- .../Controllers/InvoiceController.UI.cs | 4 ++-- BTCPayServer/Controllers/RateController.cs | 2 +- BTCPayServer/CurrencyValue.cs | 2 +- .../Services/Rates/CurrencyNameTable.cs | 23 +++++++++++++++---- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index 27290739b..301b62354 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -65,7 +65,7 @@ namespace BTCPayServer.Controllers [Route("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm) { - if (_Currencies.GetCurrencyData(vm.Currency) == null) + if (_Currencies.GetCurrencyData(vm.Currency, false) == null) ModelState.AddModelError(nameof(vm.Currency), "Invalid currency"); try { @@ -102,7 +102,7 @@ namespace BTCPayServer.Controllers if (app == null) return NotFound(); var settings = app.GetSettings(); - var currency = _Currencies.GetCurrencyData(settings.Currency); + var currency = _Currencies.GetCurrencyData(settings.Currency, false); double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility)); return View(new ViewPointOfSaleViewModel() { diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 699fcd564..4b32fc9f5 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -305,8 +305,8 @@ namespace BTCPayServer.Controllers public static string FormatCurrency(decimal price, string currency, CurrencyNameTable currencies) { - var provider = currencies.GetNumberFormatInfo(currency); - var currencyData = currencies.GetCurrencyData(currency); + var provider = currencies.GetNumberFormatInfo(currency, true); + var currencyData = currencies.GetCurrencyData(currency, true); var divisibility = currencyData.Divisibility; while (true) { diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index 9f5f9a4a2..8c35924f5 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers CryptoCode = r.Pair.Left, Code = r.Pair.Right, CurrencyPair = r.Pair.ToString(), - Name = _CurrencyNameTable.GetCurrencyData(r.Pair.Right)?.Name, + Name = _CurrencyNameTable.GetCurrencyData(r.Pair.Right, true).Name, Value = r.Value.Value }).Where(n => n.Name != null).ToArray()); } diff --git a/BTCPayServer/CurrencyValue.cs b/BTCPayServer/CurrencyValue.cs index 26d98c3b5..ef3f2d812 100644 --- a/BTCPayServer/CurrencyValue.cs +++ b/BTCPayServer/CurrencyValue.cs @@ -21,7 +21,7 @@ namespace BTCPayServer return false; var currency = match.Groups.Last().Value.ToUpperInvariant(); - var currencyData = _CurrencyTable.GetCurrencyData(currency); + var currencyData = _CurrencyTable.GetCurrencyData(currency, false); if (currencyData == null) return false; v = Math.Round(v, currencyData.Divisibility); diff --git a/BTCPayServer/Services/Rates/CurrencyNameTable.cs b/BTCPayServer/Services/Rates/CurrencyNameTable.cs index 4478d9c61..a709890e0 100644 --- a/BTCPayServer/Services/Rates/CurrencyNameTable.cs +++ b/BTCPayServer/Services/Rates/CurrencyNameTable.cs @@ -42,19 +42,21 @@ namespace BTCPayServer.Services.Rates static Dictionary _CurrencyProviders = new Dictionary(); - public NumberFormatInfo GetNumberFormatInfo(string currency) + public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback) { var data = GetCurrencyProvider(currency); if (data is NumberFormatInfo nfi) return nfi; if (data is CultureInfo ci) return ci.NumberFormat; + if (!useFallback) + return null; return CreateFallbackCurrencyFormatInfo(currency); } private NumberFormatInfo CreateFallbackCurrencyFormatInfo(string currency) { - var usd = GetNumberFormatInfo("USD"); + var usd = GetNumberFormatInfo("USD", false); var currencyInfo = (NumberFormatInfo)usd.Clone(); currencyInfo.CurrencySymbol = currency; return currencyInfo; @@ -145,10 +147,23 @@ namespace BTCPayServer.Services.Rates return dico.Values.ToArray(); } - public CurrencyData GetCurrencyData(string currency) + public CurrencyData GetCurrencyData(string currency, bool useFallback) { CurrencyData result; - _Currencies.TryGetValue(currency.ToUpperInvariant(), out result); + if(!_Currencies.TryGetValue(currency.ToUpperInvariant(), out result)) + { + if(useFallback) + { + var usd = GetCurrencyData("USD", false); + result = new CurrencyData() + { + Code = currency, + Crypto = true, + Name = currency, + Divisibility = usd.Divisibility + }; + } + } return result; } From dd1a93ee0ea8a431b4baae0d27b5c46d7c0782ba Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Mon, 21 May 2018 20:44:03 +0900 Subject: [PATCH 13/43] Revert "Remove unused Error.cshtml" This reverts commit 7b2ef9aec2c5f5afbecf1b639995b4a645390928. --- BTCPayServer/Controllers/HomeController.cs | 19 +++++++++++++++++++ BTCPayServer/Models/ErrorViewModel.cs | 11 +++++++++++ BTCPayServer/Views/Shared/Error.cshtml | 22 ++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 BTCPayServer/Models/ErrorViewModel.cs create mode 100644 BTCPayServer/Views/Shared/Error.cshtml diff --git a/BTCPayServer/Controllers/HomeController.cs b/BTCPayServer/Controllers/HomeController.cs index 336ea6118..49e7036f1 100644 --- a/BTCPayServer/Controllers/HomeController.cs +++ b/BTCPayServer/Controllers/HomeController.cs @@ -14,5 +14,24 @@ namespace BTCPayServer.Controllers { return View("Home"); } + + public IActionResult About() + { + ViewData["Message"] = "Your application description page."; + + return View(); + } + + public IActionResult Contact() + { + ViewData["Message"] = "Your contact page."; + + return View(); + } + + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } } } diff --git a/BTCPayServer/Models/ErrorViewModel.cs b/BTCPayServer/Models/ErrorViewModel.cs new file mode 100644 index 000000000..b32ee4e43 --- /dev/null +++ b/BTCPayServer/Models/ErrorViewModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace BTCPayServer.Models +{ + public class ErrorViewModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + } +} \ No newline at end of file diff --git a/BTCPayServer/Views/Shared/Error.cshtml b/BTCPayServer/Views/Shared/Error.cshtml new file mode 100644 index 000000000..086796acf --- /dev/null +++ b/BTCPayServer/Views/Shared/Error.cshtml @@ -0,0 +1,22 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if(Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

From f8bc3a5081a0eb5184bfb687c1022ef23eee8116 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Mon, 21 May 2018 20:47:07 +0900 Subject: [PATCH 14/43] bump --- BTCPayServer/BTCPayServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 2760fa082..c786c3c34 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.22 + 1.0.2.23 NU1701,CA1816,CA1308,CA1810,CA2208 From 7e6ab015a694ba93a2e14f47252a9d0ea434c0a5 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 22 May 2018 01:05:45 +0900 Subject: [PATCH 15/43] Fix bug on Error page --- BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Views/Shared/Error.cshtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index c786c3c34..5a8884c5d 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.23 + 1.0.2.24 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Views/Shared/Error.cshtml b/BTCPayServer/Views/Shared/Error.cshtml index 086796acf..245c075d9 100644 --- a/BTCPayServer/Views/Shared/Error.cshtml +++ b/BTCPayServer/Views/Shared/Error.cshtml @@ -6,7 +6,7 @@

Error.

An error occurred while processing your request.

-@if(Model.ShowRequestId) +@if(Model != null && Model.ShowRequestId) {

Request ID: @Model.RequestId From 8d41a8e98dd77bf13ba6918a1b02738288ef0d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Pinilla?= Date: Mon, 21 May 2018 23:15:04 -0500 Subject: [PATCH 16/43] =?UTF-8?q?Better=20spanish=20translation=20(t=C3=BA?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BTCPayServer/wwwroot/checkout/js/langs/es.js | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/BTCPayServer/wwwroot/checkout/js/langs/es.js b/BTCPayServer/wwwroot/checkout/js/langs/es.js index f02c40e37..f21f99d9e 100644 --- a/BTCPayServer/wwwroot/checkout/js/langs/es.js +++ b/BTCPayServer/wwwroot/checkout/js/langs/es.js @@ -5,14 +5,14 @@ const locales_es = { "Awaiting Payment...": "En espera de pago...", "Pay with": "Pagar con", "Contact and Refund Email": "Contacto y correo electrónico de reembolso", - "Contact_Body": "Por favor provea una dirección de correo electrónico a continuación. Nos pondremos en contacto con usted en esta dirección si hay un problema con su pago.", - "Your email": "Su correo electrónico", + "Contact_Body": "Por favor provee una dirección de correo electrónico a continuación. Nos pondremos en contacto contigo en esta dirección si hay un problema con tu pago.", + "Your email": "Tu correo electrónico", "Continue": "Continuar", - "Please enter a valid email address": "Por favor entre un correo electrónico válido", + "Please enter a valid email address": "Por favor ingresa un correo electrónico válido", "Order Amount": "Total del pedido", "Network Cost": "Costo de la red", - "Already Paid": "Ya pagado", - "Due": "Debido", + "Already Paid": "Ya has pagado", + "Due": "Aún debes", // Tabs "Scan": "Escanear", "Copy": "Copiar", @@ -20,22 +20,22 @@ const locales_es = { // Scan tab "Open in wallet": "Abrir en billetera", // Copy tab - "CompletePay_Body": "Para completar su pago, envíe {{btcDue}} {{cryptoCode}} a la siguiente dirección.", + "CompletePay_Body": "Para completar tu pago, envía {{btcDue}} {{cryptoCode}} a la siguiente dirección.", "Amount": "Cantidad", "Address": "Dirección", "Copied": "Copiado", // Conversion tab - "ConversionTab_BodyTop": "Puede pagar {{btcDue}} {{cryptoCode}} usando altcoins que no sean los que el comerciante soporta directamente.", - "ConversionTab_BodyDesc": "Este servicio es provisto por terceros. Tenga en cuenta que no tenemos control sobre cómo los proveedores enviarán sus fondos. La factura solo se marcará como abonada una vez que se reciban los fondos en la cadena de bloques de {{cryptoCode}} .", + "ConversionTab_BodyTop": "Puedes pagar {{btcDue}} {{cryptoCode}} usando altcoins que no sean los que el comercio soporta directamente.", + "ConversionTab_BodyDesc": "Este servicio es provisto por terceros. Ten en cuenta que no tenemos control sobre cómo estos terceros enviarán los fondos. La factura solo se marcará como abonada una vez que se reciban los fondos en la cadena de bloques de {{cryptoCode}} .", "Shapeshift_Button_Text": "Pagar con Altcoins", "ConversionTab_Lightning": "No hay proveedores de conversión disponibles para los pagos de Lightning Network.", // Invoice expired "Invoice expiring soon...": "La factura expira pronto...", "Invoice expired": "La factura expiró", "What happened?": "¿Qué sucedió?", - "InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. Puede volver a {{storeName}} si desea volver a enviar su pago.", - "InvoiceExpired_Body_2": "Si intentó enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido sus fondos.", - "InvoiceExpired_Body_3": "Si la transacción no es aceptada por la red de Bitcoin, los fondos se podrán gastar nuevamente en su billetera. Dependiendo de su billetera, esto puede tomar 48-72 horas.", + "InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. Puedes regresar a {{storeName}} si deseas volver a enviar tu pago.", + "InvoiceExpired_Body_2": "Si intentaste enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido tus fondos.", + "InvoiceExpired_Body_3": "Si la transacción no es aceptada por la red de Bitcoin, los fondos se podrán gastar nuevamente en tu billetera. Dependiendo de tu billetera, esto puede tomar 48-72 horas.", "Invoice ID": "ID de factura", "Order ID": "ID de pedido", "Return to StoreName": "Regresar a {{storeName}}", @@ -43,7 +43,7 @@ const locales_es = { "This invoice has been paid": "Esta factura ha sido pagada", // Invoice archived "This invoice has been archived": "Esta factura ha sido archivada", - "Archived_Body": "Por favor, comuníquese con la tienda para obtener información de su pedido o asistencia", + "Archived_Body": "Por favor, comunícate con la tienda para obtener información de tu pedido o asistencia", // Lightning "BOLT 11 Invoice": "Factura BOLT 11", "Node Info": "Información del nodo", From ead67887ab9cbc4c978c4b18af89dcdc1dea722f Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 22 May 2018 18:05:44 +0900 Subject: [PATCH 17/43] Enable SSL was ignored --- BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Services/Mails/EmailSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 5a8884c5d..e4baaa23c 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.24 + 1.0.2.25 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Services/Mails/EmailSettings.cs b/BTCPayServer/Services/Mails/EmailSettings.cs index 93b062eec..c7952942f 100644 --- a/BTCPayServer/Services/Mails/EmailSettings.cs +++ b/BTCPayServer/Services/Mails/EmailSettings.cs @@ -55,7 +55,7 @@ namespace BTCPayServer.Services.Mails public SmtpClient CreateSmtpClient() { SmtpClient client = new SmtpClient(Server, Port.Value); - client.EnableSsl = true; + client.EnableSsl = EnableSSL; client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential(Login, Password); client.DeliveryMethod = SmtpDeliveryMethod.Network; From ba3d13d56cad8f2c67e1a53cf07bd1e1fd562a92 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 23 May 2018 02:18:38 +0900 Subject: [PATCH 18/43] Make sure the rate of the merchant is using the ask of a divided exchange --- BTCPayServer.Tests/BTCPayServerTester.cs | 6 +- BTCPayServer.Tests/RateRulesTest.cs | 24 ++- BTCPayServer.Tests/UnitTest1.cs | 10 +- BTCPayServer/Rating/ExchangeRates.cs | 148 ++++++++++++++++-- BTCPayServer/Rating/RateRules.cs | 55 +++++-- .../Services/Rates/BitpayRateProvider.cs | 2 +- .../Services/Rates/CoinAverageRateProvider.cs | 34 +++- .../Rates/ExchangeSharpRateProvider.cs | 2 +- .../Services/Rates/QuadrigacxRateProvider.cs | 20 ++- 9 files changed, 253 insertions(+), 48 deletions(-) diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 9feaab1f5..3f0b16985 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -129,19 +129,19 @@ namespace BTCPayServer.Tests { Exchange = "coinaverage", CurrencyPair = CurrencyPair.Parse("BTC_USD"), - Value = 5000m + BidAsk = new BidAsk(5000m) }); coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate() { Exchange = "coinaverage", CurrencyPair = CurrencyPair.Parse("BTC_CAD"), - Value = 4500m + BidAsk = new BidAsk(4500m) }); coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate() { Exchange = "coinaverage", CurrencyPair = CurrencyPair.Parse("LTC_USD"), - Value = 500m + BidAsk = new BidAsk(500m) }); rateProvider.DirectProviders.Add("coinaverage", coinAverageMock); } diff --git a/BTCPayServer.Tests/RateRulesTest.cs b/BTCPayServer.Tests/RateRulesTest.cs index 09c26fc73..3a62bb448 100644 --- a/BTCPayServer.Tests/RateRulesTest.cs +++ b/BTCPayServer.Tests/RateRulesTest.cs @@ -94,12 +94,12 @@ namespace BTCPayServer.Tests Assert.Equal(test.ExpectedExchangeRates, string.Join(',', rule.ExchangeRates.OfType().ToArray())); } var rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_CAD")); - rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), 5000); + rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m)); rule2.Reevaluate(); Assert.True(rule2.HasError); Assert.Equal("5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1", rule2.ToString(true)); Assert.Equal("bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false)); - rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 2000.4m); + rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(2000.4m)); rule2.Reevaluate(); Assert.False(rule2.HasError); Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true)); @@ -116,7 +116,7 @@ namespace BTCPayServer.Tests rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")); Assert.Equal("(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * 1.1", rule2.ToString()); - rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 1000m); + rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m)); Assert.True(rule2.Reevaluate()); Assert.Equal("(2000 * (-3 + 1000 + 50 - 5)) * 1.1", rule2.ToString(true)); Assert.Equal((2000m * (-3m + 1000m + 50m - 5m)) * 1.1m, rule2.Value.Value); @@ -124,7 +124,7 @@ namespace BTCPayServer.Tests // Test inverse rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_DOGE")); Assert.Equal("(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * 1.1", rule2.ToString()); - rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 1000m); + rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m)); Assert.True(rule2.Reevaluate()); Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * 1.1", rule2.ToString(true)); Assert.Equal(( 1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value); @@ -135,8 +135,22 @@ namespace BTCPayServer.Tests builder.AppendLine("BTC_USD = kraken(BTC_USD)"); Assert.True(RateRules.TryParse(builder.ToString(), out rules)); rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD")); - rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), 1000m); + rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(1000m)); Assert.True(rule2.Reevaluate()); + + // Make sure can handle pairs + builder = new StringBuilder(); + builder.AppendLine("BTC_USD = kraken(BTC_USD)"); + rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD")); + rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("(6000, 6100)", rule2.ToString(true)); + Assert.Equal(6000m, rule2.Value.Value); + rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_BTC")); + rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("1 / (6000, 6100)", rule2.ToString(true)); + Assert.Equal(1m/6100m, rule2.Value.Value); } } } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index b5e3df28a..944edbfc1 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1464,10 +1464,10 @@ namespace BTCPayServer.Tests var quadri = new QuadrigacxRateProvider(); var rates = quadri.GetRatesAsync().GetAwaiter().GetResult(); Assert.NotEmpty(rates); - Assert.NotEqual(0.0m, rates.First().Value); - Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Value); - Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Value); - Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Value); + Assert.NotEqual(0.0m, rates.First().BidAsk.Bid); + Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid); + Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid); + Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid); Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD"))); } @@ -1492,7 +1492,7 @@ namespace BTCPayServer.Tests e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") || e.CurrencyPair == new CurrencyPair("BTC", "EUR") || e.CurrencyPair == new CurrencyPair("BTC", "USDT")) - && e.Value > 1.0m // 1BTC will always be more than 1USD + && e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD ); } } diff --git a/BTCPayServer/Rating/ExchangeRates.cs b/BTCPayServer/Rating/ExchangeRates.cs index 868c70a3b..81eb92e35 100644 --- a/BTCPayServer/Rating/ExchangeRates.cs +++ b/BTCPayServer/Rating/ExchangeRates.cs @@ -41,9 +41,9 @@ namespace BTCPayServer.Rating } else { - if (rate.Value.HasValue) + if (rate.BidAsk != null) { - _AllRates[key].Value = rate.Value; + _AllRates[key].BidAsk = rate.BidAsk; } } } @@ -58,39 +58,167 @@ namespace BTCPayServer.Rating return GetEnumerator(); } - public void SetRate(string exchangeName, CurrencyPair currencyPair, decimal value) + public void SetRate(string exchangeName, CurrencyPair currencyPair, BidAsk bidAsk) { if (ByExchange.TryGetValue(exchangeName, out var rates)) { var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair); if (rate != null) - rate.Value = value; + rate.BidAsk = bidAsk; } } - public decimal? GetRate(string exchangeName, CurrencyPair currencyPair) + public BidAsk GetRate(string exchangeName, CurrencyPair currencyPair) { if (currencyPair.Left == currencyPair.Right) - return 1.0m; + return BidAsk.One; if (ByExchange.TryGetValue(exchangeName, out var rates)) { var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair); if (rate != null) - return rate.Value; + return rate.BidAsk; } return null; } } + public class BidAsk + { + + private readonly static BidAsk _One = new BidAsk(1.0m); + public static BidAsk One + { + get + { + return _One; + } + } + + private readonly static BidAsk _Zero = new BidAsk(0.0m); + public static BidAsk Zero + { + get + { + return _Zero; + } + } + public BidAsk(decimal bid, decimal ask) + { + if (bid > ask) + throw new ArgumentException("the bid should be lower than ask", nameof(bid)); + _Ask = ask; + _Bid = bid; + } + public BidAsk(decimal v) : this(v, v) + { + + } + + private readonly decimal _Bid; + public decimal Bid + { + get + { + return _Bid; + } + } + + + private readonly decimal _Ask; + public decimal Ask + { + get + { + return _Ask; + } + } + public BidAsk Inverse() + { + return new BidAsk(1.0m / Ask, 1.0m / Bid); + } + + public static BidAsk operator+(BidAsk a, BidAsk b) + { + return new BidAsk(a.Bid + b.Bid, a.Ask + b.Ask); + } + + public static BidAsk operator +(BidAsk a) + { + return new BidAsk(a.Bid, a.Ask); + } + + public static BidAsk operator -(BidAsk a) + { + return new BidAsk(-a.Bid, -a.Ask); + } + + public static BidAsk operator *(BidAsk a, BidAsk b) + { + return new BidAsk(a.Bid * b.Bid, a.Ask * b.Ask); + } + + public static BidAsk operator /(BidAsk a, BidAsk b) + { + // This one is tricky. + // BTC_EUR = (6000, 6100) + // Implicit rule give + // EUR_BTC = 1 / BTC_EUR + // Or + // EUR_BTC = (1, 1) / BTC_EUR + // Naive calculation would give us ( 1/6000, 1/6100) = (0.000166, 0.000163) + // However, this is an invalid BidAsk!!! because 0.000166 > 0.000163 + // So instead, we need to calculate (1/6100, 1/6000) + return new BidAsk(a.Bid / b.Ask, a.Ask / b.Bid); + } + + public static BidAsk operator-(BidAsk a, BidAsk b) + { + return new BidAsk(a.Bid - b.Bid, a.Ask - b.Ask); + } + + + public override bool Equals(object obj) + { + BidAsk item = obj as BidAsk; + if (item == null) + return false; + return Bid == item.Bid && Ask == item.Ask; + } + public static bool operator ==(BidAsk a, BidAsk b) + { + if (System.Object.ReferenceEquals(a, b)) + return true; + if (((object)a == null) || ((object)b == null)) + return false; + return a.Bid == b.Bid && a.Ask == b.Ask; + } + + public static bool operator !=(BidAsk a, BidAsk b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return ToString().GetHashCode(StringComparison.InvariantCulture); + } + + public override string ToString() + { + if (Bid == Ask) + return Bid.ToString(CultureInfo.InvariantCulture); + return $"({Bid.ToString(CultureInfo.InvariantCulture)} , {Ask.ToString(CultureInfo.InvariantCulture)})"; + } + } public class ExchangeRate { public string Exchange { get; set; } public CurrencyPair CurrencyPair { get; set; } - public decimal? Value { get; set; } + public BidAsk BidAsk { get; set; } public override string ToString() { - if (Value == null) + if (BidAsk == null) return $"{Exchange}({CurrencyPair})"; - return $"{Exchange}({CurrencyPair}) == {Value.Value.ToString(CultureInfo.InvariantCulture)}"; + return $"{Exchange}({CurrencyPair}) == {BidAsk.ToString()}"; } } } diff --git a/BTCPayServer/Rating/RateRules.cs b/BTCPayServer/Rating/RateRules.cs index e7fe02e0d..cc744d376 100644 --- a/BTCPayServer/Rating/RateRules.cs +++ b/BTCPayServer/Rating/RateRules.cs @@ -18,6 +18,7 @@ namespace BTCPayServer.Rating UnsupportedOperator, MissingArgument, DivideByZero, + InvalidNegative, PreprocessError, RateUnavailable, InvalidExchangeName, @@ -139,7 +140,7 @@ namespace BTCPayServer.Rating } return new RateRule(this, currencyPair, candidate); } - + public ExpressionSyntax FindBestCandidate(CurrencyPair p) { var invP = p.Inverse(); @@ -216,8 +217,7 @@ namespace BTCPayServer.Rating } else { - var token = SyntaxFactory.ParseToken(rate.Value.ToString(CultureInfo.InvariantCulture)); - return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, token); + return RateRules.CreateExpression(rate.ToString()); } } } @@ -225,7 +225,7 @@ namespace BTCPayServer.Rating class CalculateWalker : CSharpSyntaxWalker { - public Stack Values = new Stack(); + public Stack Values = new Stack(); public List Errors = new List(); public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node) @@ -254,7 +254,15 @@ namespace BTCPayServer.Rating switch (node.Kind()) { case SyntaxKind.UnaryMinusExpression: - Values.Push(-Values.Pop()); + var v = Values.Pop(); + if(v.Bid == v.Ask) + { + Values.Push(-v); + } + else + { + Errors.Add(RateRulesErrors.InvalidNegative); + } break; case SyntaxKind.UnaryPlusExpression: Values.Push(+Values.Pop()); @@ -299,7 +307,7 @@ namespace BTCPayServer.Rating Values.Push(a * b); break; case SyntaxKind.DivideExpression: - if (b == decimal.Zero) + if (a.Ask == decimal.Zero || b.Ask == decimal.Zero) { Errors.Add(RateRulesErrors.DivideByZero); } @@ -309,19 +317,48 @@ namespace BTCPayServer.Rating } break; case SyntaxKind.SubtractExpression: - Values.Push(a - b); + if (b.Bid == b.Ask) + { + Values.Push(a - b); + } + else + { + Errors.Add(RateRulesErrors.InvalidNegative); + } break; default: throw new NotSupportedException("Should never happen"); } } + Stack _TupleValues = null; + public override void VisitTupleExpression(TupleExpressionSyntax node) + { + _TupleValues = new Stack(); + base.VisitTupleExpression(node); + if(_TupleValues.Count != 2) + { + Errors.Add(RateRulesErrors.MissingArgument); + } + else + { + var ask = _TupleValues.Pop(); + var bid = _TupleValues.Pop(); + Values.Push(new BidAsk(bid, ask)); + } + _TupleValues = null; + } + public override void VisitLiteralExpression(LiteralExpressionSyntax node) { switch (node.Kind()) { case SyntaxKind.NumericLiteralExpression: - Values.Push(decimal.Parse(node.ToString(), CultureInfo.InvariantCulture)); + var v = decimal.Parse(node.ToString(), CultureInfo.InvariantCulture); + if (_TupleValues == null) + Values.Push(new BidAsk(v)); + else + _TupleValues.Push(v); break; } } @@ -487,7 +524,7 @@ namespace BTCPayServer.Rating Errors.AddRange(calculate.Errors); return false; } - _Value = calculate.Values.Pop(); + _Value = calculate.Values.Pop().Bid; _EvaluatedNode = result; return true; } diff --git a/BTCPayServer/Services/Rates/BitpayRateProvider.cs b/BTCPayServer/Services/Rates/BitpayRateProvider.cs index 0898fd883..3571ad1b8 100644 --- a/BTCPayServer/Services/Rates/BitpayRateProvider.cs +++ b/BTCPayServer/Services/Rates/BitpayRateProvider.cs @@ -24,7 +24,7 @@ namespace BTCPayServer.Services.Rates { return new ExchangeRates((await _Bitpay.GetRatesAsync().ConfigureAwait(false)) .AllRates - .Select(r => new ExchangeRate() { Exchange = BitpayName, CurrencyPair = new CurrencyPair("BTC", r.Code), Value = r.Value }) + .Select(r => new ExchangeRate() { Exchange = BitpayName, CurrencyPair = new CurrencyPair("BTC", r.Code), BidAsk = new BidAsk(r.Value) }) .ToList()); } } diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index 0ee5c26b1..228b317ae 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -54,7 +54,7 @@ namespace BTCPayServer.Services.Rates public const string CoinAverageName = "coinaverage"; public CoinAverageRateProvider() { - + } static HttpClient _Client = new HttpClient(); @@ -69,10 +69,30 @@ namespace BTCPayServer.Services.Rates public ICoinAverageAuthenticator Authenticator { get; set; } - private bool TryToDecimal(JProperty p, out decimal v) + private bool TryToBidAsk(JProperty p, out BidAsk bidAsk) { - JToken token = p.Value[Exchange == CoinAverageName ? "last" : "bid"]; - return decimal.TryParse(token.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v); + bidAsk = null; + if (Exchange == CoinAverageName) + { + JToken last = p.Value["last"]; + if (!decimal.TryParse(last.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v) || + v <= 0) + return false; + bidAsk = new BidAsk(v); + return true; + } + else + { + JToken bid = p.Value["bid"]; + JToken ask = p.Value["bid"]; + if (!decimal.TryParse(bid.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) || + !decimal.TryParse(ask.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) || + v1 > v2 || + v1 <= 0 || v2 <= 0) + return false; + bidAsk = new BidAsk(v1, v2); + return true; + } } public async Task GetRatesAsync() @@ -108,10 +128,10 @@ namespace BTCPayServer.Services.Rates { ExchangeRate exchangeRate = new ExchangeRate(); exchangeRate.Exchange = Exchange; - if (!TryToDecimal(prop, out decimal value)) + if (!TryToBidAsk(prop, out var value)) continue; - exchangeRate.Value = value; - if(CurrencyPair.TryParse(prop.Name, out var pair)) + exchangeRate.BidAsk = value; + if (CurrencyPair.TryParse(prop.Name, out var pair)) { exchangeRate.CurrencyPair = pair; exchangeRates.Add(exchangeRate); diff --git a/BTCPayServer/Services/Rates/ExchangeSharpRateProvider.cs b/BTCPayServer/Services/Rates/ExchangeSharpRateProvider.cs index 9aeaf7fb1..6efdad8d3 100644 --- a/BTCPayServer/Services/Rates/ExchangeSharpRateProvider.cs +++ b/BTCPayServer/Services/Rates/ExchangeSharpRateProvider.cs @@ -61,7 +61,7 @@ namespace BTCPayServer.Services.Rates var rate = new ExchangeRate(); rate.CurrencyPair = pair; rate.Exchange = _ExchangeName; - rate.Value = ticker.Value.Bid; + rate.BidAsk = new BidAsk(ticker.Value.Bid, ticker.Value.Ask); return rate; } catch (ArgumentException) diff --git a/BTCPayServer/Services/Rates/QuadrigacxRateProvider.cs b/BTCPayServer/Services/Rates/QuadrigacxRateProvider.cs index 10fb75189..381392ca9 100644 --- a/BTCPayServer/Services/Rates/QuadrigacxRateProvider.cs +++ b/BTCPayServer/Services/Rates/QuadrigacxRateProvider.cs @@ -14,13 +14,19 @@ namespace BTCPayServer.Services.Rates public const string QuadrigacxName = "quadrigacx"; static HttpClient _Client = new HttpClient(); - private bool TryToDecimal(JObject p, out decimal v) + private bool TryToBidAsk(JObject p, out BidAsk v) { - v = 0.0m; - JToken token = p.Property("bid")?.Value; - if (token == null) + v = null; + JToken bid = p.Property("bid")?.Value; + JToken ask = p.Property("ask")?.Value; + if (bid == null || ask == null) return false; - return decimal.TryParse(token.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v); + if (!decimal.TryParse(bid.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) || + !decimal.TryParse(bid.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) || + v1 <= 0m || v2 <= 0m || v1 > v2) + return false; + v = new BidAsk(v1, v2); + return true; } public async Task GetRatesAsync() @@ -37,9 +43,9 @@ namespace BTCPayServer.Services.Rates continue; rate.CurrencyPair = pair; rate.Exchange = QuadrigacxName; - if (!TryToDecimal((JObject)prop.Value, out var v)) + if (!TryToBidAsk((JObject)prop.Value, out var v)) continue; - rate.Value = v; + rate.BidAsk = v; exchangeRates.Add(rate); } return exchangeRates; From e35f074b66f91e0244a378c34d6231faa88adc19 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 23 May 2018 02:43:22 +0900 Subject: [PATCH 19/43] Better label for Rate Multiplier --- BTCPayServer/Models/StoreViewModels/RatesViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs b/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs index a6e0c1346..bb0505385 100644 --- a/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs @@ -44,7 +44,7 @@ namespace BTCPayServer.Models.StoreViewModels public string ScriptTest { get; set; } public CoinAverageExchange[] AvailableExchanges { get; set; } - [Display(Name = "Multiply the rate by ...")] + [Display(Name = "Multiply the rate by... (Setting to 1.01 would apply a discount of 1% to the purchase)")] [Range(0.01, 10.0)] public double RateMultiplier { From b98993f84b80fadff3f341d3c7899ceb5784644e Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 23 May 2018 11:16:19 +0900 Subject: [PATCH 20/43] fix typo --- BTCPayServer/Services/Rates/CoinAverageRateProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index 228b317ae..79562daa1 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -84,7 +84,7 @@ namespace BTCPayServer.Services.Rates else { JToken bid = p.Value["bid"]; - JToken ask = p.Value["bid"]; + JToken ask = p.Value["ask"]; if (!decimal.TryParse(bid.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) || !decimal.TryParse(ask.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) || v1 > v2 || From 127ca7582faa3041d94c633ba9849be7d36e6137 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 23 May 2018 19:13:12 +0900 Subject: [PATCH 21/43] Make sure inverse rules have priority --- BTCPayServer.Tests/RateRulesTest.cs | 13 +++++++++++++ BTCPayServer/Rating/RateRules.cs | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/BTCPayServer.Tests/RateRulesTest.cs b/BTCPayServer.Tests/RateRulesTest.cs index 3a62bb448..2d3ce1446 100644 --- a/BTCPayServer.Tests/RateRulesTest.cs +++ b/BTCPayServer.Tests/RateRulesTest.cs @@ -141,6 +141,7 @@ namespace BTCPayServer.Tests // Make sure can handle pairs builder = new StringBuilder(); builder.AppendLine("BTC_USD = kraken(BTC_USD)"); + Assert.True(RateRules.TryParse(builder.ToString(), out rules)); rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD")); rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); Assert.True(rule2.Reevaluate()); @@ -151,6 +152,18 @@ namespace BTCPayServer.Tests Assert.True(rule2.Reevaluate()); Assert.Equal("1 / (6000, 6100)", rule2.ToString(true)); Assert.Equal(1m/6100m, rule2.Value.Value); + + // Make sure the inverse has more priority than X_X or CDNT_X + builder = new StringBuilder(); + builder.AppendLine("EUR_CDNT = 10"); + builder.AppendLine("CDNT_BTC = CDNT_EUR * EUR_BTC;"); + builder.AppendLine("CDNT_X = CDNT_BTC * BTC_X;"); + builder.AppendLine("X_X = coinaverage(X_X);"); + Assert.True(RateRules.TryParse(builder.ToString(), out rules)); + rule2 = rules.GetRuleFor(CurrencyPair.Parse("CDNT_EUR")); + rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("1 / 10", rule2.ToString(false)); } } } diff --git a/BTCPayServer/Rating/RateRules.cs b/BTCPayServer/Rating/RateRules.cs index cc744d376..7d2b898a6 100644 --- a/BTCPayServer/Rating/RateRules.cs +++ b/BTCPayServer/Rating/RateRules.cs @@ -148,9 +148,9 @@ namespace BTCPayServer.Rating foreach (var pair in new[] { (Pair: p, Priority: 0, Inverse: false), - (Pair: new CurrencyPair(p.Left, "X"), Priority: 1, Inverse: false), - (Pair: new CurrencyPair("X", p.Right), Priority: 1, Inverse: false), - (Pair: invP, Priority: 2, Inverse: true), + (Pair: invP, Priority: 1, Inverse: true), + (Pair: new CurrencyPair(p.Left, "X"), Priority: 2, Inverse: false), + (Pair: new CurrencyPair("X", p.Right), Priority: 2, Inverse: false), (Pair: new CurrencyPair(invP.Left, "X"), Priority: 3, Inverse: true), (Pair: new CurrencyPair("X", invP.Right), Priority: 3, Inverse: true), (Pair: new CurrencyPair("X", "X"), Priority: 4, Inverse: false) From 4e32dad1ea2f18abeeff129ae31d8b0566801411 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 23 May 2018 19:29:01 +0900 Subject: [PATCH 22/43] Can solve inverses at exchange level --- BTCPayServer.Tests/RateRulesTest.cs | 14 ++++++++++++-- BTCPayServer/Rating/ExchangeRates.cs | 14 +++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/BTCPayServer.Tests/RateRulesTest.cs b/BTCPayServer.Tests/RateRulesTest.cs index 2d3ce1446..53e89f842 100644 --- a/BTCPayServer.Tests/RateRulesTest.cs +++ b/BTCPayServer.Tests/RateRulesTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Text; using BTCPayServer.Rating; using Xunit; +using System.Globalization; namespace BTCPayServer.Tests { @@ -127,7 +128,7 @@ namespace BTCPayServer.Tests rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m)); Assert.True(rule2.Reevaluate()); Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * 1.1", rule2.ToString(true)); - Assert.Equal(( 1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value); + Assert.Equal((1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value); //////// // Make sure kraken is not converted to CurrencyPair @@ -151,7 +152,7 @@ namespace BTCPayServer.Tests rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); Assert.True(rule2.Reevaluate()); Assert.Equal("1 / (6000, 6100)", rule2.ToString(true)); - Assert.Equal(1m/6100m, rule2.Value.Value); + Assert.Equal(1m / 6100m, rule2.Value.Value); // Make sure the inverse has more priority than X_X or CDNT_X builder = new StringBuilder(); @@ -164,6 +165,15 @@ namespace BTCPayServer.Tests rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); Assert.True(rule2.Reevaluate()); Assert.Equal("1 / 10", rule2.ToString(false)); + + // Make sure an inverse can be solved on an exchange + builder = new StringBuilder(); + builder.AppendLine("X_X = coinaverage(X_X);"); + Assert.True(RateRules.TryParse(builder.ToString(), out rules)); + rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_BTC")); + rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal($"({(1m / 6100m).ToString(CultureInfo.InvariantCulture)}, {(1m / 6000m).ToString(CultureInfo.InvariantCulture)})", rule2.ToString(true)); } } } diff --git a/BTCPayServer/Rating/ExchangeRates.cs b/BTCPayServer/Rating/ExchangeRates.cs index 81eb92e35..67531c931 100644 --- a/BTCPayServer/Rating/ExchangeRates.cs +++ b/BTCPayServer/Rating/ExchangeRates.cs @@ -63,8 +63,16 @@ namespace BTCPayServer.Rating if (ByExchange.TryGetValue(exchangeName, out var rates)) { var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair); - if (rate != null) + if(rate != null) + { rate.BidAsk = bidAsk; + } + var invPair = currencyPair.Inverse(); + var invRate = rates.FirstOrDefault(r => r.CurrencyPair == invPair); + if (invRate != null) + { + invRate.BidAsk = bidAsk?.Inverse(); + } } } public BidAsk GetRate(string exchangeName, CurrencyPair currencyPair) @@ -135,7 +143,7 @@ namespace BTCPayServer.Rating return new BidAsk(1.0m / Ask, 1.0m / Bid); } - public static BidAsk operator+(BidAsk a, BidAsk b) + public static BidAsk operator +(BidAsk a, BidAsk b) { return new BidAsk(a.Bid + b.Bid, a.Ask + b.Ask); } @@ -169,7 +177,7 @@ namespace BTCPayServer.Rating return new BidAsk(a.Bid / b.Ask, a.Ask / b.Bid); } - public static BidAsk operator-(BidAsk a, BidAsk b) + public static BidAsk operator -(BidAsk a, BidAsk b) { return new BidAsk(a.Bid - b.Bid, a.Ask - b.Ask); } From dec5dbc0d272eaf6816ba7d523a582df62e133aa Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Thu, 24 May 2018 23:54:48 +0900 Subject: [PATCH 23/43] Ability to pass fields to POS app #181 --- BTCPayServer.Tests/ServerTester.cs | 93 ------------------- BTCPayServer.Tests/UnitTest1.cs | 2 +- BTCPayServer/BTCPayServer.csproj | 2 +- .../Controllers/AppsController.PointOfSale.cs | 65 ++++++++++++- BTCPayServer/Controllers/InvoiceController.cs | 3 + .../UpdatePointOfSaleViewModel.cs | 4 + .../Views/Apps/UpdatePointOfSale.cshtml | 32 +++++++ 7 files changed, 101 insertions(+), 100 deletions(-) diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 957501fe4..62bf76d4e 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -185,99 +185,6 @@ namespace BTCPayServer.Tests HttpClient _Http = new HttpClient(); - class MockHttpRequest : HttpRequest - { - Uri serverUri; - public MockHttpRequest(Uri serverUri) - { - this.serverUri = serverUri; - } - public override HttpContext HttpContext => throw new NotImplementedException(); - - public override string Method - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override string Scheme - { - get => serverUri.Scheme; - set => throw new NotImplementedException(); - } - public override bool IsHttps - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override HostString Host - { - get => new HostString(serverUri.Host, serverUri.Port); - set => throw new NotImplementedException(); - } - public override PathString PathBase - { - get => ""; - set => throw new NotImplementedException(); - } - public override PathString Path - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override QueryString QueryString - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override IQueryCollection Query - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override string Protocol - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override IHeaderDictionary Headers => throw new NotImplementedException(); - - public override IRequestCookieCollection Cookies - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override long? ContentLength - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override string ContentType - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public override Stream Body - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override bool HasFormContentType => throw new NotImplementedException(); - - public override IFormCollection Form - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override Task ReadFormAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - throw new NotImplementedException(); - } - } - - public BTCPayServerTester PayTester { get; set; diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 944edbfc1..7e5c97ef0 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1262,7 +1262,7 @@ namespace BTCPayServer.Tests Assert.Equal("orange", vmview.Items[1].Title); Assert.Equal(10.0m, vmview.Items[1].Price.Value); Assert.Equal("$5.00", vmview.Items[0].Price.Formatted); - Assert.IsType(apps.ViewPointOfSale(appId, 0, "orange").Result); + Assert.IsType(apps.ViewPointOfSale(appId, 0, null, null, null, null, "orange").Result); var invoice = user.BitPay.GetInvoices().First(); Assert.Equal(10.00m, invoice.Price); Assert.Equal("CAD", invoice.Currency); diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index e4baaa23c..669b425bc 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.25 + 1.0.2.26 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index 301b62354..bb8b100bf 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -17,6 +17,8 @@ using YamlDotNet.RepresentationModel; using System.IO; using BTCPayServer.Services.Rates; using System.Globalization; +using System.Text; +using System.Text.Encodings.Web; namespace BTCPayServer.Controllers { @@ -57,9 +59,50 @@ namespace BTCPayServer.Controllers var app = await GetOwnedApp(appId, AppType.PointOfSale); if (app == null) return NotFound(); - var settings = app.GetSettings(); - return View(new UpdatePointOfSaleViewModel() { Title = settings.Title, ShowCustomAmount = settings.ShowCustomAmount, Currency = settings.Currency, Template = settings.Template }); + var vm = new UpdatePointOfSaleViewModel() + { + Title = settings.Title, + ShowCustomAmount = settings.ShowCustomAmount, + Currency = settings.Currency, + Template = settings.Template + }; + if (HttpContext?.Request != null) + { + var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash() + $"apps/{appId}/pos"; + var encoder = HtmlEncoder.Default; + if (settings.ShowCustomAmount) + { + StringBuilder builder = new StringBuilder(); + builder.AppendLine($"
"); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($"
"); + vm.Example1 = builder.ToString(); + } + try + { + var items = Parse(settings.Template, settings.Currency); + var builder = new StringBuilder(); + builder.AppendLine($"
"); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($" "); + builder.AppendLine($"
"); + vm.Example2 = builder.ToString(); + } + catch { } + vm.InvoiceUrl = appUrl + "invoices/SkdsDghkdP3D3qkj7bLq3"; + } + + vm.ExampleCallback = "{\n \"id\":\"SkdsDghkdP3D3qkj7bLq3\",\n \"url\":\"https://btcpay.example.com/invoice?id=SkdsDghkdP3D3qkj7bLq3\",\n \"status\":\"paid\",\n \"price\":10,\n \"currency\":\"EUR\",\n \"invoiceTime\":1520373130312,\n \"expirationTime\":1520374030312,\n \"currentTime\":1520373179327,\n \"exceptionStatus\":false,\n \"buyerFields\":{\n \"buyerEmail\":\"customer@example.com\",\n \"buyerNotify\":false\n },\n \"paymentSubtotals\": {\n \"BTC\":114700\n },\n \"paymentTotals\": {\n \"BTC\":118400\n },\n \"transactionCurrency\": \"BCH\",\n \"amountPaid\": \"1025900\",\n \"exchangeRates\": {\n \"BTC\": {\n \"EUR\": 8721.690715789999,\n \"USD\": 10817.99\n }\n }\n}"; + return View(vm); } [HttpPost] [Route("{appId}/settings/pos")] @@ -104,6 +147,7 @@ namespace BTCPayServer.Controllers var settings = app.GetSettings(); var currency = _Currencies.GetCurrencyData(settings.Currency, false); double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility)); + return View(new ViewPointOfSaleViewModel() { Title = settings.Title, @@ -163,7 +207,13 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("{appId}/pos")] [IgnoreAntiforgeryToken] - public async Task ViewPointOfSale(string appId, decimal amount, string choiceKey) + public async Task ViewPointOfSale(string appId, + decimal amount, + string email, + string orderId, + string notificationUrl, + string redirectUrl, + string choiceKey) { var app = await GetApp(appId, AppType.PointOfSale); if (string.IsNullOrEmpty(choiceKey) && amount <= 0) @@ -173,7 +223,7 @@ namespace BTCPayServer.Controllers if (app == null) return NotFound(); var settings = app.GetSettings(); - if(string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount) + if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount) { return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); } @@ -190,16 +240,21 @@ namespace BTCPayServer.Controllers } else { + if (!settings.ShowCustomAmount) + return NotFound(); price = amount; title = settings.Title; } - var store = await GetStore(app); var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() { ItemDesc = title, Currency = settings.Currency, Price = price, + BuyerEmail = email, + OrderId = orderId, + NotificationURL = notificationUrl, + RedirectURL = redirectUrl }, store, HttpContext.Request.GetAbsoluteRoot()); return Redirect(invoice.Data.Url); } diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index f10757151..d449aa173 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -109,6 +109,9 @@ namespace BTCPayServer.Controllers } entity.ProductInformation = Map(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; + if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute)) + entity.RedirectURL = null; + entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); diff --git a/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs b/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs index 1f5fad6dc..0f8d4fe6c 100644 --- a/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs @@ -20,5 +20,9 @@ namespace BTCPayServer.Models.AppViewModels [Display(Name = "User can input custom amount")] public bool ShowCustomAmount { get; set; } + public string Example1 { get; internal set; } + public string Example2 { get; internal set; } + public string ExampleCallback { get; internal set; } + public string InvoiceUrl { get; internal set; } } } diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index 1e9304634..540f0dc5e 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -38,6 +38,31 @@ +
+
Host button externally
+

You can host point of sale buttons in an external website with the following code.

+ @if(Model.Example1 != null) + { + For anything with a custom amount +
@Model.Example1
+ } + @if(Model.Example2 != null) + { + For a specific item or your template +
@Model.Example2
+ } +

A POST callback will be sent to notification with the following form will be sent to notificationUrl:

+
@Model.ExampleCallback
+

Never trust anything but id, ignore the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:

+

+

    +
  • Build the invoice's url by yourself do not trust the url field, this can be spoofed to use attacker's server.
  • +
  • Send a the GET request to the invoice's url with Content-Type: application/json
  • +
  • Verify that the orderId is from your backend, that the price is correct and that status is either confirmed or complete
  • +
  • You can then ship your order
  • +
+

+
@@ -47,3 +72,10 @@ + +@section Scripts { + + + +} + From 1ce6ae872756931bd9f192b4b8eba3780a5aee0f Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Thu, 24 May 2018 23:58:24 +0900 Subject: [PATCH 24/43] fix typo --- BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index 540f0dc5e..eafe1d8ed 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -48,7 +48,7 @@ } @if(Model.Example2 != null) { - For a specific item or your template + For a specific item of your template
@Model.Example2
}

A POST callback will be sent to notification with the following form will be sent to notificationUrl:

From 8d6131485266107fef1573ef8652e35684e1d98b Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Fri, 25 May 2018 00:02:24 +0900 Subject: [PATCH 25/43] Use FullNotification and improve instructions --- BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Controllers/AppsController.PointOfSale.cs | 3 ++- BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 669b425bc..138963594 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.26 + 1.0.2.27 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index bb8b100bf..83687823f 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -254,7 +254,8 @@ namespace BTCPayServer.Controllers BuyerEmail = email, OrderId = orderId, NotificationURL = notificationUrl, - RedirectURL = redirectUrl + RedirectURL = redirectUrl, + FullNotifications = true }, store, HttpContext.Request.GetAbsoluteRoot()); return Redirect(invoice.Data.Url); } diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index eafe1d8ed..3c2f82cc8 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -51,7 +51,7 @@ For a specific item of your template
@Model.Example2
} -

A POST callback will be sent to notification with the following form will be sent to notificationUrl:

+

A POST callback will be sent to notification with the following form will be sent to notificationUrl once the enough is paid and once again once there is enough confirmations to the payment:

@Model.ExampleCallback

Never trust anything but id, ignore the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:

From ff9265f7219eb3c6e56ff3351192dd2f2a8fb331 Mon Sep 17 00:00:00 2001 From: Yash Bhutwala <> Date: Thu, 24 May 2018 16:26:01 -0400 Subject: [PATCH 26/43] Fix spelling of lightning --- BTCPayServer/Views/Stores/AddLightningNode.cshtml | 2 +- BTCPayServer/Views/Stores/UpdateStore.cshtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/Views/Stores/AddLightningNode.cshtml b/BTCPayServer/Views/Stores/AddLightningNode.cshtml index fc9c552c9..5a1d49355 100644 --- a/BTCPayServer/Views/Stores/AddLightningNode.cshtml +++ b/BTCPayServer/Views/Stores/AddLightningNode.cshtml @@ -16,7 +16,7 @@