yspay.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. from __future__ import unicode_literals
  4. import hashlib
  5. import requests
  6. import simplejson as json
  7. import six
  8. from library import to_binary, to_text
  9. from library.ys.base import YsErrorCode, PayOpt, TradeType
  10. from .exceptions import YsPayException, YsCommuException
  11. class YsPay(object):
  12. PAY_HOST_DEV = 'https://open.eycard.cn:8443/WorthTech_Access_AppPaySystemV2/apppayacc'
  13. PAY_HOST = 'https://open.eycard.cn:8443/WorthTech_Access_AppPaySystemV2/apppayacc'
  14. def __str__(self):
  15. _repr = '{kclass}(channelId: {channelId}, merId: {merId})'.format(
  16. kclass = self.__class__.__name__,
  17. channelId = self.channelId,
  18. merId = self.merId)
  19. if six.PY2:
  20. return to_binary(_repr)
  21. else:
  22. return to_text(_repr)
  23. def __repr__(self):
  24. return str(self)
  25. def __init__(self, channelId, merId, termId, key, debug = False):
  26. self.channelId = channelId
  27. self.merId = merId
  28. self.termId = termId
  29. self.key = key
  30. self.debug = debug
  31. if self.debug:
  32. self.host_url = YsPay.PAY_HOST_DEV
  33. else:
  34. self.host_url = YsPay.PAY_HOST
  35. def get(self, **kwargs):
  36. return self._request(
  37. url = self.host_url,
  38. method = 'get',
  39. **kwargs
  40. )
  41. def post(self, **kwargs):
  42. return self._request(
  43. url = self.host_url,
  44. method = 'post',
  45. **kwargs
  46. )
  47. def _decode_result(self, res):
  48. try:
  49. result = json.loads(res.content.decode('utf-8', 'ignore'), strict = False)
  50. except (TypeError, ValueError):
  51. return res
  52. return result
  53. def _handle_result(self, res, method = None, url = None,
  54. result_processor = None, **kwargs):
  55. if not isinstance(res, dict):
  56. result = self._decode_result(res)
  57. else:
  58. result = res
  59. if result['resultcode'] not in ['00', '98', 'AA']:
  60. raise YsPayException(
  61. errCode = result['resultcode'],
  62. errMsg = result.get('returnmsg', 'unknown'),
  63. client = self)
  64. return result if not result_processor else result_processor(result)
  65. def _request(self, url, method, **kwargs):
  66. headers = {'Content-Type': 'application/json;charset=utf-8'}
  67. if isinstance(kwargs.get('data', ''), dict):
  68. body = json.dumps(kwargs['data'], ensure_ascii = False)
  69. body = body.encode('utf-8')
  70. kwargs['data'] = body
  71. kwargs['timeout'] = kwargs.get('timeout', 15)
  72. result_processor = kwargs.pop('result_processor', None)
  73. with requests.sessions.Session() as session:
  74. res = session.request(
  75. url = url,
  76. method = method,
  77. headers = headers,
  78. **kwargs
  79. )
  80. try:
  81. res.raise_for_status()
  82. except requests.RequestException as reqe:
  83. raise YsCommuException(
  84. errCode = 'HTTP{}'.format(res.status_code),
  85. errMsg = reqe.message,
  86. result_code = '',
  87. client = self,
  88. request = reqe.request,
  89. response = reqe.response)
  90. return self._handle_result(
  91. res, method, self.host_url, result_processor, **kwargs
  92. )
  93. def sign(self, raw, order = True, token = True):
  94. if order:
  95. raw = [(k, str(raw[k])) for k in sorted(raw.keys())]
  96. else:
  97. raw = [(k, str(raw[k])) for k in raw.keys()]
  98. s = '&'.join('='.join(kv) for kv in raw)
  99. if token:
  100. s += '&key={0}'.format(self.key)
  101. return hashlib.md5(s).hexdigest().upper()
  102. def unified_order(self, pay_opt, openId, out_trade_no, total_fee, subject, notify_url, **data):
  103. """
  104. 统一下单
  105. """
  106. if pay_opt not in [PayOpt.wxPreOrder, PayOpt.apPreOrder]:
  107. raise YsPayException(errCode = YsErrorCode.MY_INVALID_PARAMETER, errMsg = u'不支持的支付类型')
  108. if not openId:
  109. raise YsPayException(errCode = YsErrorCode.MY_INVALID_PARAMETER, errMsg = u'缺少openId参数')
  110. data = {
  111. 'channelid': self.channelId,
  112. 'merid': self.merId,
  113. 'termid': self.termId,
  114. 'opt': pay_opt,
  115. 'tradetype': TradeType.JSAPI,
  116. 'tradetrace': out_trade_no,
  117. 'tradeamt': total_fee,
  118. 'body': subject,
  119. 'openid': openId,
  120. 'notifyurl': notify_url
  121. }
  122. sign = self.sign(data)
  123. data.update({
  124. 'sign': sign
  125. })
  126. from six.moves.urllib import parse
  127. params = parse.urlencode(data)
  128. result = self.get(**{
  129. 'params': params
  130. })
  131. if pay_opt == PayOpt.wxPreOrder:
  132. return result['wtorderid'], json.loads(result['prepayid'])
  133. if pay_opt == PayOpt.apPreOrder:
  134. return result['wtorderid'], {'tradeNO': str(result['prepayid'])}
  135. assert True, 'can not execute this line.'
  136. def api_trade_query(self, out_trade_no = None, trade_no = None):
  137. assert out_trade_no, 'out trade no must not be empty'
  138. data = {
  139. 'channelid': self.channelId,
  140. 'merid': self.merId,
  141. 'termid': self.termId,
  142. 'opt': PayOpt.tradeQuery,
  143. 'tradetrace': out_trade_no
  144. }
  145. sign = self.sign(data)
  146. data.update({
  147. 'sign': sign
  148. })
  149. from six.moves.urllib import parse
  150. params = parse.urlencode(data)
  151. result = self.get(**{
  152. 'params': params
  153. })
  154. return result