# coding=utf-8 import base64 import datetime import hashlib import json import logging import random import time from binascii import hexlify from collections import Iterable from urlparse import urljoin import requests from Crypto.Cipher import DES3 from apilib.monetary import JDMerchantPermillage from library.jdpsi.constants import JdPsiErrorCode, ACCESS_KEY from library.jdpsi.exceptions import JdPsiException logger = logging.getLogger(__name__) class MyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, JDMerchantPermillage): return obj.to_jd_params() return super(MyEncoder, self).default(obj) class JdPsiMerchantClient(object): BASE_URL = "https://psi.jd.com" def __init__(self, accessKey=None): self.accessKey = accessKey or ACCESS_KEY def pad(self): pass @staticmethod def sign(*args): h = hashlib.md5() h.update("".join((str(_)for _ in args))) return h.hexdigest() @staticmethod def pad(rawJson): # 转换为字节数组 rawBytes = bytes.encode(rawJson, encoding="utf-8") rawBytesLen = len(rawBytes) # 计算补位 x = (rawBytesLen + 4) % 8 y = 0 if x == 0 else 8 - x # 将有效数据长度byte[]添加到原始byte数组的头部 resultBytes = bytearray(rawBytesLen + 4 + y) resultBytes[0] = (rawBytesLen >> 24) & 0xFF resultBytes[1] = (rawBytesLen >> 16) & 0xFF resultBytes[2] = (rawBytesLen >> 8) & 0xFF resultBytes[3] = rawBytesLen & 0xFF # 填充补位数据 for i in range(rawBytesLen): resultBytes[4 + i] = ord(rawBytes[i]) for i in range(y): resultBytes[rawBytesLen + 4 + i] = 0x00 return bytes(resultBytes) def encrypt(self, raw): plaintext = json.dumps(raw, sort_keys=True, separators=(',', ':'), cls=MyEncoder) key = base64.b64decode(self.accessKey) cipher = DES3.new(key, DES3.MODE_ECB) encrypt_bytes = cipher.encrypt(JdPsiMerchantClient.pad(plaintext)) return hexlify(encrypt_bytes) def _handle_result(self, res, **kwargs): try: result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False) except (TypeError, ValueError, json.JSONDecodeError): logger.debug(u'错误的解析结构', exc_info=True) result = res if "code" not in result: return result if result["code"] == JdPsiErrorCode.SUCCESS: return result if result["code"] in []: return self._request(kwargs["path"], kwargs["agentNo"], kwargs["entity"], kwargs["images"], kwargs["signField"]) raise JdPsiException( errCode='{}'.format(result["code"]), errMsg=result["message"], client=self, ) def _request(self, path, agentNo, entity, images=None, signField=None): # 添加 签名标签 if signField and isinstance(signField, Iterable): sign = JdPsiMerchantClient.sign(*(entity.get(_) for _ in signField)) entity.update({"sign": sign}) # 组织报文 data = { "agentNo": agentNo, "entity": self.encrypt(entity) } # 图片文件 if images and isinstance(images, dict): files = {_k: (_k, _v, "image/png") for _k, _v in images.items()} else: files = None url = urljoin(JdPsiMerchantClient.BASE_URL, path) try: res = requests.post(url=url, data=data, files=files) except requests.Timeout: raise JdPsiException( errCode='timeout', errMsg="timeout", client=self ) else: try: res.raise_for_status() except requests.RequestException as rre: raise JdPsiException( errCode='HTTP{}'.format(res.status_code), errMsg=rre.message, client=self, request=rre.request, response=rre.response ) return self._handle_result( res, path=path, agentNo=agentNo, entity=entity, images=images, signField=signField ) def create_customer( self, agentNo, blicUrla, lepUrla, lepUrlb, lepUrlc, img, enterimg, innerimg, cardPhoto, settleManPhotoFront, settleManPhotoBack, settleHoldingIDCard, companyType, serialNo, regEmail, regPhone, blicCardType, blicCompanyName, abMerchantName, indTwoCode, blicProvince, blicCity, blicAddress, blicLongTerm, blicValidityStart, blicValidityEnd, lepCardType, lepName, lepCardNo, lepLongTerm, lepValidityStart, lepValidityEnd, contactName, contactPhone, contactEmail, contactProvince, contactCity, contactAddress, ifPhyStore, storeProvince, storeCity, storeAddress, settleToCard, priatePublic, bankName, subBankCode, bankAccountNo, bankAccountName, settleCardPhone, settlementPeriod, directoryList, occUrla=None, blicUscc=None, blicScope=None, merchantNo=None ): images = { "blicUrla": blicUrla, "lepUrla": lepUrla, "lepUrlb": lepUrlb, "lepUrlc": lepUrlc, "img": img, "enterimg": enterimg, "innerimg": innerimg, "cardPhoto": cardPhoto, "settleManPhotoFront": settleManPhotoFront, "settleManPhotoBack": settleManPhotoBack, "settleHoldingIDCard": settleHoldingIDCard } occUrla and images.update({"occUrla": occUrla}) entity = { "companyType": companyType, "serialNo": serialNo, "agentNo": agentNo, "regEmail": regEmail, "regPhone": regPhone, "blicCardType": blicCardType, "blicCompanyName": blicCompanyName, "abMerchantName": abMerchantName, "indTwoCode": indTwoCode, "blicProvince": blicProvince, "blicCity": blicCity, "blicAddress": blicAddress, "blicLongTerm": blicLongTerm, "blicValidityStart": blicValidityStart, "blicValidityEnd": blicValidityEnd, "lepCardType": lepCardType, "lepName": lepName, "lepCardNo": lepCardNo, "lepLongTerm": lepLongTerm, "lepValidityStart": lepValidityStart, "lepValidityEnd": lepValidityEnd, "contactName": contactName, "contactPhone": contactPhone, "contactEmail": contactEmail, "contactProvince": contactProvince, "contactCity": contactCity, "contactAddress": contactAddress, "ifPhyStore": ifPhyStore, "storeProvince": storeProvince, "storeCity": storeCity, "storeAddress": storeAddress, "settleToCard": settleToCard, "priatePublic": priatePublic, "bankName": bankName, "subBankCode": subBankCode, "bankAccountNo": bankAccountNo, "bankAccountName": bankAccountName, "settleCardPhone": settleCardPhone, "settlementPeriod": settlementPeriod, "directoryList": directoryList } blicUscc and entity.update({"blicUscc": blicUscc}) blicScope and entity.update({"blicScope": blicScope}) merchantNo and entity.update({"merchantNo": merchantNo}) logger.info("[create_customer] agentNo={}, entity={}".format( agentNo, entity )) path = "/merchant/enterSingle" return self._request(path=path, agentNo=agentNo, entity=entity, images=images, signField=[ "serialNo", "lepCardNo", "bankAccountNo", "settleCardPhone" ]) def create_product(self, agentNo, serialNo, merchantNo, productId, payToolId, mfeeType, mfee=None, ladderList=None): """ 创建 结算 支付 产品 """ logger.info("[create_product] agentNo={}, serialNo={}, merchantNo={}, productId={}, payToolId={}, mfeeType={}, mfee={}, ladderList={}".format( agentNo, serialNo, merchantNo, productId, payToolId, mfeeType, mfee, ladderList )) entity = { "agentNo": agentNo, "serialNo": serialNo, "merchantNo": merchantNo, "productId": productId, "payToolId": payToolId, "mfeeType": mfeeType } if mfee: entity.update({"mfee": mfee}) if ladderList: entity.update({"ladderList": ladderList}) path = "/merchant/applySingle" return self._request(path=path, agentNo=agentNo, entity=entity, signField=[ "serialNo", "agentNo", "merchantNo", "productId", "payToolId" ]) def query_product(self, agentNo, serialNo, merchantNo): """ 查询产品信息 """ logger.info("[query_product] agentNo={}, serialNo={}, merchantNo={}".format( agentNo, serialNo, merchantNo )) entity = { "agentNo": agentNo, "serialNo": serialNo, "merchantNo": merchantNo } path = "/merchant/status/queryApplySingle" return self._request(path=path, agentNo=agentNo, entity=entity, signField=[ "serialNo", "merchantNo" ]) def query_secret_key(self, agentNo, serialNo, merchantNo): """ 查询产品秘钥 """ logger.info("[query_secret_key] agentNo={}, serialNo={}, merchantNo={}".format( agentNo, serialNo, merchantNo )) entity = { "serialNo": serialNo, "merchantNo": merchantNo, } path = "/merchant/status/queryMerchantKeys" return self._request(path=path, agentNo=agentNo, entity=entity, signField=[ "serialNo", "merchantNo" ]) def query_bill_file(self, agentNo, merchantNo, billDate=None): """ 查询账单 的下载地址 """ logger.info("[query_bill_file] billDate={}, merchantNo={}, agentNo={}".format(billDate, merchantNo, agentNo)) billDate = billDate or datetime.datetime.now().strftime("%Y%m%d") entity = { "serialNo": "{}{}".format(int(time.time() * 1000), random.randint(1000, 9999)), "agentNo": agentNo, "billDate": billDate, "merchantNo": merchantNo } path = "/agentmerchant/download" return self._request(path=path, agentNo=agentNo, entity=entity, signField=["billDate", "agentNo"]) def query_settle(self, agentNo, startTime, endTime, pageNum=1, pageSize=10, orderStatus="", merchantNo=None): """ 查询 商户的结算信息 """ logger.info("[query_settle] merchantNo={}, agentNo={}, startTime={}, endTime={}, orderStatus={}".format( merchantNo, agentNo, startTime, endTime, orderStatus )) entity = { "agentNo": agentNo, "merchantNo": merchantNo, "pageNum": pageNum, "pageSize": pageSize, "queryStartTime": startTime, "queryEndTime": endTime, "orderStatus": orderStatus, } path = "/merchant/status/queryMerchantsSettle" return self._request(path=path, agentNo=agentNo, entity=entity, signField=[ "agentNo", "merchantNo", "queryStartTime", "queryEndTime", "orderStatus", "pageNum", "pageSize" ]) def query_sub_channel(self, agentNo, merchantNo, productCode=None): logger.info("[query_sub_channel] merchantNo={}, agentNo={}, productCode={}".format( merchantNo, agentNo, productCode )) # 查询编号固定是401 productCode = productCode or "401" entity = { "agentNo": agentNo, "merchantNo": merchantNo, "productCode": str(productCode), } path = "/merchant/status/queryMerchantWXNo" return self._request(path=path, agentNo=agentNo, entity=entity, signField=[ "agentNo", "merchantNo", "productCode" ])