https://docs.open.alipay.com/20180402104715814204/intro
https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=109099
https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=108944
首先,你要参考文档拿到ftoaken,详见普通刷脸的文档https://docs.open.alipay.com/20180402104715814204/intro
联系支付宝要添加依赖
之后在App的build.gradle中添加依赖
implementation(name: 'Alipay_UniSDK', ext: 'aar')
implementation 'com.google.protobuf:protobuf-lite:3.0.0'
添加之后可能会遇到错误:The number of method references in a .dex file cannot exceed 64K.(详见https://mp.csdn.net/postedit/86241021)
然后添加
implementation 'com.android.support:multidex:1.0.1'
在
添加multiDexEnabled true
然后开始看文档https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=109099
刷脸+预授权
业务流程图:
扫脸当做预授权一种新的授权方式,等同于现在的付款码预授权一样。
解决方案技术实现:
1. 完成客户端人脸识别的接入,前往支付级刷脸服务 ,获取ftoken
注意:针对刷脸+当面资金授权场景,刷脸初始化接口(zoloz.authentication.customer.smilepay.initialize)中bizType=12并传入身份证信息。 示例:"extInfo":{"bizType":"12" ,"certName":"身份证姓名" ,"certNo":"身份证号" ,"certType":"IDCARD"}
2. 接入当面资金授权。前往当面资金授权技术文档
(1)其中alipay.fund.auth.order.freeze(资金授权冻结接口)接口的请求参数中auth_code参数值设置为扫脸sdk返回的FToken的值。
(2)alipay.fund.auth.order.freeze(资金授权冻结接口)接口的请求参数中auth_code_type=security_code
(3)alipay.fund.auth.order.freeze(资金授权冻结接口)接口的请求参数中传入terminal_params,即机具管控sdk加签参数,请参考5.2机具管控SDK开发对接 。其中交易加签,即通过机具管控SDK提供的签名接口,将签名信息(扫脸ftoken信息等)生成为机具专用业务签名,参考机具管控对接文档中2.4 交易加签 。示例:"terminal_params":"{\"terminalType\":\"IOT\",\"signature\":\"QIIAX8DqbFbNf2oe97FI1RSLAycC/tU4GVjer3bN8K4qLtAB\",\"apdidToken\":\"xPA3ptuArwYc3F6Va_pjVwv7Qx7Tg5TJdrA_Jb_moYte9AqGZgEAAA==\",\"hardToken\":\"\",\"time\":\"1539847253\",\"bizCode\":\"11000200040004000121\",\"bizTid\":\"010100F01i1XyacMgpOinHerfdBw1xA9dNDocctlnqhLD8lfODr1A7Q\",\"signedKeys\":\"authCode,totalAmount,apdidToken,hardToken,time,bizCode,bizTid\"}"
3. 预授权转支付交易里需要传入门店ID,以获得反佣
你要确保你得设备是已经接入了IoTSDK(我这边是商米的设备,已经集成好了,)
然后开始集成android这边的文档,集成文档参见https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=108944
先初始化接口
1.APIManager.getInstance().initialize(this, isvId);(这里应该捕获异常的)参数是下面的
2.获取public String getDeviceId();不为空(建议0.5~1s请求一次确保不为空null)
DeviceAPI api = APIManager.getInstance().getDeviceAPI();
Log.e("TAG", "public String getDeviceId();---------"+api.getDeviceId());
3.然后去加参(这里根据类型去调用,详见文档https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=108944)
PaymentAPI api = APIManager.getInstance().getPaymentAPI();
String terminal_params = api.signWithFaceToken(fToken, dataBinding.depositMoneyCount.getText().toString());
(到这里已经加参结束.log打印terminal_params 就是加参返回结果)
3、加签返回结果:
本接口返回的加签结果为以json结构表示的字符串,格式如下:
{"terminalType":"IOT","signature":"ERKDmm3fhGCvZZP0ob5gHUiTuTaFbB5gjjYBYxdOVwezN+sSJdV+uJy4kegYC6RQDzOLx/vbLYPoZTzxPXsVAFZgCg==","apdidToken":"iBfdgYKtgBObNOOybNHkaItG2EQkY3bovzvKDqtyWKVQ9tfvZAEAAA==","hardToken":"0601469C6568AEB7BA92FCC21DF8C766CC8A78A5BF874FC509A2D262B8B5FB9E35FF","time":"1533061133","bizCode":"11000100020002200020","bizTid":"01010020015XmMUS5BmWl39qqatmTLukjswaEaSYJADU2EQ2kb3AjcY","signedKeys":"authCode,totalAmount,apdidToken,hardToken,time,bizCode,bizTid"}
应用开发者获取加签结果后,最终向支付宝收单平台发起交易请求时,按交易报文协议在terminal_params字段中以字符串的形式带上此加签结果,从而完成交易扣款。
特别说明:由于交易报文协议为JSON格式,在构造terminal_params属性时,必须以字符串形式,因此,务必注意子json字符串的表示格式(需要转义)。
然后把terminal_params这个参数透传给服务端,
terminal_params这个参数是初始化人脸之后,在调用支付宝那边支付的时候,把这个参数传给服务端,由服务端调用支付宝接口(因为涉及敏感字段,所以由服务端调起)
详见下面部分代码(和普通刷脸逻辑相似)
private Zoloz zoloz;
public final static String isvId="一串数字"
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//初始化SDK
try {
APIManager.getInstance().initialize(this, isvId);
} catch (APIManager.APIInitException e) {
e.printStackTrace();
}
DeviceAPI api = APIManager.getInstance().getDeviceAPI();
//初始化之后.,每个0.5S或者1s一次
//循环不为空getDeviceId不为空
Log.e("TAG", "public String getDeviceId();---------"+api.getDeviceId());
////扫脸用的,要是不行就放在首页开始初始化
zoloz = com.alipay.zoloz.smile2pay.service.Zoloz.getInstance(getApplicationContext());
dataBinding.sweepAutFace.setOnClickListener(this);//刷脸预售权
if(SystemUtil.getSystemModel().contains("T2")) {
dataBinding.sweepAutFace.setVisibility(View.VISIBLE);
}
}
@Override
public void onClick(View v) {
Intent intent;
switch (v.getId()) {
case R.id.sweep_aut_face:
//刷脸预授权
if (TextUtils.isEmpty(dataBinding.depositMoneyCount.getText().toString())) {
Snackbar.make(v, "请输入收款金额!", Snackbar.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(dataBinding.depositNote.getText().toString())) {
Snackbar.make(v, "请输入备注信息!", Snackbar.LENGTH_SHORT).show();
return;
}
Log.e("TAG", "进入刷脸预售权");
// Toast.makeText(DepositActivity.this, "进入刷脸预售权", Toast.LENGTH_SHORT).show();
smileFacePay();
break;
}
}
/**
* 发起刷脸支付请求,先zolozGetMetaInfo获取本地app信息,然后调用服务端获取刷脸付协议.
*/
// 值为"1000"调用成功
// 值为"1003"用户选择退出
// 值为"1004"超时
// 值为"1005"用户选用其他支付方式
static final String CODE_SUCCESS = "1000";
static final String CODE_EXIT = "1003";
static final String CODE_TIMEOUT = "1004";
static final String CODE_OTHER_PAY = "1005";
static final String TXT_EXIT = "已退出刷脸支付";
static final String TXT_TIMEOUT = "操作超时";
static final String TXT_OTHER_PAY = "已退出刷脸支付";
static final String TXT_OTHER = "抱歉未支付成功,请重新支付";
//刷脸支付相关
static final String SMILEPAY_CODE_SUCCESS = "10000";
static final String SMILEPAY_SUBCODE_LIMIT = "ACQ.PRODUCT_AMOUNT_LIMIT_ERROR";
static final String SMILEPAY_SUBCODE_BALANCE_NOT_ENOUGH = "ACQ.BUYER_BALANCE_NOT_ENOUGH";
static final String SMILEPAY_SUBCODE_BANKCARD_BALANCE_NOT_ENOUGH = "ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH";
static final String SMILEPAY_TXT_LIMIT = "刷脸支付超出限额,请选用其他支付方式";
static final String SMILEPAY_TXT_EBALANCE_NOT_ENOUGH = "账户余额不足,支付失败";
static final String SMILEPAY_TXT_BANKCARD_BALANCE_NOT_ENOUGH = "账户余额不足,支付失败";
static final String SMILEPAY_TXT_FAIL = "抱歉未支付成功,请重新支付";
static final String SMILEPAY_TXT_SUCCESS = "刷脸支付成功";
private ISharedPreference sharedPreference;
private List products;
//使用AsyncHttpClient,实现联网的声明
public AsyncHttpClient client = new AsyncHttpClient();
private void smileFacePay() {
zoloz.zolozGetMetaInfo(mockInfo(), new ZolozCallback() {
// 解析zolozGetMetaInfo返回的结果,如果成功,则请求商户服务端调用人脸初始化接口
@Override
public void response(Map smileToPayResponse) {
if (smileToPayResponse == null) {
Toast.makeText(DepositActivity.this, "TXT_OTHER", Toast.LENGTH_SHORT).show();
return;
}
String code = (String)smileToPayResponse.get("code");
String metaInfo = (String)smileToPayResponse.get("metainfo");
Log.e("TAG", "code----------------"+code);//1000值为"1000"调用成功
//获取metainfo成功
if (CODE_SUCCESS.equalsIgnoreCase(code) && metaInfo != null) {
// 2. 刷脸初始化(建议服务端调起)
//这里用Rxjava 联网请求报线程错误
//然后找了两个个jar包android-async-http-master.jar和fastJson-1.1.45.jar
//android-async-http-master.jar请求数据用fastJson-1.1.45.jar解析
sharedPreference = ISharedPreference.getInstance(getApplication());//获取保存的token
//联网发送信息给服务器(自己的服务器)
String register = ApiService.baseUrl+"merchant/smilepayInit";//服务器地址
//这里是要传送的参数
RequestParams params = new RequestParams();
params.put("token",sharedPreference.getToken());
params.put("all",metaInfo);
//开始联网请求
client.post(register,params ,new AsyncHttpResponseHandler(){
//成功
@Override
public void onSuccess(String content) {
//这里去解析数据content
JSONObject jsonObject = JSON.parseObject(content);
String proInfo = jsonObject.getString("data");
products = JSON.parseArray(proInfo, FacePayBean.DataBean.class);
//获取zimId和zimInitClientData调用人脸初始化
String zimId = products.get(0).getZimId();
String zimInitClientData = products.get(0).getZimInitClientData();
smile(zimId, zimInitClientData);//唤起人脸识别去支付
//这个我也不知道干嘛的,好像不能注释
UIUtils.getHandler().postDelayed(new Runnable() {
@Override
public void run() {
// removeCurrentActivity();//销毁当前的activity
}
},2000);
}
@Override
public void onFailure(Throwable error, String content) {
Toast.makeText(DepositActivity.this, "网络异常,调用失败!", Toast.LENGTH_SHORT).show();
}
} );
}
}
});
}
public static final String KEY_INIT_RESP_NAME = "zim.init.resp";
/**
* 发起刷脸支付请求.
* @param zimId 刷脸付token,从服务端获取,不要mock传入
* @param protocal 刷脸付协议,从服务端获取,不要mock传入
*/
//. 唤起人脸识别
private void smile(String zimId, String protocal) {
Map params = new HashMap();
params.put(KEY_INIT_RESP_NAME, protocal);
/* start: 如果是预输入手机号方案,请加入以下代码,填入会员绑定的手机号,必须与支付宝帐号对应的手机号一致 */
// 这句代码就是扫脸之后会有一个用户输入手机号的页面,您要是写这一行,就不需要用户去手动输入,会直接把用户手机号显示在页面,
// 不写的话需要用户手动去输入
// params.put("phone_number", "1381XXXXX");
/* end: --------------------------------------------- */
zoloz.zolozVerify(zimId, params, new ZolozCallback() {
@Override
public void response(final Map smileToPayResponse) {
if (smileToPayResponse == null) {
Toast.makeText(DepositActivity.this, TXT_OTHER, Toast.LENGTH_SHORT).show();
return;
}
String code = (String)smileToPayResponse.get("code");
String fToken = (String)smileToPayResponse.get("ftoken");
String subCode = (String)smileToPayResponse.get("subCode");
String msg = (String)smileToPayResponse.get("msg");
//刷脸成功
if (CODE_SUCCESS.equalsIgnoreCase(code) && fToken != null) {
sharedPreference = ISharedPreference.getInstance(getApplication());//获取保存的token
//联网发送信息给服务器(自己的服务器)
String register = ApiService.baseUrl+"merchant/SmFundAuthTradePayCodeType";
//这里是要传送的参数
PaymentAPI api = APIManager.getInstance().getPaymentAPI();
String terminal_params = api.signWithFaceToken(fToken,dataBinding.depositMoneyCount.getText().toString());
RequestParams params = new RequestParams();
params.put("token",sharedPreference.getToken());
params.put("remarks",dataBinding.depositNote.getText().toString());//备注
params.put("code",fToken);//传ftonen
params.put("price",dataBinding.depositMoneyCount.getText().toString());//金额
params.put("terminal_params",terminal_params);
client.post(register, params, new AsyncHttpResponseHandler(){
@Override
public void onSuccess(String content) {
ScanAutBean scanPayBean = JSON.parseObject(content, ScanAutBean.class);
if (scanPayBean != null) {
// print(scanPayBean.);//打印
// showPrintDialog(scanPayBean);//自己写
}
}
@Override
public void onFailure(Throwable error, String content) {
Toast.makeText(DepositActivity.this, "网络异常,未支付成功!", Toast.LENGTH_SHORT).show();
}
});
}
}
});
}
//将交易金额统一处理成12位,不足12位的在前面补0
private String getAmount(String amount) {
if (TextUtils.isEmpty(amount)) return null;
while (amount.length() < 12) {
amount = "0" + amount;
}
return amount;
}
//获取当前时间
private String getCurrentTime() {
SimpleDateFormat simpleDateFormat = (SimpleDateFormat) SimpleDateFormat.getDateInstance();
simpleDateFormat.applyPattern("MM-dd HH:mm:SS");
return simpleDateFormat.format(new Date());
}