| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 | 
							- # -*- coding: utf-8 -*-
 
- # !/usr/bin/env python
 
- from __future__ import unicode_literals
 
- import base64
 
- import datetime
 
- import hashlib
 
- import logging
 
- from binascii import hexlify, unhexlify
 
- from collections import OrderedDict
 
- import requests
 
- import simplejson as json
 
- import six
 
- from Crypto.Cipher import DES3
 
- from typing import Union, Dict, Optional
 
- from apilib.systypes import IterConstant
 
- from apilib.utils_url import add_query
 
- from library import to_binary, to_text
 
- from library.jd import JDErrorCode
 
- from library.jdbase.exceptions import JDNetworkException, JDException, JDSignError, JDValidationError, JDParameterError
 
- logger = logging.getLogger(__name__)
 
- __all__ = ('JDAggrePay')
 
- class PiType(IterConstant):
 
-     WX = 'WX'
 
-     ALIPAY = 'ALIPAY'
 
-     JDPAY = 'JDPAY'
 
-     UNIPAY = 'UNIPAY'
 
-     JIOU = 'JIOU'
 
- class GatewayMethod(IterConstant):
 
-     MINIPROGRAM = 'MINIPROGRAM'
 
-     SUBSCRIPTION = 'SUBSCRIPTION'
 
- class TerminalType(IterConstant):
 
-     PC = 'DT01'
 
-     APP = 'DT02'
 
-     BROWSER = 'DT03'
 
-     POS = 'DT04'
 
- class JDAggrePay(object):
 
-     PAY_HOST_DEV = 'http://testapipayx.jd.com'
 
-     PAY_HOST = 'https://apipayx.jd.com'
 
-     UN_SIGN_FIELDS = ['sign']
 
-     UN_ENCRYPTE_FIELDS = ['systemId', 'sign']
 
-     def __str__(self):
 
-         _repr = '{kclass}(merchant_no: {merchant_no})'.format(
 
-             kclass = self.__class__.__name__,
 
-             merchant_no = self.merchant_no)
 
-         if six.PY2:
 
-             return to_binary(_repr)
 
-         else:
 
-             return to_text(_repr)
 
-     def __repr__(self):
 
-         return str(self)
 
-     def __init__(self, merchant_no, desKey, saltMd5Key, systemId, debug = False):
 
-         self.merchant_no = merchant_no
 
-         self.desKey = desKey
 
-         self.saltMd5Key = saltMd5Key
 
-         self.systemId = systemId
 
-         self.debug = debug
 
-         if self.debug:
 
-             self.host_url = JDAggrePay.PAY_HOST_DEV
 
-         else:
 
-             self.host_url = JDAggrePay.PAY_HOST
 
-     def sign(self, raw, un_sign_fields):
 
-         sign_raw = []
 
-         for k in sorted(raw.keys()):
 
-             if k in un_sign_fields or raw[k] is None or raw[k] == '':
 
-                 continue
 
-             v = raw[k]
 
-             if isinstance(v, basestring):
 
-                 sign_raw.append((k, v))
 
-             elif isinstance(v, (dict, list)):
 
-                 sign_raw.append((k, json.dumps(v, sort_keys = True, separators = (',', ':'))))
 
-             else:
 
-                 sign_raw.append((k, str(v)))
 
-         s = u'&'.join('='.join(kv) for kv in sign_raw)
 
-         s = u'{}{}'.format(s, self.saltMd5Key)
 
-         m = hashlib.md5()
 
-         m.update(s.encode('utf-8'))
 
-         sign = m.hexdigest().lower()
 
-         return sign
 
-     def encrypt(self, raw, un_encrypt_fields):
 
-         # type: (dict)->str
 
-         params = {}
 
-         for k, v in raw.iteritems():
 
-             if k in un_encrypt_fields:
 
-                 continue
 
-             if isinstance(v, (dict, list)):
 
-                 params[k] = json.dumps(v, sort_keys = True, separators = (',', ':'))
 
-             else:
 
-                 params[k] = v
 
-         plaintext = json.dumps(params, sort_keys = True, separators = (',', ':'))
 
-         logger.debug('Request to JDAggre API decrypt payload: %s', str(plaintext))
 
-         BS = DES3.block_size
 
-         pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
 
-         key = base64.b64decode(self.desKey)
 
-         plaintext = pad(plaintext)
 
-         cipher = DES3.new(key, DES3.MODE_ECB)
 
-         encrypt_bytes = cipher.encrypt(plaintext.encode('utf-8'))
 
-         return hexlify(encrypt_bytes)
 
-     def decrypt(self, plaintext):
 
-         # type: (str)->str
 
-         unpad = lambda s: s[0:-ord(s[-1])]
 
-         key = base64.b64decode(self.desKey)
 
-         decrypted_text = unhexlify(plaintext)
 
-         cipher = DES3.new(key, DES3.MODE_ECB)
 
-         s = cipher.decrypt(decrypted_text)
 
-         s = unpad(s)
 
-         return s
 
-     def check(self, data):
 
-         sign = data.pop('sign')
 
-         return sign == self.sign(data)
 
-     def get(self, endpoint, **kwargs):
 
-         return self._request(
 
-             method = 'get',
 
-             endpoint = endpoint,
 
-             **kwargs
 
-         )
 
-     def post(self, endpoint, **kwargs):
 
-         return self._request(
 
-             method = 'post',
 
-             endpoint = endpoint,
 
-             **kwargs
 
-         )
 
-     def _decode_result(self, res):
 
-         try:
 
-             result = json.loads(res.content.decode('utf-8', 'ignore'), strict = False)
 
-         except (TypeError, ValueError):
 
-             return res
 
-         return result
 
-     def decrypt_response(self, payload):
 
-         # 接口调用失败, SUCCESS/FAIL
 
-         if not payload['success'] or ('errCode' in payload and payload['errCode'] != '000000'):
 
-             raise JDException(
 
-                 errCode = payload['errCode'] if 'errCode' in payload else -1,
 
-                 errMsg = payload['errCodeDes'] if 'errCodeDes' in payload else u'未知错误',
 
-                 client = self,
 
-                 request = None,
 
-                 response = None)
 
-         sign = payload['sign']
 
-         decrypt_data = json.loads(self.decrypt(payload['cipherJson']))
 
-         if sign != self.sign(decrypt_data, ['sign']):
 
-             raise JDSignError(
 
-                 errCode = JDErrorCode.MY_ERROR_SIGNATURE,
 
-                 errMsg = u'签名不一致',
 
-                 client = self)
 
-         if decrypt_data['merchantNo'] != self.merchant_no:
 
-             raise JDValidationError(
 
-                 tips = u'商户号不一致',
 
-                 lvalue = self.merchant_no,
 
-                 rvalue = decrypt_data['merchantNo'],
 
-                 client = self)
 
-         return decrypt_data
 
-     def _handle_result(self, res, method = None, url = None,
 
-                        result_processor = None, **kwargs):
 
-         if not isinstance(res, dict):
 
-             result = self._decode_result(res)
 
-         else:
 
-             result = res
 
-         logger.debug('Response from JDAggre API: %s', result)
 
-         decrypt_data = self.decrypt_response(result)
 
-         logger.debug('Response from JDAggre decrypt payload: %s', decrypt_data)
 
-         if decrypt_data['resultCode'] != 'SUCCESS':
 
-             raise JDException(
 
-                 errCode = decrypt_data['errCode'],
 
-                 errMsg = decrypt_data['errCodeDes'],
 
-                 client = self)
 
-         return decrypt_data if not result_processor else result_processor(decrypt_data)
 
-     def _request(self, method, endpoint, **kwargs):
 
-         if endpoint.startswith('http://') or endpoint.startswith('https://'):
 
-             url = endpoint
 
-         else:
 
-             url = '{base}{endpoint}'.format(
 
-                 base = self.host_url,
 
-                 endpoint = endpoint
 
-             )
 
-         headers = {'Content-Type': 'application/json;charset=utf-8'}
 
-         if isinstance(kwargs.get('data', ''), dict):
 
-             body = json.dumps(kwargs['data'], ensure_ascii = False)
 
-             body = body.encode('utf-8')
 
-             kwargs['data'] = body
 
-         kwargs['timeout'] = kwargs.get('timeout', 15)
 
-         result_processor = kwargs.pop('result_processor', None)
 
-         logger.debug('Request to JDAggre API: %s %s\n%s', method, url, kwargs)
 
-         with requests.sessions.Session() as session:
 
-             res = session.request(
 
-                 url = url,
 
-                 method = method,
 
-                 headers = headers,
 
-                 **kwargs
 
-             )
 
-             try:
 
-                 res.raise_for_status()
 
-             except requests.RequestException as reqe:
 
-                 raise JDNetworkException(
 
-                     errCode = 'HTTP{}'.format(res.status_code),
 
-                     errMsg = reqe.message,
 
-                     client = self,
 
-                     request = reqe.request,
 
-                     response = reqe.response)
 
-             return self._handle_result(
 
-                 res, method, url, result_processor, **kwargs
 
-             )
 
-     def create_pay_url(self, out_trade_no, total_fee, notify_url, piType, tradeName = u'充值', expire = 300,
 
-                        returnParams = None, **kwargs):
 
-         """
 
-         创建支付URL
 
-         """
 
-         url = 'https://payx.jd.com/getScanUrl'
 
-         params = {
 
-             'version': 'V3.0',
 
-             'businessCode': 'AGGRE',
 
-             'merchantNo': str(self.merchant_no),
 
-             'outTradeNo': out_trade_no,
 
-             'amount': total_fee,
 
-             'successNotifyUrl': notify_url,
 
-             'expireTime': str(expire),
 
-             'businessType': '00',
 
-             'returnParams': returnParams,
 
-             'piType': piType,
 
-             'tradeName': tradeName
 
-         }
 
-         sign = self.sign(params, [])
 
-         cipher_json = self.encrypt(params, [])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint = url, data = data)
 
-         return result['scanUrl']
 
-     def create_pay_static_url(self, notify_url, tradeName = u'充值', expire = 300, returnParams = None, **kwargs):
 
-         """
 
-         创建支付URL
 
-         """
 
-         url = 'https://payx.jd.com/getScanUrl'
 
-         params = {
 
-             'version': 'V3.0',
 
-             'businessCode': 'AGGRE',
 
-             'merchantNo': str(self.merchant_no),
 
-             'expireTime': str(expire),
 
-             'businessType': '00',
 
-             'tradeName': tradeName
 
-         }
 
-         if notify_url:
 
-             params.update({'successNotifyUrl': notify_url})
 
-         if returnParams:
 
-             params.update({'returnParams': returnParams})
 
-         sign = self.sign(params, ['sign'])
 
-         cipher_json = self.encrypt(params, ['systemId', 'sign'])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint = url, data = data)
 
-         return result['scanUrl']
 
-     def unified_order(self, piType, out_trade_no, total_fee, notify_url, productName = u'充值',
 
-                       expire = 300, returnParams = None, client_ip = '127.0.0.1', openId = None,
 
-                       gatewayMethod = GatewayMethod.SUBSCRIPTION, **kwargs):
 
-         # type: (str, str, str, str, basestring, int, Optional[Dict], str, Optional[str], str, Dict)->Union[str, Dict]
 
-         """
 
-         统一下单
 
-         """
 
-         if piType in [PiType.ALIPAY, PiType.WX] and not openId:
 
-             raise JDParameterError(errCode = JDErrorCode.MY_INVALID_PARAMETER, errMsg = u'缺少openid参数')
 
-         params = {
 
-             'systemId': self.systemId,
 
-             'businessCode': 'AGGRE',
 
-             'deadlineTime': (datetime.datetime.now() + datetime.timedelta(seconds = expire)).strftime("%Y%m%d%H%M%S"),
 
-             'amount': total_fee,
 
-             'merchantNo': str(self.merchant_no),
 
-             'outTradeNo': out_trade_no,
 
-             'outTradeIp': client_ip,
 
-             'productName': productName,
 
-             'currency': 'RMB',
 
-             'version': 'V3.0',
 
-             'notifyUrl': notify_url,
 
-             'piType': piType,
 
-             'gatewayPayMethod': gatewayMethod,
 
-             'deviceInfo': {
 
-                 'type': TerminalType.APP,
 
-                 'ip': '192.168.0.1',
 
-                 'imei': kwargs.get('imei')
 
-             }
 
-         }
 
-         billSplitList = kwargs.get("billSplitList")
 
-         if billSplitList:
 
-             params["billSplitList"] = billSplitList
 
-         if returnParams:
 
-             params.update({
 
-                 'returnParams': returnParams
 
-             })
 
-         if openId:
 
-             params.update({
 
-                 'openId': openId
 
-             })
 
-         sign = self.sign(params, ['systemId', 'sign'])
 
-         cipher_json = self.encrypt(params, ['systemId', 'sign'])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint = '/m/unifiedOrder', data = data)
 
-         try:
 
-             return json.loads(result['payInfo'])
 
-         except Exception:
 
-             return result['payInfo']
 
-     def generate_query_openid_url(self, piType, callback_url, payload):
 
-         assert callback_url is not None
 
-         if piType not in [PiType.ALIPAY, PiType.WX]:
 
-             raise JDParameterError(errCode = JDErrorCode.MY_INVALID_PARAMETER, errMsg = u'必须是支付宝或者微信')
 
-         if payload:
 
-             callback_url = add_query(callback_url, {'payload': payload})
 
-         params = {
 
-             'version': 'V3.0',
 
-             'businessCode': 'AGGRE',
 
-             'merchantNo': self.merchant_no,
 
-             'successPageUrl': callback_url,
 
-             'piType': piType
 
-         }
 
-         sign = self.sign(params, ['systemId', 'sign'])
 
-         cipher_json = self.encrypt(params, ['systemId', 'sign'])
 
-         return '{host}/code/authorizeCode?merchantNo={merchantNo}&cipherJson={cipherJson}&sign={sign}' \
 
-                '&systemId={systemId}'.format(host = 'http://payx.jd.com',
 
-                                              merchantNo = self.merchant_no,
 
-                                              cipherJson = cipher_json,
 
-                                              sign = sign,
 
-                                              systemId = self.systemId)
 
-     def api_trade_query(self, out_trade_no = None, trade_no = None):
 
-         assert out_trade_no or trade_no, 'out_trade_no and trade_no must not be empty at the same time'
 
-         params = {
 
-             'merchantNo': str(self.merchant_no),
 
-             'businessCode': 'AGGRE',
 
-             'version': 'V3.0',
 
-             'outTradeNo': str(out_trade_no)
 
-         }
 
-         if trade_no:
 
-             params.update({'trandNo': trade_no})
 
-         sign = self.sign(params, ['systemId', 'sign'])
 
-         cipher_json = self.encrypt(params, ['systemId', 'sign'])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint = '/m/querytrade', data = data)
 
-         return result
 
-     def api_trade_refund(self, outTradeNo, outRefundNo, amount, **kwargs):
 
-         """
 
-         :param outTradeNo:
 
-         :param outRefundNo:
 
-         :param amount:
 
-         :return:
 
-         """
 
-         params = {
 
-             "merchantNo": str(self.merchant_no),
 
-             "businessCode": "AGGRE",
 
-             "version": "V3.0",
 
-             "outTradeNo": str(outTradeNo),
 
-             "outRefundNo": str(outRefundNo),
 
-             "amount": amount
 
-         }
 
-         billSplitList = kwargs.get("billSplitList")
 
-         if billSplitList:
 
-             params["billSplitList"] = billSplitList
 
-         sign = self.sign(params, ['systemId', "sign"])
 
-         cipher_json = self.encrypt(params, ["systemId", "sign"])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint = '/m/refund', data = data)
 
-         return result
 
-     def api_refund_query(self, out_refund_no):
 
-         params = {
 
-             'merchantNo': str(self.merchant_no),
 
-             'businessCode': 'AGGRE',
 
-             'version': 'V3.0',
 
-             'outRefundNo': str(out_refund_no)
 
-         }
 
-         sign = self.sign(params, ['systemId', 'sign'])
 
-         cipher_json = self.encrypt(params, ['systemId', 'sign'])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint = '/m/queryrefund', data = data)
 
-         return result
 
- class JDJosPay(JDAggrePay):
 
-     """
 
-     JD JOS 支付的相关接口 加解密方式和JDAGGRE一直 但是部分字段实现不一致
 
-     """
 
-     def api_trade_query(self, out_trade_no=None, trade_no=None):
 
-         shopInfo = getattr(self, "shopInfo", None)
 
-         assert out_trade_no or trade_no, 'out_trade_no and trade_no must not be empty at the same time'
 
-         assert shopInfo, "shop info must be confided to josPayApp"
 
-         businessData = OrderedDict([
 
-             ("brandId", shopInfo.brandId),
 
-             ("brandName", shopInfo.brandName),
 
-             ("tradeName", shopInfo.tradeName),
 
-             ("bizId", shopInfo.bizId)
 
-         ])
 
-         params = {
 
-             'merchantNo': str(self.merchant_no),
 
-             'businessCode': 'MEMBER',
 
-             'shopId': shopInfo.exStoreId,
 
-             'version': 'V3.0',
 
-             'businessData': json.dumps(businessData, separators=(",", ":")),
 
-             'outTradeNo': str(out_trade_no),
 
-         }
 
-         if trade_no:
 
-             params.update({'trandNo': trade_no})
 
-         sign = self.sign(params, ['systemId', 'sign'])
 
-         cipher_json = self.encrypt(params, ['systemId', 'sign'])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint='/m/querytrade', data=data)
 
-         return result
 
-     def api_trade_refund(self, outTradeNo, outRefundNo, amount, **kwargs):
 
-         shopInfo = getattr(self, "shopInfo", None)
 
-         assert outTradeNo and outRefundNo, 'outTradeNo and tradeNo must not be empty'
 
-         assert shopInfo, "shop info must be confided to josPayApp"
 
-         businessData = OrderedDict([
 
-             ("brandId", shopInfo.brandId),
 
-             ("brandName", shopInfo.brandName),
 
-             ("tradeName", shopInfo.tradeName),
 
-             ("bizId", shopInfo.bizId)
 
-         ])
 
-         params = {
 
-             "merchantNo": str(self.merchant_no),
 
-             "businessCode": "MEMBER",
 
-             "version": "V3.0",
 
-             "outTradeNo": str(outTradeNo),
 
-             "outRefundNo": str(outRefundNo),
 
-             "amount": amount,
 
-             "operId": kwargs.get("operId") or "SYSTEM_AUTO",
 
-             "shopId": shopInfo.exStoreId,
 
-             'businessData': json.dumps(businessData, separators=(",", ":")),
 
-         }
 
-         sign = self.sign(params, ['systemId', "sign"])
 
-         cipher_json = self.encrypt(params, ["systemId", "sign"])
 
-         data = {
 
-             'systemId': self.systemId,
 
-             'merchantNo': self.merchant_no,
 
-             'cipherJson': cipher_json,
 
-             'sign': sign
 
-         }
 
-         result = self.post(endpoint='/m/refund', data=data)
 
-         return result
 
 
  |