小小吐槽
- 最近在研究将开好的发票插入卡包的功能,本来以为会是前端调调接口,把发票 pdf 插入微信卡包就大功告成了,结果有很多前置条件,比如要小程序公众号的 appid、secret,还要去配置 ip 白名单。
- 因为不是我自己的小程序,所以这些东西很多我没法获得和配置,最后就决定大部分接口从后端去调,前端只需要获取个权限即可,但是理论上,前端如果能配置好和获取到这些数据的话,也完全可以在前端实现。
- 然后吐槽下微信的文档实在是太乱了,单单找个关于微信插入卡包的实现方案,我从小程序文档,看到公众号文档,再看到微信支付文档,最后才找到别人踩过坑的记录,但是就连最后开始弄都要从多个api文档中慢慢摸索,反正就是很乱
- 还有微信的开放社区也很难有参考价值,大部分都是有人提出问题,然后没有后续了,或者就是有人提出了解决方案,但是也不知道有没有用,还有些说发票插入微信卡包只能用微信支付的接口
- 所以我稍微把之前别人和自己踩过的坑稍微整理了一下,希望之后碰到的人能少踩点坑,少走点弯路
- 这是别人大佬之前踩坑的记录,发票同步微信卡包
- 然后就是之后需要用到的接口文档:
选择插入卡包的模式
- 我使用的是自建平台模式,简单说说我对于插入卡包的两种接入模式的理解
- 自建平台模式:
- 自建平台指的是商户自己有能力开票,自己开完票后通过调用微信的接口插入卡包
- 不过商户自己开完的票,不能直接调用接口,还需要在其外面套一层微信的卡券模板,才能调用插入卡包的接口
- 不过卡券模板上的内容都是可以自定义的,比如金额、时间、名称,因为只是展示在微信的
- 然后发票套用了卡券模板后插入卡包的时候,猜测微信自己做了 ocr 识别,所以同一张发票不能重复插入,下一次再上传同一张发票 pdf 的时候,微信就会有报错提示
- 商户+开票平台模式:
- 商户+开票平台,其实就是指商户不能自己开票,所以委托第三方开票平台帮其开票
- 整体步骤和自建平台模式差不多,只不过最后不需要商户自己去套一层卡券模板,直接交给开票平台就行,开票平台会在开好票后,直接将发票插入卡包
前置条件
- 需要将小程序和公众号关联
- 因为需要用到公众号的电子发票的权限
- 如果只用小程序的 appid 和 secret 去获取 access_token 会报错提示没有权限
- 申请公众号的电子发票权限
- 去公众号后台,在功能插件中加入 "电子发票" 的功能
- 然后进入 "电子发票" 中开通授权
- 具体可参考这个文档:申请电子发票权限
- 查看并记录 公众号的 appid 和 secret
- 登入公众号后台后,顺便查看公众号的 appid 和 sectet,用于只有通过接口获取 access_token
- 将 ip 地址配置到接口权限白名单下
- 这一步具体在哪可能需要各位稍微探索一下了,因为我自己没有权限配置
- 如果没有配置白名单,会在获取 access_token 的时候报错提示 ip 不在白名单下
1、 根据公众号 appid 和secret 获取 access_token
- 这个 access_token 是之后调所有接口都需要用到的凭证
- 注意:access_token 会过期,需要定时调用,失效为 7200s
- URL :
https://api.weixin.qq.com/cgi-bin/token
- method:GET
- 请求参数 :
- grant_type(必输):一定为 client_credential
- appid (必输)
- secret (必输)
- 返回参数 :
- access_token (记得保存,之后的接口都需要用)
- expires_in
- 文档:获取 access_token
2、获取自身的开票平台识别码
- 商户在公众号配置了电子发票的权限后,会自动生成这个 s_pappid
- 注意:s_pappid 一个商户固定为一个,所以这个接口只需要调用一次
- URL :
https://api.weixin.qq.com/card/invoice/seturl?access_token={access_token}
- method: POST
- 请求参数 :
- {}
- 返回参数 :
- invoice_url:从这个 url 中拿到 s_pappid(保存,在第6步会用到,而且这个 s_pappid 永远不变,保存以后,下次都不需要调这个接口)
- 文档:开票平台接口文章
3、获取授权页ticket
- 在之后调用授权页时会需要用到这个 ticket
- 注意:ticket 会过期,所以需要定时调用,失效为 7200s
- URL :
https://api.weixin.qq.com/cgi-bin/ticket/getticket
- method:GET
- 请求参数 :
- type(必输):一定为 wx_card
- access_token(必输) (从之前保存里拿)
- 返回参数 :
- ticket(保存,之后第6步会用到)
- 文档:商户接口文章
4、设置用户联系方式
-
获取授权链接之前,先设置商户联系方式
-
注意:每一次用户授权之前,都需要调用这个接口
-
URL :
https://api.weixin.qq.com/card/invoice/setbizattr?action=set_contact&access_token={access_token}
-
method: POST
-
请求参数 :
- 以下代码块中的字段均为必输
js{ "contact" : { "phone" : "88888888", // 联系电话 "time_out" : 12345, // 开票超时时间 } }
-
文档:商户接口文章
5、创建发票卡券模板
-
这就是前面说的,要将发票 pdf 插入卡包之前,必须在发票 pdf 外层套一个卡券模板,调用这个接口后获得的 card_id 就是这一类模板的 id,可以保存起来反复使用
-
注意:一个类型的卡卷模板,可以重复使用,如果不换卡卷模板,只需调用一次
-
URL :
https://api.weixin.qq.com/card/invoice/platform/createcard?access_token={access_token}
-
method: POST
-
请求参数 :
- invoice_info:以下代码块中的字段均为必输
js{ "invoice_info": { "base_info": { "logo_url": "http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0", // 商户logo "title": "xx公司", // 商户名称 }, "type": " 广东省增值税普通发票 ", // 发票类型名称,字符串类型,完全自定义,微信不校验 "payee": " 测试 - 收款方 ", // 收款方名称,字符串类型,完全自定义,微信不校验 } }
-
返回参数 :
- card_id(保存,之后会用)
-
文档:开票平台接口文章
前端调授权前置条件
- 后端走完了上面 5 步,接下来 6、7 就是前端该做的了
- 首先,需要后端给一个接口返回以下之前保存的字段
- access_token:通过公众号 appid 和 secret 获取到的 token 凭证
- s_pappid:唯一的商户开票识别码
- ticket:授权页需要的 ticket 凭证
6、获取授权页链接 (前端调)
-
将订单号、平台识别码、ticket 通过该接口获得授权页面的链接
- 注意:一个订单就要获取一次授权
- 根据传入的 type 不同,会给不同的授权页链接,存在三种类型
- type=0(申请开票类型):商户从其它渠道获得用户抬头,拉起授权页发起开票,开票成功后保存到用户卡包;
- type=1(填写抬头申请开票类型):调用该类型时,页面会显示微信存储的用户常用抬头。需要留意,当使用支付后开票业务时,只能调用type=1类型。
- type=2(领取发票类型):用于商户发票已开具成功,拉起授权页后让用户将发票归集保存到卡包。自建平台模式下需用该类型。
-
URL :
https://api.weixin.qq.com/card/invoice/getauthurl?access_token={access_token}
-
method: POST
-
请求参数 :
- type:自建平台模式下为 2
- order_id:在自建平台模式下,这里就不必是订单号,可以是发票id、发票num 之类的,可以自由定义
- source:小程序为 wxa
- s_pappid:之前步骤中保存的 平台识别码
- ticket:之前步骤中保存的 授权 ticket
- money:订单金额(感觉没用,但微信文档上说必输)
- timestamp:时间戳(感觉没用,但微信文档上说必输)
- 以下代码块中的字段均为必输
js{ "s_pappid": "wxabcd", // 从之前保存的拿 "ticket": "tttt", // 从之前保存的拿 "order_id": "12345", // 订单id,在商户内单笔开票请求的唯一识别号 "money": 11, // 订单金额 "timestamp": 1474875876, // 时间戳 "source": "wxa", // wxa:小程序开发票 "type": 2 }
-
返回参数 :
- auth_url: 授权页的链接 (保存,下一步需要打开这个授权页)
- appid 小程序的appid
-
文档:商户接口文章
7、小程序打开授权页 (前端调)
-
通过调用微信的方法 wx.navigateToMiniProgram,打开上一步获得的授权页,在确定授权后通过返回的回调继续后续操作
- 注意:一个订单就要获取一次授权
-
请求参数 :
- appid:上一步返回的小程序 appid
- path:上一步返回的 auth_url
-
返回结果 :
- 小程序回调
jswx.navigateToMiniProgram({ appId: '{appid}', path: '{auth_url}', success(res) { console.log('navigateToMiniProgram success:', res) }, fail(error){ console.log('navigateToMiniProgram fail:', error) }, complete(res){ console.log('navigateToMiniProgram complete:', res) } })
-
文档:商户接口文章
前端需要传回的数据
- 经过短暂的两步操作后,又需要回到后端操作了
- 前端需要把刚刚在第6步请求参数中的 order_id 给到后端就可以了
8、上传PDF
- 将发票 pdf 上传至微信,会获得 s_media_id 标识,之后与卡券模板的 card_id 关联在一起后就可以插入卡包了。
- 注意:
- 每次上传一次 pdf,都要调用该接口
- 如果 pdf 一直未关联 卡券模板的话,三天后会被微信清理掉
- 注意:
- URL :
https://api.weixin.qq.com/card/invoice/platform/setpdf?access_token={access_token}
- method: POST
- 请求参数 :
- pdf,格式 multipart/form-data
- 返回参数 :
- s_media_id (保存,之后要用)
- 文档:开票平台接口文章
9、 将电子发票卡券插入用户卡包
-
通过该接口将 order_id、appid、card_id、s_media_id 全都关联起来,将发票套在卡券模板后,插入用户的卡包中
- 注意:每次插入卡包都要调用该接口
-
URL :
https://api.weixin.qq.com/card/invoice/insert?access_token={access_token}
-
method: POST
-
请求参数 :
- order_id:订单号,如果之前在第6步自定义了,就以自定义的为准
- appid:小程序的 appid(这里官方文档说是公众号 appid,但我不太清楚到底是哪个,大家可以试一试,反正无非就是两个,小程序或公众号的 appid)
- card_id:之前保存的卡券模板 id
- s_pdf_media_id: 上一步上传 pdf 返回的 media_id
- nonce_str :随机字符串
- billing_code:数电票时为空
- ...
- 以下代码块中的字段都为必输
js{ "card_id": "pjZ8Yt9WoOePThU0NfUKz5-tBEWU", "appid": "wxc0b84a53ed8e8d29", "order_id": "111163", "card_ext": { "nonce_str": "j!Re1WxaHv", // 随机字符串 "user_card": { "invoice_user_data": { "info": [ { "price": 10000, "name": "牙膏", } ], "billing_no": "4545145712", // 发票的发票号码 "billing_code": "4541212454512", // 发票的发票代码,如果是数电票,这个为空就行 "billing_time": "1468306058", // 发票的开票时间 "tax": 123, // 税额 "s_pdf_media_id": "s_pdf_media_id_abc123", "fee": 123, // 发票的金额 "title": "灌哥发票", // 发票的抬头 "fee_without_tax": 2345 // 不含税金额 } } }, }
-
返回参数 :
- code:发票code
- openid:用户openid
-
文档:开票平台接口文章
以下不是必要步骤,用于辅助查询的
小程序操作第三方授权 后 调用后台查询授权完成状态 (只是查询授权状态的)
- URL :
https://api.weixin.qq.com/card/invoice/getauthdata?access_token={access_token}
- method: POST
- 请求参数 :
- order_id:订单参数
- s_pappid:之前保存的平台识别码
- 返回参数 :
- invoice_status:订单授权状态
- auth_time:授权时间
- 文档:商户接口文章
查询已上传的PDF文件
- URL :
https://api.weixin.qq.com/card/invoice/platform/getpdf?action=get_url&access_token={access_token}
- method: POST
- 请求参数 :
- s_media_id:之前保存的 上传 Pdf 后的返回值
- action:一定为 get_url
- 返回参数 :
- pdf_url
- 文档:开票平台接口文章
总结
- 其实整个流程不算很困难,主要是找到这个流程得花费不少时间,因为文档实在太乱太杂,加上前期前置条件不完全,前端无法获取到 token,也无法对公众号、白名单进行配置,所以卡了很久,然后才决定这些都放到后端处理,只留授权在前端
- 但是理论上所有的流程都是可以在前端做的,如果有自己能配置权限、能获取到字段数据的,可以自己尝试看看能不能走通
- 希望这篇文章能让正在苦恼的人少走点弯路~