client.py 13 KB

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