微信刷脸支付全部流程(普通商户号)
- 1.创建移动应用
- 2.申请商户号, 开通APP支付和刷脸支付,关联APPID
- 3.开发指引 | 微信刷脸支付
- 4.刷脸支付流程
- 名词解释
- 时序图
- 5.接口调用踩坑
- (1)初始化(initWxpayface)
- (2)获取数据 (getWxpayfaceRawdata)
- (3)获取调用凭证(get_wxpayface_authinfo)
- Sign参数获取
- (4)获取人脸支付凭证(getWxpayfaceCode)
- (5)支付 (pay),并更新支付结果(updateWxpayfacePayResult)
- 1. pay
- 2. updateWxpayfacePayResult(final Map info, final IWxPayfaceCallback wxpayfaceCallBack)
- 接口参数
- 接口返回
- 实践指引
本文着重是 开发前的准备工作和开发过程中遇到的一些坑,不涉及 优化 ,订单查询 ,退款和 具体商户后台server的开发。
开发环境: rk开发板 +华捷艾米A200 camera
最终实现 用自己的应用和商户信息成功进行了一笔刷脸支付过程的demo 并且不需要搭建自己的商户后台server。
1.创建移动应用
微信开放平台
在微信开放平台中创建 移动应用,填写 包名 和 签名信息 等。创建需要等审核完成,一般审核过程很快。

然后开通微信支付,未认证用户需要进行认证。认证过程也需要审核,这个比创建应用的时候要慢。


认证过程需要填写一些企业资料 和收取一定费用 ,好像是 300/年 。
2.申请商户号, 开通APP支付和刷脸支付,关联APPID
微信商户号申请

点击APP支付 申请开通


开通后 在APPID授权管理 标签页中 关联 步骤1 中所申请的 APPID (必须已经完成认证并且开通APP支付权限)。
如果APPID未认证 会提示
如果未开通APP支付权限
所以必须先要认证并且开通支付权限。
正常情况:
然后去微信开放平台 对应的APP设置中确认 关联。

至此绑定完成
3.开发指引 | 微信刷脸支付
微信刷脸支付 开发指引
微信刷脸支付SDK 目前应该也在快速迭代中,前几天还是1.30版本,现在就已经更新2.10版本了。
使用方式:
- 安装人脸App。WxPayFace 微信刷脸支付SDK
- 商户接入人脸SDK。项目中引入1中的aar包。商户APP demo
- 商户server .商户server demo 此server是在商户自己开发整套流程的参考demo ,如果只是跑通商户App demo 则不需要此server。
4.刷脸支付流程
名词解释
人脸授权 :通过人脸识别,返回微信用户信息(openid, face_code)。
face_code:人脸凭证。常用于人脸支付,作为订单的支付凭证。
时序图
注:
- 初始化 initWxpayface, 只需要在程序启动时调用;
- 释放资源 releaseWxpayface,只需要在程序退出时调用;
每个方法的具体参数可在文档内查看。接口文档
5.接口调用踩坑
(1)初始化(initWxpayface)
这个一般放在自己定义的Application#onCreate()中调用就可以了。官方示例copy即可
//对人脸SDK进行初始化WxPayFace.getInstance().initWxpayface(this, new IWxPayfaceCallback() {@Overridepublic void response(Map info) throws RemoteException {if (info == null) {new RuntimeException("调用返回为空").printStackTrace();return ;}String code = (String) info.get("return_code");String msg = (String) info.get("return_msg");Log.d(TAG, "response info :: " + code + " | " + msg);if (code == null || !code.equals("SUCCESS")) {new RuntimeException("调用返回非成功信息: " + msg).printStackTrace();return ;}Log.d(TAG, "调用返回成功");}});(2)获取数据 (getWxpayfaceRawdata)
此过程 一定要保证 两点:
1.设备能联网,应用要添加uses-permission android:name="android.permission.INTERNET"/权限,
官方demo中还添加以下权限:
uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" / uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" / uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" / uses-permission android:name="android.permission.READ_PHONE_STATE" / uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /2.设备有SN号 。在android设备上,开机后进入setting-about devices-status-Serial number查看
当时使用的是 rk开发板,IMG没有写入SN号。当时在论坛上找的写入SN号工具只能写入 "ro.boot.serialno"的值,"ro.serialno"还是空的,所以最后只能修改源代码,重新编译烧录版本。
/** 2. 人脸识别第二步 获取raw data*/ private void getWxpayfaceRawdata() { WxPayFace.getInstance().getWxpayfaceRawdata(new IWxPayfaceCallback() { @Override public void response(Map info) throws RemoteException { if (info == null) { new RuntimeException("调用返回为空").printStackTrace(); return; } String code = (String) info.get("return_code"); String msg = (String) info.get("return_msg"); rawdata = info.get("rawdata").toString(); Log.d(TAG, "rawdata ==" + rawdata); if (code == null || rawdata == null || !code.equals("SUCCESS")) { new RuntimeException("调用返回非成功信息,return_msg:" + msg + " ").printStackTrace(); return ; } /** 在这里处理您自己的业务逻辑 可以紧接着执行第三步 获取调用凭证getAuthInfo, 这应该是向 商户server 发起请求。 */ getAuthInfo(rawdata); } }); }(3)获取调用凭证(get_wxpayface_authinfo)
这是一个后端调用接口 采用xml格式
因为demo为了省事,省去商户后台server的开发,所以这一步也是在Android端直接调用。
获取凭证需要很多的 参数
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
| store_id | 是 | string(32) | 门店编号, 由商户定义, 各门店唯一。 |
| store_name | 是 | string(128) | 门店名称,由商户定义。(可用于展示) |
| device_id | 是 | string(32) | 终端设备编号,由商户定义。 |
| attach | 否 | string | 附加字段。字段格式使用Json |
| rawdata | 是 | string(2048) | 初始化数据。由微信人脸SDK的接口返回。 获取方式参见: [获取数据 getWxpayfaceRawdata](#获取数据 getWxpayfaceRawdata) [获取数据 getWxpayfaceRawdata](#获取数据 getWxpayfaceRawdata) |
| appid | 是 | string(32) | 商户号绑定的公众号/小程序 appid |
| mch_id | 是 | string(32) | 商户号 |
| sub_appid | 否 | string(32) | 子商户绑定的公众号/小程序 appid(服务商模式) |
| sub_mch_id | 否 | string(32) | 子商户号(服务商模式) |
| now | 是 | int | 取当前时间,10位unix时间戳。 例如:1239878956 |
| version | 是 | string | 版本号。固定为1 |
| sign_type | 是 | string | 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 |
| nonce_str | 是 | string(32) | 随机字符串,不长于32位 |
| sign | 是 | string | 参数签名。详见微信支付签名算法 |
Sign参数获取
签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
◆ key设置路径:微信商户平台(pay.weixin.qq.com)–账户设置–API安全–密钥设置
,获取方法:
安装插件 ,浏览器最好别用QQ浏览器。
自家的插件不知道为什么唤不起来。重启N遍无用,推荐Chrome.
密钥是32位字符串 。
在上面第二步代码回调成功后紧接着 可以调用第三步。
private void getAuthInfo(String rawdata){ //now 参数 是unix 10位时间戳 long time = System.currentTimeMillis()/1000L; //为了对比起来方便 下面所有请求 都是用 字符串拼接的方式。 //注意参数顺序 参数名ASCII码从小到大排序(字典序). String a ="appid=appid应用ID&device_id=DEV001&mch_id=mch_id商户号&nonce_str=V37ZHZVf2OrwsUV7kXTjTguP74c0byvE&now="+time+"&rawdata="+rawdata+"&sign_type=MD5&store_id=IMG001&store_name=门店名称&version=1"; String stringSignTemp=a+"&key=32位的字符串";//注:key为商户平台设置的密钥key String sign= md5(stringSignTemp).toUpperCase(); //注:MD5签名方式 Log.d(TAG, "sign : " +sign); String finalStr = "xml" + " appidappid应用ID/appid" + " device_idDEV001/device_id" + " nonce_strV37ZHZVf2OrwsUV7kXTjTguP74c0byvE/nonce_str" + " now"+time+"/now" + " mch_idmch_id商户号/mch_id" + " rawdata"+rawdata+"/rawdata" + " store_idIMG001/store_id" + " store_name门店名称/store_name" + " sign_typeMD5/sign_type" + " version1/version" + " sign"+sign+"/sign" + "/xml"; //SSL可以不用管 try { // Create a trust manager that does not validate certificate chains final TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } } }; // Install the all-trusting trust manager final SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); // Create an ssl socket factory with our all-trusting manager final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslSocketFactory) .hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }) .build(); //请求格式xml ,请求方式post RequestBody body=RequestBody.create(MediaType.parse("application/xml"),finalStr); Request request = new Request.Builder() .url("https://payapp.weixin.qq.com/face/get_wxpayface_authinfo")//后台接口地址,具体见后台开发文档 .post(body) .build(); client.newCall(request) .enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure | getAuthInfo " + e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { try { String bodyStr = response.body().string(); Log.d(TAG, "onResponse | getAuthInfo " + bodyStr); //这里返回的不是标准的xml格式,缺少 开始标签 ,拼接后用XmlPullParser解析 String xmlStr = "?xml version="1.0" encoding="utf-8"?"+bodyStr; //ReturnXMLParser 类在官方的demo中 //最终获取到 AuthInfo信息。 mAuthInfo = ReturnXMLParser.parseGetAuthInfoXML( new ByteArrayInputStream(xmlStr.getBytes())); } catch (Exception e) { e.printStackTrace(); } } }); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } 这里需要注意的很多 :
- now 参数 注意是 10位的 时间戳 。并且拼接字符串a 时用的now 和 最后请求Body中xml内容中的Now的值一定要相同,两个地方切勿直接用 System.currentTimeMillis()/1000L
- 获取sign值 拼接的字符串a 一定要按照参数名ASCII码从小到大排序。sign值可以用签名校验工具验证
- 请求接口 https://payapp.weixin.qq.com/face/get_wxpayface_authinfo
- 返回值不是标准的xml格式,不包括
"?xml version="1.0" encoding="utf-8"?"部分,所以可以先拼接在解析。
就这样最终拿到了AuthInfo字符串 。
(4)获取人脸支付凭证(getWxpayfaceCode)
启动人脸APP主界面入口,开启人脸识别,获取用户信息(openid)和支付凭证()。
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
| appid | 是 | string | 商户号绑定的公众号/小程序 appid |
| mch_id | 是 | string | 商户号 |
| sub_appid | 否 | string(32) | 子商户绑定的公众号/小程序 appid(可不填) |
| sub_mch_id | 否 | string(32) | 子商户号(非服务商模式不填) |
| store_id | 是 | string | 门店编号 |
| telephone | 否 | string | 用户手机号。用于传递会员手机,此手机将作为默认值, 填写到手机输入栏。 |
| out_trade_no | 是 | string | 商户订单号,须与调用支付接口时字段一致,该字段在在face_code_type为"1"时可不填,为"0"时必填 |
| total_fee | 是 | string | 订单金额(数字), 单位分. 该字段在在face_code_type为"1"时可不填,为"0"时必填 |
| face_authtype | 是 | string | 可选值:FACEPAY: 人脸凭证,常用于人脸支付FACEPAY_DELAY: 延迟支付(提供商户号信息联系微信支付开通权限) |
| authinfo | 是 | string | 调用凭证。获取方式参见: get_wxpayface_authinfo |
| ask_face_permit | 是 | string | 支付成功页是否需要展示人脸识别授权项。 展示:1 不展示:0 人脸识别授权项: 用户授权后用于1:N识别,可返回用户信息openid,建议商户有自己会员系统时,填1。 |
| ask_ret_page | 否 | string | 是否展示微信支付成功页,可选值:“0”,不展示;“1”,展示 |
| face_code_type | 否 | string | 目标face_code类型,可选值:“0”,人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付;“1”,刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付。如果不填写则默认为"0" |
| ignore_update_pay_result | 否 | string | 商户端是否对SDK返回支付结果,可选值:“0”,返回支付结果,商户需在确认⽀付结果后调⽤[updateWxpayfacePayResult]通知SDK;“1”,不返回支付结果。如果不填写则默认为"0"。 |
private void getWXPayFaceCode(String mAuthInfo){













