123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import base64
- import datetime
- import hashlib
- import simplejson as 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.jdbase.exceptions import JDNetworkException
- from library.jdpsi.constants import JdPsiErrorCode
- 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, agentNo, accessKey):
- self.agentNo = agentNo
- self.accessKey = accessKey
- @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 JDNetworkException(
- errCode = 'timeout',
- errMsg = "timeout",
- client = self)
- else:
- try:
- res.raise_for_status()
- except requests.RequestException as rre:
- raise JDNetworkException(
- 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, 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": self.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(
- self.agentNo, entity
- ))
- path = "/merchant/enterSingle"
- return self._request(path = path, agentNo = self.agentNo, entity = entity, images = images, signField = [
- "serialNo", "lepCardNo", "bankAccountNo", "settleCardPhone"
- ])
- def create_product(self, serialNo, merchantNo, productId, payToolId, mfeeType, mfee = None,
- ladderList = None):
- """
- 创建 结算 支付 产品
- """
- logger.info(
- "[create_product] agentNo={}, serialNo={}, merchantNo={}, productId={}, "
- "payToolId={}, mfeeType={}, mfee={}, ladderList={}".format(
- self.agentNo, serialNo, merchantNo, productId, payToolId, mfeeType, mfee, ladderList
- ))
- entity = {
- "agentNo": self.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 = self.agentNo, entity = entity, signField = [
- "serialNo", "agentNo", "merchantNo", "productId", "payToolId"
- ])
- def query_product(self, serialNo, merchantNo):
- """
- 查询产品信息
- """
- logger.info("[query_product] agentNo={}, serialNo={}, merchantNo={}".format(
- self.agentNo, serialNo, merchantNo
- ))
- if not merchantNo:
- raise JdPsiException(
- errCode = JdPsiErrorCode.PARAMETER_ERROR,
- errMsg = u'缺少商户编号参数',
- client = self)
- entity = {
- "agentNo": self.agentNo,
- "serialNo": serialNo,
- "merchantNo": merchantNo
- }
- path = "/merchant/status/queryApplySingle"
- return self._request(path = path, agentNo = self.agentNo, entity = entity, signField = [
- "serialNo", "merchantNo"
- ])
- def query_secret_key(self, serialNo, merchantNo):
- """
- 查询产品秘钥
- """
- logger.info("[query_secret_key] agentNo={}, serialNo={}, merchantNo={}".format(
- self.agentNo, serialNo, merchantNo
- ))
- entity = {
- "serialNo": serialNo,
- "merchantNo": merchantNo,
- }
- if not merchantNo:
- raise JdPsiException(
- errCode = JdPsiErrorCode.PARAMETER_ERROR,
- errMsg = u'缺少商户编号参数',
- client = self)
- path = "/merchant/status/queryMerchantKeys"
- return self._request(path = path, agentNo = self.agentNo, entity = entity, signField = [
- "serialNo", "merchantNo"
- ])
- def query_bill_file(self, merchantNo, billDate = None):
- """
- 查询账单 的下载地址
- """
- logger.info("[query_bill_file] billDate={}, merchantNo={}, agentNo={}".format(
- billDate, merchantNo, self.agentNo))
- billDate = billDate or datetime.datetime.now().strftime("%Y%m%d")
- entity = {
- "serialNo": "{}{}".format(int(time.time() * 1000), random.randint(1000, 9999)),
- "agentNo": self.agentNo,
- "billDate": billDate,
- "merchantNo": merchantNo
- }
- path = "/agentmerchant/download"
- return self._request(path = path, agentNo = self.agentNo, entity = entity, signField = ["billDate", "agentNo"])
- def query_settle(self, startTime, endTime, pageNum = 1, pageSize = 10, orderStatus = "", merchantNo = None):
- """
- 查询 商户的结算信息
- """
- logger.info("[query_settle] merchantNo={}, agentNo={}, startTime={}, endTime={}, orderStatus={}".format(
- merchantNo, self.agentNo, startTime, endTime, orderStatus
- ))
- entity = {
- "agentNo": self.agentNo,
- "merchantNo": merchantNo,
- "pageNum": pageNum,
- "pageSize": pageSize,
- "queryStartTime": startTime,
- "queryEndTime": endTime,
- "orderStatus": orderStatus,
- }
- path = "/merchant/status/queryMerchantsSettle"
- return self._request(path = path, agentNo = self.agentNo, entity = entity, signField = [
- "agentNo", "merchantNo", "queryStartTime", "queryEndTime", "orderStatus", "pageNum", "pageSize"
- ])
- def query_sub_channel(self, merchantNo, productCode = None):
- logger.info("[query_sub_channel] merchantNo={}, agentNo={}, productCode={}".format(
- merchantNo, self.agentNo, productCode
- ))
- # 查询编号固定是401
- productCode = productCode or "401"
- entity = {
- "agentNo": self.agentNo,
- "merchantNo": merchantNo,
- "productCode": str(productCode),
- }
- path = "/merchant/status/queryMerchantWXNo"
- result = self._request(path = path, agentNo = self.agentNo, entity = entity, signField = [
- "agentNo", "merchantNo", "productCode"
- ])
- if 'data' not in result or "hlbWxSubNoResultInfo" not in result["data"]:
- logger.error("[query_sub_channel] merchantNo={}, agentNo={}, productCode={}, has no data.".format(
- merchantNo, self.agentNo, productCode
- ))
- return None
- channelsInfo = json.loads(result["data"]["hlbWxSubNoResultInfo"])
- try:
- return channelsInfo["threePartnerNoData"][0]["threePartnerNo"]
- except Exception:
- logger.error('query wx sub merchant id failure. result = {}'.format(channelsInfo))
- return None
|