mopybird 2 년 전
부모
커밋
01f9353d58

+ 11 - 0
apps/web/core/payment/type_checking.py

@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# !/usr/bin/env python
+
+from typing import Union, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from apps.web.core.payment.ali import AliPayGateway, AliPayWithdrawGateway
+    from apps.web.core.payment.wechat import WechatPaymentGateway, WechatWithdrawGateway
+
+    PaymentGatewayT = Union[AliPayGateway, WechatPaymentGateway]
+    WithdrawGatewayT = Union[AliPayWithdrawGateway, WechatWithdrawGateway]

+ 2 - 0
library/insurance/__init__.py

@@ -0,0 +1,2 @@
+# coding=utf-8
+

+ 270 - 0
library/insurance/tianan.py

@@ -0,0 +1,270 @@
+# coding=utf-8
+from typing import TYPE_CHECKING, Optional
+import json
+import logging
+import datetime
+import hashlib
+from urlparse import urljoin
+
+import requests
+
+
+logger = logging.getLogger(__name__)
+
+
+class TianAnException(Exception):
+    def __init__(self, errCode, errMsg, client=None, request=None, response=None):
+        super(TianAnException, self).__init__(errMsg)
+
+        self.errCode = errCode
+        self.errMsg = errMsg
+        self.client = client
+        self.request = request
+        self.response = response
+
+    def __str__(self):
+        if self.client:
+            _repr = '{klass}(client: {client}, errCode: {errCode}, errMsg: {errMsg})'.format(
+                klass=self.__class__.__name__,
+                client=repr(self.client),
+                errCode=self.errCode,
+                errMsg=self.errMsg)
+        else:
+            _repr = '{klass}(errCode: {errCode}, errMsg: {errMsg})'.format(
+                klass=self.__class__.__name__,
+                errCode=self.errCode,
+                errMsg=self.errMsg)
+
+        return _repr
+
+    def __repr__(self):
+        return str(self)
+
+
+class Constant(object):
+    COMPANY_CODE = 1009
+    APP_KEY = "k1009yc83einjr9hpkc21i6hzecqw2n7ou"
+
+
+class TianAn(object):
+
+    URL_BASE = "http://47.96.41.38:8089/api/"
+
+    def __init__(self, companyCode, appKey, timeout=None, autoRetry=False):
+        self._companyCode = companyCode
+        self._appKey = appKey
+        self._timeout = timeout
+        self._autoRetry = autoRetry
+
+    def _request(self, method, path, **kwargs):
+        logger.info("[TianAn request] method = {}, path = {}, kwargs = {}".format(method, path, kwargs))
+        url = urljoin(self.URL_BASE, path)
+
+        headers = {
+            "Content-Type": "application/x-www-form-urlencoded",
+        }
+        kwargs.setdefault("params", {})
+        kwargs.setdefault("timeout", self._timeout)
+        processor = kwargs.pop("processor", None)
+
+        with requests.sessions.Session() as _session:
+            res = _session.request(
+                method=method,
+                url=url,
+                headers=headers,
+                **kwargs
+            )
+            try:
+                res.raise_for_status()
+            except requests.RequestException as rre:
+                logger.info("[{} send request] error! status code = {}, error = {}".format(self.__class__.__name__, res.status_code, rre))
+                raise TianAnException(
+                    errCode='HTTP{}'.format(res.status_code),
+                    errMsg=rre.message,
+                    client=self,
+                    request=rre.request,
+                    response=rre.response
+                )
+
+        return self._handle_result(
+            res, method, url, processor, **kwargs
+        )
+
+    def _handle_result(self, res, method, url, processor, **kwargs):
+        result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
+
+        logger.info("[TianAn _handle_result] method = {}, url = {}, result = {}".format(method, url, result))
+        if processor:
+            return processor(self, result)
+
+        if "code" in result and result["code"] == "0":
+            return result
+
+        # 重试机制待调试
+        if self._autoRetry:
+            return self._request(method, res.request.path_url, processor=processor, **kwargs)
+
+        raise TianAnException(
+            errCode=result.get("code"),
+            errMsg=result.get("msg"),
+            client=self
+        )
+
+    def _post(self, **kwargs):
+        return self._request("post", **kwargs)
+
+    def _get(self, **kwargs):
+        return self._request("get", **kwargs)
+
+    def _get_token(self, qydm=None, key=None, timestamp=None):
+        """
+        :param qydm: 企业代码 固定
+        :param key: 固定
+        :param timestamp: 时间戳 各位为 yyyy-MM-dd hh24:mm:ss
+        """
+        qydm = qydm or self._companyCode
+        key = key or self._appKey
+        timestamp = timestamp or datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        logger.debug("[TianAn get_token] qydm = {}, key = {}, timestamp = {}".format(qydm, key, timestamp))
+
+        s = "{qydm}{key}{timestamp}".format(
+            qydm=qydm, key=key, timestamp=timestamp
+        )
+        token = hashlib.md5(s.encode("utf-8")).hexdigest().lower()
+        logger.debug("[TianAn get_token] token = {}".format(token))
+
+        return token
+
+    def submit_charge_insurance(self, orderNo, logicalCode, startTime, province, city, area, address, finishTime=None):
+        qydm, nowtime = self._companyCode, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        token = self._get_token(qydm, timestamp=nowtime)
+        data = {
+            "qydm": qydm,
+            "token": token,
+            "ddh": orderNo,
+            "sbh": logicalCode,
+            "kssj": startTime,
+            "jssj": finishTime or startTime,
+            "sheng": province,
+            "shi": city,
+            "xian": area,
+            "xxdz": address,
+            "nowtime": nowtime
+        }
+        return self._post(path="adddevdata", data=data)
+
+    def submit_site_insurance(self, logicalCode, partNums, addTime, address, devType=1, remarks=None):
+        """
+        :param logicalCode: 设备编号
+        :param partNums: 插座个数
+        :param addTime: 安装时间
+        :param address: 安装地址
+        :param devType: 设备类型 暂时固定为1
+        :param remarks: 备注 选填
+        """
+        qydm, nowtime = self._companyCode, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        token = self._get_token(qydm, timestamp=nowtime)
+        data = {
+            "qydm": qydm,
+            "token": token,
+            "nowtime": nowtime,
+            "czlx": "add",
+            "sbbh": logicalCode,
+            "czgs": partNums,
+            "azsj": addTime,
+            "azwz": address,
+            "sblx": devType,
+            "beizhu": remarks or ""
+        }
+        return self._post(path="sbgl", data=data)
+
+    def edit_site_insurance(self, logicalCode, partNums, addTime, address, devType=1, remarks=None):
+        qydm, nowtime = self._companyCode, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        token = self._get_token(qydm, timestamp=nowtime)
+        data = {
+            "qydm": qydm,
+            "token": token,
+            "nowtime": nowtime,
+            "czlx": "edit",
+            "sbbh": logicalCode,
+            "czgs": partNums,
+            "azsj": addTime,
+            "azwz": address,
+            "sblx": devType,
+            "beizhu": remarks or ""
+        }
+        return self._post(path="sbgl", data=data)
+
+    def del_site_insurance(self, logicalCode):
+        qydm, nowtime = self._companyCode, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        token = self._get_token(qydm, timestamp=nowtime)
+        data = {
+            "qydm": qydm,
+            "token": token,
+            "nowtime": nowtime,
+            "czlx": "del",
+            "sbbh": logicalCode,
+        }
+        return self._post(path="sbgl", data=data)
+
+    def query_site_insurance(self, startTime, endTime):   # type: (Optional[datetime.datetime, str], Optional[datetime.datetime, str]) -> list
+        """
+        :param startTime: 订单提交的起始日期
+        :param endTime: 订单提交的结束日期
+
+        放回的rows
+        [
+            {
+                'aymd': '20230403',  保单生成日期
+                'bxdh': 'testbx66',  保单号
+                'bxdhsbxxlist': [   # 对应的设备列表
+                    {'sbbh': 'xxx'}, {'sbbh': 'xxxx'}
+                ],
+                'bxjssj': '2024-04-03 14:45:47',    # 保险开始时间
+                'bxkssj': '2023-04-03 14:45:45',    # 保险结束时间
+                'bxqx': '1',        # 期限 单位年
+                'dysbnum': 2,       # 对应的设备数量
+                'qydm': '1003',     # 企业代码 无用
+                'sbxxbxdhbid': '2023040314461490434913954', 主键 遇到问题时候查询(售后)
+                'scsjendymd': '20230331',   该保险单号对应的上传设备开始日期
+                'scsjstartymd': '20230325'  该保险单号对应的上传设备结束日期
+           }
+       ]
+
+        """
+        qydm, nowtime = self._companyCode, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        token = self._get_token(qydm, timestamp=nowtime)
+        if isinstance(startTime, (unicode, str)):
+            try:
+                startTime = datetime.datetime.strptime(startTime, "%Y-%m-%d")
+            except ValueError:
+                raise TianAnException(errCode="", errMsg=u"请传入正确的时间格式:%Y-%m-%d")
+
+        startTime = startTime.strftime("%Y%m%d")
+
+        if isinstance(endTime, (unicode, str)):
+            try:
+                endTime = datetime.datetime.strptime(endTime, "%Y-%m-%d")
+            except ValueError:
+                raise TianAnException(errCode="", errMsg=u"请传入正确的时间格式:%Y-%m-%d")
+
+        endTime = endTime.strftime("%Y%m%d")
+
+        data = {
+            "qydm": qydm,
+            "token": token,
+            "nowtime": nowtime,
+            "startymd": startTime,
+            "endymd": endTime,
+        }
+        result = self._post(path="getSbglBxdh", data=data)
+        if "rows" not in result:
+            raise TianAnException(errCode="", errMsg=u"未获取到数据")
+
+        return result["rows"]
+
+
+def get_client():
+    return TianAn(Constant.COMPANY_CODE, Constant.APP_KEY)
+
+

+ 2 - 0
library/jdbase/__init__.py

@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# !/usr/bin/env python

+ 67 - 0
library/jdbase/exceptions.py

@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+# !/usr/bin/env python
+
+from typing import Optional
+
+from library.jd import JDErrorCode
+
+
+class JDException(Exception):
+    def __init__(self, errCode, errMsg, client = None,
+                 request = None, response = None):
+        super(JDException, self).__init__(errMsg)
+
+        self.errCode = errCode
+        self.errMsg = errMsg
+        self.client = client
+        self.request = request
+        self.response = response
+
+    def __str__(self):
+        if self.client:
+            _repr = '{klass}(client: {client}, errCode: {errCode}, errMsg: {errMsg})'.format(
+                klass = self.__class__.__name__,
+                client = repr(self.client),
+                errCode = self.errCode,
+                errMsg = self.errMsg)
+        else:
+            _repr = '{klass}(errCode: {errCode}, errMsg: {errMsg})'.format(
+                klass = self.__class__.__name__,
+                errCode = self.errCode,
+                errMsg = self.errMsg)
+
+        return _repr
+
+    def __repr__(self):
+        return str(self)
+
+
+class JDNetworkException(JDException):
+    pass
+
+
+class JDRequestException(JDException):
+    pass
+
+
+class JDSignError(JDException):
+    pass
+
+
+class JDValidationError(JDException):
+    def __init__(self, tips, lvalue, rvalue, client = None):
+        # type: (basestring, basestring, basestring, Optional[object])->None
+        super(JDValidationError, self).__init__(
+            errCode = str(JDErrorCode.MY_VALID_ERROR),
+            errMsg = tips,
+            client = client)
+
+        self.lvalue = lvalue
+        self.rvalue = rvalue
+
+    def __repr__(self):
+        return '{}({} != {})'.format(self.errMsg, self.lvalue, self.rvalue)
+
+
+class JDParameterError(JDException):
+    pass

+ 167 - 0
library/jdopen/client/api/settle.py

@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+# !/usr/bin/env python
+
+from library.jdbase.exceptions import JDException
+from library.jdopen import WithdrawMode
+from library.jdopen.client.api.base import BaseJdOpenAPI
+
+
+class JdOpenSettle(BaseJdOpenAPI):
+    def create_account(
+            self, customerNum, bankAccountName, bankAccountNum, province, city, bankName, bankBranchName,
+            settleAmount, payBankList, accountType, phone,
+            privateType = None, settlerCertificateCode = None, settlerCertificateStartDate = None,
+            settlerCertificateEndDate = None
+    ):
+        """
+        创建结算账户
+        """
+        url = "/v2/agent/declare/settleinfo/create"
+        data = {
+            "customerNum": customerNum,
+            "bankAccountName": bankAccountName,
+            "bankAccountNum": bankAccountNum,
+            "province": province,
+            "city": city,
+            "bankName": bankName,
+            "bankBranchName": bankBranchName,
+            "settleAmount": settleAmount,
+            "accountType": accountType,
+            "phone": phone,
+            "privateType": privateType,
+            "settlerCertificateCode": settlerCertificateCode,
+            "settlerCertificateStartDate": settlerCertificateStartDate,
+            "settlerCertificateEndDate": settlerCertificateEndDate
+        }
+        sendData = {_k: str(_v) for _k, _v in data.items() if _v is not None}
+        sendData["payBankList"] = payBankList
+        return self._post(url, data = sendData)
+
+    def modify_account(
+            self, settleNum, customerNum, bankAccountName, bankAccountNum, province, city, bankName, bankBranchName,
+            settleAmount, payBankList, accountType, phone,
+            privateType = None, settlerCertificateCode = None, settlerCertificateStartDate = None,
+            settlerCertificateEndDate = None
+    ):
+        """
+        修改结算账户
+        """
+        url = "/v2/agent/declare/settleinfo/modify"
+        data = {
+            "settleNum": settleNum,
+            "customerNum": customerNum,
+            "bankAccountName": bankAccountName,
+            "bankAccountNum": bankAccountNum,
+            "province": province,
+            "city": city,
+            "bankName": bankName,
+            "bankBranchName": bankBranchName,
+            "settleAmount": settleAmount,
+            "accountType": accountType,
+            "phone": phone,
+            "privateType": privateType,
+            "settlerCertificateCode": settlerCertificateCode,
+            "settlerCertificateStartDate": settlerCertificateStartDate,
+            "settlerCertificateEndDate": settlerCertificateEndDate
+        }
+        sendData = {_k: str(_v) for _k, _v in data.items() if _v is not None}
+        sendData["payBankList"] = payBankList
+        return self._post(url, data = sendData)
+
+    def get_account(self, settleNum):
+        """
+        获取结算账户信息
+        """
+        return self._post("/v1/agent/declare/settleinfo/{}".format(settleNum))
+
+    def query_settle(self, customerNum, startTime, endTime, pageNum = 1, pageSize = 10):
+        def processor(self, result):
+            if result['result'] == 'error':
+                raise JDException(
+                    errCode = result['error'].get('errorCode'),
+                    errMsg = result['error'].get("errorMsg"),
+                    client = self)
+            else:
+                return result
+
+        url = '/api/queryAllSettleInfo'
+        data = {
+            "customerNum": customerNum,
+            "queryStartTime": startTime.strftime('%Y-%m-%d %H:%M:%S'),
+            "queryEndTime": endTime.strftime('%Y-%m-%d %H:%M:%S'),
+            "pageNum": pageNum,
+            "pageSize": pageSize
+        }
+
+        return self._post(url = url, data = data, processor = processor)
+
+    def set_settle_way(self, customerNum, settleWay):
+        from library.jdopen import SettleWay
+        assert settleWay in SettleWay.choices(), u'settle way must be MANUAL or AUTOMATIC'
+
+        url = "/agent/settle/way/set"
+        data = {
+            "agentNum": self.agentNum,
+            "customerNum": customerNum,
+            "remitWay": settleWay
+        }
+
+        return self._post(url = url, data = data)
+
+    def query_settle_way(self, customerNum):
+        url = "/agent/settle/way/query"
+        data = {
+            "agentNum": self.agentNum,
+            "customerNum": customerNum
+        }
+
+        return self._post(url = url, data = data)
+
+    def query_balance(self, customerNum):
+        url = '/agent/account/balance/query'
+
+        data = {
+            "agentNum": self.agentNum,
+            "customerNum": customerNum
+        }
+
+        return self._post(url = url, data = data)
+
+    def withdraw_apply(self, customerNum, requestNum, totalAmount, withdrawMode = WithdrawMode.D1):
+        assert withdrawMode in WithdrawMode.choices(), u'settle way must be D0 or D1'
+
+        url = '/agent/cash/withdrawal/apply'
+
+        data = {
+            "agentNum": self.agentNum,
+            "customerNum": customerNum,
+            'totalAmount': totalAmount,
+            'requestNum': requestNum,
+            'withdrawMode': withdrawMode
+        }
+
+        return self._post(url = url, data = data)
+
+    def query_withdraw_result(self, customerNum, requestNum):
+        url = '/agent/cash/withdrawal/result/query'
+
+        data = {
+            "agentNum": self.agentNum,
+            "customerNum": customerNum,
+            'requestNum': requestNum
+        }
+
+        return self._post(url = url, data = data)
+
+    def query_withdraw_list(self, customerNum, startTime, endTime):
+        url = '/agent/deposit/fund/detail/query'
+
+        data = {
+            "agentNum": self.agentNum,
+            "customerNum": customerNum,
+            'startTime': startTime,
+            'endTime': endTime,
+            'pageNum': '1'
+        }
+
+        return self._post(url = url, data = data)

+ 103 - 0
library/wechatpayv3/client/api/apply4subject.py

@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# !/usr/bin/env python
+
+from ..api.base import BaseWeChatAPI
+from ...type import RequestType
+
+
+class Apply4Subject(BaseWeChatAPI):
+    def submit_applyment(self, business_code, contact_info, subject_info, identification_info, channel_id = None,
+               addition_info = None, ubo_info_list = []):
+        """(商户开户意愿)提交申请单
+        :param business_code: 业务申请编号,示例值:'APPLYMENT_00000000001'
+        :param contact_info: 联系人信息,示例值:{'name':'张三','id_card_number':'320311770706001','mobile':'13900000000'}
+        :param subject_info: 主体信息,示例值:{'subject_type':'SUBJECT_TYPE_ENTERPRISE','business_license_info':{'license_copy':'demo-media-id','license_number':'123456789012345678','merchant_name':'腾讯科技有限公司','legal_person':'张三','company_address':'广东省深圳市南山区xx路xx号','licence_valid_date':'["1970-01-01","forever"]'}}
+        :param identification_info: 法人身份信息,示例值:{'identification_type':'IDENTIFICATION_TYPE_IDCARD','identification_name':'张三','identification_number':'110220330044005500','identification_valid_date':'["1970-01-01","forever"]','identification_front_copy':'0P3ng6KTIW4-Q_l2FjKLZ...','identification_back_copy':'0P3ng6KTIW4-Q_l2FjKLZ...'}
+        :param channel_id: 渠道商户号,示例值:'20001111'
+        :param addition_info: 补充材料,示例值:{'confirm_mchid_list':['20001113']}
+        :param ubo_info_list: 最终受益人信息列表,示例值:[{'ubo_id_doc_type':'IDENTIFICATION_TYPE_IDCARD','ubo_id_doc_name':'张三','ubo_id_doc_number':'110220330044005500'}]
+        """
+        params = {}
+        if business_code:
+            params.update({'business_code': business_code})
+        else:
+            raise Exception('business_code is not assigned.')
+        if contact_info:
+            params.update({'contact_info': contact_info})
+        else:
+            raise Exception('contact_info is not assigned.')
+        if subject_info:
+            params.update({'subject_info': subject_info})
+        else:
+            raise Exception('subject_info is not assigned.')
+        if identification_info:
+            params.update({'identification_info': identification_info})
+        else:
+            raise Exception('identification_info is not assigned')
+        if channel_id:
+            params.update({'channel_id': channel_id})
+        if addition_info:
+            params.update({'addition_info': addition_info})
+        if ubo_info_list:
+            params.update({'ubo_info_list': ubo_info_list})
+        contact_name = params.get('contact_info').get('name')
+        if contact_name:
+            params['contact_info']['name'] = self.client.core.encrypt(contact_name)
+        contact_mobile = params.get('contact_info').get('mobile')
+        if contact_mobile:
+            params['contact_info']['mobile'] = self.client.core.encrypt(contact_mobile)
+        contact_number = params.get('contact_info').get('id_card_number')
+        if contact_number:
+            params['contact_info']['id_card_number'] = self.client.core.encrypt(contact_number)
+        identification_name = params.get('identification_info').get('identification_name')
+        if identification_name:
+            params['identification_info']['identification_name'] = self.client.core.encrypt(identification_name)
+        identification_number = params.get('identification_info').get('identification_number')
+        if identification_number:
+            params['identification_info']['identification_number'] = self.client.core.encrypt(identification_number)
+        identification_address = params.get('identification_info').get('identification_address')
+        if identification_address:
+            params['identification_info']['identification_address'] = self.client.core.encrypt(identification_address)
+        if params.get('ubo_info_list'):
+            for ubo_info in params['ubo_info_list']:
+                ubo_info['ubo_id_doc_name'] = self.client.core.encrypt(ubo_info['ubo_id_doc_name'])
+                ubo_info['ubo_id_doc_number'] = self.client.core.encrypt(ubo_info['ubo_id_doc_number'])
+                ubo_info['ubo_id_doc_address'] = self.client.core.encrypt(ubo_info['ubo_id_doc_address'])
+        path = '/v3/apply4subject/applyment'
+        return self.client.core.request(path, method = RequestType.POST, data = params, cipher_data = True)
+
+    def cancel_applyment(self, business_code = None, applyment_id = None):
+        """(商户开户意愿)撤销申请单
+        :param business_code: 业务申请编号,示例值:'2000001234567890'
+        :param applyment_id: 申请单编号,示例值:2000001234567890
+        """
+        if applyment_id:
+            path = '/v3/apply4subject/applyment/%s/cancel' % applyment_id
+        elif business_code:
+            path = '/v3/apply4subject/applyment/%s/cancel' % business_code
+        else:
+            raise Exception('business_code or applyment_id is not assigned.')
+        return self.client.core.request(path, method = RequestType.POST)
+
+    def query_applyment_audit(self, business_code = None, applyment_id = None):
+        """(商户开户意愿)查询申请单审核结果
+        :param business_code: 业务申请编号,示例值:'2000001234567890'
+        :param applyment_id: 申请单编号,示例值:2000001234567890
+        """
+        if business_code:
+            path = '/v3/apply4subject/applyment?business_code=%s' % business_code
+        elif applyment_id:
+            path = '/v3/apply4subject/applyment?applyment_id=%s' % applyment_id
+        else:
+            raise Exception('business_code or applyment_id is not assigned.')
+        return self.client.core.request(path)
+
+    def query_applyment_state(self, sub_mchid):
+        """(商户开户意愿)获取商户开户意愿确认状态
+        :param sub_mchid: 特约商户号,示例值:'1511101111'
+        """
+        if sub_mchid:
+            path = '/v3/apply4subject/applyment/merchants/%s/state' % sub_mchid
+        else:
+            raise Exception('sub_mchid is not assigned.')
+        return self.client.core.request(path)

+ 71 - 0
patch_celery.py

@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# !/usr/bin/env python
+
+
+__import__('urllib3.contrib.pyopenssl')
+
+import requests
+from OpenSSL.crypto import PKCS12, X509, PKey
+
+
+def _is_key_file_encrypted(keyfile):
+    '''In memory key is not encrypted'''
+    if isinstance(keyfile, PKey):
+        return False
+
+    return _is_key_file_encrypted.original(keyfile)
+
+
+class PyOpenSSLContext(requests.packages.urllib3.contrib.pyopenssl.PyOpenSSLContext):
+    def load_cert_chain(self, certfile, keyfile = None, password = None):
+        if isinstance(certfile, X509) and isinstance(keyfile, PKey):
+            self._ctx.use_certificate(certfile)
+            self._ctx.use_privatekey(keyfile)
+        else:
+            super(PyOpenSSLContext, self).load_cert_chain(certfile, keyfile = keyfile, password = password)
+
+
+class HTTPAdapter(requests.adapters.HTTPAdapter):
+    '''Handle a variety of cert types'''
+
+    def cert_verify(self, conn, url, verify, cert):
+        if cert:
+            # PKCS12
+            if isinstance(cert, PKCS12):
+                conn.cert_file = cert.get_certificate()
+                conn.key_file = cert.get_privatekey()
+                cert = None
+            elif isinstance(cert, tuple) and len(cert) == 2:
+                # X509 and PKey
+                if isinstance(cert[0], X509) and isinstance(cert[1], PKey):
+                    conn.cert_file = cert[0]
+                    conn.key_file = cert[1]
+                    cert = None
+                # cryptography objects
+                elif hasattr(cert[0], 'public_bytes') and hasattr(cert[1], 'private_bytes'):
+                    conn.cert_file = X509.from_cryptography(cert[0])
+                    conn.key_file = PKey.from_cryptography_key(cert[1])
+                    cert = None
+        super(HTTPAdapter, self).cert_verify(conn, url, verify, cert)
+
+
+def patch_requests(adapter = True):
+    '''You can perform a full patch and use requests as usual:
+
+    >>> patch_requests()
+    >>> requests.get('https://httpbin.org/get')
+
+    or use the adapter explicitly:
+
+    >>> patch_requests(adapter=False)
+    >>> session = requests.Session()
+    >>> session.mount('https', HTTPAdapter())
+    >>> session.get('https://httpbin.org/get')
+    '''
+    if hasattr(requests.packages.urllib3.util.ssl_, '_is_key_file_encrypted'):
+        _is_key_file_encrypted.original = requests.packages.urllib3.util.ssl_._is_key_file_encrypted
+        requests.packages.urllib3.util.ssl_._is_key_file_encrypted = _is_key_file_encrypted
+
+    requests.packages.urllib3.util.ssl_.SSLContext = PyOpenSSLContext
+    if adapter:
+        requests.sessions.HTTPAdapter = HTTPAdapter