From 7469869311a298703475bd3e1efeb80909a0da45 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 12 Mar 2026 03:55:36 -0500 Subject: [PATCH] 's' --- app/__pycache__/config.cpython-312.pyc | Bin 2773 -> 2773 bytes app/__pycache__/main.cpython-312.pyc | Bin 3499 -> 5593 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 489 -> 536 bytes .../__pycache__/factory.cpython-312.pyc | Bin 1325 -> 1736 bytes app/payment/__pycache__/zpay.cpython-312.pyc | Bin 8698 -> 9510 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 675 -> 675 bytes .../__pycache__/_digital_base.cpython-312.pyc | Bin 11689 -> 11825 bytes .../__pycache__/cnomadcna.cpython-312.pyc | Bin 354 -> 376 bytes .../__pycache__/digital.cpython-312.pyc | Bin 344 -> 358 bytes .../__pycache__/nomadvip.cpython-312.pyc | Bin 10072 -> 14976 bytes app/routers/_digital_base.py | 19 +- app/routers/cnomadcna.py | 2 +- app/routers/digital.py | 2 +- app/routers/nomadvip.py | 4 +- app/routers/wxh5.py | 270 ++++++++++++++++++ .../__pycache__/payment.cpython-312.pyc | Bin 3423 -> 4649 bytes app/services/payment.py | 30 +- 17 files changed, 319 insertions(+), 8 deletions(-) create mode 100644 app/routers/wxh5.py diff --git a/app/__pycache__/config.cpython-312.pyc b/app/__pycache__/config.cpython-312.pyc index 2a3b8d9a214b9c80d5e93a35c845e0513751d4ba..c7df1b5785cb5a95d096454c81dcf1e0258147e4 100644 GIT binary patch delta 20 acmcaAdR3JBG%qg~0}#x3BmdDY^+64*gLGP}3 z4O9Z-gt(d79;LyP*ooc5jZ<7g<7w@2pp5@?I@3R>IAgLy+8;qYrqfzB?KFM-=()QH zaK{<1=AL`*Io~;Z?mg$+)#&Cgs;z&>$}%B%-c`l}#D30N82gk#Xaot!jRY((LF~rr z%(xkt*&rL@++0ZK)`j$LJ*F5g$cGGWgUaiI#*oQv0$wlhL37CBwy3-zm=&_Rts$G+ z7P7nTp=@_{D94=>a=0BBF-YVDqhJ!u*DRwkmWG3nJ6FtYuh#gDR5S=#ZCJ3X6u4`) zQJtp82rQo3h}C$pU=(YdU>9{lwph^02{}XD=ql7*2m#>s9on*LRjN)P{?^8|#tRPB zpdqmfm&9^MR|AH9<_#ftF$D4KBW&{5f+!e5Yp*T^^mC6u7p|d8gd)+|$_Sf=bZb^y zz-q5%mG~nrVqul|8-7Ba?aULgHSI>vVs^%-dQhwO4pMXr$0Kr$FMMxeI5l-@ z`SNG;H*Y!J-)lPJOr7~8b?VyUspXNe#dn5M7sgW;V>&6m8tGS(p3~yrEb##jL zqdp|%%X|jbZpd)!v%svMPY)8~~>O21%I1tS`wI zoj-at)|@P<9k0L9_^Zb0=MUYfe`Wgc(YuFRlZRWU+oU^(f^a5h?9w1ycqCVQCEua5fVcD9zNt z5hPgp*&nggk;Zl5F4WB)L0woq<*aBUz)}NOQnzj_d=OuF{|YqY`a$ifb1sbhX8C6q zQy;!f)xZ9+lb&BMT%69OsrQ!S?<~YWQesTxgkk`By9C#(i&gZHB{?cV9f+he;1d-? zrbt1F>l98VQBh%gM5))F>5z=yW%`YEVTG3iAu$q_UEK2TK@xZhvJ(D9Mt-wuv$+p)%v%ZlBm82O?n? zM|M&P3$Ii#QLw0%kP{xQ^7L965n65sU`S_xs3n(<=~4csOYIZ>r0Iot^Biv)-gPo| zmoHB8#WQ^AbXh}^Z%8vbW96K^aKv)XGF?)8Gy9JH+2N+y@`{g+T|PG5&@x@#GTc0E z-;%VHr47hpi?#G%{Nd)aS)W@w)aqFWuTXnGERT-lU|ZfA4A-p(^N?PhP6I+}K}x9jv&vQtMTyIJ59zcajIA))fhWmjD99;z9qq_sX8=%=By0Hr2d!Zhg=jdo0EdK_vAZjsk+Q44-|%kC%~cxJ!fM4@qwXWuFIaVUPhIZ5RewmKDL##6 z*(-fX6@RS2zUy1eN<2selDih@jmk#NKRf_~6 z>Nb7iy#e$ppK%}p(sLXFg}Sa!7dc(xuQ$(O5Bz!odOAo=eMz0#%|2nDCuU(i_%0CRuYTdG% zn3M-20(pfDBJ{)2Yr-tT{nl?fE^gZ77QMrl;+lC29D6c^a9=W^a&Q{J_%~4e1d2Og beg|Cosj&6*Yqe(rsIYb>!=7i5A_g7;=6J1C diff --git a/app/payment/__pycache__/__init__.cpython-312.pyc b/app/payment/__pycache__/__init__.cpython-312.pyc index 5ecee05a116e06a729e3baf7edef8df62b46329e..79309f6c6d423fc1b551b106f34756c016187227 100644 GIT binary patch delta 194 zcmaFKJcEVzG%qg~0}upAZphp=kynz@W1_k>UoKY^7b8PDLkim>#whNIISq`=6W@vl z$fT#1#1|x1=BDNW>7x9y%#_rk_@v7Cyu{qpTYN>S#UOd8xF%Z>Gtl5777)P-BG^C# zJBZ)_5`LOIlMNZ|nTj|j=Q4Vm2myr{fw))+NPJ*sWMurv#LUR@m4ks%=`I5h$=zoN TzsL~&iG`h;sgb=%0H_QA0MIZG delta 128 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 ZKMmW^RWRV)KLNUm-#GvP delta 1226 zcmZ9MU1$_n6vxk<$!@YAGqbb1Gy4^jWnI(_79vSMXp0tNv$$Z@MyqMv*qB-4#)P$Z zCury{F+w3~plR>RDiQNkgcNGomzGu#1@llSEm$b~&?iw^3LR|vVq54vcNrR;hu{65 zbN=Vtxx>tSR(Lw3ey1pY;MctKMfuz0m+J39=_{H26a0;R@9AZoY9G}?zro7yQ@+e>I$Ebax3;0SJf1DM>S-{ zQduyR(AAo1X=io-(25nR!Bhr!BEl2U-Tg#QkJN4rL^h>YHPgQ-@M?(T5u}s-rfJJdloCXB9FjUu)}UG3)D*hPdEz8WXX39&dZ~4jy&h_xSpkr zO^zOSik)t&Zh%t~AIa^OT31J5>49s{rI;1|gRMsny#5Y1)1!!Eh*8A5i1#>Jo)V=M z+LC9glR25biTPnfks}vjFGdI1g=inU9nB~-h7BEd>AaoO*yHGs?xm&b?6ggf@IqDN zV26x?^b=v%jpg`5E%tH!QoV8bM)7L#mU$=qK-;_6;Yh4t?w0tnVSY{qSSyx3{;AiI ziPHV|MiBJm%Bw$U+nxaN89zs&m-q6D&v;sWi4VUU3k_%B?kmRdAlyr5^B%aL2@LO& z@AnhrU?4Ba>x3XZdOjhq$4NfHuEigbMsp#ti|oV$(>)wH4;?@)o`CP{_}q!Jv^-I2 zb)mG&GbLJOza$Tl>#RR@k<7AYY9G1T{5MsQ&Evf9EpcFq?>oiUpB7oMH#T1v5`m8~ zCsC0QTBZ1D(Ewr$Q9ukK#t|nG`w$a|Q;129R%f|tl_$$KEo165N6tr8)VmRfIa=~8 zou+3X$m3~@Nr@g|Bz=&qHQ!EuP00H!n0YNdgQbp>mC7{z09)q}A0otV3(d*Qb}~Oi zJbuRu5!2}uvJ14Z;}g;kiEbw#WDpICkSZ8)M-wsx#+Js&C;J^;gkgxroQRN7(4&qa zqzR#TqoXmhy0vlas`{h0-HC~O9Q^9ygt|Pic=}S$p9zsGuvB&~@Dbi4er0r=-N>fM b82cvs0=dF|&F<7I5_x%f&tHHm#edF!i7gN< diff --git a/app/routers/__pycache__/__init__.cpython-312.pyc b/app/routers/__pycache__/__init__.cpython-312.pyc index fc737c6dc30568d265ac918a3efd0d69d4ee02db..be691f36703270d03af85686b2a20766b9332d6d 100644 GIT binary patch delta 22 ccmZ3?x|o&sG%qg~0}#xpF diff --git a/app/routers/__pycache__/_digital_base.cpython-312.pyc b/app/routers/__pycache__/_digital_base.cpython-312.pyc index 25846bc60463260ba6270b184c84d8f1bf87e723..3cacc7b495839d7ba68d0567809fd2f69a3c623d 100644 GIT binary patch delta 2328 zcmah~ZA?>F7{2$my|lEYSPJE1)Y4+0YSoDwg2-S2MIDMVM*z1{+6#80w4U2SU~b@0 zfryG8ow^?z%Uskk;|E)^E$oA2%Urf(7?Me@`?n8t%OVE-;rz4rJq09Y%T98i`+nVX z-uHdZIrl>Sm0daC8;$WC3{&)K;9CdB{esHe(9H@ngU51+Gr_Nu%>H7JenliwBuZL+s=4{#t$_#inQJ`9^>V#@uh6Tb zclk_SMNNWJrQ55b$3zR=B%~$l`uHZnAGhByJUhW_>RIn0VM&s%iTB4Z@M*xeCo`E& z3b`tAHqNR_xAX}xiD^M#Tr5zZI1DM^m#~f z5)189!)K=6`1JRo-dmRr-WeU3I&uEj&yLucyDbpDGur15GzTKymX$#Xnh{>fjQ#O; z7%qVrV0A?u@ixnfI_!2L`7&)hz(TMpFL)OwEeN|#9iq9aFAe0?9 zH1dViXUIuRM|~Z_VuT|4mch2807OL_=?F`npf^kk(Nl&teQPKZXzK8=CD)_*DKrCL za?Piy?2#jcq|$h!gSS$*aTT96@{;jmt@s3l-L9gxl+>)OxrxZr zC}G};=$G08K1mV6KB!?wic=4Jj3Z8jYvR!$MBEX7zK zP;EwFy(p$CYvl?Q75yIK3;Cf^0Kz5-V%RGP>{BceTJEQ>TOH;-Oi{E^S@JZ6NRSlJ ztJZAZPj6UDcYE-g1_Uhv>l^M{Mbp$0@-V z3E*`lC~C&w4h-!DP}G4|urQYNXnc%CW1B>9_s0$aIY7-$*VcG~9I$2vJ$`S*OYi`Y zIyN8ES!V3`h;SriXg3zma*(k$=l~nt*oPixiFg`}avuH8`A65J!7^zuAKl!)dGOVt zvU3&hR-CW@#4(Y*e!@^ZY0BtpJ-u<J3xwbyMzyY3aCj z>9m_mc0Igl%6lN&Vcs4~xtc%4m#tHuwt{rYSXL~KmT@3{T`U5UH4rr4{!CD~`TqaJ z`oD$axl`O;?q!Z%k4qZyEs+@;W?)0fK{m9x6&Z|e#>Qcofp`%b0Xow<>2`;OcI9f; z`#_Ic0PJ;DrM33k9}G-g{NlH>S8jduexaQl!cPVejsPf{V5n8<=+wA7!`_Y! zb#xs=_dI>%II91}9>Fa{4j`OBz^cee09TAbataM^BFqyQI}L2pA}5dYre8>0v#UrP zfgpL?$OV_k{~L2Pmm4Rt3nmPOG3Lt04UYd|F3tV$W>WqG89UiiQ10SK`EtAZb1O(= z7Gt?n9LoakT>Ljq5zr+P{6mszfG+qi;MMXr8t|O4#z3tGl;<}fT-~8)L?UDtn!6GD z5zZhCA*2GZZQ9V#5UaO@SizCBd*Cy3oSu^fIK&UTxqAt8WZA{8 zRL!!pHTOAq&8RrhIW2%Ry@nGFy>vpfYE0(Bu^N@3z!}sOt*!!!zYnHLXm^@zF Kc#k>RbpHUeS2*?n delta 2124 zcmZuxeQZ-z6o2>i_0esu+e+7V1-iEuSi7+W3Z135QitkLOd;z|r+0p1{<$~}VDi1$-Mwl8tUO{bnVRI-S{(`U^kweB| zY0um~(Q6u;aF7a_hEr+U6BHjVg;5W`4*sdDL=X-MhsYrVGl&a_$QFxkouOZ3S=qum z#nKYPVGk!r>Y-E087g^1&8Y_xQ(Xa-=iLjR4H9B{! z0#P%h`jTOPJdmWTab_dh3VIT$Xr#~2&#Xi9ax_DU)pRVCJ7OVPNUxTk4A4Ma^{XjL zZ7gbX5i1)pEhWnMWz(mI_4B~(b_;GXA7KFk51|Yt1p)V_D-bXjO%8>7qrtEyCWDaI znAKebO(9Shf*WBG`^D;#m!i0g8Eu{#%p(^)&u}fOJnCF+P>mooAk?!i+qN1MwSw1Z zFcE^}0Vv-jh90jV@TC|T&=6!l+g#=(S2ROf4f`Vr8mFsRv3(&4vXyq1-}hhoVgSr3Ig7K2LIF%xVtCF`1P{QqI9A7yu=h&HF2}&T}>rCY~Ba; z;7OlG2(WW5H$-yH)wY6YdB{sV98aj;r!W0}n`%f0*YTcY}NqZ0d5hV&R_y zr}lqr9d(>_Tzc+H%QyBb_HU~)ZS9l3J(J#!OlK_Pk7wek%uDI4AZ!qu#J>e0uUWiN zX#1(q^FyI$vam8^sLYlNCEoit3zywh71(&l(x@XR$)+{ZDH}+mrcL$o#K!d?epN36 z(q-WEu)Y?MXBGH*uE1l9jzNLVP~avH7C7x`9vB2a$_qS#QYS(H;6UktWT4NVdk~aF zPpnUYN-AiiT`-@7nq$?Ivqf5S04kUWW*De{*D^ndt zxI++jL-!`}sdyg^c%yhtAWb96*o5#fC(@}fr5$J)KsbzW3gH6;8-R+f+S!?F)dpzQ z2UYXTr@>~%NfB=3y+L8x$f}lHw3_u*XWFI(7^ZZB?9PfHWj*Y#B@SN+Ow~>cFyK@< zf|8X$&DID;^WZjWhwZ!;ZI0g`N(B9WegWwgFw(6E+u7FYeU*@yK=JUi0$)I_Am208 r!Bd=$h1bz@ph84~+RUz3SG%(^1Ze)yoy9=Sms)e<=k3#6=R5ohgO&aT diff --git a/app/routers/__pycache__/cnomadcna.cpython-312.pyc b/app/routers/__pycache__/cnomadcna.cpython-312.pyc index 6ca5955d62c85489d8fb9ed1114df2433067c5ae..e204276351d351569d94ee385018f47449529729 100644 GIT binary patch delta 159 zcmaFF^n*$LG%qg~0}zOmZpu6Yq#uJgFu)3BeAb$%E-RG6x`u5vGejtgxsqLzV`50E z<}J3|)YOvF0!`*yYz0NBX_*zbcnXU0vr>~w;`0)7Q*W^sXO^VKXQoWNn4!ZD)XfOQ n#Zo}x12ZEd<4p#`y9@>oIpr4Q9FV>c7I%d+zLBkn7bpz?(6=d( delta 137 zcmeyt^oU9QG%qg~0}!M?T%WlKNIwQ~V1Nb6_^dHeT~=TX>uM&5KooN&n6R diff --git a/app/routers/__pycache__/digital.cpython-312.pyc b/app/routers/__pycache__/digital.cpython-312.pyc index a5279a24be085bad840c195d136967951e1cc1ac..f9cd87415ded1f2413f98cd92ac4e23f673c5c10 100644 GIT binary patch delta 138 zcmcb?^o&XUG%qg~0}u$7Zpz#Xq#uJgFu)3BeAb$%F3Xm}vW9gv^F)s<1?F391x2Z8 znH9Hq3X1ZxQj<&K^AdAYZ?P9=mZZjKrc6AUro#u+%Lv59Qb6JZGb1D8O$Ob&3T%WlLNIwQ~V1Nb6_^dHeU6y4H>uRQnURmNyx7Z4bQqwXk zZt)ZpoIpr2)?WnxM>DkCu I#0``K0A#Qq#Q*>R diff --git a/app/routers/__pycache__/nomadvip.cpython-312.pyc b/app/routers/__pycache__/nomadvip.cpython-312.pyc index 808d15ecdc32bc67c006899e8277cad5635e2d32..b6fbc5fca36d33b6f327262ba136d4d7d3166082 100644 GIT binary patch literal 14976 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 K_{qKxp8r4A# APIRouter: +def create_digital_router(prefix: str, project_name: str, site_id: str | None = None) -> APIRouter: """ 创建 digital/cnomadcna/nomadlms 的支付路由 prefix: 如 "/digital", "/cnomadcna", "/nomadlms" 或 ""(legacy) project_name: 用于日志,如 "digital", "cnomadcna", "nomadlms", "legacy" + site_id: 站点标识,用于 site_vip(digital/meetup/vip) """ router = APIRouter(prefix=prefix, tags=[project_name]) @@ -166,7 +167,13 @@ def create_digital_router(prefix: str, project_name: str) -> APIRouter: parsed = provider.parse_notify(data) if parsed.get("trade_status") != "success": return "ok" - handle_payment_success(order_id, parsed.get("pay_price", data.get("pay_price", "")), f"{project_name}-xorpay", use_memos=False) + handle_payment_success( + order_id, + parsed.get("pay_price", data.get("pay_price", "")), + f"{project_name}-xorpay", + use_memos=False, + site_id=site_id, + ) return provider.success_response() @router.get("/zpay_notify") @@ -191,7 +198,13 @@ def create_digital_router(prefix: str, project_name: str) -> APIRouter: parsed = provider.parse_notify(data) if parsed.get("trade_status") != "success": return "success" - handle_payment_success(order_id, parsed.get("pay_price", data.get("money", "")), f"{project_name}-zpay", use_memos=False) + handle_payment_success( + order_id, + parsed.get("pay_price", data.get("money", "")), + f"{project_name}-zpay", + use_memos=False, + site_id=site_id, + ) return provider.success_response() return router diff --git a/app/routers/cnomadcna.py b/app/routers/cnomadcna.py index 5f8034d..f4bbbeb 100644 --- a/app/routers/cnomadcna.py +++ b/app/routers/cnomadcna.py @@ -1,4 +1,4 @@ """cnomadcna 项目支付接口:/cnomadcna/*""" from ._digital_base import create_digital_router -router = create_digital_router(prefix="/cnomadcna", project_name="cnomadcna") +router = create_digital_router(prefix="/cnomadcna", project_name="cnomadcna", site_id="meetup") diff --git a/app/routers/digital.py b/app/routers/digital.py index 2c71aa7..56de4c0 100644 --- a/app/routers/digital.py +++ b/app/routers/digital.py @@ -1,4 +1,4 @@ """digital 项目支付接口:/digital/*""" from ._digital_base import create_digital_router -router = create_digital_router(prefix="/digital", project_name="digital") +router = create_digital_router(prefix="/digital", project_name="digital", site_id="digital") diff --git a/app/routers/nomadvip.py b/app/routers/nomadvip.py index ba59f81..9b63300 100644 --- a/app/routers/nomadvip.py +++ b/app/routers/nomadvip.py @@ -183,7 +183,7 @@ async def nomadvip_xorpay_notify(request: Request): return "ok" pay_price = parsed.get("pay_price", data.get("pay_price", "")) try: - handle_payment_success(order_id, pay_price, "nomadvip-xorpay", use_memos=True) + handle_payment_success(order_id, pay_price, "nomadvip-xorpay", use_memos=True, site_id="vip") logging.info(f"nomadvip 业务处理完成: order_id={order_id}") except Exception as e: logging.error(f"nomadvip PocketBase/Memos 处理异常: {e}", exc_info=True) @@ -216,7 +216,7 @@ async def nomadvip_zpay_notify(request: Request): return "success" pay_price = parsed.get("pay_price") or data.get("money", "") try: - handle_payment_success(order_id, pay_price, "nomadvip-zpay", use_memos=True) + handle_payment_success(order_id, pay_price, "nomadvip-zpay", use_memos=True, site_id="vip") logging.info(f"nomadvip 业务处理完成: order_id={order_id}") except Exception as e: logging.error(f"nomadvip PocketBase/Memos 处理异常: {e}", exc_info=True) diff --git a/app/routers/wxh5.py b/app/routers/wxh5.py new file mode 100644 index 0000000..2ccf9a0 --- /dev/null +++ b/app/routers/wxh5.py @@ -0,0 +1,270 @@ +"""wxH5 项目支付接口:/wxh5/*,支持 xorpay/zpay,支付成功后同步 Supabase wxgzh_vip_users""" +import logging +import random +import string +import time +from datetime import datetime, timedelta + +from fastapi import APIRouter, Request, HTTPException +from fastapi.responses import RedirectResponse, JSONResponse + +from ..config import BASE_URL +from ..payment import get_payment_provider, get_payment_provider_by_name +from ..payment.zpay import ZPayProvider +from ..services import handle_payment_success, processed_orders + +router = APIRouter(prefix="/wxh5", tags=["wxh5"]) + +# 套餐金额(分)与有效期映射 +PLAN_MAP = { + "month": (2900, 30), # 月卡 29 元,30 天 + "year": (19900, 365), # 年卡 199 元,365 天 + "lifetime": (39900, 36500), # 终身 399 元,100 年 +} + + +def _infer_plan_from_amount(amount_cents: int) -> str: + """根据支付金额(分)推断套餐""" + if amount_cents >= 39900: + return "lifetime" + if amount_cents >= 19900: + return "year" + return "month" + + +def _get_notify_path(provider_name: str) -> str: + base = BASE_URL.rstrip("/") + return f"{base}/wxh5/xorpay_notify" if provider_name == "xorpay" else f"{base}/wxh5/zpay_notify" + + +def _sync_wxh5_vip_to_supabase(user_id: str, plan: str): + """支付成功后同步 VIP 到 Supabase wxgzh_vip_users""" + try: + from supabase import create_client + from ..config import SUPABASE_URL, SUPABASE_SERVICE_KEY + url = SUPABASE_URL + key = SUPABASE_SERVICE_KEY + if not url or not key: + logging.warning("wxh5: SUPABASE_URL/SUPABASE_SERVICE_KEY 未配置,跳过 Supabase 同步") + return + days = PLAN_MAP.get(plan, PLAN_MAP["year"])[1] + expire_at = (datetime.utcnow() + timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%S+00:00") + client = create_client(url, key) + # upsert: 存在则更新 expire_at,不存在则插入 + client.table("wxgzh_vip_users").upsert( + {"user_id": user_id, "expire_at": expire_at}, + on_conflict="user_id", + ).execute() + logging.info(f"wxh5 Supabase VIP 同步成功: user_id={user_id}, plan={plan}") + except Exception as e: + logging.error(f"wxh5 Supabase 同步失败: {e}", exc_info=True) + + +@router.post("/payh5") +async def wxh5_payh5(item: dict): + """wxH5 专用:创建支付订单,支持 per-request provider 覆盖""" + req_provider = (item.get("provider") or "").strip().lower() + provider = get_payment_provider_by_name(req_provider) if req_provider in ("xorpay", "zpay") else get_payment_provider() + plan = (item.get("plan") or "year").lower() + total_fee = PLAN_MAP.get(plan, PLAN_MAP["year"])[0] + total_fee = int(item.get("total_fee", total_fee)) + total_fee_yuan = f"{total_fee / 100:.2f}" + user_id = item.get("user_id", "unknown") + pay_type = (item.get("type") or "vip").lower() + random_suffix = "".join(random.choices(string.hexdigits.lower(), k=8)) + timestamp = int(time.time()) + order_id = f"{user_id}_{pay_type}_order_{timestamp}_{random_suffix}" + return_url = item.get("return_url", "") + if return_url and "order_id=" not in return_url: + sep = "&" if "?" in return_url else "?" + return_url = f"{return_url}{sep}order_id={order_id}" + type_map = {"book": "购买书籍", "meetup": "社区报名", "video": "视频解锁", "vip": "VIP会员充值"} + name = type_map.get(pay_type, "VIP会员充值") + notify_url = _get_notify_path(provider.name) + channel = item.get("channel", "alipay") + result = provider.create_order( + order_id=order_id, + name=name, + total_fee_yuan=total_fee_yuan, + pay_type=channel, + notify_url=notify_url, + return_url=return_url, + client_ip=item.get("client_ip"), + ) + if provider.name == "xorpay": + return { + "status": "ok", + "xorpay_params": result.get("params", {}), + "xorpay_url": result.get("pay_url", ""), + "provider": provider.name, + "order_id": order_id, + } + params = result.get("params", {}) + params_str = {k: str(v) for k, v in params.items() if v is not None and str(v).strip() != ""} + return { + "status": "ok", + "params": params_str, + "pay_url": result.get("pay_url", ""), + "provider": provider.name, + "submit_method": "POST", + "order_id": order_id, + } + + +@router.post("/payh5/redirect") +async def wxh5_payh5_redirect(item: dict, request: Request): + """wxH5 专用:ZPAY mapi 接口(xorpay 无需 redirect)""" + req_provider = (item.get("provider") or "").strip().lower() + provider = get_payment_provider_by_name("zpay") if req_provider == "zpay" else get_payment_provider() + if not isinstance(provider, ZPayProvider): + raise HTTPException(status_code=400, detail="payh5/redirect 仅支持 zpay") + plan = (item.get("plan") or "year").lower() + total_fee = PLAN_MAP.get(plan, PLAN_MAP["year"])[0] + try: + total_fee = int(item.get("total_fee", total_fee)) + except (TypeError, ValueError): + total_fee = PLAN_MAP["year"][0] + total_fee_yuan = f"{total_fee / 100:.2f}" + user_id = item.get("user_id", "unknown") + pay_type = (item.get("type") or "vip").lower() + channel = item.get("channel", "alipay") + device = item.get("device", "pc") + client_ip = item.get("client_ip") or (request.client.host if request.client else "127.0.0.1") + forwarded = request.headers.get("x-forwarded-for") + if forwarded: + client_ip = forwarded.split(",")[0].strip() + random_suffix = "".join(random.choices(string.hexdigits.lower(), k=8)) + order_id = f"{user_id}_{pay_type}_order_{int(time.time())}_{random_suffix}" + return_url_raw = item.get("return_url", "") + if return_url_raw and "order_id=" not in return_url_raw: + sep = "&" if "?" in return_url_raw else "?" + return_url = f"{return_url_raw}{sep}order_id={order_id}" + else: + return_url = return_url_raw + type_map = {"book": "购买书籍", "meetup": "社区报名", "video": "视频解锁", "vip": "VIP会员充值"} + name = type_map.get(pay_type, "VIP会员充值") + notify_url = f"{BASE_URL.rstrip('/')}/wxh5/zpay_notify" + result = provider.create_order_api( + order_id=order_id, + name=name, + total_fee_yuan=total_fee_yuan, + pay_type=channel, + notify_url=notify_url, + return_url=return_url, + client_ip=client_ip, + device=device, + ) + if result.get("status") != "ok": + err_msg = result.get("msg", "ZPAY mapi 创建订单失败") + raise HTTPException(status_code=500, detail=err_msg) + payurl = result.get("payurl") + payurl2 = result.get("payurl2") + img = result.get("img") + if device == "pc" and img: + return JSONResponse(status_code=200, content={ + "status": "ok", "qrcode_mode": True, "order_id": order_id, + "img": img, "payurl": payurl, "return_url": return_url, "channel": channel, + }) + if device == "wechat" and channel.lower() in ("wxpay", "wx", "wechat"): + return JSONResponse(status_code=200, content={"use_form": True, "order_id": order_id}) + headers = {"X-Pay-Order-Id": order_id} + prefer_json = str(item.get("prefer_json", "")).lower() in ("1", "true", "yes") + if prefer_json: + target = payurl2 if (device == "h5" and channel.lower() in ("wxpay", "wx", "wechat")) else payurl + if target: + return JSONResponse(status_code=200, content={ + "status": "ok", "redirect_url": target, "order_id": order_id, "provider": "zpay", + }) + if device == "h5" and channel.lower() in ("wxpay", "wx", "wechat") and payurl2: + return RedirectResponse(url=payurl2, status_code=302, headers=headers) + if payurl: + return RedirectResponse(url=payurl, status_code=302, headers=headers) + raise HTTPException(status_code=500, detail="ZPAY 未返回支付链接") + + +def _parse_wxh5_user_id(order_id: str) -> str: + """从 order_id 解析 user_id,格式: user_id_vip_order_ts""" + if "_order_" not in order_id: + return "" + prefix = order_id.split("_order_")[0] + if "_" in prefix: + return prefix.rsplit("_", 1)[0] + return prefix + + +def _handle_wxh5_payment_success(order_id: str, pay_price: str, provider_name: str): + """wxH5 支付成功:PocketBase + Supabase""" + user_id = _parse_wxh5_user_id(order_id) + if not user_id: + return + handle_payment_success(order_id, pay_price, provider_name, use_memos=False) + amount_cents = int(float(pay_price or 0) * 100) + plan = _infer_plan_from_amount(amount_cents) + _sync_wxh5_vip_to_supabase(user_id, plan) + + +@router.get("/order_status") +async def wxh5_order_status(order_id: str): + """wxH5 通用:查询订单支付状态""" + zpay = get_payment_provider_by_name("zpay") + if isinstance(zpay, ZPayProvider): + result = zpay.query_order_status(order_id, timeout=15) + if not result.get("error") and result.get("paid"): + return result + try: + from ..services.pb import get_pb_client + pb = get_pb_client() + records = pb.collection("payments").get_list(1, 1, {"filter": f'order_id = "{order_id}"'}) + return {"paid": len(records.items) > 0} + except Exception as e: + logging.warning(f"wxh5 PocketBase 查询失败: {e}") + return {"paid": False, "error": str(e)} + + +@router.post("/xorpay_notify") +async def wxh5_xorpay_notify(request: Request): + """wxH5 XorPay 异步通知""" + form_data = await request.form() + data = {k: v for k, v in form_data.items()} + order_id = data.get("order_id", "") + if order_id in processed_orders: + return "ok" + provider = get_payment_provider_by_name("xorpay") + if not provider.verify_notify(data): + raise HTTPException(status_code=400, detail="sign error") + parsed = provider.parse_notify(data) + if parsed.get("trade_status") != "success": + return "ok" + try: + _handle_wxh5_payment_success(order_id, parsed.get("pay_price", ""), "wxh5-xorpay") + except Exception as e: + logging.error(f"wxh5 业务处理异常: {e}", exc_info=True) + raise + return provider.success_response() + + +@router.get("/zpay_notify") +@router.post("/zpay_notify") +async def wxh5_zpay_notify(request: Request): + """wxH5 ZPAY 异步通知""" + provider = get_payment_provider_by_name("zpay") + if request.method == "GET": + data = dict(request.query_params) + else: + form_data = await request.form() + data = {k: v for k, v in form_data.items()} + order_id = data.get("out_trade_no", "") + if order_id in processed_orders: + return "success" + if not provider.verify_notify(data): + raise HTTPException(status_code=400, detail="sign error") + parsed = provider.parse_notify(data) + trade_status = parsed.get("trade_status") or data.get("trade_status", "") + if trade_status not in ("success", "TRADE_SUCCESS"): + return "success" + try: + _handle_wxh5_payment_success(order_id, parsed.get("pay_price") or data.get("money", ""), "wxh5-zpay") + except Exception as e: + logging.error(f"wxh5 业务处理异常: {e}", exc_info=True) + raise + return provider.success_response() diff --git a/app/services/__pycache__/payment.cpython-312.pyc b/app/services/__pycache__/payment.cpython-312.pyc index d7985b9acef8d089a6156d3636c2ef5768e14d7f..05d5e40351c435a78a4c02a98c90d05ac39e27a3 100644 GIT binary patch delta 2252 zcmZ`)e{2&~9DjG$Yp>VqueJNxZada?TNz{TqBM#|bNaHt!@p@yR%gZ_H- zH*gFzq8=a60MJfD(~x?v{t)gqPk(wC-;T$$JweXX$#r%IW165Z#Cd!$rVV%xc>=tz zjf?36e0Q%8a=fR@+sW}ch>g!hO&gKs#ZfQkAdeqBO^SY^aF7hE1u}>o!a~X_0C52y z1&|O()T#tEYDqzbTD1U1!8kpMrSLEysG;USIik@E1g{H`DL()Lxo8cQT$2|&INIsl|U!onIsEogc%9*GoIi&d(^L7lP&G6RTA8HFh=2pJW~$+QvH zqV!VK(}LD-R^~H3)IUa`1=?@P%qSK#pr0`*glU|}f@YXChEJxI3hM-&_?s#>SLe4X z0hu1^M25_FF$g$6<5etyfs_CQ3R=d9NnHGlEL&$y(qsz^5<{gVhJmh~2*IYrWqN^3 zBjkwZ$%=~H|BBecRw7STE@LFSTG9pnN6Cut6{^ zt6o7`KbxROSI9I@`mjmjxzs+wM!~q0c6=iOTuQYiS0|OuqXhu%un-+jO3tFBoW(Nh zA~~xO^pbK4`XmA?%jsCLbaaSS4#4japHw>r?aJ5*_>!<`!4)r3W->i25loA3dMkif z*mT5nqAu0AcjC^acj9lHiNAg>6^y3DW=*|11t1P-I7XH}rrO`#eSo)% ze{0U`sMkRSsC|`oS1EvzTv{cE3-N2UcI^=fa$(R_;^#^=B7xZ{T zJu8r3iDhR;FU-C-HG6D0{?-TVY8F*a9+k^lc0jjE0jp)*D!x=^maAkc2ltD}T#OVy zHX6Tt>HkPQl?(Y&+m3%cefPpSq|u#=C*$8t$HkL(E?%Df>b1Fvw~;Fh+7b-_5(tskHLs1mzbZQ`PxWF`})( zSdpRgtIb?{JJ%NS^>Uu{sqv^RgBZj0`+^~a>_K|QFfJ%pLHVe~Rdj{99u4HLL{N>c z)~jH?Kzy05w6sevbx0k&6g({Pf<%XJs*Z>s()+Lt;v@ReV8R64)l-c(%InTFUM-WT z{9knT1a8&0U{RYhVym37RZh`A+tv?L(c-d5an($5)s%T^W7KLttDD#|u}-RKkT&m@ z8d{{5JyKC?g2121dH`_NGR!2@z{*A}t{WB?T7G)l$hNc15$D<&=h~@PW}NFH&bk@r zU|rO?Dq33p7fC#oGY^RD+<6LQ+fO%-G)s<#t1VZXuDu|&`lUSwq!kBmSvzm1fTE6- zQAbTWR7<*0D2YOuWkYXTI}xxpKSJ4ODEs?+Zc(NG%xi#kb>V%0q0Rnk$~|oyF-j%d zrRR4@+jmOru83*(jA{471R>8qMA1yPXkymyYCsBqhi$5ADhJ=MsbAAr3$8h{(coIG zw#h|YC-RzD;<}AQp6e_rdt8KUmut5ofAz|(HsVJciEOtartx@Scbmtqtq6q0heFp)?a6OAUiBaN^yE{HRaK3hz@FYn%S@7#Otd{8FseLP9vK5jeF1F=i&&b0 z7KbETBo~B+1w%24Oll`R%Z4Ao>w?&zrbcN3>#|5{tB3d@w(|5TBkKqAzR8F@?1SheyG=s2#pqm;dFY!sw3yLmwGb|SgfnnLU_HO zG#M9RfXo=f-Im01`5Ooo)PcE?Kw>|3MQGT6kAME7UJ4K4)o`Rf6p!7&0VYQJX z{