client.py 14 KB

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