码农之家

【微信小程序】 将已开好的发票 插入微信卡包

小小吐槽

  • 最近在研究将开好的发票插入卡包的功能,本来以为会是前端调调接口,把发票 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
  • URLhttps://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 一个商户固定为一个,所以这个接口只需要调用一次
  • URLhttps://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
  • URLhttps://api.weixin.qq.com/cgi-bin/ticket/getticket
  • method:GET
  • 请求参数
    • type(必输):一定为 wx_card
    • access_token(必输) (从之前保存里拿)
  • 返回参数
    • ticket(保存,之后第6步会用到)
  • 文档:商户接口文章

4、设置用户联系方式

  • 获取授权链接之前,先设置商户联系方式
  • 注意:每一次用户授权之前,都需要调用这个接口
  • URLhttps://api.weixin.qq.com/card/invoice/setbizattr?action=set_contact&access_token={access_token}
  • method: POST
  • 请求参数
    • 以下代码块中的字段均为必输
    • {
      	"contact" :
          {
              "phone" : "88888888",		// 联系电话
              "time_out" : 12345,			// 开票超时时间
          }
      }
      
  • 文档:商户接口文章

5、创建发票卡券模板

  • 这就是前面说的,要将发票 pdf 插入卡包之前,必须在发票 pdf 外层套一个卡券模板,调用这个接口后获得的 card_id 就是这一类模板的 id,可以保存起来反复使用
  • 注意:一个类型的卡卷模板,可以重复使用,如果不换卡卷模板,只需调用一次
  • URLhttps://api.weixin.qq.com/card/invoice/platform/createcard?access_token={access_token}
  • method: POST
  • 请求参数
    • invoice_info:以下代码块中的字段均为必输
    • {
      	"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(领取发票类型):用于商户发票已开具成功,拉起授权页后让用户将发票归集保存到卡包。自建平台模式下需用该类型。
  • URLhttps://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:时间戳(感觉没用,但微信文档上说必输)
    • 以下代码块中的字段均为必输
    • {
          "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
  • 返回结果
    • 小程序回调
    • wx.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 一直未关联 卡券模板的话,三天后会被微信清理掉
  • URLhttps://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 全都关联起来,将发票套在卡券模板后,插入用户的卡包中
    • 注意:每次插入卡包都要调用该接口
  • URLhttps://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:数电票时为空
    • 以下代码块中的字段都为必输
    • {
          "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
  • 文档:开票平台接口文章

以下不是必要步骤,用于辅助查询的

小程序操作第三方授权 后 调用后台查询授权完成状态 (只是查询授权状态的)

  • URLhttps://api.weixin.qq.com/card/invoice/getauthdata?access_token={access_token}
  • method: POST
  • 请求参数
    • order_id:订单参数
    • s_pappid:之前保存的平台识别码
  • 返回参数
    • invoice_status:订单授权状态
    • auth_time:授权时间
  • 文档:商户接口文章

查询已上传的PDF文件

  • URLhttps://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,也无法对公众号、白名单进行配置,所以卡了很久,然后才决定这些都放到后端处理,只留授权在前端
  • 但是理论上所有的流程都是可以在前端做的,如果有自己能配置权限、能获取到字段数据的,可以自己尝试看看能不能走通
  • 希望这篇文章能让正在苦恼的人少走点弯路~

原文链接:https://juejin.cn/post/7323055938066939955 作者:Vaner_42