123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import json
- import logging
- import time
- from urllib import urlencode
- import OpenSSL
- import requests
- import sys
- reload(sys)
- sys.setdefaultencoding("utf-8")
- logger = logging.getLogger(__name__)
- class HeNanRuralCreditUnion(object):
- DEFAULT = {
- 'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36'
- }
-
- def __init__(self, appCode, channelId, branchId, SericeCenterUrl, privateKey, publicKey):
- self.appCode = appCode
- self.channelId = channelId
- self.branchId = branchId
- self.SericeCenterUrl = SericeCenterUrl
- self._PRIVATE_KEY = privateKey
- self._PUBLIC = publicKey
-
- # def _load_pfx(self):
- # """
- # 读取证书里面的私钥
- # :return:
- # """
- # pfx = open(self.PFX_PATH, 'rb').read()
- # p12 = OpenSSL.crypto.load_pkcs12(pfx, self.PFX_PASSWD)
- # return OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey())
- #
- # def _load_pub(self):
- # """
- # 读取校验公钥
- # :return:
- # """
- # pub = open(self.PUB_PATH, 'rb').read()
- # return OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pub)
-
- def _check_empty(self, kw):
- # type: (dict) -> dict
- """
- 过滤值为空的数据
- :param kw:
- :return:
- """
- return dict(filter(lambda _: _[1] is not None, kw.items()))
-
- def _sign(self, string):
- # type: (str) -> str
- """
- 用私钥签名
- :param string:
- :return:
- """
- pri_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, self._PRIVATE_KEY)
- sign = OpenSSL.crypto.sign(pri_key, string.encode(), 'SHA256')
- return sign.encode('hex')
-
- def _verify(self, signature, plain):
- """
- 用公钥验签
- :param string:
- :return:
- """
- pub_key = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, self._PUBLIC)
- return OpenSSL.crypto.verify(pub_key, signature, plain, 'SHA256')
-
- def _connection_string(self, kw):
- # type: (dict) -> unicode
- """
- 拼接字符串
- :param kw:
- :return:
- """
- return "&".join("{}={}".format(*_) for _ in kw.items())
-
- def public_part(self):
- now = time.time()
- sequenceId = self.channelId
- sequenceId += time.strftime('%y%m%d', time.localtime(now))
- sequenceId += '{}{:>3.0f}'.format(self.appCode, now * 1000)[:24]
-
- public_part = {
- # 公共部分
- 'appCode': self.appCode,
- 'channelId': self.channelId,
- 'branchId': self.branchId,
- 'sequenceId': sequenceId,
- 'timestamp': time.strftime('%Y%m%d%H%m%S', time.localtime(now)) + '{:0>3.0f}'.format(
- (now - int(now)) * 1000),
- 'charset': 'UTF-8',
- 'algorithm': 'SHA256withRSA',
- 'deviceId': '123456',
- 'version': '1.0',
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'userId': None,
- 'userName': None,
- 'entoperId': None,
- 'entoperName': None,
- }
-
- return public_part
-
- def generate_signature(self, public, payload):
-
-
- # 公共部分
- plain_dic = {
- # 公共部分
- 'appCode': public.get('appCode'),
- 'version': public.get('version'),
- 'sequenceId': public.get('sequenceId'),
- 'timestamp': public.get('timestamp'),
- 'charset': public.get('charset'),
- 'algorithm': public.get('algorithm'),
- 'deviceId': public.get('deviceId'),
- 'channelId': public.get('channelId'),
- 'branchId': public.get('branchId'),
- }
-
- # 业务部分
- plain_dic.update(payload)
-
- plain_list = sorted(plain_dic.items(), key=lambda _: _[0])
-
- plain = urlencode(plain_list)
-
- return self._sign(plain)
-
- def verify_signature(self, public, payload):
-
- signature = public.get('signature')
- signature = signature.decode('hex')
-
- # 公共部分
- plain_dic = {
- 'code': public.get('code'),
- 'message': public.get('message'),
- 'sequenceId': public.get('sequenceId'),
- 'jnlId': public.get('jnlId'),
- 'timestamp': public.get('timestamp'),
- 'algorithm': public.get('algorithm'),
- 'charset': public.get('charset'),
- 'messagedigest': public.get('messagedigest'),
- 'serverIp': public.get('serverIp'),
- }
-
- # 业务部分
- plain_dic.update(payload)
-
- result = sorted(plain_dic.items(), key=lambda _: _[0])
-
- plain = urlencode(result)
-
- try:
- if self._verify(signature=signature, plain=plain) == None:
- return True
- except:
- import traceback
- logger.info(traceback.format_exc())
- return False
-
- def pre_pay_order(self, payload):
-
- public = self.public_part()
-
- # 组织请求头
- headers = public.copy()
- headers['transCode'] = 'hnnx.trade.sandbox.upp.qrCodePrePay'
- if not payload.get('userAgent'):
- headers['User-Agent'] = self.DEFAULT.get('userAgent')
- else:
- headers['User-Agent'] = payload.get('userAgent')
-
- payload = {
- 'MerNbr': payload.get('MerNbr'), # 原支付交易一级商户号
- 'EncryptSubMerchantId': payload.get('EncryptSubMerchantId'), # 加密二级商户号
- 'SubMerchantId': payload.get('SubMerchantId'), # 二级商户号
- 'SubMerchantName': payload.get('SubMerchantName'), # 二级商户名称
- 'MerTransDateTime': payload.get('MerTransDateTime'), # 交易时间
- 'TransAmt': payload.get('TransAmt'), # 交易金额
- 'AuthCode': payload.get('AuthCode'), # 授权code
- 'ChannelNbr': payload.get('ChannelNbr'), # 支付渠道
- 'PayTypCd': payload.get('PayTypCd'), # 支付类型
- 'OrderTitle': payload.get('OrderTitle'), # 订单标题
- 'CurrencyCd': payload.get('CurrencyCd'), # 币种
- 'MerUrl': payload.get('MerUrl'), # 异步通知Url
- 'OperatorId': payload.get('OperatorId'), # 操作员
- 'Remarks': payload.get('Remarks'), # 备注
- 'Remarks1': payload.get('Remarks1', ), # 备用字段
- 'Remarks2': payload.get('Remarks2'), # 备用字段
- 'MerSeqNbr': payload.get('MerSeqNbr'), # 支付订单号
- 'SubAppid': payload.get('SubAppid'), # 子商户公众账号ID
- }
- # logger.info('payload: {}'.format(payload))
-
- public = self._check_empty(public)
- payload = self._check_empty(payload)
-
- signature = self.generate_signature(public, payload)
-
- send_data = public
- send_data['payload'] = json.dumps(payload, sort_keys = True, separators=(',', ':'))
- send_data['signature'] = signature
-
- send_data = self._connection_string(send_data)
-
- logger.info(json.dumps(headers, ensure_ascii=False))
- logger.info(json.dumps(payload, ensure_ascii=False))
- logger.info(send_data)
-
- try:
- response = requests.post(self.SericeCenterUrl, data=send_data, headers=headers)
- except:
- raise Exception('访问出错')
- if response.status_code != 200:
- raise Exception('返回数据有问题 code: {}'.format(response.status_code))
- resp = response.json()
-
- logger.info(json.dumps(resp, ensure_ascii=False))
-
- if resp.get('code') != '000000':
- raise Exception('code: {}, msg: {}'.format(resp.get('code'), json.dumps(resp, ensure_ascii=False)))
-
- payload = json.loads(response.json().get('payload'))
- result = self.verify_signature(resp, payload)
- if not result:
- raise Exception('签名校验错误')
- else:
- return resp
-
- def get_order_status(self, payload):
-
- public = self.public_part()
- headers = public.copy()
- headers['transCode'] = 'hnnx.trade.sandbox.upp.qryQrOrderStatus'
- if not payload.get('userAgent'):
- headers['User-Agent'] = self.DEFAULT.get('userAgent')
- else:
- headers['User-Agent'] = payload.get('userAgent')
-
- payload = {
- 'MerNbr': payload.get('MerNbr'),
- 'MerSeqNbr': payload.get('MerSeqNbr'),
- }
-
- public = self._check_empty(public)
- payload = self._check_empty(payload)
-
- signature = self.generate_signature(public, payload)
-
- send_data = public
- send_data['payload'] = json.dumps(payload, sort_keys=True, separators=(',', ':'))
- send_data['signature'] = signature
-
- send_data = self._connection_string(send_data)
- logger.info(json.dumps(headers, ensure_ascii=False))
- logger.info(json.dumps(payload, ensure_ascii=False))
- logger.info(send_data)
-
- try:
- response = requests.post(self.SericeCenterUrl, data=send_data, headers=headers)
- except:
- import traceback
- logger.info(traceback.format_exc())
- raise Exception('访问出错')
-
- if response.status_code != 200:
- raise Exception('返回数据有问题')
- resp = response.json()
- logger.info(json.dumps(resp, ensure_ascii=False))
-
- if resp.get('code') != '000000':
- raise Exception('code: {}, msg: {} data:{}'.format(resp.get('code'), resp.get('message'), resp))
-
- payload = json.loads(response.json().get('payload'))
- result = self.verify_signature(resp, payload)
- if not result:
- raise Exception('签名校验错误')
- else:
- return resp
-
- def refund_order(self, payload):
-
- public = self.public_part()
-
- headers = public.copy()
- headers['transCode'] = 'hnnx.trade.sandbox.upp.returntrans'
-
- if not payload.get('userAgent'):
- headers['User-Agent'] = self.DEFAULT.get('userAgent')
- else:
- headers['User-Agent'] = payload.get('userAgent')
-
- payload = {
- 'MerNbr': payload.get('MerNbr'),
- 'MerSeqNbr': payload.get('MerSeqNbr'),
- 'MerTransDateTime': payload.get('MerTransDateTime'),
- 'OrigMerSeqNbr': payload.get('OrigMerSeqNbr'),
- 'OrigMerDate': payload.get('OrigMerDate'),
- 'OrigTransAmt': payload.get('OrigTransAmt'),
- 'SubTransAmt': payload.get('SubTransAmt'),
- 'OrigSubMerSeqNo': payload.get('OrigSubMerSeqNo'),
- 'OrigSubMerDate': payload.get('OrigSubMerDate'),
- 'SubMerchantId': payload.get('SubMerchantId'),
- 'SubMerSeqNo': payload.get('SubMerSeqNo'),
- 'SubMerDateTime': payload.get('SubMerDateTime'),
- 'OrderNbr': payload.get('OrderNbr'),
- }
-
- public = self._check_empty(public)
- payload = self._check_empty(payload)
-
- signature = self.generate_signature(public, payload)
-
- send_data = public
- send_data['payload'] = json.dumps(payload, sort_keys=True, separators=(',', ':'))
- send_data['signature'] = signature
-
- send_data = self._connection_string(send_data)
- logger.info(json.dumps(headers, ensure_ascii=False))
- logger.info(json.dumps(payload, ensure_ascii=False))
- logger.info(send_data)
-
- try:
- response = requests.post(self.SericeCenterUrl, data=send_data, headers=headers)
- except:
- raise Exception('访问出错')
-
- if response.status_code != 200:
- raise Exception('返回数据有问题')
- resp = response.json()
- logger.info(json.dumps(resp, ensure_ascii=False))
-
- if resp.get('code') != '000000':
- raise Exception('code: {}, msg: {} data:{}'.format(resp.get('code'), resp.get('message'), resp))
-
- payload = json.loads(response.json().get('payload'))
- result = self.verify_signature(resp, payload)
- if not result:
- raise Exception('签名校验错误')
- else:
- return resp
-
- def check(self, payload):
- signature = payload.pop('signature', None)
- signature = signature.decode('hex')
- result = sorted(payload.items(), key=lambda _: _[0])
- plain = urlencode(result)
- logger.info(plain)
- try:
- if self._verify(signature=signature, plain=plain) == None:
- return True
- except:
- import traceback
- logger.info(traceback.format_exc())
- return False
-
-
-
- # if __name__ == '__main__':
- # rcu = {
- # "companyName": "",
- # "appName": "测试2",
- # "remarks": "",
- # "appCode": "20200719001",
- # "MerNbr": "010020170801000001",
- # "SubMerchantId": "AA1810240024494",
- # "SubMerchantName": "test_pay",
- # "branchId": "1656199999",
- # "entoperId": "200000000000101342",
- # "channelId": "45",
- # "cert_path": "library/RuralCreditUnion/gaoan.pfx",
- # "cert_passwd": "1699999999",
- # "authCodeUrl": "https://wxbank.hnnx.com/mer/payOut/getAuthCode.do",
- # "SericeCenterUrl": "https://open.hnnx.com:8019/SericeCenter"
- # }
- # nxh = HeNanRuralCreditUnion(**rcu)
- # 公共部分
- # public = {
- # 'appCode': '00000000003',
- # 'version': '1.0',
- # 'sequenceId': '45190904000000000031234567{}'.format(random.randint(100000, 999999)), # 前台流水号
- # 'timestamp': '20190904220701873',
- # 'charset': 'UTF-8',
- # 'algorithm': 'SHA256withRSA',
- # 'deviceId': '123456',
- # 'channelId': '45',
- # 'Content-Type': 'application/x-www-form-urlencoded',
- # 'userId': None,
- # 'userName': None,
- # 'entoperId': None,
- # 'entoperName': None,
- # 'branchId': '1600299999',
- # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36'
- # }
- # # TODO 预付费接口
- # payload = {
- # 'MerNbr': '010020170801000001',
- # 'EncryptSubMerchantId': None,
- # 'SubMerchantId': 'AA1905050000819',
- # 'SubMerchantName': '测试商户',
- # 'MerTransDateTime': '20190904220701',
- # 'TransAmt': '0.01',
- # 'AuthCode': '0912bYZv3AMA8X229F3w3o78JL02bYZf',
- # 'ChannelNbr': '03',
- # 'PayTypCd': 'C',
- # 'OrderTitle': 'test_wechat_pay',
- # 'CurrencyCd': 'CNY',
- # 'MerUrl': 'https://develop.5tao5ai.com/user/authCode',
- # 'OperatorId': '200000000000101342',
- # 'Remarks': None,
- # 'Remarks1': None,
- # 'Remarks2': None,
- # 'MerSeqNbr': '20210929113354PQ00000005',
- # 'SubAppid': None
- # }
- # payload = {
- # "OrderTitle": "\\u81ea\\u52a9\\u670d\\u52a1-\\u5145\\u7535\\u6869/407614",
- # "EncryptSubMerchantId": None,
- # "Remarks1": None,
- # "OperatorId": "200000000000101342",
- # "Remarks2": None,
- # "PayTypCd": "B",
- # "MerNbr": "010020170801000001",
- # "SubMerchantId": "AA1905050000819",
- # "AuthCode": "0912bYZv3AMA8X229F3w3o78JL02bYZf",
- # "SubMerchantName": "\\u81ea\\u52a9\\u670d\\u52a1-\\u5145\\u7535\\u6869/407614",
- # "MerUrl": "https://develop.5tao5ai.com/user/wechat/finishedPay",
- # "TransAmt": "0.01",
- # "CurrencyCd": "CNY",
- # "MerTransDateTime": "20210929110955",
- # # "Remarks": "\\u81ea\\u52a9\\u670d\\u52a1-\\u5145\\u7535\\u6869/407614",
- # "SubAppid": None,
- # "MerSeqNbr": "20210929113354PQ0000000407614F2D",
- # "ChannelNbr": "03"
- # }
- # for _ in range(30):
- # res = nxh.pre_pay_order(payload)
- # print json.dumps(res, ensure_ascii=False)
- # print res.get('code')
- #
- # # result
- # result = {
- # "messagedigest": "",
- # "sequenceId": "45190904000000000031234567571034",
- # "code": "000000",
- # "algorithm": "SHA256withRSA",
- # "timestamp": "2021-09-24 17:47:09.019",
- # "charset": "UTF-8",
- # "serverIp": "",
- # "jnlId": "578400991112863744",
- # "message": "交易成功",
- # "payload": "{\"MerSeqNbr\":\"1234564545451212121512213\",\"AppId\":\"wx2421b1c4370ec43b\",\"InnerFundTransNbr\":\"000013802361\",\"TransSeqNbr\":\"202109240020305052\",\"Charset\":\"fb311c7377bb4ddfaf5b0f793c0bf1b9\",\"Sign\":\"dDXH8+ldwt61Z7Ly2VwSqMNRRnTO8i/1UcLzqw/3nYrC51Xgdy/SbpnacDo92U9pfBAO5lplHP+pAHQGLH6h61lYAqV8DNVLExO4gV5sGSgML5FL62pVx6HqTDxQVjvaMsRwaJb/SEGRGn4q4xqXFDydkEBXwaHpqaYPmuwtUQhWcvt/QKsUvz20u8nHMbuIcA3Vn78IkMEImrSUMPzD1lINCDWN7UTXYwXWs9JkslwNdzX7bI+EyUPPd2Cj5v91W9w3foiOQe354K6xMLIwjiQfDY7l9//JbJqmp4w1E0HopUOQEb6xy3vZUXZ4TCcj+6j/2jmYDey92Ueq4f869A==\",\"Subject\":\"prepay_id=wx2416474387379398553e0411ec39e20000\",\"TimeStamp\":\"1632473263\",\"SignType\":\"RSA\"}"
- # }
- #
- # # TODO 查询接口
- # time.sleep(1)
- # payload = {
- # 'MerNbr': '010020170801000001',
- # 'MerSeqNbr': '20210930140930PQ87390576551893U9',
- # }
- # print json.dumps(nxh.get_order_status(payload), ensure_ascii=False)
- #
- # result = {
- # "messagedigest": "",
- # "sequenceId": "45190904000000000031234567584924",
- # "code": "000000",
- # "algorithm": "SHA256withRSA",
- # "timestamp": "2021-09-24 17:48:43.251",
- # "charset": "UTF-8",
- # "serverIp": "",
- # "jnlId": "578401388393144320",
- # "message": "交易成功",
- # "payload": "{\"InnerFundTransNbr\":\"000013802361\",\"TransSeqNbr\":\"202109240020305052\",\"TransStatus\":\"5\",\"PayTypCd\":\"C\"}"
- # }
- #
- # result = {
- # "messagedigest": "",
- # "sequenceId": "45190904000000000031234567540500",
- # "code": "000000",
- # "algorithm": "SHA256withRSA",
- # "timestamp": "2021-09-24 17:55:44.683",
- # "charset": "UTF-8",
- # "serverIp": "",
- # "jnlId": "578403156082565120",
- # "message": "交易成功",
- # "payload": "{\"InnerFundTransNbr\":\"000013802361\",\"TransSeqNbr\":\"202109240020305052\",\"TransStatus\":\"1\",\"PayTypCd\":\"C\"}"
- # }
- #
- # # TODO 退费接口
- # payload = {
- # # "MerNbr": "010020170801000001", # 原支付交易一级商户号
- # "SubMerchantId": "AA1905050000819", # 原支付交易二级商户号
- #
- # "MerSeqNbr": "123456454545121212151221375", # 退货交易一级商户交易流水号(随机生成,不允许出现重复)
- # "SubMerSeqNo": "1234564545451212121512215", # 退货二级商户流水号(随机生成,不允许出现重复,可以和一级商户交易流水号保持一致)
- #
- # "OrigMerSeqNbr": "12345645454512121215122137", # 原支付交易一级商户号流水号
- # "OrderNbr": "12345645454512121215122137", # 订单号 与二级商户流水号保持一致
- # "OrigSubMerSeqNo": "12345645454512121215122137", # 原二级商户支付流水号(与原支付交易一级商户号流水号一致)
- #
- # "OrigTransAmt": "0.01", # 原支付交易总金额
- # "SubTransAmt": "0.01", # 二级商户退货交易退货金额
- #
- # "OrigMerDate": "20210928", # 原支付交易日期(例:20190810)
- # "OrigSubMerDate": "20210928", # 原二级商户支付交易日期(例:20190810)
- #
- # "MerTransDateTime": "20210928002030", # 商户时间戳 退货交易时间
- # "SubMerDateTime": "20210928112217", # 二级商户退货交易时间
- #
- # }
- # print json.dumps(nxh.refund_order(payload), ensure_ascii=False)
|