抬头应用

1简介

用户在开票时,常常因为忘记抬头或者抬头录入太繁琐而烦恼。支付宝提供的抬头应用主要解决这两个问题,一是方便用户记录和保管抬头;二是方便用户快速录入或者选择抬头达到便捷开票的目的。

2发票管家-发票抬头管理

作用:帮助用户记录和管理抬头,便于后续开票使用。

在支付宝首页搜索发票或者【支付宝】-【更多】-【便民生活】里可以打开发票管家,在发票管家里可以找到发票抬头,用户可在这里新增、修改、删除发票抬头。如下图所示:

image.png

用户在新增、修改抬头时支持模糊查询抬头,帮助用户快速录入抬头。

image.png

3发票抬头二维码规范

二维码示例如下:

image.png

二维码编码规范

抬头输出二维码采用QR二维码。
 
二维码内容文本格式为:
 
表单标签:1(首位,固定值)
 
1标签1内容→2标签2内容→3标签3内容→4标签4内容→A扩展标签A内容…
 
用户抬头信息由多个信息标签组成,以分隔符“→”区隔不同的信息标签
       
标签号,代表标签内容编码:
1 代表购买方公司名称,长度在100个字节以内
2 代表购买方公司纳税人识别号,长度在15~20个字节,内容要求是数字或大写字母(半角输入)
3 代表购买方公司地址 电话,长度在100字节以内
4 代表购买方开户行及账号,长度在100字节以内
A 代表扩展内容A,例如购买方ID号

Z 代表扩展内容Z
 
标签内容中不能含有分隔符“→”,内容采用UTF-8编码。
 
二维码内容示例如下
11支付宝(中国)网络技术有限公司→2173777383832737→3杭州市西湖区天目山路黄龙时代广场 18366622223→4招商银行6238866628882726→AALIPAY_p2i9H2QO73toXgTfIYkBsW/VMpl8CkCZ→

解析获取

以如下二维码内容为例:
11支付宝(中国)网络技术有限公司→2173777383832737→3杭州市西湖区天目山路黄龙时代广场 18366622223→4招商银行6238866628882726→AALIPAY_p2i9H2QO73toXgTfIYkBsW/VMpl8CkCZ→
 
第一步:
去掉第一位标签位”1”(固定值,isv无需关注,可直接跳过),得到:
1支付宝(中国)网络技术有限公司→2173777383832737→3杭州市西湖区天目山路黄龙时代广场 18366622223→4招商银行6238866628882726→AALIPAY_p2i9H2QO73toXgTfIYkBsW/VMpl8CkCZ→
 
第二步:
以”→”为分隔符进行分割,得到内容分组[1支付宝(中国)网络技术有限公司][2173777383832737][3杭州市西湖区天目山路黄龙时代广场 18366622223][4招商银行 6238866628882726][ AALIPAY_p2i9H2QO73toXgTfIYkBsW/VMpl8CkCZ]
 
第三步:
 
顺次解析每个分组内容,检验标签标号是否正确,如取第一个分组第一个字符[1支付宝(中国)网络技术有限公司],判断是否等于“1“,如果是,去掉第一个字符后的内容即为购买方抬头名称即”支付宝(中国)网络技术有限公司”,依次类推。
 
注意:字符采用UTF-8编码,在文件前三字节会带有BOM头,如发现扫码前缀带有“0xEF,0xBB,0xBF”需跳过前三字节,不同的编辑器及浏览器厂商会针对UTF-8的BOM前缀自动过滤处理。关于BOM可参看:https://en.wikipedia.org/wiki/Byte_order_mark

DEMO示例

 package com.alipay.invoice.demo;

public class TitleDemo {

    public static void main(String[] args) {

        String qrCode = "11支付宝(中国)网络技术有限公司→2173777383832737→3杭州市西湖区天目山路黄龙时代广场 18366622223→4招商银行 6238866628882726→AALIPAY_p2i9H2QO73toXgTfIYkBsW/VMpl8CkCZ→";

        String firstKey = qrCode.substring(0, 1);

        if (!"1".equals(firstKey)) {
            System.out.println("解析格式失败,首字符不为1");
            return;
        }

        qrCode = qrCode.substring(1, qrCode.length());

        if (null == qrCode || "".equals(qrCode)) {
            System.out.println("解析格式失败");
            return;
        }

        String[] titleContents = qrCode.split("→");

        if (titleContents == null || titleContents.length < 1) {
            System.out.println("解析格式失败");
        }

        //        购买方公司名称(必填)
        String firstContent = titleContents[0];
        if (!"1".equals(firstContent.substring(0, 1))) {
            System.out.println("解析格式失败,获取获取购买方公司名称失败");
        }

        String titleName = firstContent.substring(1, firstContent.length());
        System.out.println("购买方公司名称:" + titleName);

        //        购买方公司纳税人识别号(可空)
        String secdContent = getContent(titleContents, "2");
        if (null == secdContent || "".equals(secdContent)) {
            System.out.println("解析格式失败,获取购买方公司纳税人识别号失败");
        }

        String regNO = secdContent;
        System.out.println("购买方公司纳税人识别号:" + regNO);

        //        购买方公司地址 电话(可空)
        String thirdContent = getContent(titleContents, "3");
        if (null == thirdContent || "".equals(thirdContent)) {
            System.out.println("解析格式失败,获取购买方公司地址 电话失败");
        }

        String addressAndTel = thirdContent;
        System.out.println("购买方公司地址 电话:" + addressAndTel);

        //        购买方开户行及账号(可空)
        String fourContent = getContent(titleContents, "4");
        if (null == fourContent || "".equals(fourContent)) {
            System.out.println("解析格式失败,获取购买方开户行及账号失败");
        }

        String bankAndAccount = fourContent;
        System.out.println("购买方开户行及账号:" + bankAndAccount);

        String alipayUid = getContent(titleContents, "A");
        if (null == alipayUid || "".equals(alipayUid) || alipayUid.length() <= 7) {
            System.out.println("解析格式失败,获取购买方支付宝id失败");
        }
        String uid = alipayUid.substring(7, alipayUid.length());
        System.out.println("购买方支付宝id:" + uid);

    }

    private static String getContent(String[] titleContents, String key) {
        for (int i = 0; i < titleContents.length; i++) {
            String content = titleContents[i];
            String firstChar = titleContents[i].substring(0, 1);
            if (key.equals(firstChar)) {
                return content.substring(1, content.length());
            }
        }
        return null;
    }

}

4抬头输出-开票时选抬头

作用:在用户主动选择的情况下,将用户选择的抬头输出给开票商家,方便用户快速完成开票。

模式:目前支付宝主推抬头组件的方式用于开发票时选择抬头。

发票抬头选择是由支付宝发票管家业务提供的一项面向支付宝内ISV提供的通用服务。通过在支付宝H5容器或支付宝小程序环境下可直接通过api唤起抬头组件,让用户选择预录到发票管家的抬头用于快速开票。由于支付宝内即可调用该组件,出于用户隐私保护的考虑,本组件仅返回抬头的动态码。仅限开通了发票抬头获取功能的服务商通过openAPI换取真实抬头信息。

DEMO示例

image.png

接入说明

系统流程

image.png

H5API

支付宝内H5页面即可调用

使用方式

AlipayJSBridge.call('startBizService', {
  name: 'invoice-title' // name为服务名称,发票抬头写死为invoice-title
}, function(result){
  // 签约代扣的结果,或是报错
  // 示例结果
  // result = {
  //   resultStatus: '9000', // 9000 成功,4000 系统异常,6002 网络异常,尽量按此约定执行
  //   result: { // 回调是json object
  //     dynamicCode: '' //抬头动态码
  //   }
  // }
});

小程序API

my.chooseInvoiceTitle(Object),基础库 1.5.4 开始支持,低版本需做兼容处理

调起用户的支付宝发票抬头界面,并在用户点击选择后返回用户选择的抬头动态码。

入参

名称 类型 必填 描述
success Function 调用成功的回调函数
fail Function 调用失败的回调函数
complete Function 调用结束的回调函数(调用成功、失败都会执行)

success 返回

名称 类型 描述
dynamicCode string 用户选择的抬头动态码,目前有效期限制1小时,需尽快使用

示例代码

my.chooseInvoiceTitle({
  success: (res) => {
    my.alert({
      title: 'ok', // alert 框的标题
      content: JSON.stringify(res),
    });
			// 这里可以拿到 dynamicCode
			// 将 dynamicCode 传给服务端,服务端再调用openapi 获取真实的发票抬头
			
  },
  fail: (res) => {
    my.alert({
      title: 'fail', // alert 框的标题
      content: JSON.stringify(res),
    });
  },
});

服务端接口

服务端通过动态码换取真实抬头信息,可看服务端openApi文档:
alipay.ebpp.invoice.title.dynamic.get(根据动态码查询发票抬头)

onlineServer