From 12d39527a74e31264359a2173f9b63502bc9484f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 3 Nov 2026 21:44:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?= =?UTF-8?q?H5/Native=20=E9=80=9A=E9=81=93=20+=20payment=5Flog=20+=20?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=9B=9E=E8=B0=83=20dspy=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=97=A7=20weixin=5Fpay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 16 + models/payment_log.xlsx | Bin 0 -> 18678 bytes pf_pay/init.py | 2 + pf_pay/payment_log.py | 31 ++ pf_pay/wechat_pay_h5.py | 357 ++++++++++++++++++ .../{weixin_pay.py => weixin_native_pay.py} | 0 wwwroot/alipay/recharge_callback/index.dspy | 19 + wwwroot/wxpay/recharge_callback/index.dspy | 16 + 8 files changed, 441 insertions(+) create mode 100644 .gitignore create mode 100644 models/payment_log.xlsx create mode 100644 pf_pay/payment_log.py create mode 100644 pf_pay/wechat_pay_h5.py rename pf_pay/{weixin_pay.py => weixin_native_pay.py} (100%) create mode 100644 wwwroot/alipay/recharge_callback/index.dspy create mode 100644 wwwroot/wxpay/recharge_callback/index.dspy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c340881 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# build/test artifacts +.DS_Store +__pycache__/ +*.pyc +*.pyo +build/ +*.egg-info/ +*.dist-info/ +*.whl +logs/ +*.log +.idea/ +~$* +models/mysql.ddl.sql +*.pid +init/ diff --git a/models/payment_log.xlsx b/models/payment_log.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a844e4998eebe4aa301ddb7313d4ca6677761ba7 GIT binary patch literal 18678 zcmeIaWl&vP);5X;cXxMp4Fq?03j_=9?ry=|3GVJ5+}$052X}{WCw;ol>GbKU`}0=4 zy^7ju{7)@M}?O}usI*V}$rfxJDV~-R(a_ZVbj`o2V z#I}x@Cs#O*I61Lp`2czH)hzBnrj-5S&D#mB)qkpnIu0C+b=){7$w>r+-FbP@lze>y zpNE53KvA`HT4S&mT0T6ekix$)0YaK{S)Ux5*qerI4_p~qX}{}oxHnJ|i9e|LnmVhP zYS63Eoy`6-6s=>?JYrARd+FJ>yq!-&l?$W>1|oKi>uOp?g08x$+DQJxfpgx{+*Zs? zTqa2qG>NiP5c1eCKqbSo5IvDwXd#?TsCK!ZeUQ^qyo?g;48eN3U3j-=8#!lbR%#!^ z-1jDs!h?21z}*zl_UV)IpKU&|J6R>3qMK@_s^`=x_Iuf%okyi9^lP0B5;}S*G!cn) zlHu;tZ8e!d4H!vrR%mwHzR2PBzQ;0b!}l`G9nAy{tD^>vS}Vvo;M*4jpRDX%^Kx_W z*;sDnbOCDz+m9*K_SsXp$ER|17^p=AV?Jk5W@p3MB zxF**n7t!4bIZ*`4FWq>iRkd5{rKt%5Db7wb;#j=fm)N4@sPu*GVO$2yr2zi`7zeMadAiQ)RxId@Fe?+wP)CODAC*J~dCR%C{5l|%6X zA_Mgf2nZVp64=Fp;U7}tY;9+*Z*6V<(+2)if`9>r5U}t6?X4|d#>$@&G4L+I4PwWW zDLSA|MVehD-(uw&2ptv^++xSpyU9~bStbm%8E0oVeD{to;=Og_+mF@*P}&Ajn8bdl zFCUT$%p6G;S1uk-Ger*aDhxlfLb8L@c-p$xy8FsdYiI+kFK@6=9#zzma<5F6WIb67Z6Wk% zCCkS-JT0UwAvO7jWo2X9Flb72-hH!~SmN9e|Fk4U@s`1^6E@q3`hZFZetiK0XKTOw zI5jlh#2obE79g`Ww#Qy?{ZE4H0eYU;{#VJkv`eu^xVNejXBn!l?~@uSNap!#k)l;g z6M+2gWZ4@V$3z#@s3w-SRa@SbmoU^#rVPi#%xI1wSEwWBNMVMdt;jVEM$52SxxkkO zs5eBNJ2LFMLxB#yP^w8MDp!Y6qKR96e(W4B+08Tr5w_s1B7jSR9fuc(T=h-t2iBRr zQ#KMMxkB8sX1fpO=tI!9v`OGobMS|WscP$oc+4%-e}e&^R)+xcpV8`Rm>2*CdNQ0W zKK>{HnrU6oh()k$EVCw{kty%Var$ z2N%j;^X~NkgOjA45WP-ei9SD-TNr-l%q7_Xa98T`m__@xBK&nHERXlB^Kxy{z0e=^d=Cc~9Srna(ZkhFE>Yx^(Q~@KtxR|tKa+u`g`OQrVAFIM z+PC;%odlHA!A;@76l%ec+$XoV8W8a@R@0^FnXTX$(cH99ef6>Y`r#;bSro4h?;vR~ zEx?2VVTngnee6-aF+3A5-B7`
  • 8lw^q?YQ4biMio@7yTZNB7W3)4<~ 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 P3J?q+-Z&@pbM*fJURKKy literal 0 HcmV?d00001 diff --git a/pf_pay/init.py b/pf_pay/init.py index 94deee2..3c1e310 100644 --- a/pf_pay/init.py +++ b/pf_pay/init.py @@ -1,9 +1,11 @@ from ahserver.serverenv import ServerEnv from pf_pay.ali_pay import Zhifubao_Pay +from wechat_pay_h5 import WechatPayClient def load_pf_pay(): g = ServerEnv() zfb = Zhifubao_Pay() g.alipay_payment = zfb.alipay_payment g.alipay_callback_verify = zfb.alipay_callback_verify + g.WechatPayClient = WechatPayClient diff --git a/pf_pay/payment_log.py b/pf_pay/payment_log.py new file mode 100644 index 0000000..2df7fd1 --- /dev/null +++ b/pf_pay/payment_log.py @@ -0,0 +1,31 @@ +from sqlor.dbpools import DBPools +from appPublic.timeUtils import timestampstr + +async def new_payment_log(request, params_kw=None): + db = DBPools() + env = request._run_ns + if not params_kw: + params_kw = env.params_kw + dbname = env.get_module_dbname('pf_pay') + userid = await env.get_user() + userorgid = await env.get_userorgid() + async with db.sqlorContext(dbname) as sor: + ts = timestampstr() + ns = { + "id": ts + getID(), + "notify_url": env.entire_url(f'/pf_pay/{params_kw.payment_channel}/callback.dspy'), + "customerid": userorgid, + "userid": userid, + "payment_channel": params_kw.payment_channel, + "payment_name": params_kw.payment_name, + "amount_total": params_kw.amount_total, + "payer_client_ip": request['client_ip'], + "currency": params_kw.currency, + "payment_status": "0", + "init_timestamp": ts + } + await sor.C('payment_log', ns.copy()) + return ns + return None + + diff --git a/pf_pay/wechat_pay_h5.py b/pf_pay/wechat_pay_h5.py new file mode 100644 index 0000000..a8250c5 --- /dev/null +++ b/pf_pay/wechat_pay_h5.py @@ -0,0 +1,357 @@ +# wechat_h5.py +import time +import json +import secrets +import base64 +from urllib.parse import urlparse +from typing import Optional, Dict, Any + +import aiohttp +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +from appPublic.jsonConfig import getConfig +from .payment_log import new_payment_log + + +class WechatError(Exception): + pass + + +def _now_ts() -> str: + return str(int(time.time())) + + +def _nonce_str(n: int = 32) -> str: + return secrets.token_hex(n // 2) + + +def _canonical_url_path(url: str) -> str: + p = urlparse(url) + path = p.path or "/" + if p.query: + return path + "?" + p.query + return path + + +def _build_sign_message(method: str, canonical_url: str, timestamp: str, nonce: str, body: str) -> bytes: + # method \n canonical_url \n timestamp \n nonce \n body \n + s = "\n".join([method.upper(), canonical_url, timestamp, nonce, body, ""]) + return s.encode("utf-8") + +def load_public_key_from_file(fpath, password=None): + with open(fpath, 'b') as f: + b = read() + return serialization.load_pem_public_key(b, password=password) + +def load_private_key_from_file(fpath, password=None): + with open(fpath, 'b') as f: + b = read() + return serialization.load_pem_private_key(b, password=password) + +def load_private_key(pem_bytes: bytes): + return serialization.load_pem_private_key(pem_bytes, password=None) + + +def sign_with_rsa_sha256(private_key, message: bytes) -> str: + sig = private_key.sign(message, padding.PKCS1v15(), hashes.SHA256()) + return base64.b64encode(sig).decode() + + +def verify_rsa_sha256(pubkey, message: bytes, signature_b64: str) -> bool: + sig = base64.b64decode(signature_b64) + try: + pubkey.verify(sig, message, padding.PKCS1v15(), hashes.SHA256()) + return True + except Exception: + return False + + +def aes_gcm_decrypt(api_v3_key: bytes, associated_data: str, nonce: str, ciphertext_b64: str) -> bytes: + aesgcm = AESGCM(api_v3_key) + ct = base64.b64decode(ciphertext_b64) + return aesgcm.decrypt(nonce.encode(), ct, associated_data.encode() if associated_data else None) + + +class WechatPayClient: + """ + Async Wechat Pay v3 client for H5 (and basic refund/query). + - mchid: merchant id + - serial_no: merchant certificate serial number (string) + - private_key_pem: merchant private key PEM bytes + - platform_public_pem: WeChat platform certificate PEM bytes (for notif verify) + - api_v3_key: 32-byte key (bytes) for AES-GCM decrypt + config:{ + "pay":{ + "wxpay":{ + "public_key_file":, + "private_key_file":, + "mchid": + "apikey": + "serial_no": + } + } + } + """ + + BASE = "https://api.mch.weixin.qq.com" + + def __init__( + + self, + api_v3_key: bytes, + session: Optional[aiohttp.ClientSession] = None, + timeout: int = 15, + ): + config = getConfig() + payinfo = config.pay.wxpay + self.mchid = payinfo.mchid + self.serial_no = payinfo.serial_no + self._private_key = load_private_key_from_file(payinfo.private_key_file) + self._platform_pub = load_public_key_from_file(payinfo.public_key_file) + api_v3_key = payinfo.apikey + if len(api_v3_key) not in (16, 24, 32): + raise ValueError("api_v3_key must be 16/24/32 bytes (32 recommended)") + self.api_v3_key = api_v3_key + self._session = session or aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=timeout)) + + async def close(self): + await self._session.close() + + def _build_authorization(self, method: str, path_or_url: str, body: Any): + if isinstance(body, (dict, list)): + body_str = json.dumps(body, separators=(",", ":"), ensure_ascii=False) + else: + body_str = body or "" + ts = _now_ts() + nonce = _nonce_str() + canonical = _canonical_url_path(path_or_url) + msg = _build_sign_message(method, canonical, ts, nonce, body_str) + signature = sign_with_rsa_sha256(self._private_key, msg) + auth = ( + f'WECHATPAY2-SHA256-RSA2048 mchid="{self.mchid}",' + f'serial_no="{self.serial_no}",nonce_str="{nonce}",timestamp="{ts}",signature="{signature}"' + ) + return auth, body_str + + async def _request(self, method: str, path: str, json_body: Optional[dict] = None, extra_headers: Optional[dict] = None): + url = self.BASE + path + auth_header, body_str = self._build_authorization(method, url, json_body or "") + headers = { + "Authorization": auth_header, + "Content-Type": "application/json", + "Accept": "application/json", + } + if extra_headers: + headers.update(extra_headers) + async with self._session.request(method.upper(), url, json=json_body, headers=headers) as resp: + text = await resp.text() + try: + data = await resp.json(content_type=None) + except Exception: + data = text + if resp.status >= 400: + raise WechatError(f"{resp.status} {resp.reason}: {text}") + return data + + # ----------------- Business APIs ----------------- + + async def create_h5_payment( + self, + request, + recharge, + action) -> str: + """ + Create H5 payment. Returns the API response which includes 'h5_url'. + - out_trade_no: merchant order id (unique) + - description: order title + - amount_total: total in fen (int) + - payer_client_ip: user's client IP + - notify_url: merchant notify URL + - scene_info: optional dict for H5 info; if None a minimal scene_info will be set + - attach: optional dict for merchant extra data (will be JSON string) + """ + path = "/v3/pay/transactions/h5" + env = request._run_ns + callback_url= env.entire_url('/pf_pay/wxpay/recharge_callback') + body = { + "mchid": self.mchid, + "out_trade_no": recharge.id, + "description": "RECHARGE:" + str(recharge.recharge_amt), + "notify_url": callback_url, + "amount": { + "total": recharge.recharge_amt, + "currency": recharge.currency or "CNY" + }, + # attach must be a string per API; we JSON-encode merchant extras + "attach": json.dumps(attach) if attach else "", + } + # scene_info: ensure payer_client_ip included + if scene_info is None: + scene_info = { + "payer_client_ip": request['client_ip'], + "h5_info": {"type": "Wap"} + } + else: + scene_info.setdefault("payer_client_ip", request['client_ip']) + body["scene_info"] = scene_info + dic = await self._request("POST", path, json_body=body) + return dic.get('h5_url') or dic.get('mweb_url') + + async def create_native_payment(self, out_trade_no: str, description: str, amount_total: int, notify_url: str, currency: str = "CNY") -> dict: + """ + Create Native payment (QR code). Returns API response which includes 'code_url'. + """ + path = "/v3/pay/transactions/native" + body = { + "mchid": self.mchid, + "out_trade_no": out_trade_no, + "description": description, + "notify_url": notify_url, + "amount": {"total": amount_total, "currency": currency}, + } + return await self._request("POST", path, json_body=body) + + async def refund(self, out_refund_no: str, out_trade_no: str, reason: str, refund_amount: int, total_amount: int) -> dict: + """ + Refund (domestic). refund_amount & total_amount in fen. + Path: /v3/refund/domestic/refunds + """ + path = "/v3/refund/domestic/refunds" + body = { + "out_refund_no": out_refund_no, + "out_trade_no": out_trade_no, + "reason": reason, + "amount": {"refund": refund_amount, "total": total_amount, "currency": "CNY"}, + } + return await self._request("POST", path, json_body=body) + + async def query_order_by_out_trade_no(self, out_trade_no: str, sp_mchid: Optional[str] = None) -> dict: + p = f"/v3/pay/transactions/out-trade-no/{out_trade_no}" + if sp_mchid: + p += f"?sp_mchid={sp_mchid}" + return await self._request("GET", p) + + async def query_refund(self, out_refund_no: str) -> dict: + p = f"/v3/refund/domestic/refunds/out-refund-no/{out_refund_no}" + return await self._request("GET", p) + + # ----------------- Notification utils ----------------- + + def verify_notification_signature(self, headers: dict, body: str) -> bool: + """ + Verify notification signature using platform public key provided at constructor. + Required headers: + - Wechatpay-Timestamp + - Wechatpay-Nonce + - Wechatpay-Signature + - Wechatpay-Serial (optional to check which platform cert used) + """ + timestamp = headers.get("Wechatpay-Timestamp") or headers.get("wechatpay-timestamp") + nonce = headers.get("Wechatpay-Nonce") or headers.get("wechatpay-nonce") + signature = headers.get("Wechatpay-Signature") or headers.get("wechatpay-signature") + if not (timestamp and nonce and signature): + return False + message = "\n".join([timestamp, nonce, body, ""]).encode("utf-8") + return verify_rsa_sha256(self._platform_pub, message, signature) + + def decrypt_notification_resource(self, resource: dict) -> dict: + """ + resource example: + {"algorithm":"AEAD_AES_256_GCM","ciphertext":"...","nonce":"...","associated_data":"..."} + """ + alg = resource.get("algorithm") + if alg != "AEAD_AES_256_GCM": + raise WechatError("Unsupported algorithm: " + str(alg)) + pt = aes_gcm_decrypt(self.api_v3_key, resource.get("associated_data", ""), resource.get("nonce"), resource.get("ciphertext")) + try: + return json.loads(pt.decode()) + except Exception: + return {"raw": pt} + + + async def pay_notice_verify(self, request: web.Request): + body = await request.text() + headers = dict(request.headers) + # verify signature + ok = self.verify_notification_signature(headers, body) + if not ok: + return web.Response(status=401, text="invalid signature") + resource = await request.params_kw.resource + try: + dec = self.decrypt_notification_resource(resource) + except Exception as e: + return web.Response(status=400, text="decrypt failed") + return dec + trade_state = dec.get("trade_state") or dec.get("status") + rechargeid = dec.get("out_trade_no") + attach_raw = dec.get("attach") + merchant_extra = None + if attach_raw: + try: + merchant_extra = json.loads(attach_raw) + except Exception: + merchant_extra = attach_raw + # TODO: implement idempotent order update here, e.g. mark order paid if trade_state == "SUCCESS" + + print("payment notify dec:", dec) + # respond success + return web.json_response({"code": "SUCCESS", "message": "OK"}) + +# 3) refund notify +async def wx_refund_notify(request: web.Request): + body = await request.text() + headers = dict(request.headers) + ok = client.verify_notification_signature(headers, body) + if not ok: + return web.Response(status=401, text="invalid signature") + payload = await request.json() + resource = payload.get("resource", {}) + try: + dec = client.decrypt_notification_resource(resource) + except Exception: + return web.Response(status=400, text="decrypt failed") + # dec contains refund info + print("refund notify dec:", dec) + # TODO: update refund status idempotently + return web.json_response({"code": "SUCCESS", "message": "OK"}) + + +# 4) create native (QR) example (returns code_url) +async def create_native(request: web.Request): + try: + data = await request.json() + out_trade_no = data["out_trade_no"] + description = data["description"] + amount = int(data["amount"]) + notify_url = data["notify_url"] + resp = await client.create_native_payment(out_trade_no, description, amount, notify_url) + return web.json_response(resp) + except Exception as e: + return json_error(str(e)) + + +# 5) refund request +async def refund(request: web.Request): + try: + data = await request.json() + out_refund_no = data["out_refund_no"] + out_trade_no = data["out_trade_no"] + reason = data.get("reason", "refund") + refund_amount = int(data["refund_amount"]) + total_amount = int(data["total_amount"]) + resp = await client.refund(out_refund_no, out_trade_no, reason, refund_amount, total_amount) + return web.json_response(resp) + except Exception as e: + return json_error(str(e)) + + +# 6) query order +async def query_order(request: web.Request): + out_trade_no = request.match_info.get("out_trade_no") + try: + resp = await client.query_order_by_out_trade_no(out_trade_no) + return web.json_response(resp) + except Exception as e: + return json_error(str(e)) diff --git a/pf_pay/weixin_pay.py b/pf_pay/weixin_native_pay.py similarity index 100% rename from pf_pay/weixin_pay.py rename to pf_pay/weixin_native_pay.py diff --git a/wwwroot/alipay/recharge_callback/index.dspy b/wwwroot/alipay/recharge_callback/index.dspy new file mode 100644 index 0000000..559ac79 --- /dev/null +++ b/wwwroot/alipay/recharge_callback/index.dspy @@ -0,0 +1,19 @@ +debug(f'/api/callback/alipay/index.dspy:{params_kw=}') +d = {k:v for k,v in params_kw.items() if k not in ['sign_type', 'sign' ] } +sign_type = params_kw.sign_type +sign = params_kw.sign +try: + check = await alipay_callback_verify(d, sign) + if not check: + debug(f'{d=}, {sign=}, {check=}') + raise Exception('sign check error') + d = DictObject(**d) + pbar = PlatformBizAccRecharge(d.out_trade_no) + db = DBPools() + async with db.sqlorContext('sage') as sor: + await change_recharge_status(sor, d.out_trade_no, '1', d.trade_no) + await pbar.accounting(sor) + return "充值成功" +except Exception as e: + debug(f'{e=}') + return f"程序出错({e}),请联系客服,充值日志id=" + d.out_trade_no + '支付宝交易id=' + d.trade_no diff --git a/wwwroot/wxpay/recharge_callback/index.dspy b/wwwroot/wxpay/recharge_callback/index.dspy new file mode 100644 index 0000000..13a0134 --- /dev/null +++ b/wwwroot/wxpay/recharge_callback/index.dspy @@ -0,0 +1,16 @@ +debug(f'/pf_pay/wxpay/recharge_callback/index.dspy:{params_kw=}') +client = WechatPayClient() +try: + check = await client.pay_notice_verify(request) + if not check: + raise Exception('WX PAY check error') + d = DictObject(**check) + pbar = PlatformBizAccRecharge(d.out_trade_no) + db = DBPools() + async with db.sqlorContext('sage') as sor: + # await change_recharge_status(sor, d.out_trade_no, '1', d.trade_no) + await change_recharge_status(sor, d.out_trade_no, '1', None) # 需确认wxpay的交易号在哪里 + await pbar.accounting(sor) + return {"code": "SUCCESS", "message": "OK"} +except Exception as e: + raise Exception(f'{e}\n{format_exc()}')