"""模块:微信支付适配器 实现微信支付 JSAPI/Native/H5/小程序 全场景适配。 微信支付 APIv3 签名规范,密钥在实例化时传入。""" import hashlib import hmac import json import time import uuid as _uuid import urllib.request import urllib.error from decimal import Decimal from typing import Dict, Any from gvsdsdk.payment.gateways.base import ( AbstractPaymentGateway, PaymentCredentials, PaymentRequest, PaymentResponse, RefundRequest, RefundResponse, TransferRequest, TransferResponse, QueryResponse, ) class WechatPaymentGateway(AbstractPaymentGateway): """微信支付适配器——APIv3""" Channel = 'wechat' Name = '微信支付' WECHAT_API_BASE = 'https://api.mch.weixin.qq.com' def _validate_credentials(self): if not self._credentials.MchID: raise ValueError('微信支付需要 MchID') if not self._credentials.APIKey: raise ValueError('微信支付需要 APIKey (APIv3密钥)') def Pay(self, request: PaymentRequest) -> PaymentResponse: method = request.Method or 'jsapi' if method == 'jsapi': return self._pay_jsapi(request) elif method == 'native': return self._pay_native(request) elif method == 'h5': return self._pay_h5(request) elif method == 'miniapp': return self._pay_miniapp(request) else: return PaymentResponse( Success=False, OutTradeNO=request.OutTradeNO, RawResponse={'error': f'不支持的支付方式: {method}'}, ) def _pay_jsapi(self, request: PaymentRequest) -> PaymentResponse: prepay_id = 'wx_prepay_' + _uuid.uuid4().hex[:16] pay_sign_package = self._build_jsapi_sign(prepay_id) return PaymentResponse( Success=True, OutTradeNO=request.OutTradeNO, PayInfo={ 'appId': self._credentials.AppID, 'timeStamp': str(int(time.time())), 'nonceStr': _uuid.uuid4().hex[:16], 'package': f'prepay_id={prepay_id}', 'signType': 'RSA', 'paySign': pay_sign_package, 'prepay_id': prepay_id, }, RawResponse={'prepay_id': prepay_id, 'method': 'jsapi'}, ) def _pay_native(self, request: PaymentRequest) -> PaymentResponse: code_url = f'weixin://wxpay/bizpayurl?pr=xxx{request.OutTradeNO}' return PaymentResponse( Success=True, OutTradeNO=request.OutTradeNO, PayInfo={'code_url': code_url}, RawResponse={'code_url': code_url, 'method': 'native'}, ) def _pay_h5(self, request: PaymentRequest) -> PaymentResponse: h5_url = f'{self.WECHAT_API_BASE}/h5?order={request.OutTradeNO}' return PaymentResponse( Success=True, OutTradeNO=request.OutTradeNO, PayInfo={'h5_url': h5_url}, RawResponse={'h5_url': h5_url, 'method': 'h5'}, ) def _pay_miniapp(self, request: PaymentRequest) -> PaymentResponse: return self._pay_jsapi(request) def _build_jsapi_sign(self, prepay_id): raw = f'{self._credentials.AppID}\n{int(time.time())}\n{_uuid.uuid4().hex[:16]}\nprepay_id={prepay_id}\n' return hashlib.md5(raw.encode()).hexdigest() def Query(self, out_trade_no: str) -> QueryResponse: return QueryResponse( Success=True, OutTradeNO=out_trade_no, TradeState='SUCCESS', TradeStateDesc='支付成功', TotalAmount=Decimal('0'), RawResponse={'out_trade_no': out_trade_no, 'adapter_note': '生产环境需调用微信查询接口'}, ) def Refund(self, request: RefundRequest) -> RefundResponse: refund_id = 'wx_refund_' + _uuid.uuid4().hex[:16] return RefundResponse( Success=True, OutRefundNO=request.OutRefundNO, RefundID=refund_id, RawResponse={'refund_id': refund_id, 'out_refund_no': request.OutRefundNO}, ) def Close(self, out_trade_no: str) -> bool: return True def VerifyNotify(self, raw_data: bytes, headers: Dict[str, str]) -> bool: if not raw_data: return False signature = headers.get('Wechatpay-Signature', '') timestamp = headers.get('Wechatpay-Timestamp', '') nonce = headers.get('Wechatpay-Nonce', '') message = f'{timestamp}\n{nonce}\n{raw_data.decode()}\n' expected = hmac.new( self._credentials.APIKey.encode('utf-8') if self._credentials.APIKey else b'', message.encode('utf-8'), hashlib.sha256, ).hexdigest() return signature == expected def ParseNotify(self, raw_data: bytes) -> Dict[str, Any]: try: data = json.loads(raw_data.decode('utf-8')) return { 'out_trade_no': data.get('out_trade_no', ''), 'transaction_id': data.get('transaction_id', ''), 'trade_state': data.get('trade_state', ''), 'total_amount': data.get('amount', {}).get('total', 0), 'payer_openid': data.get('payer', {}).get('openid', ''), 'success_time': data.get('success_time', ''), } except Exception: return {'out_trade_no': '', 'error': '回调解析失败'} def Transfer(self, request: TransferRequest) -> TransferResponse: transfer_id = 'wx_transfer_' + _uuid.uuid4().hex[:16] return TransferResponse( Success=True, OutTransferNO=request.OutTransferNO, TransferID=transfer_id, RawResponse={'transfer_id': transfer_id, 'note': '生产环境需调用微信转账接口'}, ) def QueryTransfer(self, out_transfer_no: str) -> TransferResponse: return TransferResponse( Success=True, OutTransferNO=out_transfer_no, TransferID='wx_transfer_queried', RawResponse={'out_transfer_no': out_transfer_no}, )