# -*- coding: utf-8 -*- # !/usr/bin/env python from __future__ import unicode_literals import hashlib import datetime import requests import simplejson as json from collections import OrderedDict import six from library import to_binary, to_text from .exceptions import SaobeiException, SaobeiValidationError, SaobeiSignatureError __all__ = ('SaobeiPay') FAIL = '02' SUCCESS = '01' class SaobeiPay(object): PAY_HOST_DEV = 'http://test.lcsw.cn:8045/lcsw' PAY_HOST = 'https://pay.lcsw.cn/lcsw' def __str__(self): _repr = '{kclass}(merchant_no: {merchant_no}, terminal_id: {terminal_id})'.format( kclass = self.__class__.__name__, merchant_no = self.merchant_no, terminal_id = self.terminal_id) if six.PY2: return to_binary(_repr) else: return to_text(_repr) def __repr__(self): return str(self) def __init__(self, merchant_no, terminal_id, access_token, debug = False): self.merchant_no = merchant_no self.terminal_id = terminal_id self.access_token = access_token self.debug = debug if self.debug: self.host_url = SaobeiPay.PAY_HOST_DEV else: self.host_url = SaobeiPay.PAY_HOST def sign(self, raw, order = True, token = True): if order: raw = [(k, str(raw[k])) for k in sorted(raw.keys())] else: raw = [(k, str(raw[k])) for k in raw.keys()] s = '&'.join('='.join(kv) for kv in raw) if token: s += '&access_token={0}'.format(self.access_token) return hashlib.md5(s).hexdigest() def check(self, data): sign = data.pop('key_sign') return sign == self.sign(data) def get(self, endpoint, **kwargs): return self._request( method = 'get', endpoint = endpoint, **kwargs ) def post(self, endpoint, **kwargs): return self._request( method = 'post', endpoint = endpoint, **kwargs ) def _decode_result(self, res): try: result = json.loads(res.content.decode('utf-8', 'ignore'), strict = False) except (TypeError, ValueError): return res return result def _handle_result(self, res, method = None, url = None, result_processor = None, **kwargs): if not isinstance(res, dict): result = self._decode_result(res) else: result = res if result['return_code'] == '02': raise SaobeiException( return_code = '02', return_msg = result['return_msg'], result_code = result['result_code'] if 'result_code' in result else '', client = self, request = None, response = None) return result if not result_processor else result_processor(result) def _request(self, method, endpoint, **kwargs): url = '{base}{endpoint}'.format( base = self.host_url, endpoint = endpoint ) headers = {'Content-Type': 'application/json'} if isinstance(kwargs.get('data', ''), dict): body = json.dumps(kwargs['data'], ensure_ascii = False) body = body.encode('utf-8') kwargs['data'] = body kwargs['timeout'] = kwargs.get('timeout', 15) result_processor = kwargs.pop('result_processor', None) with requests.sessions.Session() as session: res = session.request( url = url, method = method, headers = headers, **kwargs ) try: res.raise_for_status() except requests.RequestException as reqe: raise SaobeiException( return_code = 'HTTP{}'.format(res.status_code), return_msg = reqe.message, result_code = '', client = self, request = reqe.request, response = reqe.response ) return self._handle_result( res, method, url, result_processor, **kwargs ) def generate_wap_pay_url(self, pay_trace, pay_time, total_fee, **data): """ 统一下单 """ url = self.host_url + '/open/wap/110/pay' data.update({ 'merchant_no': str(self.merchant_no), 'terminal_id': str(self.terminal_id), 'terminal_trace': str(pay_trace), 'terminal_time': str(pay_time), 'total_fee': str(total_fee) }) data.setdefault('auto_pay', '0') data.setdefault('repeated_trace', '0') data.setdefault('key_sign', self.sign(data)) from six.moves.urllib import parse return '{path}?{params}'.format(path = url, params = parse.urlencode(data)) def api_trade_query(self, pay_type, pay_trace, pay_time): ''' 查询订单 ''' # 查询流水号就设置为商户订单号 data = OrderedDict() data['pay_ver'] = '100' data['pay_type'] = pay_type data['service_id'] = '020' data['merchant_no'] = self.merchant_no data['terminal_id'] = self.terminal_id data['terminal_trace'] = pay_trace data['terminal_time'] = str(datetime.datetime.now().strftime('%Y%m%d%H%M%S')) data['out_trade_no'] = '' data.update({'key_sign': self.sign(data, order = False)}) data['pay_trace'] = pay_trace data['pay_time'] = pay_time result = self.post(endpoint = '/pay/100/query', data = data) if self.merchant_no != result['merchant_no']: raise SaobeiValidationError(errmsg = u'无效的merchant_no', lvalue = str(self.merchant_no), rvalue = str(result['merchant_no']), client = self) if self.terminal_id != result['terminal_id']: raise SaobeiValidationError(errmsg = u'无效的terminal_id', lvalue = str(self.terminal_id), rvalue = str(result['terminal_id']), client = self) key_sign = result['key_sign'] sign_data = OrderedDict() sign_data['return_code'] = result['return_code'] sign_data['return_msg'] = result['return_msg'] sign_data['result_code'] = result['result_code'] sign_data['pay_type'] = result['pay_type'] sign_data['merchant_name'] = result['merchant_name'] sign_data['merchant_no'] = result['merchant_no'] sign_data['terminal_id'] = result['terminal_id'] sign_data['terminal_trace'] = result['terminal_trace'] sign_data['terminal_time'] = result['terminal_time'] sign_data['total_fee'] = result['total_fee'] sign_data['end_time'] = result['end_time'] sign_data['out_trade_no'] = result['out_trade_no'] re_key_sign = self.sign(sign_data, order = False, token = False) if key_sign != re_key_sign: raise SaobeiSignatureError(lvalue = re_key_sign, rvalue = key_sign, client = self) return result