invoice.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, unicode_literals
  3. from library.wechatpy.client.api.base import BaseWeChatAPI
  4. class WeChatInvoice(BaseWeChatAPI):
  5. API_BASE_URL = 'https://api.weixin.qq.com/card/invoice/'
  6. def get_url(self):
  7. """
  8. 获取自身开票平台专用的授权链接
  9. 详情请参考
  10. https://mp.weixin.qq.com/wiki?id=mp1496561481_1TyO7
  11. :return:该开票平台专用的授权链接
  12. """
  13. return self._post(
  14. 'seturl',
  15. data={},
  16. result_processor=lambda x: x['invoice_url'],
  17. )
  18. def create_card(self, base_info, payee, invoice_type, detail=None):
  19. """
  20. 创建发票卡券模板
  21. 注意这里的对象和会员卡有类似之处,但是含义有不同。创建发票卡券模板是创建发票卡券的基础。
  22. 详情请参考
  23. https://mp.weixin.qq.com/wiki?id=mp1496561481_1TyO7
  24. :param base_info:发票卡券模板基础信息
  25. :type base_info: dict
  26. :param payee: 收款方(开票方)全称,显示在发票详情内。建议一个收款方对应一个发票卡券模板
  27. :param invoice_type: 发票类型描述
  28. :param detail: 备注详情
  29. :return: 发票卡券模板的编号,用于后续该商户发票生成后,作为必填参数在调用插卡接口时传入
  30. """
  31. return self._post(
  32. 'platform/createcard',
  33. data={
  34. 'invoice_info': {
  35. 'base_info': base_info,
  36. 'payee': payee,
  37. 'type': invoice_type,
  38. 'detail': detail,
  39. },
  40. },
  41. result_processor=lambda x: x['card_id'],
  42. )
  43. def get_auth_url(self, s_pappid, order_id, money, timestamp, source, ticket, auth_type, redirect_url=None):
  44. """
  45. 获取授权页链接
  46. 详情请参考
  47. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  48. :param s_pappid: 开票平台在微信的标识号,商户需要找开票平台提供
  49. :param order_id: 订单id,在商户内单笔开票请求的唯一识别号
  50. :param money: 订单金额,以分为单位
  51. :type money: int
  52. :param timestamp: Unix 时间戳
  53. :type timestamp: int
  54. :param source: 开票来源。app: App开票, web: 微信H5开票, wap: 普通网页开票
  55. :param ticket: 根据获取授权ticket接口取得
  56. :param auth_type: 授权类型。0: 开票授权,1: 填写字段开票授权,2: 领票授权
  57. :type auth_type: int
  58. :param redirect_url: 授权成功后跳转页面。本字段只有在source为H5的时候需要填写。
  59. :return: 获取授权页链接
  60. """
  61. if source not in {'app', 'web', 'wap'}:
  62. raise ValueError('Unsupported source. Valid sources are "app", "web" or "wap"')
  63. if source == 'web' and redirect_url is None:
  64. raise ValueError('redirect_url is required if source is web')
  65. if not (0 <= auth_type <= 2):
  66. raise ValueError('Unsupported auth type. Valid auth types are 0, 1 or 2')
  67. return self._post(
  68. 'getauthurl',
  69. data={
  70. 's_pappid': s_pappid,
  71. 'order_id': order_id,
  72. 'money': money,
  73. 'timestamp': timestamp,
  74. 'source': source,
  75. 'ticket': ticket,
  76. 'type': auth_type,
  77. 'redirect_url': redirect_url,
  78. },
  79. result_processor=lambda x: x['auth_url'],
  80. )
  81. def set_auth_field(self, user_field, biz_field):
  82. """
  83. 设置授权页字段信息
  84. 详情请参考
  85. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  86. :param user_field: 授权页个人发票字段
  87. :type user_field: dict
  88. :param biz_field: 授权页单位发票字段
  89. :type biz_field: dict
  90. """
  91. return self._post(
  92. 'setbizattr',
  93. params={
  94. 'action': 'set_auth_field',
  95. },
  96. data={
  97. 'auth_field': {
  98. 'user_field': user_field,
  99. 'biz_field': biz_field,
  100. },
  101. },
  102. )
  103. def get_auth_field(self):
  104. """
  105. 获取授权页字段信息
  106. 详情请参考
  107. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  108. :return: 授权页的字段设置
  109. :rtype: dict
  110. """
  111. return self._post(
  112. 'setbizattr',
  113. params={
  114. 'action': 'get_auth_field',
  115. },
  116. data={},
  117. )
  118. def get_auth_data(self, s_pappid, order_id):
  119. """
  120. 查询授权数据
  121. 详情请参考
  122. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  123. :param s_pappid: 开票平台在微信的标识号,商户需要找开票平台提供
  124. :param order_id: 订单id,在商户内单笔开票请求的唯一识别号
  125. :return: 用户的开票信息
  126. :rtype: dict
  127. """
  128. return self._post(
  129. 'getauthdata',
  130. data={
  131. 's_pappid': s_pappid,
  132. 'order_id': order_id,
  133. },
  134. )
  135. def reject_insert(self, s_pappid, order_id, reason, redirect_url=None):
  136. """
  137. 拒绝用户的开发票请求
  138. 详情请参考
  139. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  140. :param s_pappid: 开票平台在微信的标识号,商户需要找开票平台提供
  141. :param order_id: 订单id,在商户内单笔开票请求的唯一识别号
  142. :param reason: 拒绝原因
  143. :param redirect_url: 跳转链接
  144. """
  145. return self._post(
  146. 'rejectinsert',
  147. data={
  148. 's_pappid': s_pappid,
  149. 'order_id': order_id,
  150. 'reason': reason,
  151. 'url': redirect_url,
  152. },
  153. )
  154. def insert(self, order_id, card_id, appid, card_ext):
  155. """
  156. 制作发票卡券,并放入用户卡包
  157. 详情请参考
  158. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  159. :param order_id: 订单id,在商户内单笔开票请求的唯一识别号
  160. :param card_id: 发票卡券模板的编号
  161. :param appid: 商户 AppID
  162. :param card_ext: 发票具体内容
  163. :type card_ext: dict
  164. :return: 随机防重字符串,以及用户 Open ID
  165. """
  166. return self._post(
  167. 'insert',
  168. data={
  169. 'order_id': order_id,
  170. 'card_id': card_id,
  171. 'appid': appid,
  172. 'card_ext': card_ext,
  173. },
  174. )
  175. def upload_pdf(self, pdf):
  176. """
  177. 上传电子发票中的消费凭证 PDF
  178. 详情请参考
  179. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  180. :param pdf: 要上传的 PDF 文件,一个 File-object
  181. :return: 64位整数,在将发票卡券插入用户卡包时使用用于关联pdf和发票卡券。有效期为3天。
  182. """
  183. return self._post(
  184. 'platform/setpdf',
  185. files={
  186. 'pdf': pdf,
  187. },
  188. result_processor=lambda x: x['s_media_id'],
  189. )
  190. def get_pdf(self, s_media_id):
  191. """
  192. 查询已上传的 PDF
  193. 详情请参考
  194. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  195. :param s_media_id: PDF 文件上传时的 s_media_id
  196. :return: PDF 文件的 URL,以及过期时间
  197. :rtype: dict
  198. """
  199. return self._post(
  200. 'platform/getpdf',
  201. params={
  202. 'action': 'get_url',
  203. },
  204. data={
  205. 's_media_id': s_media_id,
  206. },
  207. )
  208. def update_status(self, card_id, code, reimburse_status):
  209. """
  210. 更新发票卡券的状态
  211. 详情请参考
  212. https://mp.weixin.qq.com/wiki?id=mp1497082828_r1cI2
  213. :param card_id: 发票卡券模板的编号
  214. :param code: 发票卡券的编号
  215. :param reimburse_status: 发票报销状态
  216. """
  217. return self._post(
  218. 'platform/updatestatus',
  219. data={
  220. 'card_id': card_id,
  221. 'code': code,
  222. 'reimburse_status': reimburse_status,
  223. },
  224. )
  225. def set_pay_mch(self, mchid, s_pappid):
  226. """
  227. 关联商户号与开票平台,设置支付后开票
  228. 详情请参考
  229. https://mp.weixin.qq.com/wiki?id=mp1496561731_2Z55U
  230. :param mchid: 微信支付商户号
  231. :param s_pappid: 开票平台在微信的标识号,商户需要找开票平台提供
  232. """
  233. return self._post(
  234. 'setbizattr',
  235. params={
  236. 'action': 'set_pay_mch',
  237. },
  238. data={
  239. 'paymch_info': {
  240. 'mchid': mchid,
  241. 's_pappid': s_pappid,
  242. },
  243. },
  244. )
  245. def get_pay_mch(self):
  246. """
  247. 查询商户号与开票平台关联情况
  248. 详情请参考
  249. https://mp.weixin.qq.com/wiki?id=mp1496561731_2Z55U
  250. :return: mchid 和 s_pappid
  251. :rtype: dict
  252. """
  253. return self._post(
  254. 'setbizattr',
  255. params={
  256. 'action': 'get_pay_mch',
  257. },
  258. data={},
  259. )
  260. def get_reimburse(self, card_id, encrypt_code):
  261. """
  262. 报销方查询发票信息
  263. 详情请参考
  264. https://mp.weixin.qq.com/wiki?id=mp1496561749_f7T6D
  265. :param card_id: 发票卡券的 Card ID
  266. :param encrypt_code: 发票卡券的加密 Code
  267. :return: 电子发票的结构化信息
  268. :rtype: dict
  269. """
  270. return self._post(
  271. 'reimburse/getinvoiceinfo',
  272. data={
  273. 'card_id': card_id,
  274. 'encrypt_code': encrypt_code,
  275. },
  276. )
  277. def update_reimburse(self, card_id, encrypt_code, reimburse_status):
  278. """
  279. 报销方更新发票信息
  280. 详情请参考
  281. https://mp.weixin.qq.com/wiki?id=mp1496561749_f7T6D
  282. :param card_id: 发票卡券的 Card ID
  283. :param encrypt_code: 发票卡券的加密 Code
  284. :param reimburse_status: 发票报销状态
  285. """
  286. return self._post(
  287. 'reimburse/updateinvoicestatus',
  288. data={
  289. 'card_id': card_id,
  290. 'encrypt_code': encrypt_code,
  291. 'reimburse_status': reimburse_status,
  292. },
  293. )
  294. def batch_update_reimburse(self, openid, reimburse_status, invoice_list):
  295. """
  296. 报销方批量更新发票信息
  297. 详情请参考
  298. https://mp.weixin.qq.com/wiki?id=mp1496561749_f7T6D
  299. :param openid: 用户的 Open ID
  300. :param reimburse_status: 发票报销状态
  301. :param invoice_list: 发票列表
  302. :type invoice_list: list[dict]
  303. """
  304. return self._post(
  305. 'reimburse/updatestatusbatch',
  306. data={
  307. 'openid': openid,
  308. 'reimburse_status': reimburse_status,
  309. 'invoice_list': invoice_list,
  310. },
  311. )
  312. def get_user_title_url(
  313. self, user_fill, title=None, phone=None, tax_no=None, addr=None, bank_type=None, bank_no=None,
  314. out_title_id=None):
  315. """
  316. 获取添加发票链接
  317. 获取链接,发送给用户。用户同意以后,发票抬头信息将会录入到用户微信中
  318. 详情请参考
  319. https://mp.weixin.qq.com/wiki?id=mp1496554912_vfWU0
  320. :param user_fill: 企业设置抬头为0,用户自己填写抬头为1
  321. :type user_fill: bool
  322. :param title: 抬头,当 user_fill 为 False 时必填
  323. :param phone: 联系方式
  324. :param tax_no: 税号
  325. :param addr: 地址
  326. :param bank_type: 银行类型
  327. :param bank_no: 银行号码
  328. :param out_title_id: 开票码
  329. :return: 添加发票的链接
  330. """
  331. if user_fill and title is None:
  332. raise ValueError('title is required when user_fill is False')
  333. return self._post(
  334. 'biz/getusertitleurl',
  335. data={
  336. 'user_fill': int(user_fill),
  337. 'title': title,
  338. 'phone': phone,
  339. 'tax_no': tax_no,
  340. 'addr': addr,
  341. 'bank_type': bank_type,
  342. 'bank_no': bank_no,
  343. 'out_title_id': out_title_id,
  344. },
  345. result_processor=lambda x: x['url'],
  346. )
  347. def get_select_title_url(self, attach=None):
  348. """
  349. 获取商户专属开票链接
  350. 商户调用接口,获取链接。用户扫码,可以选择抬头发给商户。可以将链接转成二维码,立在收银台。
  351. 详情请参考
  352. https://mp.weixin.qq.com/wiki?id=mp1496554912_vfWU0
  353. :param attach: 附加字段,用户提交发票时会发送给商户
  354. :return: 商户专属开票链接
  355. """
  356. return self._post(
  357. 'biz/getselecttitleurl',
  358. data={
  359. 'attach': attach,
  360. },
  361. result_processor=lambda x: x['url'],
  362. )
  363. def scan_title(self, scan_text):
  364. """
  365. 根据扫描码,获取用户发票抬头
  366. 商户扫用户“我的—个人信息—我的发票抬头”里面的抬头二维码后,通过调用本接口,可以获取用户抬头信息
  367. 详情请参考
  368. https://mp.weixin.qq.com/wiki?id=mp1496554912_vfWU0
  369. :param scan_text: 扫码后获取的文本
  370. :return: 用户的发票抬头数据
  371. :rtype: dict
  372. """
  373. return self._post(
  374. 'scantitle',
  375. data={
  376. 'scan_text': scan_text,
  377. },
  378. )