client.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. # coding=utf-8
  2. import base64
  3. import datetime
  4. import hashlib
  5. import json
  6. import logging
  7. import random
  8. import time
  9. from binascii import hexlify
  10. from collections import Iterable
  11. from urlparse import urljoin
  12. import requests
  13. from Crypto.Cipher import DES3
  14. from apilib.monetary import JDMerchantPermillage
  15. from library.jdpsi.constants import JdPsiErrorCode, ACCESS_KEY
  16. from library.jdpsi.exceptions import JdPsiException
  17. logger = logging.getLogger(__name__)
  18. class MyEncoder(json.JSONEncoder):
  19. def default(self, obj):
  20. if isinstance(obj, JDMerchantPermillage):
  21. return obj.to_jd_params()
  22. return super(MyEncoder, self).default(obj)
  23. class JdPsiMerchantClient(object):
  24. BASE_URL = "https://psi.jd.com"
  25. def __init__(self, accessKey=None):
  26. self.accessKey = accessKey or ACCESS_KEY
  27. def pad(self):
  28. pass
  29. @staticmethod
  30. def sign(*args):
  31. h = hashlib.md5()
  32. h.update("".join((str(_)for _ in args)))
  33. return h.hexdigest()
  34. @staticmethod
  35. def pad(rawJson):
  36. # 转换为字节数组
  37. rawBytes = bytes.encode(rawJson, encoding="utf-8")
  38. rawBytesLen = len(rawBytes)
  39. # 计算补位
  40. x = (rawBytesLen + 4) % 8
  41. y = 0 if x == 0 else 8 - x
  42. # 将有效数据长度byte[]添加到原始byte数组的头部
  43. resultBytes = bytearray(rawBytesLen + 4 + y)
  44. resultBytes[0] = (rawBytesLen >> 24) & 0xFF
  45. resultBytes[1] = (rawBytesLen >> 16) & 0xFF
  46. resultBytes[2] = (rawBytesLen >> 8) & 0xFF
  47. resultBytes[3] = rawBytesLen & 0xFF
  48. # 填充补位数据
  49. for i in range(rawBytesLen):
  50. resultBytes[4 + i] = ord(rawBytes[i])
  51. for i in range(y):
  52. resultBytes[rawBytesLen + 4 + i] = 0x00
  53. return bytes(resultBytes)
  54. def encrypt(self, raw):
  55. plaintext = json.dumps(raw, sort_keys=True, separators=(',', ':'), cls=MyEncoder)
  56. key = base64.b64decode(self.accessKey)
  57. cipher = DES3.new(key, DES3.MODE_ECB)
  58. encrypt_bytes = cipher.encrypt(JdPsiMerchantClient.pad(plaintext))
  59. return hexlify(encrypt_bytes)
  60. def _handle_result(self, res, **kwargs):
  61. try:
  62. result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
  63. except (TypeError, ValueError, json.JSONDecodeError):
  64. logger.debug(u'错误的解析结构', exc_info=True)
  65. result = res
  66. if "code" not in result:
  67. return result
  68. if result["code"] == JdPsiErrorCode.SUCCESS:
  69. return result
  70. if result["code"] in []:
  71. return self._request(kwargs["path"], kwargs["agentNo"], kwargs["entity"], kwargs["images"], kwargs["signField"])
  72. raise JdPsiException(
  73. errCode='{}'.format(result["code"]),
  74. errMsg=result["message"],
  75. client=self,
  76. )
  77. def _request(self, path, agentNo, entity, images=None, signField=None):
  78. # 添加 签名标签
  79. if signField and isinstance(signField, Iterable):
  80. sign = JdPsiMerchantClient.sign(*(entity.get(_) for _ in signField))
  81. entity.update({"sign": sign})
  82. # 组织报文
  83. data = {
  84. "agentNo": agentNo,
  85. "entity": self.encrypt(entity)
  86. }
  87. # 图片文件
  88. if images and isinstance(images, dict):
  89. files = {_k: (_k, _v, "image/png") for _k, _v in images.items()}
  90. else:
  91. files = None
  92. url = urljoin(JdPsiMerchantClient.BASE_URL, path)
  93. try:
  94. res = requests.post(url=url, data=data, files=files)
  95. except requests.Timeout:
  96. raise JdPsiException(
  97. errCode='timeout',
  98. errMsg="timeout",
  99. client=self
  100. )
  101. else:
  102. try:
  103. res.raise_for_status()
  104. except requests.RequestException as rre:
  105. raise JdPsiException(
  106. errCode='HTTP{}'.format(res.status_code),
  107. errMsg=rre.message,
  108. client=self,
  109. request=rre.request,
  110. response=rre.response
  111. )
  112. return self._handle_result(
  113. res, path=path, agentNo=agentNo, entity=entity, images=images, signField=signField
  114. )
  115. def create_customer(
  116. self, agentNo, blicUrla, lepUrla, lepUrlb, lepUrlc, img, enterimg, innerimg, cardPhoto, settleManPhotoFront,
  117. settleManPhotoBack, settleHoldingIDCard,
  118. companyType, serialNo, regEmail, regPhone, blicCardType, blicCompanyName, abMerchantName, indTwoCode, blicProvince,
  119. blicCity, blicAddress, blicLongTerm, blicValidityStart, blicValidityEnd, lepCardType, lepName, lepCardNo, lepLongTerm,
  120. lepValidityStart, lepValidityEnd, contactName, contactPhone, contactEmail, contactProvince, contactCity, contactAddress,
  121. ifPhyStore, storeProvince, storeCity, storeAddress, settleToCard, priatePublic, bankName, subBankCode, bankAccountNo,
  122. bankAccountName, settleCardPhone, settlementPeriod, directoryList,
  123. occUrla=None, blicUscc=None, blicScope=None, merchantNo=None
  124. ):
  125. images = {
  126. "blicUrla": blicUrla,
  127. "lepUrla": lepUrla,
  128. "lepUrlb": lepUrlb,
  129. "lepUrlc": lepUrlc,
  130. "img": img,
  131. "enterimg": enterimg,
  132. "innerimg": innerimg,
  133. "cardPhoto": cardPhoto,
  134. "settleManPhotoFront": settleManPhotoFront,
  135. "settleManPhotoBack": settleManPhotoBack,
  136. "settleHoldingIDCard": settleHoldingIDCard
  137. }
  138. occUrla and images.update({"occUrla": occUrla})
  139. entity = {
  140. "companyType": companyType,
  141. "serialNo": serialNo,
  142. "agentNo": agentNo,
  143. "regEmail": regEmail,
  144. "regPhone": regPhone,
  145. "blicCardType": blicCardType,
  146. "blicCompanyName": blicCompanyName,
  147. "abMerchantName": abMerchantName,
  148. "indTwoCode": indTwoCode,
  149. "blicProvince": blicProvince,
  150. "blicCity": blicCity,
  151. "blicAddress": blicAddress,
  152. "blicLongTerm": blicLongTerm,
  153. "blicValidityStart": blicValidityStart,
  154. "blicValidityEnd": blicValidityEnd,
  155. "lepCardType": lepCardType,
  156. "lepName": lepName,
  157. "lepCardNo": lepCardNo,
  158. "lepLongTerm": lepLongTerm,
  159. "lepValidityStart": lepValidityStart,
  160. "lepValidityEnd": lepValidityEnd,
  161. "contactName": contactName,
  162. "contactPhone": contactPhone,
  163. "contactEmail": contactEmail,
  164. "contactProvince": contactProvince,
  165. "contactCity": contactCity,
  166. "contactAddress": contactAddress,
  167. "ifPhyStore": ifPhyStore,
  168. "storeProvince": storeProvince,
  169. "storeCity": storeCity,
  170. "storeAddress": storeAddress,
  171. "settleToCard": settleToCard,
  172. "priatePublic": priatePublic,
  173. "bankName": bankName,
  174. "subBankCode": subBankCode,
  175. "bankAccountNo": bankAccountNo,
  176. "bankAccountName": bankAccountName,
  177. "settleCardPhone": settleCardPhone,
  178. "settlementPeriod": settlementPeriod,
  179. "directoryList": directoryList
  180. }
  181. blicUscc and entity.update({"blicUscc": blicUscc})
  182. blicScope and entity.update({"blicScope": blicScope})
  183. merchantNo and entity.update({"merchantNo": merchantNo})
  184. logger.info("[create_customer] agentNo={}, entity={}".format(
  185. agentNo, entity
  186. ))
  187. path = "/merchant/enterSingle"
  188. return self._request(path=path, agentNo=agentNo, entity=entity, images=images, signField=[
  189. "serialNo", "lepCardNo", "bankAccountNo", "settleCardPhone"
  190. ])
  191. def create_product(self, agentNo, serialNo, merchantNo, productId, payToolId, mfeeType, mfee=None, ladderList=None):
  192. """
  193. 创建 结算 支付 产品
  194. """
  195. logger.info("[create_product] agentNo={}, serialNo={}, merchantNo={}, productId={}, payToolId={}, mfeeType={}, mfee={}, ladderList={}".format(
  196. agentNo, serialNo, merchantNo, productId, payToolId, mfeeType, mfee, ladderList
  197. ))
  198. entity = {
  199. "agentNo": agentNo,
  200. "serialNo": serialNo,
  201. "merchantNo": merchantNo,
  202. "productId": productId,
  203. "payToolId": payToolId,
  204. "mfeeType": mfeeType
  205. }
  206. if mfee:
  207. entity.update({"mfee": mfee})
  208. if ladderList:
  209. entity.update({"ladderList": ladderList})
  210. path = "/merchant/applySingle"
  211. return self._request(path=path, agentNo=agentNo, entity=entity, signField=[
  212. "serialNo", "agentNo", "merchantNo", "productId", "payToolId"
  213. ])
  214. def query_product(self, agentNo, serialNo, merchantNo):
  215. """
  216. 查询产品信息
  217. """
  218. logger.info("[query_product] agentNo={}, serialNo={}, merchantNo={}".format(
  219. agentNo, serialNo, merchantNo
  220. ))
  221. entity = {
  222. "agentNo": agentNo,
  223. "serialNo": serialNo,
  224. "merchantNo": merchantNo
  225. }
  226. path = "/merchant/status/queryApplySingle"
  227. return self._request(path=path, agentNo=agentNo, entity=entity, signField=[
  228. "serialNo", "merchantNo"
  229. ])
  230. def query_secret_key(self, agentNo, serialNo, merchantNo):
  231. """
  232. 查询产品秘钥
  233. """
  234. logger.info("[query_secret_key] agentNo={}, serialNo={}, merchantNo={}".format(
  235. agentNo, serialNo, merchantNo
  236. ))
  237. entity = {
  238. "serialNo": serialNo,
  239. "merchantNo": merchantNo,
  240. }
  241. path = "/merchant/status/queryMerchantKeys"
  242. return self._request(path=path, agentNo=agentNo, entity=entity, signField=[
  243. "serialNo", "merchantNo"
  244. ])
  245. def query_bill_file(self, agentNo, merchantNo, billDate=None):
  246. """
  247. 查询账单 的下载地址
  248. """
  249. logger.info("[query_bill_file] billDate={}, merchantNo={}, agentNo={}".format(billDate, merchantNo, agentNo))
  250. billDate = billDate or datetime.datetime.now().strftime("%Y%m%d")
  251. entity = {
  252. "serialNo": "{}{}".format(int(time.time() * 1000), random.randint(1000, 9999)),
  253. "agentNo": agentNo,
  254. "billDate": billDate,
  255. "merchantNo": merchantNo
  256. }
  257. path = "/agentmerchant/download"
  258. return self._request(path=path, agentNo=agentNo, entity=entity, signField=["billDate", "agentNo"])
  259. def query_settle(self, agentNo, startTime, endTime, pageNum=1, pageSize=10, orderStatus="", merchantNo=None):
  260. """
  261. 查询 商户的结算信息
  262. """
  263. logger.info("[query_settle] merchantNo={}, agentNo={}, startTime={}, endTime={}, orderStatus={}".format(
  264. merchantNo, agentNo, startTime, endTime, orderStatus
  265. ))
  266. entity = {
  267. "agentNo": agentNo,
  268. "merchantNo": merchantNo,
  269. "pageNum": pageNum,
  270. "pageSize": pageSize,
  271. "queryStartTime": startTime,
  272. "queryEndTime": endTime,
  273. "orderStatus": orderStatus,
  274. }
  275. path = "/merchant/status/queryMerchantsSettle"
  276. return self._request(path=path, agentNo=agentNo, entity=entity, signField=[
  277. "agentNo", "merchantNo", "queryStartTime", "queryEndTime", "orderStatus", "pageNum", "pageSize"
  278. ])
  279. def query_sub_channel(self, agentNo, merchantNo, productCode=None):
  280. logger.info("[query_sub_channel] merchantNo={}, agentNo={}, productCode={}".format(
  281. merchantNo, agentNo, productCode
  282. ))
  283. # 查询编号固定是401
  284. productCode = productCode or "401"
  285. entity = {
  286. "agentNo": agentNo,
  287. "merchantNo": merchantNo,
  288. "productCode": str(productCode),
  289. }
  290. path = "/merchant/status/queryMerchantWXNo"
  291. return self._request(path=path, agentNo=agentNo, entity=entity, signField=[
  292. "agentNo", "merchantNo", "productCode"
  293. ])