Files
Django/gvsdsdk/payment/gateways/wechat.py

167 lines
6.1 KiB
Python

"""模块:微信支付适配器
实现微信支付 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},
)