今天公司让实现支付宝刷脸支付对接自己的程序实现刷脸,在网上看了一些博客发现大部分都有点乱,现在我来总结一下自己的实现方法,整个文章分为四个部分:流程分析、客户端实现、服务端实现、实现程序所必须的数据。

当然,在看该文章时最好先浏览一下官方文档,结合理解,同时下载相应的sdk等。
这个官方文档流程说的比较清晰↓
https://docs.alipay.com/pre-open/20180402104715814204/quickstart 这个官方文档的代码比较清晰↓
https://docs.alipay.com/pre-open/20171214172813219030/qs8myr

一:流程分析

首先我们先来看一下官方的时序图,明确一下接入流程。

分析时序图一方面是为了能够弄清楚整个接入的运行流程,另一方面则是为了明确什么是我们应该做的,什么是支付宝以及给我们实现好的。

android 支付宝 人脸支付 对接 支付宝人脸支付接口_数据


从时序图上看,我们需要重点关注的只有从商户APP和商户服务端发出和接收的箭头

也就是说橙色块的是需要我们自己实现的。蓝色色块的全部都是支付宝已经实现的部分。

从我的实践上来讲,刷脸SDK和刷脸APP我们也需要在意一下。

当然我们并不需要了解这两个部分是怎么实现的,但是我们需要注意下我们的要开发的程序与这两个部分的关系。

刷脸SDK:我们的程序内部必须含有该部分,官方文档有相应下载。

刷脸APP:这东西需要看你要开发的产品中是否已经安装了,如果没安装考虑是内嵌的自己的程序中一起安装还是先在相应的设备进行安装。

下面我们开始结合时序图和官方demo代码来进行分析。
(我步骤的标号和时序图上的步骤标号一一对应。)

二:客户端

0.服务的初始化

Zoloz Zoloz; // 单例
Zoloz = com.alipay.zoloz.smile2pay.service.Zoloz.getInstance(getApplicationContext());
Zoloz.zolozInstall(mockInfo());

在官方时序图上没有这个步骤,可是这个代码必须在你所有步骤之前就先进行调用。
以下是官方文档的对于这步骤的解释:开发者通过该接口启动蚂蚁佐罗的服务初始化,进而使用刷脸识别用户的服务。
按我的理解就是说:在开始时先调用这段代码就是向刷脸服务器提交一个申请告诉对方,我需要使用你的扫脸服务了,请批准。然后将自己的的申请表发过去。

其中这个 mockInfo() 就是你的申请表,我们来看一下这个方法的内容:

Map mockInfo() {
        Map merchantInfo = new HashMap();
        
        merchantInfo.put("partnerId", "2088201820182018");
        merchantInfo.put("merchantId", "2088201820182018");
        merchantInfo.put("appId", "2088201820182018");        
        merchantInfo.put("storeCode", "我是门店编码");
        merchantInfo.put("brandCode", "我是品牌编码");
        merchantInfo.put("areaCode", "");
        merchantInfo.put("geo", "0.000000,0.000000");
        merchantInfo.put("wifiMac", "");
        merchantInfo.put("wifiName", "");
        merchantInfo.put("deviceNum", "990000001212");
        merchantInfo.put("deviceMac", "CC:B8:A8:0F:63:3C");
		return merchantInfo;
    }

这段代码相当简单,就是返回一个Map数据。
这个Map中的key是固定的字符串,value是你的具体数值。

我们来看一下这些key分别代表的含义:

string merchantId;      // 必选,商户ID,来自蚂蚁开放平台,示例:"2088011211821038"
string partnerId;       // 必选,合作商户ID,来自蚂蚁开放平台,示例:"2088011211821038"
string deviceNum;       // 必选,设备唯一编号,商户自定义
string deviceMac;       // 必选,设备Mac 地址,例如:"CC:B8:A8:0F:63:3C"
string brandCode;       // 必选,品牌编码,商户自定义
string appId;           // 可选,商户应用ID,来自蚂蚁开发平台
string storeCode;       // 可选,门店编码,商户自定义
string areaCode;        // 可选,区域编码,商户自定义
string geo;             // 可选,机具坐标,例如"0.000000,0.000000"
string wifiMac;         // 可选,wifi Mac地址
string wifiName;        // 可选,wifi Mac名字

从列表中我们可以很清楚这些key分别对应了什么value。
什么是必选的什么是可选的。我们先来总结一下必选的数据有哪些:
商户ID、合作商户ID、设备唯一编号、设备Mac 地址、品牌编码
以上就是我们所必须的一部分资料。

2.1代码执行对应的时序图

现在,服务初始化已经完成了,我们开始时序图第一步1. 设备信息相关操作,使用刷脸SDK获取设备信息。
先来看一下官方API:

/**
     * zoloz getMetaInfo,zoloz获取metaInfo
     * @param info
     * @param zolozCallback
     * @return
     */
    public void zolozGetMetaInfo(final Map info, final ZolozCallback zolozCallback)

支付宝的API做的并不好,我们先来分析一下这个API。
这个方法有两个参数,其中第一个Map就是我们上面提到的mockInfo()。我们需要将自己的设备信息通过该函数发送到刷脸SDK执行了就是在执行 1.获取设备信息 的过程。
第二个ZolozCallback是一个官方的回调方法,它就是在执行时序图中 1.1返回设备信息 这个过程。
zoloz getMetaInfo,zoloz获取metaInfo,而这句话的意思就是说支付宝通过zoloz对象和这个方法可以获得设备信息。

现在我们来看一下官方的代码:

android 支付宝 人脸支付 对接 支付宝人脸支付接口_客户端_02


这一段就是判断传过去mockInfo()是否是空值。如果是空值就不必再执行下面的流程了。

如果如果我们传过去的设备信息不是空值得话,获得刷脸APP返回的的设备信息。
这个信息包含两个数据:code和metainfo。
其中code是用来判断我们传入的设备信息是否顺利通过了。
关于code值所代表的含义详细的可以去查看官方文档。我们只需要知道“1000”这个值是检查成功的意思,其余的值都代表各种各样的错误。

而metainfo代表的是我们设备的信息(暂不确定,清明过后测试一下,再作确定。)

android 支付宝 人脸支付 对接 支付宝人脸支付接口_客户端_03


现在我们来审视一下这一整段代码:

android 支付宝 人脸支付 对接 支付宝人脸支付接口_java_04


经过前面的过程我们已经可以顺利判断,该设备可以是否可以使用刷脸功能。

接下来我们就开始准备执行人脸初始化,当我们判断code是否等于“1000”的时候其实就是在执行2.人脸初始化(可用性判断) 这个过程。

红色箭头指向的位置需要写上我们向服务器进行请求获得zinInit与zimIntiClienData。这两个关键值的代码,下面是我自己实现的连接服务器客户端的代码:
这里我使用Socket来进行网络编程:
客户端

//连接商户服务器,获得zinInit与zimIntiClienData。
//这里填写商户服务端的Ip地址
String host="";
//创建Socket,不用分配端口号客户端会自动分配限制的端口。
Socket socket=new Socket(InAddress.getByName(host));
//创建输入流对象
InputStream in=socket.getInputStream();
byte[] bys=new byte[1024];
int len=in.read (bys);// 返回的值为 bys 所存储了字节的大小
String string=new String (bys,0,len);// 这个构造函数的作用为将 bys 中从 0 到 len 长度的字节转换为 String 同时拷贝到 string;
//我们将这段字符串转换为JSON对象
 JSONObject json = new JSONObject(string);  
 String zimId=json.get("zimId");
 String zimInitClientData=json.get("zimInitClientData");
 //至此客户端获得了调用人脸支付的必要信息,关闭输入流
 in.close();

接下来的人脸初始化开放请求发生在服务端,了解这一过程,请点击这里跳转到相应的位置.
现在假设我们已经假设我们已经通过服务器获得了相应的关键信息。
其中最重要的是zimId和zimInitClientData这两个参数。因为我们需要这两个参数来进行下面的唤醒人脸的操作。

现在来执行3.唤起人脸识别。唤起人脸识别的过程直接由我们的前端执行相应的代码调用SDK就可以。


下面为官方唤起人脸识别的API:


/**
     * zoloz verify,zoloz调用smile2pay
     * @param zimId
     * @param params
     * @param zolozCallback
     * @return
     */
    public void zolozVerify(final String zimId, final Map params, ZolozCallback zolozCallback)

我们现在分析一下这个方法的参数。这个方法有三个参数,分别是zimId,params、zolozCallback.其中zimId,params是由我们的服务器端返回的初始化结果。zolozCallback照例是一个回调函数。zoloz verify,zoloz调用smile2pay,这句话就是说我们可以通过zoloz对象的这个方法调出刷脸支付的效果。.

android 支付宝 人脸支付 对接 支付宝人脸支付接口_客户端_05


通过代码我们可以看出,除了方法上的不同唤起人脸识别的过程与人脸的初始化没有太大的差别。只不过人脸识别传回来的是参数“ftoken”。

关于这个参数官方的解释是ftoken

标识刷脸认证成功,作为查询接口的入參。由此可以这个参数是刷脸认证成功的返回值也就是3.1返回人脸识别的结果中的结果。而那个所谓的查询接口是指“当面付”的接口。我们的服务器可以使用foken来作为从支付宝扣钱的凭证。

最后一行代码为 服务释放 表示此刻使用扫脸付的功能完毕。

@Override
    protected void onDestroy() {
        super.onDestroy();
        Zoloz.zolozUninstall();
    }

最后我们总结一下客户端运行起来所必须的参数包含哪些。
其中“商户ID、合作商户ID、设备唯一编号、设备Mac 地址、品牌编码”这些为在客户端就必须交给客户端的信息。初次之外我们还需要获得从服务器传来的zimId,params这两个数据。

那么服务器怎么获得这些数据呢?我们来看下服务器端的代码。

支付功能
现在假设我们已经获得了所有应得的数据了,那么我们怎么进行支付呢?

//根据时序图,我们需要向服务器端发起收单请求
void pay(String ftoken)
{
//必要入参:我们需要向服务器提供ftoken
//我们依旧使用Scoket编程java5之后的语法,只不过我们这次是向服务器端发送数据
//这里填写商户服务端的Ip地址
String host="";
//创建Socket,不用分配端口号客户端会自动分配限制的端口。
Socket socket=new Socket(InAddress.getByName(host));
//创建一个输出流对象
OutputStream out=socket.getOutputStream();
 //再将String对象转换成Byte类型的数据
 byte[] ftokenByte = ftoken.getBytes();  
//再将该数据发送出去
out.write(ftokenByte);
 //接下来我们需接收到相应的收单结果并将结果返回
 
 //..根据获得结果会进行不同的反应,这里再写下去就超出刷脸支付的范围了,等以后有机会了在补上
 
 //最后关闭输出流
 out.close();
}

三:服务器


从时序图上可以看出我们服务器只需要负责两件事情就行了。


我们先来看看第一件 :向支付宝网关发送人脸初始化请求
我们来看一下官方的代码↓

//实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
"app_id",
"your private_key",
"json",
"GBK",
"alipay_public_key",
"RSA2");
/*实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:zoloz.authentication.customer.smilepay.initialize.该接口可以获取刷脸服务的初始化信息。*/
ZolozAuthenticationCustomerSmilepayInitializeRequest request = new ZolozAuthenticationCustomerSmilepayInitializeRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数
request.setBizContent("{" +"\"zimmetainfo\":\"{
 \\\"apdidToken\\\":  \\\"设备指纹\\\", 
  \\\"appName\\\": \\\"应用名称\\\",
   \\\"appVersion\\\": \\\"应用版本\\\",
    \\\"bioMetaInfo\\\": \\\"生物信息如2.3.0:3,-4\\\" }
    \"" +"  }");
   //可能只有支付宝自己知道这是干啥的。反正具体就是大概就是将我们的参数传给支付宝,再返回我们我们所需要的结果。
ZolozAuthenticationCustomerSmilepayInitializeResponse response = alipayClient.execute(request);
if(response.isSuccess()){
System.out.println("调用成功");
//使用人脸初始化方法
FaceInitialization(response);
//使用支付功能
 SeverPay(alipayClient);
} else {
System.out.println("调用失败");
} 

//我们使用这个方法用来接收客户端的必要信息同时用来发送相应的zimId与zimInitClientData
//因此该方法需要一个reponse的入参
void FaceInitialization(ZolozAuthenticationCustomerSmilepayInitializeResponse  zolozResponse)
{
//这里需要打开一个异步线程,用来接收客户端的数据。这里我们使用java5之后的语法
 Executor executor = Executors.newFixedThreadPool(10);
 executor.execute(new Runnable() {
   public void run() {
//这这里将相应的数据传回客户端
//发送数据
 //使用9955端口来接收数据
ServerSocket severSocket=new ServerSocket(9955);
//先获得相应的zimId和,zimInitClientData
 String zimId = zolozResponse.getZimId();
 String zimInitClientData = zolozResponse.getZimInitClientData();
//服务端进行监听
Socket socket=severSocket.accept();
//创建一个输出流对象
OutputStream out=socket.getOutputStream();
 //先将相应的数据转换转成json类型方便传输
Map IdClientData=new HashMap();
IdClientData.put("zimId ",zimId );
IdClientData.put("zimInitClientData",zimInitClientData);
 JSONObject json = new JSONObject(IdClientData);  
 //将JSON对象转换成String类型
  String jsonString=json.toString();  
  //再将String对象转换成Byte类型的数据
   byte[] jsonByte = jsonString.getBytes();  
   //再将该数据发送出去
   out.write(jsonByte);
   //最后关闭输出流
   out.close();
   }
  });
}


//向刷脸服务端发送收单请求,并将收单结果发送给客户端
//根据官方demo我们需要一个客户端实例,这里我将其作为参数直接传入
void SeverPay(AlipayClient alipayClient)
{
//另起一个线程
//这里需要打开一个异步线程,用来接收客户端的数据。这里我们使用java5之后的语法
 Executor executor = Executors.newFixedThreadPool(10);
 executor.execute(new Runnable() {
   public void run() {
   //先创建一个Socket来接收ftoken
   //使用9955端口来接收数据
ServerSocket severSocket=new ServerSocket(9955); 
//服务端进行监听
Socket socket=severSocket.accept();
//创建输入流对象
InputStream in=severSocket.getInputStream();
byte[] bys=new byte[1024];
int len=in.read (bys);// 返回的值为 bys 所存储了字节的大小
String string=new String (bys,0,len);// 这个构造函数的作用为将 bys 中从 0 到 len 长度的字节转换为 String 同时拷贝到 string;
//我们将这段字符串转换为JSON对象
 JSONObject json = new JSONObject(string);  
 //获得ftoken
 String  ftoken=json.get("auth_code");

   //发送一个交易请求
AlipayTradePayRequest alipayTradePayRequest = new AlipayTradePayRequest();
//没有找到这个对象的官方文档,根据类名与下面的代码判断应该是用来添加交易参数的
TradepayParam tradepayParam = new TradepayParam();
tradepayParam.setOut_trade_no(UUID.randomUUID().toString());

//auth_code和scene填写需要注意
//下面为这些参数的含义
//out_trade_no	商户订单号,需要保证不重复
//scene	刷脸支付固定传入 security_code
//auth_code	用户付款码,刷脸支付传入 ftoken
//subject	订单标题
//store_id	商户门店编号
//total_amount	订单金额
//timeout_express	交易超时时间
tradepayParam.setAuth_code(ftoken); // 人脸zolozVerify接口返回的ftoken
tradepayParam.setScene("security_code");//对于刷脸付,必须写为security_code
//下面的数据不是必填项
//tradepayParam.setSubject("smilepay");
//tradepayParam.setStore_id("smilepay test");
//tradepayParam.setTimeout_express("5m");
//tradepayParam.setTotal_amount(amount);
//接收到请求的结果
AlipayTradePayResponse response = alipayTradePayRequest.setBizContent(JSON.toJSONString(tradepayParam));
if (response != null && SMILEPAY_CODE_SUCCESS.equals(response.getCode())) {
//支付成功后将结果返回给客户端 
//这里不再写这些内容只对支付结果返回值有兴趣的可以查看官方的这个网站
//https://docs.open.alipay.com/api_1/alipay.trade.pay            
}
//最后关闭该线程
severSocket.close();
}
});
}

实际上这段代码中每句代码的功能我们并不需要强行理解。我们只知道在这段代码中放入我们的“入参”就会得到相应的“出参就可以了。”
现在我们就来分析一下段代码中哪些是必须的“入参”,我们又会得到怎么样的“出参”呢?
这些“出参”能够干什么呢?

首先我们需要"app_id",“your private_key”,“alipay_public_key”。也就是你的应用ID,你的开发者密钥,支付宝公钥。而那些业务参数是可有可无的,如果你是在做实际项目请
填上真实数据,如果测试就不用理他们了。
那我们的“出参”是什么?
出参是一个String类型的result其内容为:

{“retCode”:“返回码”,
 “retMessage”:“返回信息”,
 “result”:
 { “zimId”:“唤起人脸关键参数”,
 “type”:“zimInit”}
 }


这是一串用String类型存起来的json数据。我可以通过解析这些数据将必要的值返回我们的客户端,也可以直接发给客户端由客户端进行解析。再由客户端来执行下面的操作

至此扫脸的过程执行完毕。接下来是支付的客户端服务器 端的实现.
我先来写客户端的实现

四:实现程序所必须的数据

经过我上面的总结该程序所必须的数据:
客户端:商户ID、合作商户ID、设备唯一编号、设备Mac 地址、品牌编码
服务端:应用ID,你的开发者密钥,支付宝公钥

我们必须有这些数据才能进行测试,当然这些数据可以是真实的也可以是隔离沙箱生成的。

经验证,本文代码上有好几处较小的错误,暂时没来得及修正,如果你看到了这句话并且写的程序出现了错误可以发送留言交流。但是整个程序的流程解释是没问题的可以放心按照相应的解释理解