From 1588f49b4f9f4e3e27afa41504f25dc6f8bf0acb Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Wed, 13 Dec 2023 16:55:23 +0530 Subject: [PATCH] test_stripe: Add end-to-end test for RemoteRealm billing flow. --- corporate/lib/stripe.py | 4 +- ...ponsorship_billing--Customer.create.1.json | Bin 0 -> 796 bytes ...ponsorship_billing--Customer.modify.1.json | Bin 0 -> 821 bytes ...nsorship_billing--Customer.retrieve.1.json | Bin 0 -> 1918 bytes ...nsorship_billing--Customer.retrieve.2.json | Bin 0 -> 1918 bytes ...nsorship_billing--Customer.retrieve.3.json | Bin 0 -> 1918 bytes ...nsorship_billing--Customer.retrieve.4.json | Bin 0 -> 1918 bytes ...nsorship_billing--Customer.retrieve.5.json | Bin 0 -> 1920 bytes ...non_sponsorship_billing--Event.list.1.json | Bin 0 -> 1696 bytes ...non_sponsorship_billing--Event.list.2.json | Bin 0 -> 15722 bytes ...non_sponsorship_billing--Event.list.3.json | Bin 0 -> 21712 bytes ...non_sponsorship_billing--Event.list.4.json | Bin 0 -> 24143 bytes ...non_sponsorship_billing--Event.list.5.json | Bin 0 -> 81 bytes ...sponsorship_billing--Invoice.create.1.json | Bin 0 -> 5614 bytes ...p_billing--Invoice.finalize_invoice.1.json | Bin 0 -> 5956 bytes ...n_sponsorship_billing--Invoice.list.1.json | Bin 0 -> 83 bytes ...sorship_billing--InvoiceItem.create.1.json | Bin 0 -> 1116 bytes ...sorship_billing--InvoiceItem.create.2.json | Bin 0 -> 1093 bytes ...rship_billing--PaymentIntent.create.1.json | Bin 0 -> 6038 bytes ...sorship_billing--SetupIntent.create.1.json | Bin 0 -> 930 bytes ...onsorship_billing--SetupIntent.list.1.json | Bin 0 -> 1116 bytes ...rship_billing--SetupIntent.retrieve.1.json | Bin 0 -> 930 bytes ...ip_billing--checkout.Session.create.1.json | Bin 0 -> 2288 bytes ...ship_billing--checkout.Session.list.1.json | Bin 0 -> 2678 bytes corporate/tests/test_stripe.py | 126 ++++++++++++++++-- corporate/views/billing_page.py | 10 +- corporate/views/event_status.py | 2 +- corporate/views/session.py | 2 +- corporate/views/upgrade.py | 12 +- 29 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.2.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.3.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.4.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.5.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.5.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.finalize_invoice.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.list.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.2.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json create mode 100644 corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.list.1.json diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 059e7e0e1e..160b1e6735 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -3384,7 +3384,7 @@ class RemoteRealmBillingSession(BillingSession): self.remote_realm.save(update_fields=["org_type"]) @override - def sync_license_ledger_if_needed(self) -> None: # nocoverage + def sync_license_ledger_if_needed(self) -> None: last_ledger = self.get_last_ledger_for_automanaged_plan_if_exists() if last_ledger is None: return @@ -3406,7 +3406,7 @@ class RemoteRealmBillingSession(BillingSession): current_plan, audit_log.event_time ) if end_of_cycle_plan is None: - return + return # nocoverage current_plan = end_of_cycle_plan def get_push_service_validity_dict(self) -> RemoteRealmDictValue: diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json new file mode 100644 index 0000000000000000000000000000000000000000..a9f571a450f3bb9852fed86cf6939981399061aa GIT binary patch literal 796 zcmaKq%Wm5+5JmU?3PbC-uq`Q(x@y`c>mb{rAV%aUV#-(eFq{PW_YNuBu~DE)&g0IV zGkjkxmlX)%F@);t(j=v>a%cbrh9ia6<;^`pL_zP(&3fD3T-S9oCzAJQ`0IJIz!8#) zjQ=MWZ1P3_G*Y0F<|Uztq&0#HSOy3T+)EdwH59Mjl9KLA#1I4e9r|TA49&W4`Ks-p znmc6RHWitZIBJk8_fMb|#&5F>w|Z`od9#YAz#hIoJ>E7y+Q-_-=I0@6c2;s^&f_Te zxw5|Ro4fn^_A(zZMroch$ajKB7)dc&%|?kzob#pB5t4&fO*=Dw8l%p&S{3z=yxkGJ5zTGE9QU?JMKCO8wipc1$O0&s^O#L6aWj``cz+6 EFJwFO%m4rY literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json new file mode 100644 index 0000000000000000000000000000000000000000..cd9f838240fe30554f87d0fe20eb9b71d724ebb4 GIT binary patch literal 821 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 literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json new file mode 100644 index 0000000000000000000000000000000000000000..e2ff6da703f39e46ce1e1bee8bfb18dcd9ead086 GIT binary patch literal 15722 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 JkNo`8e*pfnyRrZP literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json new file mode 100644 index 0000000000000000000000000000000000000000..15e823a6104b89dc09b33bbb17fd95fb33d23920 GIT binary patch literal 21712 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 literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json new file mode 100644 index 0000000000000000000000000000000000000000..42236941d150b17468a544f8a889783973c5255c GIT binary patch literal 24143 zcmeHP>vP*S693-6Lc>pYGj*(|C3U9LOZ;fvIId;O_Su~d2O=R05nm=j*>NWS`z>C4 z0hE$SJD1+UA7oox0E@-$Zx_1&KYrKgI6k7t`JwYW{N3q%{ay#J;sao*rPyQe#ge~yf}}j%Rkc( zY^>|6VhqMoK@y=5v*&M7tkdNu-tiP`5 zQGy7YnDJSPLqsAHJ+u*IE0QF}=SdO}4^bghovre*yTsTCYYI;@ifFE6E)|7|{8NpG z1qlL>j|&Qv3H=IDl%t@&klqTV4~$1mP+lAapi3Y|H^g)mv!!wbxr<hILBadrgp81W|c?LbZOM(NlR(YHNr<|EQb1&XJT)rM2jxGlY86KHB!zrwU zpW;kOticT%5NMdb1+3Q<3Ik04^^yl9dF91nOP|tEbg@YW-DDLIx zGeu;~MXXQr1WHr<{f`=jkC!M9sGFbZ?>V1uOqOiZTQaG95q zq|N?(l5rSj;19?*Dn|SQ$Shp!#o4bsso1OgBIv<|T>@{CO-t1Y%_APrM`OWi&jbj*8!JPpo^5jqM3 zTKkb28E6`rJ@wYZoB6`Tq_i}bfZJw@Nr=|`HeHTi@VM@>B<}$LNErLu_y*)eD(0#= zTiHbseL3KHv0%iopb1V1cc;2GaNp0Py}@YQosK5Mk=}s_0aA{2gZ^kZhL~_vi30>T zCI%D0K7*c1_LEc~oRKZW5w?9%fhiSnWZnvkC=zDs<`JQW6Eg}X zcIXrI6A^FS5@{5@lv*bi&?v$rjs2YO3>@~SdvWk|HtpTa?(lMSU-t(^$0=rl7cqA0 z99Sf&N973Hu$`b4mh~Vm>UC}Egu;X#1rL}jPq9|L&Y3M%YD58XJ6(s$TFuQdi zI&vui-80Idt{MzKcagb1OeKVv8Tw}Q&gWSfcoVh%XoEml1VIBsP>4Jk6dLUgjKl{- zgZKhqjZra?eHI%P!SxI?b+UX>bpz(5aM=0^7uAZ^?ZKc}bVn_ZL}(7sy68c53e(lR zol*Q6#OV?&-RoYP!xXdAMr@_ujYdwx!Cr{SyYr7jl=>YJh;~TS8TN<$e;RbPaFDws zuN>)L=5ODPX8%5zgd-D^ZT@yrBI)F3?`(dSZA{H~#z%nYUF1Qor;XvsDZ$nLl)VMd1_EavQ?Ssv@CE`lohqBFQnK`yAW2vB#Pu;J?SiIG&kO$ zf=phKCKZ9V8$ntyD>sz2KpM7E_>1TtbLh<^YJh$WIa9aL8f~p1XYNDZfEPsSVXjIL zu`nD0LWnucdnHFC?COoV%pb6ukr%#$g48!lgI0tMb%n}*b5U zTo9C7A#M76<+kN-Sy2SC4v^ng!ME6tZb@#Hg%n{)Rb7MxUsusPe3 zcm@fFC|v_l9;uoNq{l>)<~;dwfbnkc4^q*-8kF_Wx;{I$cr}02x)H5{;jYm*g2Niu z%>>vG(B8~p*2zGDP3pl@uwD>*YvWVqGqe%KaB6$hx{Z<^X9ru{D{b2cJ6ubaknED# z!RE3$Pk6C{5H$lKvv?Nv^vxwM)=*hnVQDf~g*Q+#UJ z0$>M)KA8n`#(3JFdP}s|9pcH}(4QXcO-56^=S>d!%c(y>sOu@R>-<)$skA=w4zI4?%zr5h-2>_v51>m>S%VeCb9;Vx`7oy(!}p|Gq;}%Zow)fh#%$g>G9- z|3=;oW2@=x<+Am2)Apjef^_(8FRI&%>f-jiy{HaIQM@Ff@%kToQQclt|7$L)KXq^J tpHJz@|4}u)csYbDsY79%pXf85CQczGzNi0cJvQmhS)dBG`5{u(=^NUh}B58>^#i?My{G_bZ Xv?vE8pkHRFpIVlhS5mCRRm%kck@XgY literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json new file mode 100644 index 0000000000000000000000000000000000000000..effea70934fea2db2620f6532db9035ba5ef190e GIT binary patch literal 5614 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 n!+QJb^1x&zhGx}u{MkT|Yq2&0CNj12 literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.list.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.list.1.json new file mode 100644 index 0000000000000000000000000000000000000000..e39960ab7254f5b071e40ef45d6574c984929f3a GIT binary patch literal 83 zcmb>CQczGzNi0cJvQmhS)dBG`5{u(=^NUh}B58>^#i?My{G_bZ Zv?vE8pkHRFpP5&dpP8Imti)Bz1pvYK7f%2H literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json new file mode 100644 index 0000000000000000000000000000000000000000..ddb9d9e39e4b260fbcb91f2cf081b98d14bea914 GIT binary patch literal 1116 zcmZuw+invv5Pk1gto8{ckaF3iDR0n32&mdnp&~%Ya=cD5jo04#5(rWMof)r_-9Ywf zJu^OY&YAK4BFl1KnV^0CCVM^U;rEJSB3LW6*kLpeuFO-=c~ePCK@l9AzgykEEv`rR z!y1ZdQ&Vy;G2g%J4MvyaVKIm|OX-A#+Jn(_dCqs0)INK0&25=UU4mX^ptE973|=N2 zaDr&@g(AEqS57uiDd8~s<7W&(NuU6%e|P(FG?~smb8DWak8bi_+1 z9h8bU6>Mc?%F_;&^c-byEZjfGGP8J;f*i^d26$ODptZDMsQ(^qL*q~k9!}+B*m-UV zpiN+T170gmrW`rdwjr4Sx$sC53yu~b+yazB&zKWySy3`a9xQ0^krh@5D{&HuOK&tD zO@;+&q12h##xPAcg0SOC99Ut#aXGR>qi1f&TciIx=!oUaI>jM;>mZ6xhg z2l_w9u~viFXdcCND%(jkHYG{5HDzcfM8E8A`)#=Xw7MOSzkZPO11l>@I{J3n-SgjU zA=i8hCa!kSu9gCpfc7PP57Ii8Hl;(yTdv*G*or*`u08mFg!x?^r(?8a9#Bleqe-^2X5WVMDFh2Kyu^mHiP067j(kMbqI!gV;Eo9LVp5fLQI-|r zJ-Jn!T-a-}t{0gIxdR?er=u}~ba1yQWwAACgoK7Zn zp0u1ACRIclXGg;JE3i|A@!sW59UA=szS)D(tJ_Qc{V9Cfy?)A9q)^7Jqh)JJi!`7< zavXCth_&WjbA!yoBzl`tOV*ht_7kGN?CSi_=UM*a`lno0cXp@BNopN^d;NOgPi!IY z`36k4%TaqP1uOyWOZ)+(^DJ%3Ku&O6`=xO$dyHHM2>&$}o^3ZBqa_P~%_KgW)WKxB zv*Q43P(;fWO_wsV!^Sw{csic_R%8POd4IJ&E{1ou)16VnNFWC%gdc$kWyE^mO?F!5 K5Iz?9a`q4FLOweH literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json new file mode 100644 index 0000000000000000000000000000000000000000..4e7ba8777562184fd0fb825630e3906aac33f91c GIT binary patch literal 6038 zcmbVQSyLo85`Oou=uqsV1G=GMz!4kvFw9^UgBh5f1ILC!tx|Vus6*&z8XWWAosvpb zQdM#6@&ZVi%KOXA`unHR2$7;PrAEJu4#x2Fb9;m-5!y&H&l%h%HyP7}=L(Mh26}iz zb7BAa0ssAopOB0)zGhkaybw93MDwbI^U~zGXK9b|f-&4E)>0T<6~ITaAP)sAH7=N5 zRhdL?xbSLh)RWfTUq@eFd`C!A*UT(5xHcM$r;fE=>BpJ7}bt& z&S(^GJZg^{SJ!?`osv>WwlHPpD4gHJZR?@PwEm@a--`r&|JD!9Np4+;wIrn?v>U%KDCSGT~Z>t2)B=+$FdD!~P&vKotxKKqV;x zvrV4xgT$&T!$or*y@@EvsevSg^mQ7zB+rQ1x!&=FRLc+tFa4~r^-r0MzzdN18*G5WKQtM%4RF=GH6x1AZlBrSn@@RSWcKUAb zYFF^-aZDMs^i_>!Vkt?+M!Fi^n4F8zkx>v2rG6g$>tMV!9&JXGafe^?vWp7dbtvOX zm?j{R>h*;+Z9#Gs8@+(w7|HN(J_lf^1q9;3j6mVG0R71_L@-igA&JT2l#heTt#*j6 z`sH&ZF3--t^4JZ$Tvwb9h{yp)3!krDO92H*4M!f2Yoq3MDFPeGeR+Wz7RPXdZY0ki zBKeTjflJX9%ZvkmG)YMSzFDaNElCOPut{nyY~CCswk3~}kb^7%P=hu1`865urkp~8 z94wf`wLtrX1=W;Hf?qVVpq>|a1(j{d9&OTXX{NMdat#97Xo1{>#Q+5kwTxDz$eI4l z^V6z$iP1;5;~)j~2I%4&YtL%^AZ&n<@il@5p`e<7XVjVELl)_QsuIuHisTDiE#RR} zE>|*h4(%Qr4dz(ENs^RI-&gY1;f--YB0|Utnl_$4;QBTa?!4D&pAEoCu5NM8*37~p zDl1u#yu)iL6s@FHWV(|?MX@}GP_$KsE`6ULf#L$9!nV@Kf+HdGR+Z$B0C9u96vRcT zgNk8@;H9oJSnz(0p4@$Un4eumf>B`f(}%TG6nJ8Lpq1EuY~z80Xr*a0}oE2~9>?oO!%3_MH%TzMco{CcL&`R441 z@86!DKE@5zlf!Ta9H6D`%o*3+F!2)3Ev&+f5(54TWlc%e(DdBI{;Y?2X~&=3O0db$ zk21=M(o?J8bZ5HLRS#ev+D*SlIpYeG!-Vcuhc4E*`_{LwRyTkUs56w*0l{>2%j`zc z3nu`4Gax+c7eGDvjOqp1@WK@JhN`U~W%E+Nn z)SvqWaWRw%Pwp4C&@P60 zTUfJ6y>kC*=!Z5hkolUOE0DQCzS*{(+OyD08&1W*{TV0i@<69`J8!Tq53~2{SFH(fly{k_>Ai6toGT5%LJ~3C2V_6 zcN;*@%xjo#s6`+2vcw4F4p%6~aBuQL4Ua1?T^ zqVU}$?JyAd*Nh%O)@fRh5ZVa5Fz{)bVbl2>Gt|ubZEUdH*uqc0RQc2x>Sg`T6s-(t PPkOQ&wHHYr`tj30vE`)> literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json new file mode 100644 index 0000000000000000000000000000000000000000..bbed0c4ce859b4c9a37caf6cf6c4fb1a5add77bd GIT binary patch literal 930 zcmaJ=O>Yx15WVMDM0@6tG+9!(LAPpJp_WtvK@qYh_H5$fwH<%7L{a`bV{ZbxX~kt_ zkLSI4^JaE87!C`tR!I&)8eO~}>ZsJ49FD;>6y?mqwm}_OgJEri?%JiYW9m8sJI<+s!dd}=sE%g{NKWc;<1d_sDK5U{aFKj`+t5jne@Ppj(u zKq;TFF#?II2IVog*?<;A*QKZV0L6e}yM0d5_?yb*Zn}wP`A98NBsD}rr4BTvwwTdu ziEB_bGj$i!UxI|S%?sUS8Ao5fy}N3u`VhvAuH`Dac3A|8m8$3QNxsSRNf|rZM_N=4 zE={4m(fwcn`$pL|E8b zOtOA-WYO_ww%kj-Qplo5_R5HSfDjY5@R4&wK^vT5(Zec>Fm+9myib4QU_bZ+G#e+$ literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json new file mode 100644 index 0000000000000000000000000000000000000000..81f681388450887f0a7222c3eb774e3f40f47986 GIT binary patch literal 1116 zcmaJ=!EW0y487+o44ntGX_6J#ZCi#7&Co6Fb|{8HU}ZW{D@(2@HO-3r`zXbY6KCti zFi7h0@jdc+P!yts0ODQoi~oz_Ie$3{O=Glzpq=HUjmEs$rDAY3XOwJUU84=M#;|lH z^$QhPg~ntM=@IA{OBtO22$jdc?*(Dda`^4?VsbkDGzaer`d z0@h8^>M*Mxv34bLyaGeGo3aMW4yX7*JzEB?^Us@IvBf9ukca6%aJP3nB}dad%u(CG z&UN&imAAMAQ%P6#-yZJC>shIst@I-L?b?*gQ=21Y4-uuy?xd=h1?Cl2;FJDcgk!B5 zdk^+iNf#h2hL!X8J(fA;@gXJ;!qOwkGOc6uNm>CfHzC?9DT+<|*Qwhh9o7vd%G=A! z4Kn5S-S3?%r0v3>jD+$(Zc=~{6P8F6c^|2Fo1IUQV38@is^S5N(uL=iiEwTzmO!$0 fd9vVR-X+obZ!~Rv7tx#4^W<@OvcD802AjcO8a+r4 literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json new file mode 100644 index 0000000000000000000000000000000000000000..bbed0c4ce859b4c9a37caf6cf6c4fb1a5add77bd GIT binary patch literal 930 zcmaJ=O>Yx15WVMDM0@6tG+9!(LAPpJp_WtvK@qYh_H5$fwH<%7L{a`bV{ZbxX~kt_ zkLSI4^JaE87!C`tR!I&)8eO~}>ZsJ49FD;>6y?mqwm}_OgJEri?%JiYW9m8sJI<+s!dd}=sE%g{NKWc;<1d_sDK5U{aFKj`+t5jne@Ppj(u zKq;TFF#?II2IVog*?<;A*QKZV0L6e}yM0d5_?yb*Zn}wP`A98NBsD}rr4BTvwwTdu ziEB_bGj$i!UxI|S%?sUS8Ao5fy}N3u`VhvAuH`Dac3A|8m8$3QNxsSRNf|rZM_N=4 zE={4m(fwcn`$pL|E8b zOtOA-WYO_ww%kj-Qplo5_R5HSfDjY5@R4&wK^vT5(Zec>Fm+9myib4QU_bZ+G#e+$ literal 0 HcmV?d00001 diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json new file mode 100644 index 0000000000000000000000000000000000000000..13f92597f0fcbb8732650ec967508cb011ea7e7e GIT binary patch literal 2288 zcmb_dTXXU@5Pi?D$hfaf2-mD5bAjm zXpOLh0JaaF(U5^4ab#gROJmfJN=1Y4mr@%{KQ*9@hUg8VN(58{mA30P&N@wY(rnZA zNu%brPpI!TPC8zv2AyiFQ-x+FR*Q&wP;n9{OmIK~BJd#s>_@nLLP215tbs*fN(VC3 zn0O%cv)3R*lkihLKdUv*TlIP)MG!}}^QKfPi(#!0pQrc6%3_T(AU+4-S#tmWC##f% z4XiD$Y%ZxhaKHiIMQdlXi!uhv*g0j%#fVdg5MkWG zZ!*4I++Q@h&HK9IjjN3NZ?$UY z*}xNpIZP9)U&5-dta@9&8@8TCPn*AjU_84w=Z&_yTu&cw7tb%%=fVB^-LT27+Wqcu z-F$Any`I%uz1x&T9==H&#SR-z7CK0);BOG13*h}?PaPo{6{QjZlI~GM@mj`uglPtF zQE<^ni~>kE#i`~5n~kz197ZFL#0s;)DVWMyIvPx@QsB~k!JSVsmcUjrHm4fRz)C$) zv!a{8t|kx7-lF9v=pif2FG$bh2!1KO@*X-=ywi;?mo6@bK3~MCeeUS(XN9G*R+T6+ z^|QUkmWgC7Q=M5tEEDOH=64HcbV5n;U<9u*kI*Gj2CP6y7zeK1kzJ2G?70Qo5ip6i z0Uq(u`gbWx`2V8R&V!TcXgFw|U0vVNNWNaZ=!T-IP9N`56uC{5%IsN38C}6!o=UzL z%dDz6LDEaY`=UiE$dr&;5y=GhRXuh!i+D1O61W&$3I8hp_m3m{YgORQ$}$D|yEcdb z&=3>l;!gbb(d}P%{~S#o$;0)-!|iBDZm&L5DwBWdA%Vr5L!tuSnJ6QK>HxHME7P1TGl zS5eGEx9a<)t6G^{l&iRUQ7ZV=3p@%+7qy^Ppf#^j^Jv+Py3Q67;fw-7ijJtF za3^B6tUaPPTQnt#np{vs8WNmX{q@a$+M$Y-MYGJM-K<022ujyS z+JeLybVDOXd0>V3`FAF35;AmQ@n-Z~w;D7_$PO#9nogLM8QBg%*&QcNLIZ~yi4n3& zF(4NcLZz|VZj$6$WDTDxh&%8fb#KSdH>G;{xhPrbE~8sgCJYFOa&$b>L@1yJGj6HK zR=n=Od?_@9&GzyM4mmY{tYXgOB@yBFYN$8DVOl^+F09Ilh>k47^uQF;P#&Sy##kbg z?YNU~+@k)0Jpyieji7N6Rcx6a%0uQ2Qmp3QZs zDsLD4*T?buhxgukoEY!)dx`EH3}j9_+DfB^I%v2ne*qrHt*2BR#iucPA1k zT~zclW(u5a$C4cc6vd%$=QHc^89fi)keo`25&;%R12lps?vTzzyPXVHONP8oyH&or zyMMw;eBOQNlEkuzU!Qi!QY*`)JFvKY)O$RqDbSALjKyxFAXcuick~HKGhMZ|Pqb;? z{o7(+6U7oSbD(2zQ+P%H??-a;O1pGsTYU}scWnR#1tU);0%q^GuX^*o{%5E6ieBzt zULHGb^mzBZqdNJfIMu8%L+KRS2R++7nQAn=`D8nb2>|QFE^(pv-2ZNDzl>Ng4f>1D zpgE7a%fR!wTnw7SMW1`U;m1RVtcI=k-hH9lldE38-F+(zWxyX+y*iPtLc55=Nq^7` zR)Z#qy2XbgY23g%pN<-GPTn4>LI1|)E%*(?aQ_w$Ln%pPqFT#oG-#OVu)xP}eLfwA zw+5b@3FIoYR<%V?ydBeekFO{38)hS!MR=1m@TP2I85JkrT^ZZrOq+y%XTza8^S1oK N$5W!5cFw-f{sCV|A>#l5 literal 0 HcmV?d00001 diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index dafa711050..275925d961 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -34,6 +34,7 @@ import stripe.util import time_machine from django.conf import settings from django.core import signing +from django.test import override_settings from django.urls.resolvers import get_resolver from django.utils.crypto import get_random_string from django.utils.timezone import now as timezone_now @@ -96,7 +97,8 @@ from zerver.actions.create_user import ( ) from zerver.actions.realm_settings import do_deactivate_realm, do_reactivate_realm from zerver.actions.users import do_deactivate_user -from zerver.lib.test_classes import ZulipTestCase +from zerver.lib.remote_server import send_server_data_to_push_bouncer +from zerver.lib.test_classes import BouncerTestCase, ZulipTestCase from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.utils import assert_is_not_none from zerver.models import ( @@ -432,7 +434,9 @@ class StripeTestCase(ZulipTestCase): hamlet.is_billing_admin = True hamlet.save(update_fields=["is_billing_admin"]) - self.billing_session = RealmBillingSession(user=hamlet, realm=realm) + self.billing_session: Union[ + RealmBillingSession, RemoteRealmBillingSession + ] = RealmBillingSession(user=hamlet, realm=realm) def get_signed_seat_count_from_response(self, response: "TestHttpResponse") -> Optional[str]: match = re.search(r"name=\"signed_seat_count\" value=\"(.+)\"", response.content.decode()) @@ -575,7 +579,14 @@ class StripeTestCase(ZulipTestCase): **kwargs: Any, ) -> "TestHttpResponse": if upgrade_page_response is None: - upgrade_page_response = self.client_get("/upgrade/", {}) + if self.billing_session.billing_base_url: + upgrade_page_response = self.client_get( + f"{self.billing_session.billing_base_url}/upgrade/", {}, subdomain="selfhosting" + ) + else: + upgrade_page_response = self.client_get( + f"{self.billing_session.billing_base_url}/upgrade/", {} + ) params: Dict[str, Any] = { "schedule": "annual", "signed_seat_count": self.get_signed_seat_count_from_response(upgrade_page_response), @@ -629,15 +640,22 @@ class StripeTestCase(ZulipTestCase): self.send_stripe_webhook_events(last_event) return upgrade_json_response - def add_card_and_upgrade(self, user: UserProfile, **kwargs: Any) -> stripe.Customer: + def add_card_and_upgrade( + self, user: Optional[UserProfile] = None, **kwargs: Any + ) -> stripe.Customer: # Add card with time_machine.travel(self.now, tick=False): self.add_card_to_customer_for_upgrade() # Check that we correctly created a Customer object in Stripe - stripe_customer = stripe_get_customer( - assert_is_not_none(Customer.objects.get(realm=user.realm).stripe_customer_id) - ) + if user is not None: + stripe_customer = stripe_get_customer( + assert_is_not_none(Customer.objects.get(realm=user.realm).stripe_customer_id) + ) + else: + customer = self.billing_session.get_customer() + assert customer is not None + stripe_customer = stripe_get_customer(assert_is_not_none(customer.stripe_customer_id)) self.assertTrue(stripe_customer_has_credit_card_as_default_payment_method(stripe_customer)) with time_machine.travel(self.now, tick=False): @@ -714,7 +732,7 @@ class StripeTestCase(ZulipTestCase): def client_billing_patch(self, url_suffix: str, info: Mapping[str, Any] = {}) -> Any: url = f"/json{self.billing_session.billing_base_url}" + url_suffix if self.billing_session.billing_base_url: - response = self.client_patch(url, info, subdomain="selfhosting") + response = self.client_patch(url, info, subdomain="selfhosting") # nocoverage else: response = self.client_patch(url, info) return response @@ -5587,3 +5605,95 @@ class TestRemoteBillingWriteAuditLog(StripeTestCase): assert_audit_log( audit_log, None, support_admin, audit_log_class.CUSTOMER_PLAN_CREATED, event_time ) + + +@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com") +class TestRemoteRealmBillingFlow(StripeTestCase, BouncerTestCase): + @override + def setUp(self) -> None: + # We need to time travel to 2012-1-2 because super().setUp() + # creates users and changes roles with event_time=timezone_now(). + # That affects the LicenseLedger queries as their event_time would + # be more recent than other operations we perform in this test. + with time_machine.travel(datetime(2012, 1, 2, 3, 4, 5, tzinfo=timezone.utc), tick=False): + super().setUp() + + hamlet = self.example_user("hamlet") + remote_realm = RemoteRealm.objects.get(uuid=hamlet.realm.uuid) + self.billing_session = RemoteRealmBillingSession(remote_realm=remote_realm) + + @responses.activate + @mock_stripe() + def test_non_sponsorship_billing(self, *mocks: Mock) -> None: + self.add_mock_response() + with time_machine.travel(self.now, tick=False): + send_server_data_to_push_bouncer(consider_usage_statistics=False) + + self.login("hamlet") + hamlet = self.example_user("hamlet") + + result = self.execute_remote_billing_authentication_flow(hamlet) + self.assertEqual(result.status_code, 302) + self.assertEqual(result["Location"], f"{self.billing_session.billing_base_url}/plans/") + + # upgrade to business plan + with time_machine.travel(self.now, tick=False): + result = self.client_get( + f"{self.billing_session.billing_base_url}/upgrade/", subdomain="selfhosting" + ) + self.assertEqual(result.status_code, 200) + self.assert_in_success_response(["Add card", "Purchase Zulip Business"], result) + + self.assertFalse(Customer.objects.exists()) + self.assertFalse(CustomerPlan.objects.exists()) + self.assertFalse(LicenseLedger.objects.exists()) + + with time_machine.travel(self.now, tick=False): + stripe_customer = self.add_card_and_upgrade() + + customer = Customer.objects.get(stripe_customer_id=stripe_customer.id) + plan = CustomerPlan.objects.get(customer=customer) + LicenseLedger.objects.get(plan=plan) + + with time_machine.travel(self.now + timedelta(days=1), tick=False): + response = self.client_get( + f"{self.billing_session.billing_base_url}/billing/", subdomain="selfhosting" + ) + for substring in [ + "Zulip Business", + "Number of licenses", + "10 (managed automatically)", + "Your plan will automatically renew on", + "Visa ending in 4242", + "Update card", + ]: + self.assert_in_response(substring, response) + + # Verify that change in user count updates LicenseLedger. + audit_log_count = RemoteRealmAuditLog.objects.count() + self.assertEqual(LicenseLedger.objects.count(), 1) + + with time_machine.travel(self.now + timedelta(days=2), tick=False): + user_count = self.billing_session.current_count_for_billed_licenses( + self.now + timedelta(days=2) + ) + for count in range(10): + do_create_user( + f"email {count}", + f"password {count}", + hamlet.realm, + "name", + role=UserProfile.ROLE_MEMBER, + acting_user=None, + ) + + with time_machine.travel(self.now + timedelta(days=3), tick=False): + send_server_data_to_push_bouncer(consider_usage_statistics=False) + + self.assertEqual( + RemoteRealmAuditLog.objects.count(), + audit_log_count + 10, + ) + latest_ledger = LicenseLedger.objects.last() + assert latest_ledger is not None + self.assertEqual(latest_ledger.licenses, user_count + 10) diff --git a/corporate/views/billing_page.py b/corporate/views/billing_page.py index 1f3076e997..48ad663f6e 100644 --- a/corporate/views/billing_page.py +++ b/corporate/views/billing_page.py @@ -99,7 +99,7 @@ def remote_realm_billing_page( billing_session: RemoteRealmBillingSession, *, success_message: str = "", -) -> HttpResponse: # nocoverage +) -> HttpResponse: realm_uuid = billing_session.remote_realm.uuid context: Dict[str, Any] = { # We wouldn't be here if user didn't have access. @@ -109,11 +109,11 @@ def remote_realm_billing_page( "billing_base_url": billing_session.billing_base_url, } - if billing_session.remote_realm.plan_type == RemoteRealm.PLAN_TYPE_COMMUNITY: + if billing_session.remote_realm.plan_type == RemoteRealm.PLAN_TYPE_COMMUNITY: # nocoverage return HttpResponseRedirect(reverse("remote_realm_sponsorship_page", args=(realm_uuid,))) customer = billing_session.get_customer() - if customer is not None and customer.sponsorship_pending: + if customer is not None and customer.sponsorship_pending: # nocoverage # Don't redirect to sponsorship page if the remote realm is on a paid plan or scheduled for an upgrade. if ( not billing_session.on_paid_plan() @@ -136,12 +136,12 @@ def remote_realm_billing_page( RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY, ] ) - ): + ): # nocoverage return HttpResponseRedirect(reverse("remote_realm_plans_page", args=(realm_uuid,))) try: main_context = billing_session.get_billing_page_context() - except MissingDataError: + except MissingDataError: # nocoverage return billing_session.missing_data_error_page(request) if main_context: diff --git a/corporate/views/event_status.py b/corporate/views/event_status.py index 54d8715445..16542bc8d7 100644 --- a/corporate/views/event_status.py +++ b/corporate/views/event_status.py @@ -48,7 +48,7 @@ def remote_realm_event_status( *, stripe_session_id: Optional[str] = None, stripe_payment_intent_id: Optional[str] = None, -) -> HttpResponse: # nocoverage +) -> HttpResponse: event_status_request = EventStatusRequest( stripe_session_id=stripe_session_id, stripe_payment_intent_id=stripe_payment_intent_id ) diff --git a/corporate/views/session.py b/corporate/views/session.py index f5ec1dcaa9..0371ac07a2 100644 --- a/corporate/views/session.py +++ b/corporate/views/session.py @@ -75,7 +75,7 @@ def start_card_update_stripe_session_for_remote_realm_upgrade( billing_session: RemoteRealmBillingSession, *, manual_license_management: Json[bool] = False, -) -> HttpResponse: # nocoverage +) -> HttpResponse: session_data = billing_session.create_card_update_session_for_upgrade(manual_license_management) return json_success( request, diff --git a/corporate/views/upgrade.py b/corporate/views/upgrade.py index eeda0f7994..74fc9cba79 100644 --- a/corporate/views/upgrade.py +++ b/corporate/views/upgrade.py @@ -97,7 +97,7 @@ def remote_realm_upgrade( ), licenses: Optional[int] = REQ(json_validator=check_int, default=None), remote_server_plan_start_date: Optional[str] = REQ(default=None), -) -> HttpResponse: # nocoverage +) -> HttpResponse: try: upgrade_request = UpgradeRequest( billing_modality=billing_modality, @@ -112,7 +112,7 @@ def remote_realm_upgrade( ) data = billing_session.do_upgrade(upgrade_request) return json_success(request, data) - except BillingError as e: + except BillingError as e: # nocoverage billing_logger.warning( "BillingError during upgrade: %s. remote_realm=%s (%s), billing_modality=%s, " "schedule=%s, license_management=%s, licenses=%s", @@ -125,7 +125,7 @@ def remote_realm_upgrade( licenses, ) raise e - except Exception: + except Exception: # nocoverage billing_logger.exception("Uncaught exception in billing:", stack_info=True) error_message = BillingError.CONTACT_SUPPORT.format(email=settings.ZULIP_ADMINISTRATOR) error_description = "uncaught exception during upgrade" @@ -215,7 +215,7 @@ def remote_realm_upgrade_page( *, manual_license_management: Json[bool] = False, success_message: str = "", -) -> HttpResponse: # nocoverage +) -> HttpResponse: initial_upgrade_request = InitialUpgradeRequest( manual_license_management=manual_license_management, tier=CustomerPlan.TIER_SELF_HOSTED_BUSINESS, @@ -223,10 +223,10 @@ def remote_realm_upgrade_page( ) try: redirect_url, context = billing_session.get_initial_upgrade_context(initial_upgrade_request) - except MissingDataError: + except MissingDataError: # nocoverage return billing_session.missing_data_error_page(request) - if redirect_url: + if redirect_url: # nocoverage return HttpResponseRedirect(redirect_url) response = render(request, "corporate/upgrade.html", context=context)