├── .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%s>' % (k, v, k))
44 | else:
45 | xml.append('<%s>%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.+)(?P=key)>')
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 |
--------------------------------------------------------------------------------