pay.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. from __future__ import unicode_literals
  4. import hashlib
  5. import datetime
  6. import requests
  7. import simplejson as json
  8. from collections import OrderedDict
  9. import six
  10. from library import to_binary, to_text
  11. from .exceptions import SaobeiException, SaobeiValidationError, SaobeiSignatureError
  12. __all__ = ('SaobeiPay')
  13. FAIL = '02'
  14. SUCCESS = '01'
  15. class SaobeiPay(object):
  16. PAY_HOST_DEV = 'http://test.lcsw.cn:8045/lcsw'
  17. PAY_HOST = 'https://pay.lcsw.cn/lcsw'
  18. def __str__(self):
  19. _repr = '{kclass}(merchant_no: {merchant_no}, terminal_id: {terminal_id})'.format(
  20. kclass = self.__class__.__name__,
  21. merchant_no = self.merchant_no,
  22. terminal_id = self.terminal_id)
  23. if six.PY2:
  24. return to_binary(_repr)
  25. else:
  26. return to_text(_repr)
  27. def __repr__(self):
  28. return str(self)
  29. def __init__(self, merchant_no, terminal_id, access_token, debug = False):
  30. self.merchant_no = merchant_no
  31. self.terminal_id = terminal_id
  32. self.access_token = access_token
  33. self.debug = debug
  34. if self.debug:
  35. self.host_url = SaobeiPay.PAY_HOST_DEV
  36. else:
  37. self.host_url = SaobeiPay.PAY_HOST
  38. def sign(self, raw, order = True, token = True):
  39. if order:
  40. raw = [(k, str(raw[k])) for k in sorted(raw.keys())]
  41. else:
  42. raw = [(k, str(raw[k])) for k in raw.keys()]
  43. s = '&'.join('='.join(kv) for kv in raw)
  44. if token:
  45. s += '&access_token={0}'.format(self.access_token)
  46. return hashlib.md5(s).hexdigest()
  47. def check(self, data):
  48. sign = data.pop('key_sign')
  49. return sign == self.sign(data)
  50. def get(self, endpoint, **kwargs):
  51. return self._request(
  52. method = 'get',
  53. endpoint = endpoint,
  54. **kwargs
  55. )
  56. def post(self, endpoint, **kwargs):
  57. return self._request(
  58. method = 'post',
  59. endpoint = endpoint,
  60. **kwargs
  61. )
  62. def _decode_result(self, res):
  63. try:
  64. result = json.loads(res.content.decode('utf-8', 'ignore'), strict = False)
  65. except (TypeError, ValueError):
  66. return res
  67. return result
  68. def _handle_result(self, res, method = None, url = None,
  69. result_processor = None, **kwargs):
  70. if not isinstance(res, dict):
  71. result = self._decode_result(res)
  72. else:
  73. result = res
  74. if result['return_code'] == '02':
  75. raise SaobeiException(
  76. return_code = '02',
  77. return_msg = result['return_msg'],
  78. result_code = result['result_code'] if 'result_code' in result else '',
  79. client = self,
  80. request = None,
  81. response = None)
  82. return result if not result_processor else result_processor(result)
  83. def _request(self, method, endpoint, **kwargs):
  84. url = '{base}{endpoint}'.format(
  85. base = self.host_url,
  86. endpoint = endpoint
  87. )
  88. headers = {'Content-Type': 'application/json'}
  89. if isinstance(kwargs.get('data', ''), dict):
  90. body = json.dumps(kwargs['data'], ensure_ascii = False)
  91. body = body.encode('utf-8')
  92. kwargs['data'] = body
  93. kwargs['timeout'] = kwargs.get('timeout', 15)
  94. result_processor = kwargs.pop('result_processor', None)
  95. with requests.sessions.Session() as session:
  96. res = session.request(
  97. url = url,
  98. method = method,
  99. headers = headers,
  100. **kwargs
  101. )
  102. try:
  103. res.raise_for_status()
  104. except requests.RequestException as reqe:
  105. raise SaobeiException(
  106. return_code = 'HTTP{}'.format(res.status_code),
  107. return_msg = reqe.message,
  108. result_code = '',
  109. client = self,
  110. request = reqe.request,
  111. response = reqe.response
  112. )
  113. return self._handle_result(
  114. res, method, url, result_processor, **kwargs
  115. )
  116. def generate_wap_pay_url(self, pay_trace, pay_time, total_fee, **data):
  117. """
  118. 统一下单
  119. """
  120. url = self.host_url + '/open/wap/110/pay'
  121. data.update({
  122. 'merchant_no': str(self.merchant_no),
  123. 'terminal_id': str(self.terminal_id),
  124. 'terminal_trace': str(pay_trace),
  125. 'terminal_time': str(pay_time),
  126. 'total_fee': str(total_fee)
  127. })
  128. data.setdefault('auto_pay', '0')
  129. data.setdefault('repeated_trace', '0')
  130. data.setdefault('key_sign', self.sign(data))
  131. from six.moves.urllib import parse
  132. return '{path}?{params}'.format(path = url, params = parse.urlencode(data))
  133. def api_trade_query(self, pay_type, pay_trace, pay_time):
  134. '''
  135. 查询订单
  136. '''
  137. # 查询流水号就设置为商户订单号
  138. data = OrderedDict()
  139. data['pay_ver'] = '100'
  140. data['pay_type'] = pay_type
  141. data['service_id'] = '020'
  142. data['merchant_no'] = self.merchant_no
  143. data['terminal_id'] = self.terminal_id
  144. data['terminal_trace'] = pay_trace
  145. data['terminal_time'] = str(datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
  146. data['out_trade_no'] = ''
  147. data.update({'key_sign': self.sign(data, order = False)})
  148. data['pay_trace'] = pay_trace
  149. data['pay_time'] = pay_time
  150. result = self.post(endpoint = '/pay/100/query', data = data)
  151. if self.merchant_no != result['merchant_no']:
  152. raise SaobeiValidationError(errmsg = u'无效的merchant_no',
  153. lvalue = str(self.merchant_no),
  154. rvalue = str(result['merchant_no']),
  155. client = self)
  156. if self.terminal_id != result['terminal_id']:
  157. raise SaobeiValidationError(errmsg = u'无效的terminal_id',
  158. lvalue = str(self.terminal_id),
  159. rvalue = str(result['terminal_id']),
  160. client = self)
  161. key_sign = result['key_sign']
  162. sign_data = OrderedDict()
  163. sign_data['return_code'] = result['return_code']
  164. sign_data['return_msg'] = result['return_msg']
  165. sign_data['result_code'] = result['result_code']
  166. sign_data['pay_type'] = result['pay_type']
  167. sign_data['merchant_name'] = result['merchant_name']
  168. sign_data['merchant_no'] = result['merchant_no']
  169. sign_data['terminal_id'] = result['terminal_id']
  170. sign_data['terminal_trace'] = result['terminal_trace']
  171. sign_data['terminal_time'] = result['terminal_time']
  172. sign_data['total_fee'] = result['total_fee']
  173. sign_data['end_time'] = result['end_time']
  174. sign_data['out_trade_no'] = result['out_trade_no']
  175. re_key_sign = self.sign(sign_data, order = False, token = False)
  176. if key_sign != re_key_sign:
  177. raise SaobeiSignatureError(lvalue = re_key_sign, rvalue = key_sign, client = self)
  178. return result