pay.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import json
  4. import logging
  5. import time
  6. from urllib import urlencode
  7. import OpenSSL
  8. import requests
  9. import sys
  10. reload(sys)
  11. sys.setdefaultencoding("utf-8")
  12. logger = logging.getLogger(__name__)
  13. class HeNanRuralCreditUnion(object):
  14. DEFAULT = {
  15. 'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36'
  16. }
  17. def __init__(self, appCode, channelId, branchId, SericeCenterUrl, privateKey, publicKey):
  18. self.appCode = appCode
  19. self.channelId = channelId
  20. self.branchId = branchId
  21. self.SericeCenterUrl = SericeCenterUrl
  22. self._PRIVATE_KEY = privateKey
  23. self._PUBLIC = publicKey
  24. # def _load_pfx(self):
  25. # """
  26. # 读取证书里面的私钥
  27. # :return:
  28. # """
  29. # pfx = open(self.PFX_PATH, 'rb').read()
  30. # p12 = OpenSSL.crypto.load_pkcs12(pfx, self.PFX_PASSWD)
  31. # return OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey())
  32. #
  33. # def _load_pub(self):
  34. # """
  35. # 读取校验公钥
  36. # :return:
  37. # """
  38. # pub = open(self.PUB_PATH, 'rb').read()
  39. # return OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pub)
  40. def _check_empty(self, kw):
  41. # type: (dict) -> dict
  42. """
  43. 过滤值为空的数据
  44. :param kw:
  45. :return:
  46. """
  47. return dict(filter(lambda _: _[1] is not None, kw.items()))
  48. def _sign(self, string):
  49. # type: (str) -> str
  50. """
  51. 用私钥签名
  52. :param string:
  53. :return:
  54. """
  55. pri_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, self._PRIVATE_KEY)
  56. sign = OpenSSL.crypto.sign(pri_key, string.encode(), 'SHA256')
  57. return sign.encode('hex')
  58. def _verify(self, signature, plain):
  59. """
  60. 用公钥验签
  61. :param string:
  62. :return:
  63. """
  64. pub_key = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, self._PUBLIC)
  65. return OpenSSL.crypto.verify(pub_key, signature, plain, 'SHA256')
  66. def _connection_string(self, kw):
  67. # type: (dict) -> unicode
  68. """
  69. 拼接字符串
  70. :param kw:
  71. :return:
  72. """
  73. return "&".join("{}={}".format(*_) for _ in kw.items())
  74. def public_part(self):
  75. now = time.time()
  76. sequenceId = self.channelId
  77. sequenceId += time.strftime('%y%m%d', time.localtime(now))
  78. sequenceId += '{}{:>3.0f}'.format(self.appCode, now * 1000)[:24]
  79. public_part = {
  80. # 公共部分
  81. 'appCode': self.appCode,
  82. 'channelId': self.channelId,
  83. 'branchId': self.branchId,
  84. 'sequenceId': sequenceId,
  85. 'timestamp': time.strftime('%Y%m%d%H%m%S', time.localtime(now)) + '{:0>3.0f}'.format(
  86. (now - int(now)) * 1000),
  87. 'charset': 'UTF-8',
  88. 'algorithm': 'SHA256withRSA',
  89. 'deviceId': '123456',
  90. 'version': '1.0',
  91. 'Content-Type': 'application/x-www-form-urlencoded',
  92. 'userId': None,
  93. 'userName': None,
  94. 'entoperId': None,
  95. 'entoperName': None,
  96. }
  97. return public_part
  98. def generate_signature(self, public, payload):
  99. # 公共部分
  100. plain_dic = {
  101. # 公共部分
  102. 'appCode': public.get('appCode'),
  103. 'version': public.get('version'),
  104. 'sequenceId': public.get('sequenceId'),
  105. 'timestamp': public.get('timestamp'),
  106. 'charset': public.get('charset'),
  107. 'algorithm': public.get('algorithm'),
  108. 'deviceId': public.get('deviceId'),
  109. 'channelId': public.get('channelId'),
  110. 'branchId': public.get('branchId'),
  111. }
  112. # 业务部分
  113. plain_dic.update(payload)
  114. plain_list = sorted(plain_dic.items(), key=lambda _: _[0])
  115. plain = urlencode(plain_list)
  116. return self._sign(plain)
  117. def verify_signature(self, public, payload):
  118. signature = public.get('signature')
  119. signature = signature.decode('hex')
  120. # 公共部分
  121. plain_dic = {
  122. 'code': public.get('code'),
  123. 'message': public.get('message'),
  124. 'sequenceId': public.get('sequenceId'),
  125. 'jnlId': public.get('jnlId'),
  126. 'timestamp': public.get('timestamp'),
  127. 'algorithm': public.get('algorithm'),
  128. 'charset': public.get('charset'),
  129. 'messagedigest': public.get('messagedigest'),
  130. 'serverIp': public.get('serverIp'),
  131. }
  132. # 业务部分
  133. plain_dic.update(payload)
  134. result = sorted(plain_dic.items(), key=lambda _: _[0])
  135. plain = urlencode(result)
  136. try:
  137. if self._verify(signature=signature, plain=plain) == None:
  138. return True
  139. except:
  140. import traceback
  141. logger.info(traceback.format_exc())
  142. return False
  143. def pre_pay_order(self, payload):
  144. public = self.public_part()
  145. # 组织请求头
  146. headers = public.copy()
  147. headers['transCode'] = 'hnnx.trade.sandbox.upp.qrCodePrePay'
  148. if not payload.get('userAgent'):
  149. headers['User-Agent'] = self.DEFAULT.get('userAgent')
  150. else:
  151. headers['User-Agent'] = payload.get('userAgent')
  152. payload = {
  153. 'MerNbr': payload.get('MerNbr'), # 原支付交易一级商户号
  154. 'EncryptSubMerchantId': payload.get('EncryptSubMerchantId'), # 加密二级商户号
  155. 'SubMerchantId': payload.get('SubMerchantId'), # 二级商户号
  156. 'SubMerchantName': payload.get('SubMerchantName'), # 二级商户名称
  157. 'MerTransDateTime': payload.get('MerTransDateTime'), # 交易时间
  158. 'TransAmt': payload.get('TransAmt'), # 交易金额
  159. 'AuthCode': payload.get('AuthCode'), # 授权code
  160. 'ChannelNbr': payload.get('ChannelNbr'), # 支付渠道
  161. 'PayTypCd': payload.get('PayTypCd'), # 支付类型
  162. 'OrderTitle': payload.get('OrderTitle'), # 订单标题
  163. 'CurrencyCd': payload.get('CurrencyCd'), # 币种
  164. 'MerUrl': payload.get('MerUrl'), # 异步通知Url
  165. 'OperatorId': payload.get('OperatorId'), # 操作员
  166. 'Remarks': payload.get('Remarks'), # 备注
  167. 'Remarks1': payload.get('Remarks1', ), # 备用字段
  168. 'Remarks2': payload.get('Remarks2'), # 备用字段
  169. 'MerSeqNbr': payload.get('MerSeqNbr'), # 支付订单号
  170. 'SubAppid': payload.get('SubAppid'), # 子商户公众账号ID
  171. }
  172. # logger.info('payload: {}'.format(payload))
  173. public = self._check_empty(public)
  174. payload = self._check_empty(payload)
  175. signature = self.generate_signature(public, payload)
  176. send_data = public
  177. send_data['payload'] = json.dumps(payload, sort_keys = True, separators=(',', ':'))
  178. send_data['signature'] = signature
  179. send_data = self._connection_string(send_data)
  180. logger.info(json.dumps(headers, ensure_ascii=False))
  181. logger.info(json.dumps(payload, ensure_ascii=False))
  182. logger.info(send_data)
  183. try:
  184. response = requests.post(self.SericeCenterUrl, data=send_data, headers=headers)
  185. except:
  186. raise Exception('访问出错')
  187. if response.status_code != 200:
  188. raise Exception('返回数据有问题 code: {}'.format(response.status_code))
  189. resp = response.json()
  190. logger.info(json.dumps(resp, ensure_ascii=False))
  191. if resp.get('code') != '000000':
  192. raise Exception('code: {}, msg: {}'.format(resp.get('code'), json.dumps(resp, ensure_ascii=False)))
  193. payload = json.loads(response.json().get('payload'))
  194. result = self.verify_signature(resp, payload)
  195. if not result:
  196. raise Exception('签名校验错误')
  197. else:
  198. return resp
  199. def get_order_status(self, payload):
  200. public = self.public_part()
  201. headers = public.copy()
  202. headers['transCode'] = 'hnnx.trade.sandbox.upp.qryQrOrderStatus'
  203. if not payload.get('userAgent'):
  204. headers['User-Agent'] = self.DEFAULT.get('userAgent')
  205. else:
  206. headers['User-Agent'] = payload.get('userAgent')
  207. payload = {
  208. 'MerNbr': payload.get('MerNbr'),
  209. 'MerSeqNbr': payload.get('MerSeqNbr'),
  210. }
  211. public = self._check_empty(public)
  212. payload = self._check_empty(payload)
  213. signature = self.generate_signature(public, payload)
  214. send_data = public
  215. send_data['payload'] = json.dumps(payload, sort_keys=True, separators=(',', ':'))
  216. send_data['signature'] = signature
  217. send_data = self._connection_string(send_data)
  218. logger.info(json.dumps(headers, ensure_ascii=False))
  219. logger.info(json.dumps(payload, ensure_ascii=False))
  220. logger.info(send_data)
  221. try:
  222. response = requests.post(self.SericeCenterUrl, data=send_data, headers=headers)
  223. except:
  224. import traceback
  225. logger.info(traceback.format_exc())
  226. raise Exception('访问出错')
  227. if response.status_code != 200:
  228. raise Exception('返回数据有问题')
  229. resp = response.json()
  230. logger.info(json.dumps(resp, ensure_ascii=False))
  231. if resp.get('code') != '000000':
  232. raise Exception('code: {}, msg: {} data:{}'.format(resp.get('code'), resp.get('message'), resp))
  233. payload = json.loads(response.json().get('payload'))
  234. result = self.verify_signature(resp, payload)
  235. if not result:
  236. raise Exception('签名校验错误')
  237. else:
  238. return resp
  239. def refund_order(self, payload):
  240. public = self.public_part()
  241. headers = public.copy()
  242. headers['transCode'] = 'hnnx.trade.sandbox.upp.returntrans'
  243. if not payload.get('userAgent'):
  244. headers['User-Agent'] = self.DEFAULT.get('userAgent')
  245. else:
  246. headers['User-Agent'] = payload.get('userAgent')
  247. payload = {
  248. 'MerNbr': payload.get('MerNbr'),
  249. 'MerSeqNbr': payload.get('MerSeqNbr'),
  250. 'MerTransDateTime': payload.get('MerTransDateTime'),
  251. 'OrigMerSeqNbr': payload.get('OrigMerSeqNbr'),
  252. 'OrigMerDate': payload.get('OrigMerDate'),
  253. 'OrigTransAmt': payload.get('OrigTransAmt'),
  254. 'SubTransAmt': payload.get('SubTransAmt'),
  255. 'OrigSubMerSeqNo': payload.get('OrigSubMerSeqNo'),
  256. 'OrigSubMerDate': payload.get('OrigSubMerDate'),
  257. 'SubMerchantId': payload.get('SubMerchantId'),
  258. 'SubMerSeqNo': payload.get('SubMerSeqNo'),
  259. 'SubMerDateTime': payload.get('SubMerDateTime'),
  260. 'OrderNbr': payload.get('OrderNbr'),
  261. }
  262. public = self._check_empty(public)
  263. payload = self._check_empty(payload)
  264. signature = self.generate_signature(public, payload)
  265. send_data = public
  266. send_data['payload'] = json.dumps(payload, sort_keys=True, separators=(',', ':'))
  267. send_data['signature'] = signature
  268. send_data = self._connection_string(send_data)
  269. logger.info(json.dumps(headers, ensure_ascii=False))
  270. logger.info(json.dumps(payload, ensure_ascii=False))
  271. logger.info(send_data)
  272. try:
  273. response = requests.post(self.SericeCenterUrl, data=send_data, headers=headers)
  274. except:
  275. raise Exception('访问出错')
  276. if response.status_code != 200:
  277. raise Exception('返回数据有问题')
  278. resp = response.json()
  279. logger.info(json.dumps(resp, ensure_ascii=False))
  280. if resp.get('code') != '000000':
  281. raise Exception('code: {}, msg: {} data:{}'.format(resp.get('code'), resp.get('message'), resp))
  282. payload = json.loads(response.json().get('payload'))
  283. result = self.verify_signature(resp, payload)
  284. if not result:
  285. raise Exception('签名校验错误')
  286. else:
  287. return resp
  288. def check(self, payload):
  289. signature = payload.pop('signature', None)
  290. signature = signature.decode('hex')
  291. result = sorted(payload.items(), key=lambda _: _[0])
  292. plain = urlencode(result)
  293. logger.info(plain)
  294. try:
  295. if self._verify(signature=signature, plain=plain) == None:
  296. return True
  297. except:
  298. import traceback
  299. logger.info(traceback.format_exc())
  300. return False
  301. # if __name__ == '__main__':
  302. # rcu = {
  303. # "companyName": "",
  304. # "appName": "测试2",
  305. # "remarks": "",
  306. # "appCode": "20200719001",
  307. # "MerNbr": "010020170801000001",
  308. # "SubMerchantId": "AA1810240024494",
  309. # "SubMerchantName": "test_pay",
  310. # "branchId": "1656199999",
  311. # "entoperId": "200000000000101342",
  312. # "channelId": "45",
  313. # "cert_path": "library/RuralCreditUnion/gaoan.pfx",
  314. # "cert_passwd": "1699999999",
  315. # "authCodeUrl": "https://wxbank.hnnx.com/mer/payOut/getAuthCode.do",
  316. # "SericeCenterUrl": "https://open.hnnx.com:8019/SericeCenter"
  317. # }
  318. # nxh = HeNanRuralCreditUnion(**rcu)
  319. # 公共部分
  320. # public = {
  321. # 'appCode': '00000000003',
  322. # 'version': '1.0',
  323. # 'sequenceId': '45190904000000000031234567{}'.format(random.randint(100000, 999999)), # 前台流水号
  324. # 'timestamp': '20190904220701873',
  325. # 'charset': 'UTF-8',
  326. # 'algorithm': 'SHA256withRSA',
  327. # 'deviceId': '123456',
  328. # 'channelId': '45',
  329. # 'Content-Type': 'application/x-www-form-urlencoded',
  330. # 'userId': None,
  331. # 'userName': None,
  332. # 'entoperId': None,
  333. # 'entoperName': None,
  334. # 'branchId': '1600299999',
  335. # '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'
  336. # }
  337. # # TODO 预付费接口
  338. # payload = {
  339. # 'MerNbr': '010020170801000001',
  340. # 'EncryptSubMerchantId': None,
  341. # 'SubMerchantId': 'AA1905050000819',
  342. # 'SubMerchantName': '测试商户',
  343. # 'MerTransDateTime': '20190904220701',
  344. # 'TransAmt': '0.01',
  345. # 'AuthCode': '0912bYZv3AMA8X229F3w3o78JL02bYZf',
  346. # 'ChannelNbr': '03',
  347. # 'PayTypCd': 'C',
  348. # 'OrderTitle': 'test_wechat_pay',
  349. # 'CurrencyCd': 'CNY',
  350. # 'MerUrl': 'https://develop.5tao5ai.com/user/authCode',
  351. # 'OperatorId': '200000000000101342',
  352. # 'Remarks': None,
  353. # 'Remarks1': None,
  354. # 'Remarks2': None,
  355. # 'MerSeqNbr': '20210929113354PQ00000005',
  356. # 'SubAppid': None
  357. # }
  358. # payload = {
  359. # "OrderTitle": "\\u81ea\\u52a9\\u670d\\u52a1-\\u5145\\u7535\\u6869/407614",
  360. # "EncryptSubMerchantId": None,
  361. # "Remarks1": None,
  362. # "OperatorId": "200000000000101342",
  363. # "Remarks2": None,
  364. # "PayTypCd": "B",
  365. # "MerNbr": "010020170801000001",
  366. # "SubMerchantId": "AA1905050000819",
  367. # "AuthCode": "0912bYZv3AMA8X229F3w3o78JL02bYZf",
  368. # "SubMerchantName": "\\u81ea\\u52a9\\u670d\\u52a1-\\u5145\\u7535\\u6869/407614",
  369. # "MerUrl": "https://develop.5tao5ai.com/user/wechat/finishedPay",
  370. # "TransAmt": "0.01",
  371. # "CurrencyCd": "CNY",
  372. # "MerTransDateTime": "20210929110955",
  373. # # "Remarks": "\\u81ea\\u52a9\\u670d\\u52a1-\\u5145\\u7535\\u6869/407614",
  374. # "SubAppid": None,
  375. # "MerSeqNbr": "20210929113354PQ0000000407614F2D",
  376. # "ChannelNbr": "03"
  377. # }
  378. # for _ in range(30):
  379. # res = nxh.pre_pay_order(payload)
  380. # print json.dumps(res, ensure_ascii=False)
  381. # print res.get('code')
  382. #
  383. # # result
  384. # result = {
  385. # "messagedigest": "",
  386. # "sequenceId": "45190904000000000031234567571034",
  387. # "code": "000000",
  388. # "algorithm": "SHA256withRSA",
  389. # "timestamp": "2021-09-24 17:47:09.019",
  390. # "charset": "UTF-8",
  391. # "serverIp": "",
  392. # "jnlId": "578400991112863744",
  393. # "message": "交易成功",
  394. # "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\"}"
  395. # }
  396. #
  397. # # TODO 查询接口
  398. # time.sleep(1)
  399. # payload = {
  400. # 'MerNbr': '010020170801000001',
  401. # 'MerSeqNbr': '20210930140930PQ87390576551893U9',
  402. # }
  403. # print json.dumps(nxh.get_order_status(payload), ensure_ascii=False)
  404. #
  405. # result = {
  406. # "messagedigest": "",
  407. # "sequenceId": "45190904000000000031234567584924",
  408. # "code": "000000",
  409. # "algorithm": "SHA256withRSA",
  410. # "timestamp": "2021-09-24 17:48:43.251",
  411. # "charset": "UTF-8",
  412. # "serverIp": "",
  413. # "jnlId": "578401388393144320",
  414. # "message": "交易成功",
  415. # "payload": "{\"InnerFundTransNbr\":\"000013802361\",\"TransSeqNbr\":\"202109240020305052\",\"TransStatus\":\"5\",\"PayTypCd\":\"C\"}"
  416. # }
  417. #
  418. # result = {
  419. # "messagedigest": "",
  420. # "sequenceId": "45190904000000000031234567540500",
  421. # "code": "000000",
  422. # "algorithm": "SHA256withRSA",
  423. # "timestamp": "2021-09-24 17:55:44.683",
  424. # "charset": "UTF-8",
  425. # "serverIp": "",
  426. # "jnlId": "578403156082565120",
  427. # "message": "交易成功",
  428. # "payload": "{\"InnerFundTransNbr\":\"000013802361\",\"TransSeqNbr\":\"202109240020305052\",\"TransStatus\":\"1\",\"PayTypCd\":\"C\"}"
  429. # }
  430. #
  431. # # TODO 退费接口
  432. # payload = {
  433. # # "MerNbr": "010020170801000001", # 原支付交易一级商户号
  434. # "SubMerchantId": "AA1905050000819", # 原支付交易二级商户号
  435. #
  436. # "MerSeqNbr": "123456454545121212151221375", # 退货交易一级商户交易流水号(随机生成,不允许出现重复)
  437. # "SubMerSeqNo": "1234564545451212121512215", # 退货二级商户流水号(随机生成,不允许出现重复,可以和一级商户交易流水号保持一致)
  438. #
  439. # "OrigMerSeqNbr": "12345645454512121215122137", # 原支付交易一级商户号流水号
  440. # "OrderNbr": "12345645454512121215122137", # 订单号 与二级商户流水号保持一致
  441. # "OrigSubMerSeqNo": "12345645454512121215122137", # 原二级商户支付流水号(与原支付交易一级商户号流水号一致)
  442. #
  443. # "OrigTransAmt": "0.01", # 原支付交易总金额
  444. # "SubTransAmt": "0.01", # 二级商户退货交易退货金额
  445. #
  446. # "OrigMerDate": "20210928", # 原支付交易日期(例:20190810)
  447. # "OrigSubMerDate": "20210928", # 原二级商户支付交易日期(例:20190810)
  448. #
  449. # "MerTransDateTime": "20210928002030", # 商户时间戳 退货交易时间
  450. # "SubMerDateTime": "20210928112217", # 二级商户退货交易时间
  451. #
  452. # }
  453. # print json.dumps(nxh.refund_order(payload), ensure_ascii=False)