From d888d9231f4950b005db51e1b0b2437d5abe4b2a Mon Sep 17 00:00:00 2001 From: db_huang <18006480240@163.com> Date: Mon, 2 Nov 2026 09:48:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=94=AF=E4=BB=98=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=90=8E=E6=94=AF=E4=BB=98=E7=8A=B6=E6=80=81=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env.example | 1 + .../alipay_provider.cpython-313.pyc | Bin 24407 -> 27336 bytes .../app/__pycache__/config.cpython-313.pyc | Bin 8069 -> 8194 bytes backend/app/__pycache__/main.cpython-313.pyc | Bin 359789 -> 366949 bytes backend/app/alipay_provider.py | 62 ++++++++ backend/app/config.py | 1 + backend/app/main.py | 142 +++++++++++++++++- ...ipay_callback.cpython-313-pytest-9.1.1.pyc | Bin 7162 -> 10683 bytes ...ipay_provider.cpython-313-pytest-9.1.1.pyc | Bin 33096 -> 35375 bytes backend/tests/test_alipay_callback.py | 25 ++- backend/tests/test_alipay_provider.py | 26 ++++ 11 files changed, 251 insertions(+), 6 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index fa424f2..0603085 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -54,3 +54,4 @@ ALIPAY_TRANSFER_API=alipay.fund.trans.uni.transfer ALIPAY_TRANSFER_QUERY_API=alipay.fund.trans.common.query ALIPAY_TRANSFER_SCENE_NAME=佣金报酬 ALIPAY_HTTP_TIMEOUT_SECONDS=30 +ALIPAY_RECONCILE_INTERVAL_SECONDS=30 diff --git a/backend/app/__pycache__/alipay_provider.cpython-313.pyc b/backend/app/__pycache__/alipay_provider.cpython-313.pyc index e16d0d203da1d73684c87d921c4615a2b5053a59..9856ee09e258cbf05e89bee59a1df989d89d82c4 100644 GIT binary patch delta 4695 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 delta 1992 zcmYk6X>1c$5Xax!^{yS`1QJ6|k~j%YVjwApL%2f1(U2fYfyLp7RmX8QiIZT`w@xV_ zLDV98NR>eIsa0E`2Z!i~g4I$LDDcR`f1kPkg()`52A{q5|3=FPl$ z&-+e%!!LZq({87xIvHNoM}F#>+PEz(o1flR_B{7(dw0VoSJ17c;++z7`&BJ7mvz}h zu9k&Lb})TN&9T%xEjO2m(OModUmG1PKyRTYSJ)Zz+q8Tu#xta+qf%fgMMKK)hmnPr zUaWbrM$w{RiB^n83EpGy#*}`S=Cwk`SRtg?v{Fm)YGuK~pc|(uDcBE2Q=xLpSK1-_ z#wIePG^x5_?(|^5(1j74-L^U!GvXo~oTnSz@io1{$eK3&>EK#D))wxHhJ&r)NH^NS zaBpug;kE57O#D6mFz2tld8pFnFxAyz(X9(OHp$C(C4Qdd;kKDFW8m_SmtrzZ_ZLV3 z8CLnr>9N_rkRIp!UcLqH`>VJODyt@Q1zM{nZ&I7b(GJiRP^$QlCHG_|EykK3d0+)> z5B#g0%@ZH0>O(apn-#M_O2Fxtv|-r0Lvsaq70!#Um_1;RIUe^lXC>b;a}LhTUwU?7n0Omus0fe8)8F4O+$8Qsn2RyC7B)^x=ci!n zv}v=&W2pMyeatSm*g{XoiAF-*`a1ZvW_*sBHl#;7 z!@|(xrhUkOf>|}2wo|elgjEPrjjV~aM?1uPQW^-2gni_5MS7xoEFOyVgbm|=lOBdn zT$SWiS7$AaX~!^%7Dv$g0p3Oc|0;w1?1p4hBK(r0;gVy)O%*Q8+MS`K6x?8lGo{n+ z(DOzbKy4SF0squ`b2hu`89UCL^?CAfr>j0wKAx$d9g*N{$;iZ#x+019L4(h$EZfV4 z3)atBYB!bEb#dK*vvYdYQ&{>UOU$2piz_DIAS9SLZ)9TGqa(R=k3m=ca@Dl8M`LYJ zI5r!;s}HFcalmz!sGHx-%a5TgK1cX$f^;#OkcTkk4n4kCvKCF8g!2uR>Hx}rv4p+x z8F|Jrw5@^SI9Xp1J|=vEs24|(4^mzxnNkV|;d}UO;RNLhno?qL;ams2>>DjF6GvfQ z;2r4)2Dbu>)Zfr+7D22j6M{`H>38g-X_9o6!Rt*m_G^>{1Ftk~mVRN-xxA4dfdk7M z)w|?41$>1&ZGaLex)5uGxWb@##e|82DETsQAO`(N_=)f{;THnc6u%R$60R|Lc17i| z6J%S7Pr~69waO^mUrIQEx74XQ^lmj(IzTEdoJb>aIDxt{ zokm}4$cTsIeTK+|{+7Hd+7zENnKp~!tSTeOauck!tkm=gCO3H`oM|akJm_vo+-V7M zz6X{x<|J0P_DE&e-teb%5p7==S{sdbhJ+C|ZAP>sCf=h7*p_OuC)JDnaI>wqx`YZ? zGOt;bJS${zj>^11*n=?W7{FsDdB~i{7qb+0w!2}U?&dGTQGGca^m)}{%5)iu+ZU9* zPeWHCe2SGpkk3q^DPY?|7p)YKfS20Kl?rq{llZW`l=Cw9wIiQ@3u>o_AA+*ZNX=A= zt0q{BOka>0gjRJL9WjJe$JjguJN9JA+AATjz00hAR(9Fl?;BG4x4pF^jYMYa}z6WDJJG* z79>{27o{fW=Ot(6q{e6Fm82GxCFV>HmT+U7yLpyG2&?J^7WtbR+AEwkGH-C&?zzeH zf?eW;Nk5*?#gDSp#1qwi6~IVMc5x4JE=X*iP|^0Ljx_5@B|1 zr`1G&R_98&?tc^rC7uz`r5g>V9R3cn}?YyA~kSr1b`bCqkC}MJB7XoBhqcv$N^Rk0JceOT>t<8 delta 288 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

Ak)Ea`}n(R4h}bUPN?+yw;AzPYJQK7 zt$Xv0j%;(j4nvBHN4@|FGWcKm-al%2yl@8m^K7B&9bw0P- zPN;&orZIq*pCMXZN7+rjG46R~V^69ye>{|n^nlyvq>7775s^ll680Qf8|2!zk@nux zP#fDL=E{Kk>}hq{fk(xXvuY$P9tY2=0qqYVL3z`%#E#v@Bavj!ed-Gge4FI52`dR_ z39`0Cc+2q*_(0^s1cp`OT$6*;eU*PT;wzTR|@Fdq`Q$ePDF0FwBZ zX@h>wR@KunJd%+HhTbyO;RdKR_7LLfYl>I-qZL=q*q4k;b4` zv~&;=1wkYni9U{8KV$!XL3M#}33aHK?u%6W-Li{nKpQyothuUgPnQL%JEKa7MKQgB z(2DQ0XRfN5Fp!sCQ-ghBiFVmFm7CPMP;7@gd64hOoI8(-`nBesL_7WjS}qoZ&Xsfh$X8@NaSB`l<>3$VX^37qJFL6i@_}MY|ac~ zM7tR;#PqU@1&}$6p9?%|sz@9MK?P^ghlJL&M@g5ih`|WRn5G)%EaOaFlosa(4!e#I z3nk2RxLtHzW%syQhM^WDaWe|h>)KP*Nb^*UPp_+h9*3vz?fYN#kY6@Ulw&FLWMQ_v zWrdb9vCLI5E%pNR=hIs%qOC}X?yFii*GE18Knt_GqN;mqA_nO=nbP;p=ddR|nNq7=ehC+!sAO-l&n1ZX&gaCRPh;gE6?acxDa!kgMn7Hu4aoMJ1X-mScAp%!Ulf zL@YKLDRz^ARb1Vu64s%;*y!Fa(xeJIJ3%M;r=WBb6Wvt_x|@^0KPC#%Fx_}x1?>KS zF7Qvo^I3`ZNo$D@3 z(i5E&Q6Q_B57##Im7lQaS8ZdmE{YRsA?_C1q7>cNKMS!JCb|n#^j-yT$6o1rp(os7 zphOwN8+yW#rXerX2it8ubw&y=kgo7<$})8zb%0IsvoPCdgwAshb=M1gSU!jL)WdUs z$rhBgN|fYK7n43E(oEBp6hgX(eh}pT=$`7S$2&RljWygnj8W=cg#E7wVYs&H8>{X3 zJe{K#A>~nbd7jRX@GJq3Fx~g<{{Ffk`WVB<2^;M7{<{B!cNvy~tz$@*lSK?YMcz#! z+(SN`z_W75E@C?n5le^(#DOVPs0CKE<SH_t5xs zHqX25L<}joj14zHSHhT40k>_a%g_*ymFT1*X@N*jzFfmX(d-h8xe=Q};&ZI1*kr!X zX8b+=VgizRAi-vj(%t=!B4ytsTQ*8>OKppJ42>*pPe8Z^KVx$i+SpQEHH6x@5lf_~ z>6U-tg8-d_*=%JFzvVLuufZ7P4^wP!m=!Tj%Ix-1U`%DH}Vec_eDwB zcj5)z7&|+2?BFqaN-E77-Yu$aNLRe>L=?Ghca71L>%ZXBzY#>6Z;?ZA68N_hK@M}tWvDknZVK+l&~1dF1UU+2K0`kxEGMi4 z)SE{cl*5X*FeF}lyphU$NH|aUoDc8@+`QQqH=YpDj>|#9Z)3hbj#}avHqsb&>5wZzA2)zjR67D1LlreKZ zVG4mKVwvfLYJ#}2&Sywm*A_FxqkzmZ!fJwe05>qi9ZT3Y!}k9V245y{2ZiAV0`uR5 ziv;nD4dc8NL7<(4;=?iMAhMFCCjVU9nZlb+woF3%Fxy#1sE8+35Hur#z z;_J}pWAKYH?%FcFwykr?4NTQ*BZoGo<~wnlW?l-z&(VRU-f!82M)^Of;xd05r#rVf zNn7hKhvDbSA}7kpxY9n-Y4=sK`)sv--pTCh6gis)HHPu`rHH@DFEbr<38&l_IgNL@ KRdaM?!v6vN$Q7#q delta 43220 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 Khe>B;wnK~?DaEi~#GH}RVn38C9>XjX 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 diff --git a/backend/app/alipay_provider.py b/backend/app/alipay_provider.py index fddede3..b3fa2d8 100644 --- a/backend/app/alipay_provider.py +++ b/backend/app/alipay_provider.py @@ -151,6 +151,58 @@ def _same_rsa_key(private_key: str, public_key: str) -> bool: return (private_public.n, private_public.e) == (public.n, public.e) +def _signed_response_content(response_value, public_key: str, charset: str = "utf-8") -> str: + """Verify a certificate-mode response and return its API response object. + + Old alipay-sdk-python releases include the top-level ``alipay_cert_sn`` + field in the bytes they verify. Alipay signs only the inner ``*_response`` + JSON object, so otherwise a valid code=10000 response is rejected. + """ + from alipay.aop.api.util.SignatureUtils import verify_with_rsa + + response_text = response_value.decode(charset) if isinstance(response_value, bytes) else str(response_value) + marker = re.search(r'"[^"\\]+_response"\s*:\s*', response_text) + if not marker: + raise AlipayTransferError("支付宝返回内容缺少业务响应对象", ambiguous=True) + start = response_text.find("{", marker.end()) + if start < 0: + raise AlipayTransferError("支付宝返回业务响应格式无效", ambiguous=True) + depth = 0 + quoted = False + escaped = False + end = -1 + for index in range(start, len(response_text)): + char = response_text[index] + if quoted: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == '"': + quoted = False + continue + if char == '"': + quoted = True + elif char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + end = index + 1 + break + if end < 0: + raise AlipayTransferError("支付宝返回业务响应不完整", ambiguous=True) + try: + envelope = json.loads(response_text) + except (TypeError, ValueError) as exc: + raise AlipayTransferError("支付宝返回内容无法解析", ambiguous=True) from exc + signature = str(envelope.get("sign") or "") + response_content = response_text[start:end] + if not signature or not verify_with_rsa(public_key, response_content.encode(charset), signature): + raise AlipayTransferError("支付宝响应验签失败", "ALIPAY_RESPONSE_UNVERIFIED", ambiguous=True) + return response_content + + def _read_key(path_value: str, label: str) -> str: if not path_value: raise AlipayTransferError(f"未配置{label}文件路径") @@ -206,6 +258,16 @@ def _client(): common["alipay_root_cert_sn"] = alipay_root_cert_sn return common + def _DefaultAlipayClient__parse_response(self, response_value): + response_text = response_value.decode(settings.alipay_charset) if isinstance(response_value, bytes) else str(response_value) + if '"alipay_cert_sn"' in response_text: + return _signed_response_content( + response_text, + config.alipay_public_key, + settings.alipay_charset, + ) + return super()._DefaultAlipayClient__parse_response(response_value) + client_class = CertificateAlipayClient else: config.alipay_public_key = _sdk_key(_read_key(settings.alipay_public_key_path, "支付宝公钥"), private=False) diff --git a/backend/app/config.py b/backend/app/config.py index 3c037e6..f9f6240 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -79,6 +79,7 @@ class Settings: alipay_transfer_query_api = os.getenv("ALIPAY_TRANSFER_QUERY_API", "alipay.fund.trans.common.query") alipay_transfer_scene_name = os.getenv("ALIPAY_TRANSFER_SCENE_NAME", "").strip() alipay_http_timeout_seconds = int(os.getenv("ALIPAY_HTTP_TIMEOUT_SECONDS", "30")) + alipay_reconcile_interval_seconds = max(10, int(os.getenv("ALIPAY_RECONCILE_INTERVAL_SECONDS", "30"))) settings = Settings() diff --git a/backend/app/main.py b/backend/app/main.py index 412fc76..ab6caa4 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -12,6 +12,7 @@ import logging import random import re import secrets +import threading import time import urllib.parse import urllib.request @@ -481,6 +482,8 @@ def startup(): connection.execute(text("CREATE UNIQUE INDEX ix_admin_users_answer_public_token ON admin_users (answer_public_token)")) finally: db.close() + if settings.alipay_enabled: + _start_alipay_reconcile_worker() def _migrate_alipay_transfer_attempts(bind): @@ -3578,6 +3581,139 @@ def _snapshot_refund(db, record, kind, user, payer_account="", alipay_trade_no=" )) +def _complete_alipay_refund_order(db: Session, order: AlipayRefundOrder, trade_no: str) -> bool: + """Atomically persist a verified provider success and advance the task.""" + trade_no = str(trade_no or "").strip() + if not trade_no: + return False + if order.target_kind == "ask_batch": + target = db.get(AskAssignmentBatch, order.target_id) + elif order.target_kind == "ask_assignment": + target = db.get(AskAssignment, order.target_id) + elif order.target_kind == "answer_submission": + target = db.get(AnswerSubmission, order.target_id) + else: + return False + if not target: + return False + existing = db.scalar(select(RefundRecord.id).where( + RefundRecord.target_kind == order.target_kind, + RefundRecord.target_id == order.target_id, + )) + if not existing: + db.add(RefundRecord( + target_kind=order.target_kind, + target_id=order.target_id, + owner_id=order.owner_id, + shop_name=order.shop_name or "", + order_no=order.order_no or "", + payee_name=order.payee_name or "", + payee_account=order.account or "", + amount_fen=order.amount_fen, + operator_name=order.operator_name or "系统自动对账", + payer_account=order.payer_account or "", + alipay_trade_no=trade_no, + )) + if order.target_kind == "ask_batch": + target.status = "paid" + target.updated_at = datetime.utcnow() + _sync_ask_task_status(target.assignment.task) + elif order.target_kind == "ask_assignment": + target.status = "已完成" + target.updated_at = datetime.utcnow() + _sync_ask_task_status(target.task) + elif order.target_kind == "answer_submission": + target.status = "已完成" + order.status = "success" + order.alipay_trade_no = trade_no + order.error_code = "" + order.error_message = "" + return True + + +def _reconcile_pending_alipay_orders_once() -> int: + """Query pending/legacy uncertain transfers and finalize verified results.""" + if not settings.alipay_enabled: + return 0 + from .alipay_provider import query + + with SessionLocal() as lookup_db: + order_ids = lookup_db.scalars( + select(AlipayRefundOrder.id).where(or_( + AlipayRefundOrder.status == "pending", + (AlipayRefundOrder.status == "failed") & (AlipayRefundOrder.error_code == "10000"), + )).order_by(AlipayRefundOrder.id).limit(100) + ).all() + completed = 0 + for order_id in order_ids: + with SessionLocal() as db: + order = db.scalar( + select(AlipayRefundOrder) + .where(AlipayRefundOrder.id == order_id) + .with_for_update() + ) + if not order or (order.status != "pending" and order.error_code != "10000"): + continue + try: + result = query(out_biz_no=order.out_biz_no) + except AlipayTransferError as exc: + order.status = "pending" + order.error_code = exc.code[:128] + order.error_message = str(exc)[:500] + db.commit() + logging.getLogger(__name__).warning( + "Alipay automatic reconciliation deferred: out_biz_no=%s code=%s message=%s", + order.out_biz_no, exc.code, exc, + ) + continue + provider_status = str(result.get("status") or "").upper() + if provider_status == "SUCCESS": + trade_no = str(result.get("trade_no") or "").strip() + if trade_no and _complete_alipay_refund_order(db, order, trade_no): + db.commit() + completed += 1 + logging.getLogger(__name__).info( + "Alipay automatic reconciliation completed: out_biz_no=%s trade_no=%s", + order.out_biz_no, trade_no, + ) + else: + order.error_message = "支付宝确认成功,但暂未返回资金流水号" + db.commit() + elif provider_status in {"FAIL", "REFUND"}: + order.status = "failed" + order.error_code = "" + order.error_message = str(result.get("fail_reason") or "支付宝确认转账失败")[:500] + db.commit() + else: + order.status = "pending" + order.error_message = f"支付宝转账状态:{provider_status or '处理中'}" + db.commit() + return completed + + +def _alipay_reconcile_loop() -> None: + interval = settings.alipay_reconcile_interval_seconds + # Give application startup and database migrations time to finish. + time.sleep(min(5, interval)) + while True: + try: + _reconcile_pending_alipay_orders_once() + except Exception: + logging.getLogger(__name__).exception("Alipay automatic reconciliation failed") + time.sleep(interval) + + +def _start_alipay_reconcile_worker() -> None: + if getattr(app.state, "alipay_reconcile_started", False): + return + app.state.alipay_reconcile_started = True + threading.Thread( + target=_alipay_reconcile_loop, + name="alipay-reconcile", + daemon=True, + ).start() + + def _refund_record_query(user, q, start_date, end_date): query = select(RefundRecord) if user.role_code != "super_admin": @@ -3821,10 +3957,8 @@ async def alipay_callback(request: Request, db: Session = Depends(get_db)): trade_no = str(params.get("pay_fund_order_id") or params.get("order_id") or "").strip() if not trade_no: return Response(content="failure", media_type="text/plain", status_code=400) - order.status = "success" - order.alipay_trade_no = trade_no - order.error_code = "" - order.error_message = "" + if not _complete_alipay_refund_order(db, order, trade_no): + return Response(content="failure", media_type="text/plain", status_code=400) elif provider_status in {"FAIL", "REFUND"}: order.status = "failed" order.error_code = str(params.get("error_code") or params.get("sub_order_error_code") or "")[:128] diff --git a/backend/tests/__pycache__/test_alipay_callback.cpython-313-pytest-9.1.1.pyc b/backend/tests/__pycache__/test_alipay_callback.cpython-313-pytest-9.1.1.pyc index 279cb963f7ac1aeadbd1c8e38579a2b16805bda4..4f4d813155f911f8abdc0a9cbe77dacc175f25e6 100644 GIT binary patch literal 10683 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 GJpK#9n7By* delta 2009 zcmdT_TWl0n7@o_{&Skr|?xouJX8-P+62OSG+|Y}wLcRAA4pHm-=jF z2=Y)~NbpT^UI+=%2Q`rwFpUWa5lBpIjFM_Wo8iG%ABIEjE(njZHSCCiXNaqM(3mur z+_~9Xl}1kFqEX$*gS@>q)Huu;Vp>e@`lqEKAz$HqS{|8Vn8KybnnCU}7sF)g9Fy!K zyCeP`$e&gQSrnLlXOJ7R)glEm-F2_YUN9rKWUu4lr!guEkoO$HO|t%)O!gdPQE*e7 ziXtG@3%6B6dfak5YU<^ox`qRVP;=T5V=G)kW~jc7OeZ<*Z6(Vth5Xd$j!S#k;>R@m zXX^TgZ<>9}qq6_^t%bre+(flkXxdE*W(vr+usVI z{+BLb1>%haw)R1@P~UFa4C>Y`|DT&dtTux{-nUnh_B`J1w?2_c*P7t>#yvd$;+Nz# zpGIEwJ;iQcJnMUjZSrOf^F$sWHOn*ex@qbKLubCaFiOq@x*e8S%o{n~cq1y2ZvrXF zvYVxBsce$z;D|j$|0Ap}o(~SPTvQ|XL#o}f&1ZFk{1Mu#dE;|>F?%wZKUOw!2XQWs zEnYWD7N_SdKAX#t3(X&~Dv7uF`=d_FF<&s=%AYJ|OS8u@IJWGwb9x@^b2FB$Z00eJ zK|BqhgM8kS5KK6N$?@=nBTWgJPBXxhtKl=l{?HHpuJ8O^tK0f+4BRMRKXLQawNpz+ zz8!mIWi+!I-gzamJeFJycVA81oV+%!^xF-=l#jo?W1kDfgfC)~8s)zBOuAE? zu(DmE%t}l_WAe%f(04f{<&p0;h(P<4RGTSi;WV>+ zD2+Xo!f=yV{7g3YwmrGOXFQ&Wau{k*sUB{ll>RRDh~Zld2S4rrfP)#siH&Hn4g(|s5I~B82`Ou}fnjT{-csHy9nH>>Vn?TNgi0cd7dsBKY=ZpSIlzvSNVLDD ox}+Dk)&>Q(t5U+h0GQ5rP5=M^ diff --git a/backend/tests/__pycache__/test_alipay_provider.cpython-313-pytest-9.1.1.pyc b/backend/tests/__pycache__/test_alipay_provider.cpython-313-pytest-9.1.1.pyc index 72718e50825e8b4aea616a4aa808c48fe5f9bde1..b6a2933bed8ced3607b338fa5753b192a66396cb 100644 GIT binary patch delta 2350 zcmcgsZERCj7{2Fzv|aDoZgk)4${pJ>F0gK_+mP`&jBEp~=v~c_gi_kut)si`&g}*p zADfyG0zViYh#!9#!52=jc z%wX{Uy~h~0nvYR6RJYk{t^*P%qPk29kZ6h7NNoTLEl^$xB{$|6ytKr60V=9nf-l;L6}v&2i8E=ICQR>`)UQ5XcAg4N4NS@krjgrXD{Ep-a$&Q`u8nFM`wmKUcmfP+8ps(e!Y8mA9HYFTjgdel*1R;9Xd-nmBJ0tNfv6If zmAICnhC390ycf!CZ~=CUtj7Cdz1t(3#z1Tf*=gRY+~m_ z{vEQfPln-8zxHBWR^x-dfDGf|P&nX^%RVTpv8bZXP{Ok6iz<eEq`+cEaCFqO7 zAg(qX959ltRjJBACOJ4cSk>o`^vWWmRr(|089Mr+v}CPjoiUIApQ>mq30N=E8d#!% zMv;bjWC^n>$NbPAk3x08oIdv|a_$$=*%rs3YQ>zt=g~VNg2x6W8W+Nf2nO;h7RLIQ zimH&d2t=(DRlRGXMYE^lUH)jy<&TA3z4%Pv$7GM&2$1Il3kZ;*p%wVj`_em8p!aR& zs=ytD6d({pC-&MMJd6Ee*$JCkcn5g3NRF-qfGH5I|+_6%*MI&8PHe*)pP|N*!l+8J5Z5XpQoZj_`W8CT) zYW|M59d10>INUMLJ4ff&CVBTLyXN#R9g_T-Np|KNW7mxG?zB2FM;<5aS=Ow8&4Vf4 zG0H9*X?b^ZvV0kqM$4Ba`DK&r%r}NDyd$kn%#p`+<}7Vi!1fexAKsYcg;BP4#G^x! zubpIPzA?6Tlo!(K1Y27%tBe!+ENxc69GHVX;tCVY)5kgRM&e{kIpA*LCLB&&Z}nNh zS7=H5r{G)kOZzZ4h0P<0*EVm;0GH5bN-27<{T*-_y&b3qkI<1-1vb-qJQY0tZW-Rk z)Jte8kPDt5WAHM#g02VO2iMRpxyQlc?p`BpCU$T0N4--u4cw2=%v_=G?KPIQw9zt1|K zpr4sSbUu2>s1X|tkSDgyX2eAN=-GHbeiuAI7h-$BZPeJacmt0;9?=9#2-`_uJ^_J1 z2KGTSfq6tNC6I|#z5evzh5F`8uu(&&ddkvgVF2h>PZezi$ONOT4LcF&%tx=oyv%H@ z=x-d_0s1C?QK6Wrr_dNY5;aEcuGJVh5Fp3-5SHr4S%lo`3a%0dex6X(s|GNYSUIo~ zfV-%FciZ9`lCTt4CIVQEi%p0(XKT7RE R6GIZ058QX_P~|J7e*n-5m4N^N delta 715 zcmYjPZAep57{2e_u5*&hYU;ElPNSGZ3neJ}0)NzxNbJMtS8fdn+U&Zs!nPT#63Yr| zy+PEU{sg%~VqEZ#3MLB%8U3hG7L|!EDk5!a79qXwT|}3A9^Ui3&vVXm&b^Os$*Bvn zC0kfnAkot@Jah81BWAI|SZw2S;|XwD6U%ZVz6qMO$$j?}IHyGqtOsS3v}d&V!Bz{T z(Cm2vWBAN-LrD|eqq&ban<0(~-&1&qo&HTQg%A8yYu{2cP9tk#m9!v(xlUNA78#uO zC!rtX?T;XVEvEt-z7n0F5zJ{P>rK@r&q-LWRzn7Nsg=qn z>h+^nt(O-i?Y8<7Op9z;k}we1N}6Mqa!dwxE6c?8Y5C%g0z;J5VHcCEd4@S4{}2}+|2Z-kJ$v6Y!t*Z@x@ms=Jq_%|pX@dxQ(uQDrecI6G_41v) zia7q>Sy9(^rHIxa)SA0ec`&Y(UEBdMfk(OzyUK{yV>v UU02XRCB0J6j|07j&~nA`2MW8?o&W#< diff --git a/backend/tests/test_alipay_callback.py b/backend/tests/test_alipay_callback.py index 126b4a3..6db0e1a 100644 --- a/backend/tests/test_alipay_callback.py +++ b/backend/tests/test_alipay_callback.py @@ -12,14 +12,29 @@ def test_alipay_callback_reconciles_verified_success(monkeypatch): with TestClient(main.app) as client: with main.SessionLocal() as db: user = db.scalar(main.select(main.AdminUser).where(main.AdminUser.username == "operator")) + answer_user = main.AnswerUser( + public_id=f"U{uuid4().hex[:20]}", phone=f"1{uuid4().int % 10**10:010d}", + auth_token=uuid4().hex, + ) + db.add(answer_user) + db.flush() + submission = main.AnswerSubmission( + submission_no=f"ANS{uuid4().hex[:20]}", user_id=answer_user.id, + owner_id=user.id, shop_name="回调测试店", status="已通过", + ) + db.add(submission) + db.flush() order = main.AlipayRefundOrder( - target_kind="answer_submission", target_id=int(uuid4().int % 1_000_000_000), + target_kind="answer_submission", target_id=submission.id, out_biz_no=out_biz_no, amount_fen=300, account="buyer@example.com", - payee_name="测试收款人", created_by_id=user.id, status="pending", + payee_name="测试收款人", created_by_id=user.id, owner_id=user.id, + order_no=submission.submission_no, shop_name=submission.shop_name, + operator_name=user.display_name, payer_account="公司账户", status="pending", ) db.add(order) db.commit() order_id = order.id + submission_id = submission.id response = client.post("/alipay_callback", data={ "sign": "valid", "out_biz_no": out_biz_no, "status": "SUCCESS", "order_id": "ALI-CALLBACK-001", @@ -30,6 +45,12 @@ def test_alipay_callback_reconciles_verified_success(monkeypatch): order = db.get(main.AlipayRefundOrder, order_id) assert order.status == "success" assert order.alipay_trade_no == "ALI-CALLBACK-001" + assert db.get(main.AnswerSubmission, submission_id).status == "已完成" + refund = db.scalar(main.select(main.RefundRecord).where( + main.RefundRecord.target_kind == "answer_submission", + main.RefundRecord.target_id == submission_id, + )) + assert refund.alipay_trade_no == "ALI-CALLBACK-001" def test_alipay_callback_rejects_invalid_signature(monkeypatch): diff --git a/backend/tests/test_alipay_provider.py b/backend/tests/test_alipay_provider.py index 21b847e..6aebb0a 100644 --- a/backend/tests/test_alipay_provider.py +++ b/backend/tests/test_alipay_provider.py @@ -114,6 +114,32 @@ def test_transfer_keeps_unverified_success_envelope_ambiguous(monkeypatch): assert "勿重复发起" in str(error.value) +def test_certificate_response_verifies_only_inner_response_object(monkeypatch): + captured = {} + + def fake_verify(_public_key, content, signature): + captured["content"] = content + captured["signature"] = signature + return True + + from alipay.aop.api.util import SignatureUtils + monkeypatch.setattr(SignatureUtils, "verify_with_rsa", fake_verify) + envelope = json.dumps({ + "alipay_fund_trans_common_query_response": { + "code": "10000", "msg": "Success", "status": "SUCCESS", + "pay_fund_order_id": "ALI006", + }, + "alipay_cert_sn": "cert-sn", + "sign": "signed-value", + }, ensure_ascii=False, separators=(",", ":")) + + content = alipay_provider._signed_response_content(envelope, "public-key") + + assert json.loads(content)["pay_fund_order_id"] == "ALI006" + assert b"alipay_cert_sn" not in captured["content"] + assert captured["signature"] == "signed-value" + + def test_query_parses_processing_status(monkeypatch): client = CapturingClient({ "code": "10000",