# -*- coding: utf-8 -*- import os.path from .type import RequestType from .utils import sm3 # https://pay.weixin.qq.com/wiki/doc/apiv3/Offline/open/chapter4_8_1.shtml def fapiao_card_template(self, card_template_information, card_appid=None): """创建电子发票卡券模板 :param card_template_information: 卡券模板信息。示例值:{'logo_url':'http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xX'} :param card_appid: 插卡公众号AppID,若是服务商模式,则可以是服务商申请的appid,也可以是子商户申请的appid;若是直连模式,则是直连商户申请的appid。示例值:wxb1170446a4c0a5a2 """ if not card_appid: card_appid = self._appid params = {} params.update({'card_appid': card_appid}) if card_template_information: params.update({'card_template_information': card_template_information}) else: raise Exception('card_template_information is not assigned.') path = '/v3/new-tax-control-fapiao/card-template' return self._core.request(path, method=RequestType.POST, data=params) def fapiao_set_merchant_config(self, callback_url=None): """配置开发选项 :param callback_url: 商户回调地址。收取微信的授权通知、开票通知、插卡通知等相关通知。示例值:'https://pay.weixin.qq.com/callback' """ if not callback_url: callback_url = self._notify_url params = {} params.update({'callback_url': callback_url}) path = '/v3/new-tax-control-fapiao/merchant/development-config' return self._core.request(path, method=RequestType.PATCH, data=params) def fapiao_merchant_config(self): """查询商户配置的开发选项 """ path = '/v3/new-tax-control-fapiao/merchant/development-config' return self._core.request(path) def fapiao_title_url(self, fapiao_apply_id, source, total_amount, openid, appid=None, seller_name=None, show_phone_cell=False, must_input_phone=False, show_email_cell=False, must_input_email=False): """获取抬头填写链接 :param fapiao_apply_id: 发票申请单号,示例值:'4200000444201910177461284488' :param source: 开票来源,WEB:微信H5开票,MINIPROGRAM:微信小程序开票,示例值:'WEB' :param total_amount: 总金额,单位:分,示例值:100 :param openid: 需要填写发票抬头的用户在商户AppID下的OpenID,示例值:'plN5twRbHym_j-QcqCzstl0HmwEs' :param appid: 若开票来源是WEB,则为商户的公众号AppID;若开票来源是MINIPROGRAM,则为商户的小程序AppID,示例值:'wxb1170446a4c0a5a2' :param seller_name: 销售方名称,若不传则默认取商户名称,示例值:'深圳市南山区测试商户' :param show_phone_cell: 是否需要展示手机号填写栏 :param must_input_phone: 是否必须填写手机号,仅当需要展示手机号填写栏时生效 :param show_email_cell: 是否需要展示邮箱地址填写栏 :param must_input_email: 是否必须填写邮箱地址,仅当需要展示邮箱地址填写栏时生效 """ path = '/v3/new-tax-control-fapiao/user-title/title-url?' if fapiao_apply_id: path += 'fapiao_apply_id=%s' % fapiao_apply_id else: raise Exception('fapiao_apply_id is not assigned.') if source: path += '&source=%s' % source else: raise Exception('source is not assigned.') if total_amount: path += '&total_amount=%s' % total_amount else: raise Exception('total_amount is not assigned.') if appid: path += '&appid=%s' % appid else: path += '&appid=%s' % self._appid if openid: path += '&openid=%s' % openid else: raise Exception('openid is not assigned.') if seller_name: path += '&seller_name=%s' % seller_name if show_phone_cell: path += '&show_phone_cell=true' if must_input_phone: path += '&must_input_phone=true' if show_email_cell: path += '&show_email_cell=true' if must_input_email: path += '&must_input_email=true' return self._core.request(path) def fapiao_title(self, fapiao_apply_id, scene='WITH_WECHATPAY'): """获取用户填写的抬头 :param fapiao_apply_id: 发票申请单号,示例值:'4200000444201910177461284488' :param scene: 场景值,目前只支持WITH_WECHATPAY。示例值:'WITH_WECHATPAY' """ path = '/v3/new-tax-control-fapiao/user-title?' if fapiao_apply_id: path += 'fapiao_apply_id=%s' % fapiao_apply_id else: raise Exception('fapiao_apply_id is not assigned.') path += '&scene=%s' % scene return self._core.request(path) def fapiao_tax_codes(self, offset=0, limit=20): """获取商品和服务税收分类对照表 :param offset: 查询的起始位置,示例值:0 :param limit: 查询的最大数量,最大值20 """ path = '/v3/new-tax-control-fapiao/merchant/tax-codes?offset=%s&limit=%s' % (offset, limit) return self._core.request(path) def fapiao_merchant_base_info(self): """获取商户开票基础信息 """ path = '/v3/new-tax-control-fapiao/merchant/base-information' return self._core.request(path) def fapiao_applications(self, fapiao_apply_id, buyer_information, fapiao_information, scene='WITH_WECHATPAY'): """开具电子发票 :param fapiao_apply_id: 发票申请单号,示例值:'4200000444201910177461284488' :param buyer_information: 购买方信息,示例值:{'type':'ORGANIZATION','name':'深圳市南山区测试企业'} :param fapiao_information: 需要开具的发票信息,示例值:[{'fapiao_id':'20200701123456','total_amount':382895,'need_list':False,'items':[{'tax_code':'3010101020203000000','quantity':100000000,'total_amount':'429900','discount':False}]}] :param scene: 场景值,目前只支持WITH_WECHATPAY。示例值:'WITH_WECHATPAY' """ params = {} if fapiao_apply_id: params.update({'fapiao_apply_id': fapiao_apply_id}) else: raise Exception('fapiao_aply_id is not assigned.') cipher_data = False if buyer_information: if buyer_information.get('phone'): buyer_information.update({'phone': self._core.encrypt(buyer_information.get('phone'))}) cipher_data = True if buyer_information.get('email'): buyer_information.update({'email': self._core.encrypt(buyer_information.get('email'))}) cipher_data = True params.update({'buyer_information': buyer_information}) else: raise Exception('buyer_information is not assigned.') if fapiao_information: params.update({'fapiao_information': fapiao_information}) else: raise Exception('fapiao_information is not assigned.') params.update({'scene': scene}) path = '/v3/new-tax-control-fapiao/fapiao-applications' return self._core.request(path, method=RequestType.POST, data=params, cipher_data=cipher_data) def fapiao_query(self, fapiao_apply_id, fapiao_id=None): """查询电子发票 :param fapiao_apply_id: 发票申请单号,示例值:'4200000444201910177461284488' :param fapiao_id: 商户发票单号,示例值:'20200701123456' """ path = '/v3/new-tax-control-fapiao/fapiao-applications/%s' % fapiao_apply_id if fapiao_id: path += '?fapiao_id=%s' % fapiao_id return self._core.request(path) def fapiao_reverse(self, fapiao_apply_id, reverse_reason, fapiao_information): """冲红电子发票 :param fapiao_apply_id: 发票申请单号,示例值:'4200000444201910177461284488' :param reverse_reason: 冲红原因,示例值:'退款' :param fapiao_information: 需要冲红的发票信息,示例值:{'fapiao_id':'20200701123456','fapiao_code':'044001911211','fapiao_number':'12897794'} """ if fapiao_apply_id: path = '/v3/new-tax-control-fapiao/fapiao-applications/%s/reverse' % fapiao_apply_id else: raise Exception('fapiao_apply_id is not assigned.') params = {} if reverse_reason: params.update({'reverse_reason': reverse_reason}) else: raise Exception('reverse_reason is not assigned.') if fapiao_information: params.update({'fapiao_information': fapiao_information}) else: raise Exception('fapiao_information is not assigned.') return self._core.request(path, method=RequestType.POST, data=params) def fapiao_upload_file(self, filepath): """上传电子发票文件 :filepath: 电子发票文件路径,只支持pdf和odf两种格式,示例值:'./fapiao/0001.pdf' """ if not (filepath and os.path.exists(filepath) and os.path.isfile(filepath)): raise Exception('filepath is not assigned or not exists') with open(filepath, mode='rb') as f: content = f.read() filename = os.path.basename(filepath) filetype = os.path.splitext(filename)[-1][1:].upper() mimes = { 'PDF': 'application/pdf', 'ODF': 'application/odf' } if filetype not in mimes: raise Exception(f'wechatpayv3 does not support this file type: {filetype}') params = {} params.update({'meta': '{"file_type":"%s","digest_alogrithm":"SM3","digest":"%s"}' % (filetype, sm3(content))}) files = [('file', (filename, content, mimes[filetype]))] path = '/v3/new-tax-control-fapiao/fapiao-applications/upload-fapiao-file' return self._core.request(path, method=RequestType.POST, data=params, sign_data=params.get('meta'), files=files) def fapiao_insert_cards(self, fapiao_apply_id, buyer_information, fapiao_card_information, scene='WITH_WECHATPAY'): """将电子发票插入微信用户卡包 :param fapiao_apply_id: 发票申请单号,示例值:'4200000444201910177461284488' :param buyer_information: 购买方信息,即发票抬头。示例值:{'type':'ORGANIZATION','name':'深圳市南山区测试企业'} :param fapiao_card_information: 电子发票卡券信息列表,最多五条。示例值:[{'fapiao_media_id':'ASNFZ4mrze/+3LqYdlQyEA==','fapiao_number':'123456','fapiao_code':'044001911211','fapiao_time':'2020-07-01T12:00:00+08:00','check_code':'69001808340631374774'......}] :param scene: 场景值,目前只支持WITH_WECHATPAY。示例值:'WITH_WECHATPAY' """ if fapiao_apply_id: path = '/v3/new-tax-control-fapiao/fapiao-applications/%s/insert-cards' % fapiao_apply_id else: raise Exception('fapiao_apply_id is not assigned.') params = {} if buyer_information: params.update({'buyer_information': buyer_information}) else: raise Exception('buyer_information is not assigned.') if fapiao_card_information: params.update({'fapiao_card_information': fapiao_card_information}) else: raise Exception('fapiao_card_information is not assigned.') params.update({'scene': scene}) return self._core.request(path, method=RequestType.POST, data=params) def fapiao_check_submch(self, sub_mchid): """检查子商户开票功能状态 :param sub_mch: 子商户号,微信支付分配的子商户号。示例值:'1900000001' """ if sub_mchid: path = '/v3/new-tax-control-fapiao/merchant/%s/check' % sub_mchid else: raise Exception('sub_mchid is not assigned.') return self._core.request(path) def fapiao_query_files(self, fapiao_apply_id, sub_mchid=None, fapiao_id=None): """获取发票下载信息 :param fapiao_apply_id: 发票申请单号,开票时指定的发票申请单号。 :param sub_mchid: 子商户号,微信支付分配的子商户号。示例值:'1900000001' :param fapiao_id: 商户发票单号,开票时指定的商户发票单号,唯一标识一张电子发票。 """ if fapiao_apply_id: path = '/v3/new-tax-control-fapiao/fapiao-applications/%s/fapiao-files' % fapiao_apply_id else: raise Exception('fapiao_apply_id is not assigned.') params = {} if sub_mchid: params.update({'sub_mchid': sub_mchid}) if fapiao_id: params.update({'fapiao_id': fapiao_id}) return self._core.request(path, method=RequestType.POST, data=params) def fapiao_download_file(self, url, openid, invoice_code, invoice_no, fapiao_id, sub_mchid=None): """下载发票文件 :param url: 获取发票下载信息接口返回的download_url,保留其中的token字段不要删除。 """ if not (url and openid and invoice_code and invoice_no and fapiao_id): raise Exception('url, openid, invoice_code, invocide_no or fapiao_id is not assigned.') else: path = '%s&mchid=%s&openid=%s&invoice_code=%s&invoice_no=%s&fapiao_id=%s' % (url, self._mchid, openid, invoice_code, invoice_no, fapiao_id) if self._partner_mode: if sub_mchid: path = '%s&sub_mchid=%s' % (path, sub_mchid) else: raise Exception('sub_mchid is not assigned.') return self._core.request(path)