├── .gitignore ├── CHANGES.rst ├── MANIFEST.in ├── README.md ├── setup.py └── src └── wechatpay ├── __init__.py └── wechatpay.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | venv 3 | .DS_Store 4 | *.pyc 5 | *.pyo 6 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ============================== 3 | 1.1.5 - Aug.14, 2015 4 | ------------------------------ 5 | - condition check bug fixed 6 | - NOTE: for general usage, please use version 1.0 7 | - please do not use WeChatPay from this release 8 | 9 | 10 | 1.1.4 - Aug.13, 2015 11 | ------------------------------ 12 | - changed post_xml_ssh to post_xml in RefundQuery 13 | - NOTE: for general usage, please use version 1.0 14 | - please do not use WeChatPay from this release 15 | 16 | 17 | 1.1.3 - Aug.11,, 2015 18 | ------------------------------ 19 | - bug fixing 20 | - NOTE: for general usage, please use version 1.0 21 | - please do not use WeChatPay from this release 22 | 23 | 24 | 1.1.2 - Aug.11,, 2015 25 | ------------------------------ 26 | - rename package name to wechatpay 27 | - NOTE: for general usage, please use version 1.0 28 | - please do not use WeChatPay from this release 29 | 30 | 1.1.1 - Aug.11, 2015 31 | ------------------------------ 32 | - fixed bugs, adding exceptions handling. 33 | - NOTE: for general usage, please use version 1.0 34 | 35 | 36 | 1.1.0 - Aug.03, 2015 37 | ------------------------------ 38 | - fixed bug in getbill for reconciliations when no bill exists. 39 | - NOTE: for general usage, please use version 1.0 40 | 41 | 1.0.9 - Jul.30, 2015 42 | ------------------------------ 43 | - changed name to wechatpay 44 | - NOTE: for general usage, please use version 1.0 45 | 46 | 1.0.8 - Jul.30, 2015 47 | ------------------------------ 48 | - changed name to wechatpay 49 | - NOTE: for general usage, please use version 1.0 50 | 51 | 52 | 53 | 1.0.7 - Jul.16, 2015 54 | ------------------------------ 55 | - Added date_validation() in DownloadBill() 56 | - NOTE: for general usage, please use version 1.0 57 | 58 | 59 | 1.0.6 - Jul.13, 2015 60 | ------------------------------ 61 | - Removed get_access_token() 62 | - Updated get_jsapi_ticket() 63 | - NOTE: for general usage, please use version 1.0 64 | 65 | 66 | 1.0.5 - Jul.07, 2015 67 | ------------------------------ 68 | - Django ORM added in DownloadBill 69 | - NOTE: for general usage, please use version 1.0 70 | 71 | 1.0.4 - Jul.07, 2015 72 | ------------------------------ 73 | - Bug fixing. 74 | - changed DownloadBill().post() to DownloadBill().get_bill() 75 | - NOTE: for general usage, please use version 1.0 76 | 77 | 1.0.3 - Jul.03, 2015 78 | ------------------------------ 79 | - Bug fixing. 80 | - NOTE: for general usage, please use version 1.0 81 | 82 | 1.0.2 - Jul.02, 2015 83 | ------------------------------ 84 | - Bug fixing. 85 | 86 | 87 | 1.0.1 - Jul.02, 2015 88 | ------------------------------ 89 | 90 | - Changed get_access_token() and get_jsapi_ticket() to get data 91 | from our production url. 92 | - NOTE: for general usage, please use version 1.0 93 | 94 | 95 | 96 | 1.0 - Jun.25, 2015 97 | ------------------------------ 98 | 99 | - Initail commit 100 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES.rst 2 | include README.md 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeChat微信支付 2 | ------ 3 | 4 | ##Overview 5 | 6 | Python-Django WeChat payment API, it contains 7 main classes: 7 | 8 | > * UnifiedOrderPay 9 | > * NativeOrderPay 10 | > * JsAPIOrderPay 11 | > * OrderQuery 12 | > * Refund 13 | > * RefundQuery 14 | > * DownloadBill 15 | 16 | ------ 17 | 18 | Installation 19 | ------------ 20 | 21 | Install using pip: 22 | 23 | ```bash 24 | pip install wechatpay 25 | ``` 26 | 27 | APIs 28 | --- 29 | for latest version. 30 | Init accounts 31 | ------------ 32 | Account info needs to be retrieved form ChannelAccount table: 33 | ```python 34 | list_of_wechat_accounts = ChannelAccount.objects.filter(channel=1) 35 | for account in list_of_wechat_accounts: 36 | wecaht_config=WechatConfig(account.config) 37 | ``` 38 | OrderPays 39 | ------------ 40 | Three order pays are similar. 41 | * UnifiedOrderPay 42 | * NativeOrderPay 43 | * JsAPIOrderPay 44 | For example: 45 | 46 | ```python 47 | from wechatpay.wechatpay import (NativeOrderPay as 48 | WeChatNativePayRequest, 49 | OrderQuery as WeChatOrderQuery) 50 | 51 | def get_channel_account_config_dict(trade): 52 | order = trade.order_set.first() 53 | seller = Seller.objects.get_seller(order.sys_code, order.channel, order.seller_id) 54 | 55 | config = {} 56 | for (k, v) in seller.channel_account.config.items(): 57 | config[str(k)] = str(v) 58 | 59 | return config 60 | 61 | 62 | pay_request = WeChatNativePayRequest(WechatConfig(get_channel_account_config_dict(trade))) 63 | ``` 64 | 65 | 66 | DownloadBill 67 | ------------ 68 | To donwload bills of date '2015-07-26' of multiple wechat accounts , based on your data structure, init corresponding accounts info and call get_bill(): 69 | ```python 70 | list_of_wechat_accounts = ChannelAccount.objects.filter(channel=1) 71 | for account in list_of_wechat_accounts: 72 | DownloadBill(WechatConfig(account.config)).get_bill('2015-10-15') 73 | ``` 74 | 75 | 76 | ------ -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | 5 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 6 | README = open(os.path.join(BASE_DIR, 'README.md')).read() 7 | CHANGES = open(os.path.join(BASE_DIR, 'CHANGES.rst')).read() 8 | 9 | setup(name='wechatpay', 10 | version='1.1.5', 11 | description='Python-Django WeChat payment API.', 12 | long_description=README + '\n\n' + CHANGES, 13 | author='Haotong Chen', 14 | author_email='hereischen@gmail.com', 15 | url='https://github.com/hereischen/WeChat', 16 | license='BSD License', 17 | packages=find_packages('src'), 18 | package_dir={'': 'src'}, 19 | classifiers=[ 20 | 'Development Status :: 5 - Production/Stable', 21 | 'Intended Audience :: Developers', 22 | 'Topic :: Software Development :: Build Tools', 23 | 'License :: OSI Approved :: BSD License', 24 | 'Programming Language :: Python :: 2.7', 25 | 26 | ], 27 | include_package_data=True, 28 | zip_safe=False, 29 | ) 30 | -------------------------------------------------------------------------------- /src/wechatpay/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/wechatpay/wechatpay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import time 4 | import re 5 | import logging 6 | import datetime 7 | import json 8 | import requests 9 | 10 | from django.conf import settings 11 | from django.core.exceptions import ImproperlyConfigured 12 | 13 | from llt.utils import random_str, smart_str 14 | from llt.url import sign_url 15 | from reconciliations.models import BillLog 16 | from core.models import ChannelAccount 17 | 18 | # Get an instance of a logger 19 | logger = logging.getLogger(__name__) 20 | 21 | # 微信下载bill的时间,每日13点 22 | GET_BILL_TIME = 13 23 | 24 | 25 | def get_config(name, config_default=None): 26 | """ 27 | Get configuration variable from environment variable 28 | or django setting.py 29 | """ 30 | config = os.environ.get(name, getattr(settings, name, config_default)) 31 | if config is not None: 32 | return config 33 | else: 34 | raise ImproperlyConfigured("Can't find config for '%s' either in environment" 35 | "variable or in settings.py" % name) 36 | WC_BILLS_PATH = get_config('BILLS_DIR') 37 | 38 | 39 | def dict_to_xml(params, sign): 40 | xml = [''] 41 | for (k, v) in params.items(): 42 | if v.isdigit(): 43 | xml.append('<%s>%s' % (k, v, k)) 44 | else: 45 | xml.append('<%s>' % (k, v, k)) 46 | 47 | if sign: 48 | xml.append('' % sign) 49 | xml.append('') 50 | return ''.join(xml) 51 | 52 | 53 | def xml_to_dict(xml): 54 | if xml[0:5].upper() != "" and xml[-6].upper() != "": 55 | return None, None 56 | 57 | result = {} 58 | sign = None 59 | content = ''.join(xml[5:-6].strip().split('\n')) 60 | 61 | pattern = re.compile(r'<(?P.+)>(?P.+)') 62 | m = pattern.match(content) 63 | while m: 64 | key = m.group('key').strip() 65 | value = m.group('value').strip() 66 | if value != '': 67 | pattern_inner = re.compile(r'.+)\]\]>') 68 | inner_m = pattern_inner.match(value) 69 | if inner_m: 70 | value = inner_m.group('inner_val').strip() 71 | if key == 'sign': 72 | sign = value 73 | else: 74 | result[key] = value 75 | 76 | next_index = m.end('value') + len(key) + 3 77 | if next_index >= len(content): 78 | break 79 | content = content[next_index:] 80 | m = pattern.match(content) 81 | 82 | return sign, result 83 | 84 | 85 | class WechatConfig(object): 86 | def __init__(self, channel_config): 87 | self.app_id = channel_config['app_id'] 88 | self.mch_id = channel_config['mch_id'] 89 | self.api_key = channel_config['api_key'] 90 | self.app_secret = channel_config['app_secret'] 91 | self.api_cert_file = os.path.join(settings.ROOT_DIR, channel_config['api_cert_file']) 92 | self.api_key_file = os.path.join(settings.ROOT_DIR, channel_config['api_key_file']) 93 | self.jsapi_ticket_id = channel_config.get('jsapi_ticket_id', '') 94 | self.jsapi_ticket_url = channel_config.get('jsapi_ticket_url', '') 95 | 96 | def __str__(self): 97 | return "WechatConfig object: " + str(self.__dict__) 98 | 99 | 100 | class WeChatPay(object): 101 | 102 | def __init__(self, wechat_config): 103 | self.app_id = wechat_config.app_id 104 | self.mch_id = wechat_config.mch_id 105 | self.api_key = wechat_config.api_key 106 | self.app_secret = wechat_config.app_secret 107 | self.cert_file = wechat_config.api_cert_file 108 | self.key_file = wechat_config.api_key_file 109 | self.jsapi_ticket_id = wechat_config.jsapi_ticket_id 110 | self.jsapi_ticket_url = wechat_config.jsapi_ticket_url 111 | 112 | self.common_params = {'appid': self.app_id, 113 | 'mch_id': self.mch_id} 114 | self.params = {} 115 | self.url = '' 116 | 117 | def set_params(self, **kwargs): 118 | self.params = {} 119 | for (k, v) in kwargs.items(): 120 | self.params[k] = smart_str(v) 121 | 122 | self.params['nonce_str'] = random_str(length=32) 123 | self.params.update(self.common_params) 124 | 125 | def post_xml(self): 126 | xml = self.dict2xml(self.params) 127 | response = requests.post(self.url, data=xml) 128 | logger.info('Make post request to %s' % response.url) 129 | logger.debug('Request XML: %s' % xml) 130 | logger.debug('Response encoding: %s' % response.encoding) 131 | logger.debug('Response XML: %s' % ''.join(response.text.splitlines())) 132 | 133 | return self.xml2dict(response.text.encode(response.encoding)) if response.encoding else response.text 134 | 135 | def post_xml_ssl(self): 136 | xml = self.dict2xml(self.params) 137 | 138 | logger.debug('Cert file: %s' % self.cert_file) 139 | logger.debug('Key file: %s' % self.key_file) 140 | response = requests.post( 141 | self.url, data=xml, verify=True, cert=(self.cert_file, self.key_file)) 142 | logger.info('Make SSL post request to %s' % response.url) 143 | logger.debug('Request XML: %s' % xml) 144 | logger.debug('Response encoding: %s' % response.encoding) 145 | logger.debug('Response XML: %s' % ''.join(response.text.splitlines())) 146 | 147 | return self.xml2dict(response.text.encode(response.encoding)) if response.encoding else response.text 148 | 149 | def dict2xml(self, params, with_sign=True): 150 | sign = sign_url( 151 | params, self.api_key, key_name='key', upper_case=True) if with_sign else None 152 | return dict_to_xml(params, sign) 153 | 154 | def xml2dict(self, xml): 155 | sign, params = xml_to_dict(xml) 156 | if not sign or not params: 157 | raise ValueError('Convert xml to dict failed, xml: [%s]' % xml) 158 | 159 | if params['appid'] != self.app_id or params['mch_id'] != self.mch_id: 160 | raise ValueError('Invalid appid or mch_id, appid: [%s], mch_id: [%s]' % (params['appid'], 161 | params['mch_id'])) 162 | 163 | if params['return_code'] != 'SUCCESS': 164 | raise ValueError('WeChat proccess request failed, return code: [%s], return msg: [%s]' % 165 | (params['return_code'], params.get('return_msg', ''))) 166 | 167 | calc_sign = sign_url( 168 | params, self.api_key, key_name='key', upper_case=True) 169 | if calc_sign != sign: 170 | raise ValueError( 171 | 'Invalid sign, calculate sign: [%s], sign: [%s]' % (calc_sign, sign)) 172 | 173 | if params['result_code'] != 'SUCCESS': 174 | logger.error('WeChat process request failed, result_code: [%s], err_code: [%s], err_code_des: [%s]' % 175 | (params['result_code'], params.get('err_code', ''), params.get('err_code_des', ''))) 176 | return params 177 | 178 | def get_jsapi_ticket(self): 179 | """ 180 | 获取jsapi_ticket 181 | :return: jsapi_ticket 182 | """ 183 | 184 | params = {'wechatid': self.jsapi_ticket_id} 185 | response = requests.post(self.jsapi_ticket_url, data=params) 186 | logger.info('Make request to %s' % response.url) 187 | 188 | resp_dict = json.loads(response.content) 189 | 190 | if resp_dict['code'] == 0: 191 | # print resp_dict 192 | # print resp_dict['data']['jsapi_ticket'] 193 | return resp_dict['data']['jsapi_ticket'] 194 | else: 195 | logger.info('code: %s, data: %s' % 196 | (resp_dict['code'], resp_dict['data'])) 197 | return '' 198 | 199 | def get_js_config_params(self, url, nonce_str, time_stamp): 200 | """ 201 | 获取js_config初始化参数 202 | """ 203 | params = {'noncestr': nonce_str, 204 | 'jsapi_ticket': self.get_jsapi_ticket(), 205 | 'timestamp': '%d' % time_stamp, 206 | 'url': url} 207 | 208 | # params['signature'] = calculate_sign(params, sign_type='sha1', 209 | # upper_case=False) 210 | params['signature'] = sign_url(params, '', sign_type='sha1') 211 | return params 212 | 213 | 214 | class UnifiedOrderPay(WeChatPay): 215 | 216 | def __init__(self, wechat_config): 217 | super(UnifiedOrderPay, self).__init__(wechat_config) 218 | self.url = 'https://api.mch.weixin.qq.com/pay/unifiedorder' 219 | self.trade_type = '' 220 | 221 | def _post(self, body, out_trade_no, total_fee, spbill_create_ip, notify_url, **kwargs): 222 | params = {'body': body, 223 | 'out_trade_no': out_trade_no, 224 | 'total_fee': total_fee, 225 | 'spbill_create_ip': spbill_create_ip, 226 | 'notify_url': notify_url, 227 | 'trade_type': self.trade_type} 228 | params.update(**kwargs) 229 | 230 | self.set_params(**params) 231 | return self.post_xml() 232 | 233 | 234 | class NativeOrderPay(UnifiedOrderPay): 235 | 236 | """ 237 | Native 统一支付类 238 | """ 239 | 240 | def __init__(self, wechat_config): 241 | super(NativeOrderPay, self).__init__(wechat_config) 242 | self.trade_type = 'NATIVE' 243 | 244 | def post(self, body, out_trade_no, total_fee, spbill_create_ip, notify_url): 245 | return super(NativeOrderPay, self)._post(body, out_trade_no, total_fee, spbill_create_ip, notify_url) 246 | 247 | 248 | class AppOrderPay(UnifiedOrderPay): 249 | 250 | """ 251 | App 统一支付类 252 | """ 253 | 254 | def __init__(self, wechat_config): 255 | super(AppOrderPay, self).__init__( 256 | wechat_config) 257 | self.trade_type = 'APP' 258 | 259 | def post(self, body, out_trade_no, total_fee, spbill_create_ip, notify_url): 260 | return super(AppOrderPay, self)._post(body, out_trade_no, total_fee, spbill_create_ip, notify_url) 261 | 262 | 263 | class JsAPIOrderPay(UnifiedOrderPay): 264 | 265 | """ 266 | H5页面的js调用类 267 | """ 268 | 269 | def __init__(self, wechat_config): 270 | super(JsAPIOrderPay, self).__init__(wechat_config) 271 | self.trade_type = 'JSAPI' 272 | 273 | def post(self, body, out_trade_no, total_fee, spbill_create_ip, notify_url, open_id, url): 274 | # 直接调用基类的post方法查询prepay_id,如果成功,返回一个字典 275 | print "starting to post..." 276 | unified_order = super(JsAPIOrderPay, self)._post(body, out_trade_no, total_fee, spbill_create_ip, 277 | notify_url, open_id=open_id) 278 | print "post done!" 279 | nonce_str = random_str(length=32) 280 | time_stamp = time.time() 281 | 282 | pay_params = {'appId': self.app_id, 283 | 'timeStamp': '%d' % time_stamp, 284 | 'nonceStr': nonce_str, 285 | 'package': 'prepay_id=%s' % unified_order.get('prepay_id'), 286 | 'signType': 'MD5'} 287 | print "starting to sign url" 288 | pay_params['paySign'] = sign_url( 289 | pay_params, self.api_key, key_name='key', upper_case=True) 290 | 291 | print "sgin done!" 292 | 293 | unified_order.update({'pay_params': pay_params, 294 | 'config_params': self.get_js_config_params(url, nonce_str, time_stamp)}) 295 | 296 | return unified_order 297 | 298 | 299 | class OrderQuery(WeChatPay): 300 | 301 | def __init__(self, wechat_config): 302 | super(OrderQuery, self).__init__(wechat_config) 303 | self.url = 'https://api.mch.weixin.qq.com/pay/orderquery' 304 | 305 | def post(self, out_trade_no): 306 | params = {'out_trade_no': out_trade_no} 307 | self.set_params(**params) 308 | return self.post_xml() 309 | 310 | 311 | class Notify(WeChatPay): 312 | pass 313 | 314 | 315 | class Refund(WeChatPay): 316 | 317 | def __init__(self, wechat_config): 318 | super(Refund, self).__init__(wechat_config) 319 | self.url = 'https://api.mch.weixin.qq.com/secapi/pay/refund' 320 | 321 | def post(self, out_trade_no, out_refund_no, total_fee, refund_fee): 322 | params = {'out_trade_no': out_trade_no, 323 | 'out_refund_no': out_refund_no, 324 | 'total_fee': total_fee, 325 | 'refund_fee': refund_fee, 326 | 'op_user_id': self.mch_id} 327 | self.set_params(**params) 328 | return self.post_xml_ssl() 329 | 330 | 331 | class RefundQuery(WeChatPay): 332 | 333 | def __init__(self, wechat_config): 334 | super(RefundQuery, self).__init__(wechat_config) 335 | self.url = 'https://api.mch.weixin.qq.com/pay/refundquery' 336 | 337 | def post(self, out_refund_no): 338 | params = {'out_refund_no': out_refund_no} 339 | self.set_params(**params) 340 | return self.post_xml() 341 | 342 | 343 | class DownloadBill(WeChatPay): 344 | 345 | def __init__(self, wechat_config): 346 | super(DownloadBill, self).__init__(wechat_config) 347 | self.url = 'https://api.mch.weixin.qq.com/pay/downloadbill' 348 | self.unique_id = 'wechat_%s_%s' % (wechat_config.app_id, wechat_config.mch_id) 349 | self.channel_account = ChannelAccount.objects.get(unique_id=self.unique_id) 350 | 351 | def post_xml(self): 352 | xml = self.dict2xml(self.params) 353 | response = requests.post(self.url, data=xml) 354 | logger.info('Make post request to %s' % response.url) 355 | logger.debug('Request XML: %s' % xml) 356 | logger.debug('Response encoding: %s' % response.encoding) 357 | logger.debug('Response XML: %s' % ''.join(response.text.splitlines())) 358 | 359 | return self.xml2dict_for_bill(response.text.encode(response.encoding)) if response.encoding else response.text 360 | 361 | def xml2dict_for_bill(self, xml): 362 | sign, params = xml_to_dict(xml) 363 | return params 364 | 365 | def get_yesterday_date_str(self): 366 | today = datetime.date.today() 367 | t = datetime.timedelta(days=1) 368 | # e.g. 20150705 369 | yesterday = str(today - t) 370 | return yesterday 371 | 372 | def is_record_writen(self): 373 | bill_log = BillLog.objects.filter( 374 | date=self.bill_date, channel_account=self.channel_account) 375 | return bill_log 376 | 377 | def date_validation(self, input_date): 378 | today = datetime.date.today() 379 | t = datetime.timedelta(days=1) 380 | yesterday = (today - t) 381 | now = datetime.datetime.now() 382 | if input_date < today: 383 | if input_date == yesterday: 384 | if now.hour >= GET_BILL_TIME: 385 | return True 386 | else: 387 | raise ValueError( 388 | "Get bill time:[%s] o‘clock must later then %s o‘clock." % ( 389 | now.hour, GET_BILL_TIME)) 390 | else: 391 | return True 392 | else: 393 | raise ValueError( 394 | "Bill_date given: [%s] should before today's date: [%s]." % (input_date, today)) 395 | 396 | def is_responese_string(self, res): 397 | if type(res) is unicode: 398 | return True 399 | elif type(res) is dict: 400 | return False 401 | else: 402 | raise Exception(u'Invalid response type %s.' % type(res)) 403 | 404 | def get_res(self, bill_date=None, bill_type='ALL'): 405 | params = {} 406 | if bill_date: 407 | input_bill_date = datetime.datetime.strptime( 408 | bill_date, '%Y-%m-%d').date() 409 | if self.date_validation(input_bill_date): 410 | self.bill_date = str(input_bill_date) 411 | else: 412 | self.bill_date = self.get_yesterday_date_str() 413 | # reformat date string from yyyy-mm-dd to yyyymmdd 414 | print 'input_date>>>', self.bill_date 415 | self.rf_bill_date = self.bill_date.replace('-', '') 416 | 417 | params['bill_date'] = self.rf_bill_date 418 | params['bill_type'] = bill_type 419 | 420 | self.set_params(**params) 421 | 422 | return self.post_xml() 423 | 424 | def create_bill_log(self, bill_status, file_path, remark): 425 | BillLog.objects.create(date=self.bill_date, 426 | bill_status=bill_status, 427 | file_path=file_path, 428 | remark=remark, 429 | channel_account=self.channel_account, 430 | ) 431 | 432 | def get_bill(self, bill_date=None, bill_type='ALL'): 433 | res = self.get_res(bill_date, bill_type) 434 | 435 | month_dir = '%s' % self.rf_bill_date[:6] 436 | bill_file_dir = os.path.join(WC_BILLS_PATH, month_dir) 437 | if not os.path.exists(bill_file_dir): 438 | os.makedirs(bill_file_dir) 439 | 440 | self.file_path = os.path.join( 441 | bill_file_dir, "%s_%s.csv" % (self.unique_id, self.rf_bill_date)) 442 | self.rel_dir_name = os.path.relpath(self.file_path) 443 | 444 | # 成功取回外部账单 445 | if self.is_responese_string(res): 446 | res = res.replace('`', '') 447 | 448 | if not self.is_record_writen(): 449 | with open(self.file_path, "wb") as f: 450 | f.write(res.encode("UTF-8")) 451 | f.close() 452 | self.create_bill_log('SUCCESS', self.rel_dir_name, '{}') 453 | else: 454 | # 对账单文件为空,不创建,只写入数据库信息 455 | if res['return_msg'] == 'No Bill Exist': 456 | remark = json.dumps(res) 457 | if not self.is_record_writen(): 458 | self.create_bill_log('EMPTY', file_path='N/A', remark=remark) 459 | else: 460 | remark = json.dumps(res) 461 | if not self.is_record_writen(): 462 | self.create_bill_log('FAIL', file_path='N/A', remark=remark) 463 | 464 | # ================================================================== 465 | # l = ChannelAccount.objects.filter(channel=1) 466 | # for d in l: 467 | # DownloadBill(WechatConfig(d.config)).get_bill('2015-10-15') 468 | --------------------------------------------------------------------------------