Python3实现微信支付对账单下载导出CSV
来源:互联网 发布:解方程组矩阵的方法 编辑:程序博客网 时间:2024/06/02 09:40
微信下载对账单官方内容:
应用场景
商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致,通过对账单核对后可校正支付状态。
注意:
1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致;
2、微信在次日9点启动生成前一天的对账单,建议商户10点后再获取;
3、对账单中涉及金额的字段单位为“元”。
4、对账单接口只能下载三个月以内的账单。
接口链接
https://api.mch.weixin.qq.com/pay/downloadbill
是否需要证书
不需要。
请求参数
<xml> <appid>wx2421b1c4370ec43b</appid> <bill_date>20141110</bill_date> <bill_type>ALL</bill_type> <mch_id>10000100</mch_id> <nonce_str>21df7dc9cd8616b56919f20d9f679233</nonce_str> <sign>332F17B766FC787203EBE9D6E40457A1</sign></xml>
返回结果
失败时,返回以下字段
成功时,数据以文本表格的方式返回,第一行为表头,后面各行为对应的字段内容,字段内容跟查询订单或退款结果一致,具体字段说明可查阅相应接口。
第一行为表头,根据请求下载的对账单类型不同而不同(由bill_type决定),目前有:
当日所有订单
交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
当日成功支付的订单
交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,商品名称,商户数据包,手续费,费率
当日退款的订单
交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
从第二行起,为数据记录,各参数以逗号分隔,参数前增加`符号,为标准键盘1左边键的字符,字段顺序与表头一致。
倒数第二行为订单统计标题,最后一行为统计数据
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
举例如下:
交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率`2014-11-1016:33:45,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1001690740201411100005734289,`1415640626,`085e9858e3ba5186aafcbaed1,`MICROPAY,`SUCCESS,`CFT,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60%`2014-11-1016:46:14,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1002780740201411100005729794,`1415635270,`085e9858e90ca40c0b5aee463,`MICROPAY,`SUCCESS,`CFT,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60%总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额`2,`0.02,`0.0,`0.0,`0
错误码
main.py:
# -*- coding: utf-8 -*-import osimport subprocessimport sysimport timeimport loggingimport threadingfrom datetime import datetime, timedeltafrom tkinter import *from tkinter import filedialog, Frame, Tk, N, W, messageboxfrom tkinter import fontfrom tkinter.ttk import Progressbarfrom weixin_mch_api import download_billimport configlogging.basicConfig(level=logging.DEBUG)try: cur_path = os.path.dirname(os.path.abspath(__file__))except NameError: cur_path = os.path.dirname(os.path.abspath(sys.argv[0]))class Application(Frame): def run(self): appid = self.var_appid.get().strip() if len(appid) == 0: messagebox.showinfo(title=u"错误", message="请输入公众账号ID") return mch_id = self.var_mch_id.get().strip() if len(mch_id) == 0: messagebox.showinfo(title=u"错误", message="请输入商户号") return mch_key = self.var_mch_key.get().strip() if len(mch_key) == 0: messagebox.showinfo(title=u"错误", message="请输入商户密钥") return bill_date_from = self.var_bill_date_from.get().strip() if len(bill_date_from) == 0: messagebox.showinfo(title=u"错误", message="请输入开始日期") return path = self.var_path.get().strip() if len(path) == 0: messagebox.showinfo(title=u"错误", message="请选择输出目录") return sub_mch_id = self.txt_sub_mch_id.get(1.0, END) if len(sub_mch_id) > 0: sub_mch_ids = list(set(sub_mch_id.split())) else: sub_mch_ids = [] # save settings config.save(appid, mch_id, mch_key, sub_mch_id, path) bill_date_to = self.var_bill_date_to.get().strip() if len(bill_date_to) == 0: bill_date_to = (datetime.now() + timedelta(days=-1)).strftime("%Y%m%d") s = datetime.strptime(bill_date_from, "%Y%m%d") e = datetime.strptime(bill_date_to, "%Y%m%d") def work_proc(self, path, s, e, sub_mch_ids, event): self.btn_run.config(state='disabled') days = (e - s).days + 1 max = days * (len(sub_mch_ids) if len(sub_mch_ids) > 0 else 1) self.prg_bar.config({'maximum': max}) while s <= e: if len(sub_mch_ids) > 0: for sub_mch_id in sub_mch_ids: r = download_bill(mch_key, appid, mch_id, s, sub_mch_id) self.save_bill(r, s, mch_id, sub_mch_id) self.prg_bar.step() else: r = download_bill(mch_key, appid, mch_id, s) print(type(r)) self.save_bill(r, s, mch_id) self.prg_bar.step() s += timedelta(days=1) self.btn_run.config(state='normal') event.set() self.running = True work_thread = threading.Thread(target=work_proc, args=(self, path, s, e, sub_mch_ids, self.event)) work_thread.start() def save_bill(self, text, bill_date, mch_id, sub_mch_id=None): bill_date = bill_date.strftime("%Y%m%d") if text.decode('utf-8').startswith('<xml>'): f = '%s_%s_%s.xml' % (bill_date, mch_id, sub_mch_id) \ if sub_mch_id \ else '%s_%s_.xml' % (bill_date, mch_id) else: f = '%s_%s_%s.csv' % (bill_date, mch_id, sub_mch_id) \ if sub_mch_id \ else '%s_%s_.csv' % (bill_date, mch_id) fullname = os.path.join(self.var_path.get().strip(), f) with open(fullname, 'wb') as csv: csv.write(text) def select_path(self): self.var_path.set(filedialog.askdirectory()) def createWidgets(self, settings): row = 0 # 公众号ID self.lbl_appid = Label(self, text=u"公众帐号ID", fg='red') self.lbl_appid.grid(column=0, row=row, sticky=(E, N)) self.var_appid = StringVar(self, value=settings.get('appid')) self.txt_appid = Entry(self, textvariable=self.var_appid, width=60, font=self.font) self.txt_appid.grid(column=1, row=row, columnspan=2, sticky=(W, N)) row += 1 # 商户号 self.lbl_mch_id = Label(self, text=u"商户号", fg='red') self.lbl_mch_id.grid(column=0, row=row, sticky=(E, N)) self.var_mch_id = StringVar(self, value=settings.get('mch_id')) self.txt_mch_id = Entry(self, textvariable=self.var_mch_id, width=60, font=self.font) self.txt_mch_id.grid(column=1, row=row, columnspan=2, sticky=(W, N)) row += 1 # 商户密钥 self.lbl_mch_key = Label(self, text=u"商户密钥", fg='red') self.lbl_mch_key.grid(column=0, row=row, sticky=(E, N)) self.var_mch_key = StringVar(self, value=settings.get('mch_key')) self.txt_mch_key = Entry(self, textvariable=self.var_mch_key, width=60, font=self.font) self.txt_mch_key.grid(column=1, row=row, columnspan=2, sticky=(W, N)) row += 1 # 子商户号 self.lbl_sub_mch_id = Label(self, text=u"子商户号") self.lbl_sub_mch_id.grid(column=0, row=row, sticky=(E, N)) self.txt_sub_mch_id = Text(self, height=5, width=60, font=self.font) if settings.get('sub_mch_id'): self.txt_sub_mch_id.insert(END, settings.get('sub_mch_id')) self.txt_sub_mch_id.grid(column=1, row=row, columnspan=2, sticky=(W, N)) row += 1 # 开始日期 self.lbl_bill_date_from = Label(self, text=u"开始日期", fg='red') self.lbl_bill_date_from.grid(column=0, row=row, sticky=(E, N)) self.var_bill_date_from = StringVar() self.var_bill_date_from.set((datetime.now() - timedelta(days=1)).strftime("%Y%m%d")) self.txt_bill_date_from = Entry(self, width=8, textvariable=self.var_bill_date_from, font=self.font) self.txt_bill_date_from.grid(column=1, row=row, columnspan=2, sticky=(W, N)) row += 1 # 结束日期 self.lbl_bill_date_to = Label(self, text=u"结束日期") self.lbl_bill_date_to.grid(column=0, row=row, sticky=(E, N)) self.var_bill_date_to = StringVar() self.txt_bill_date_to = Entry(self, width=8, textvariable=self.var_bill_date_to, font=self.font) self.txt_bill_date_to.grid(column=1, row=row, columnspan=2, sticky=(W, N)) row += 1 # 输出目录 self.lbl_path = Label(self, text=u"输出目录", fg='red') self.lbl_path.grid(column=0, row=row, sticky=(E, N)) path = settings.get('path') self.var_path = StringVar(self, value=path if path else os.path.dirname(os.path.abspath(cur_path))) self.txt_path = Entry(self, textvariable=self.var_path, width=55, font=self.font) self.txt_path.grid(column=1, row=row, sticky=(W, N, E)) self.btn_select_path = Button(self, text=u"...", command=self.select_path) self.btn_select_path.grid(column=2, row=row, sticky=(E)) row += 1 # 进度条 self.prg_bar = Progressbar(self) self.prg_bar.grid(column=0, row=row, columnspan=3, sticky=(W, E), pady=(10, 0)) row += 1 # 执行按钮 buttonFrame = Frame(self) buttonFrame.grid(column=0, row=row, columnspan=3, pady=(15, 0), sticky=(W, E)) self.btn_run = Button(buttonFrame, width=20, text=u"下载", fg='blue', command=self.run) self.btn_run.pack() def loop(self): if self.running and self.event.is_set(): self.running = False self.event.clear() messagebox.showinfo(title=u"成功", message="账单下载结束") subprocess.call('explorer "%s"' % self.var_path.get().strip().replace('/', '\\'), shell=True) self.master.after(100, self.loop) def __init__(self, master=None): Frame.__init__(self, master) self.font = font.nametofont("TkDefaultFont") self.font.configure(family='MS Gothic', size=9) self.grid(column=0, row=0, sticky=(N, W, E, S), padx=10, pady=10) self.rowconfigure(0, weight=1) self.columnconfigure (0, weight=1) settings = config.load() self.createWidgets(settings) self.event = threading.Event() self.running = False self.loop()root = Tk()root.title(u"微信商户平台对账单下载")root.rowconfigure(0, weight=1)root.columnconfigure(0, weight=1)root.resizable(width=False, height=False)app = Application(master=root)root.mainloop()
weixin_mch_api.py:
# -*- coding: utf-8 -*-import cgiimport jsonimport loggingimport randomimport stringimport urllibfrom hashlib import md5from datetime import datetime, datefrom urllib import requestdef dict2xml(params): xml = "<xml>" for k, v in params.items(): '''对于已编码的字节字符串,文本字符串的许多特性和方法已经不能使用。 ''' v =v.encode('utf-8').decode('utf-8') k = k.encode('utf-8').decode('utf-8') xml += "<" + k + ">" + cgi.escape(v) + "</" + k + ">" xml += "</xml>" print(xml) return xmldef get_nonce_str(length=32): rule = string.ascii_letters + string.digits str = random.sample(rule, length) return "".join(str)def sign(params, key=None): """ md5签名 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3 """ l = ['%s=%s' % (k, params.get(k, '').strip()) for k in sorted(params.keys())] s = '&'.join(l) if key: s += '&key=' + key return md5(s.encode('utf-8')).hexdigest().upper()def download_bill(key, appid, mch_id, bill_date, sub_mch_id=None): """ 下载对账单 :param bill_date: 下载对账单的日期 :return: 返回的结果数据 """ while True: try: if isinstance(bill_date, (datetime, date)): bill_date = bill_date.strftime('%Y%m%d') data = { 'appid': appid, 'mch_id': mch_id, 'bill_date': bill_date, 'nonce_str': get_nonce_str(), 'bill_type': 'ALL', } if sub_mch_id: data['sub_mch_id'] = sub_mch_id data["sign"] = sign(data, key) url = 'https://api.mch.weixin.qq.com/pay/downloadbill' req = request.Request(url) req.data =dict2xml(data).encode('utf-8') r = urllib.request.urlopen(req) logging.debug('downloaded.') return r.read() except Exception as e: import sys import traceback exc_info = sys.exc_info() traceback.print_exception(*exc_info)
config.py代码:
# -*- coding: utf-8 -*-from configparser import ConfigParserSETTINGS_FILENAME = 'settings.ini'def load(): config = ConfigParser() config.read(SETTINGS_FILENAME) if config.has_section('main'): return { 'appid': config.get('main', 'appid'), 'mch_id': config.get('main', 'mch_id'), 'mch_key': config.get('main', 'mch_key'), 'sub_mch_id': config.get('main', 'sub_mch_id'), 'path': config.get('main', 'path'), } return {}def save(appid, mch_id, mch_key, sub_mch_id, path): config = ConfigParser() config.add_section('main') config.set('main', 'appid', appid) config.set('main', 'mch_id', mch_id) config.set('main', 'mch_key', mch_key) config.set('main', 'sub_mch_id', sub_mch_id) config.set('main', 'path', path) with open(SETTINGS_FILENAME, 'w') as f: config.write(f)
运行结果如图:
- Python3实现微信支付对账单下载导出CSV
- 微信支付之下载账单
- 微信支付开发(3) 对账单
- 微信开发下载对账单-公众号支付开发-视频教程12
- 使用Delphi Xe8 开发微信功能 -- (一)微信支付商户平台之对账单下载
- 微信支付对账单的详细说明
- golang下载支付宝对账单
- 微信对账单处理
- 微信支付v3 账单处理
- 后台业务账单和微信支付后台的订单对账步骤
- 使用golang快速开发微信公众平台(九):下载对账单
- 微信支付V2账单查询接口orderquery示例代码
- 支付宝处理对账单
- 实现支付宝账单功能
- Servlet实现导出下载csv文件
- 用CSV框架导出csv文件实现下载(jsp)
- Java 导出CSV文件及实现web下载CSV
- 微信支付证书下载
- 【转载】PECL 简介
- 12.3 表现层
- java web 过滤器跟拦截器的区别和使用
- CentOS 5.5下c语言的编译与执行
- gcc编译C++程序
- Python3实现微信支付对账单下载导出CSV
- 远程解决centos的网络问题: service network start 出现RTNETLINK answers: File exists错误 【转】
- CentOS 6.4的安装 【摘】
- centos时间的修改
- Linux修改IP的三种方法
- 数据库中表的十二个设计原则 【转】
- hdoj1032 The 3n + 1 problem (数学模拟)
- Cow Marathon 【树的直径】
- PHP的缓存机制