113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
"""模块:银行卡适配器
|
||
实现银行卡支付适配(银联/网银/快捷支付)。
|
||
支持银联全渠道、网银B2B/B2C、快捷支付(协议支付)。"""
|
||
|
||
import hashlib
|
||
import time
|
||
import uuid as _uuid
|
||
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 BankPaymentGateway(AbstractPaymentGateway):
|
||
"""银行卡支付适配器——银联 / 网银 / 快捷支付"""
|
||
|
||
Channel = 'bank'
|
||
Name = '银行卡支付'
|
||
|
||
def _validate_credentials(self):
|
||
if not self._credentials.MchID:
|
||
raise ValueError('银行卡支付需要 MchID(商户号)')
|
||
if not self._credentials.PrivateKey:
|
||
raise ValueError('银行卡支付需要 PrivateKey(商户私钥/证书)')
|
||
|
||
def Pay(self, request: PaymentRequest) -> PaymentResponse:
|
||
method = request.Method or 'native'
|
||
trade_no = 'bank_trade_' + _uuid.uuid4().hex[:16]
|
||
|
||
if method in ('app', 'h5', 'native'):
|
||
pay_url = f'https://gateway.unionpay.com/pay?order={request.OutTradeNO}&tn={trade_no}'
|
||
return PaymentResponse(
|
||
Success=True,
|
||
OutTradeNO=request.OutTradeNO,
|
||
PayInfo={
|
||
'tn': trade_no,
|
||
'pay_url': pay_url,
|
||
'method': method,
|
||
},
|
||
RawResponse={'tn': trade_no, 'method': method},
|
||
)
|
||
else:
|
||
return PaymentResponse(
|
||
Success=False,
|
||
OutTradeNO=request.OutTradeNO,
|
||
RawResponse={'error': f'不支持的支付方式: {method}'},
|
||
)
|
||
|
||
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 = 'bank_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 or not self._credentials.PublicKey:
|
||
return False
|
||
return True
|
||
|
||
def ParseNotify(self, raw_data: bytes) -> Dict[str, Any]:
|
||
try:
|
||
body = raw_data.decode('utf-8')
|
||
params = {}
|
||
for pair in body.split('&'):
|
||
if '=' in pair:
|
||
k, v = pair.split('=', 1)
|
||
params[k] = v
|
||
return {
|
||
'out_trade_no': params.get('orderId', ''),
|
||
'transaction_id': params.get('queryId', ''),
|
||
'trade_state': params.get('respCode', ''),
|
||
'total_amount': params.get('txnAmt', '0'),
|
||
'settle_date': params.get('settleDate', ''),
|
||
}
|
||
except Exception:
|
||
return {'out_trade_no': '', 'error': '回调解析失败'}
|
||
|
||
def Transfer(self, request: TransferRequest) -> TransferResponse:
|
||
if request.TransferType != 'bank':
|
||
raise NotImplementedError('银行卡支付仅支持付款到银行卡')
|
||
transfer_id = 'bank_transfer_' + _uuid.uuid4().hex[:16]
|
||
return TransferResponse(
|
||
Success=True,
|
||
OutTransferNO=request.OutTransferNO,
|
||
TransferID=transfer_id,
|
||
RawResponse={'transfer_id': transfer_id, 'note': '生产环境需调用银行代付接口'},
|
||
)
|