微信开放平台
在微信开放平台中创建 移动应用,填写 包名 和 签名信息 等。创建需要等审核完成,一般审核过程很快。
然后开通微信支付,未认证用户需要进行认证。认证过程也需要审核,这个比创建应用的时候要慢。
认证过程需要填写一些企业资料 和收取一定费用 ,好像是 300/年 。
微信商户号申请
点击APP支付 申请开通
开通后 在APPID授权管理 标签页中 关联 步骤1 中所申请的 APPID (必须已经完成认证并且开通APP支付权限)。
如果APPID未认证 会提示
如果未开通APP支付权限
所以必须先要认证并且开通支付权限。
正常情况:
然后去微信开放平台 对应的APP设置中确认 关联。
至此绑定完成
微信刷脸支付 开发指引
微信刷脸支付SDK 目前应该也在快速迭代中,前几天还是1.30版本,现在就已经更新2.10版本了。
使用方式:
人脸授权 :通过人脸识别,返回微信用户信息(openid, face_code)。
face_code:人脸凭证。常用于人脸支付,作为订单的支付凭证。
注:
这个一般放在自己定义的Application#onCreate()中调用就可以了。官方示例copy即可
//对人脸SDK进行初始化
WxPayFace.getInstance().initWxpayface(this, 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");
Log.d(TAG, "response info :: " + code + " | " + msg);
if (code == null || !code.equals("SUCCESS")) {
new RuntimeException("调用返回非成功信息: " + msg).printStackTrace();
return ;
}
Log.d(TAG, "调用返回成功");
}
});
此过程 一定要保证 两点:
1.设备能联网,应用要添加
权限,
官方demo中还添加以下权限:
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);
}
});
}
这是一个后端调用接口 采用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 | 参数签名。详见微信支付签名算法 |
签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合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 = "\n" +
" appid应用ID \n" +
" DEV001 \n" +
" V37ZHZVf2OrwsUV7kXTjTguP74c0byvE \n" +
" " +time+"\n" +
" mch_id商户号 \n" +
" " +rawdata+"\n" +
" IMG001 \n" +
" 门店名称 \n" +
" MD5 \n" +
" 1 \n" +
" " +sign+"\n" +
"";
//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 = "\n"+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);
}
这里需要注意的很多 :
"\n"
部分,所以可以先拼接在解析。就这样最终拿到了AuthInfo字符串 。
启动人脸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){
if (mAuthInfo== null){
Log.d(TAG, "mAuthInfo 为 null");
return;
}
HashMap params = new HashMap();
params.put(BaseUtils.FACE_AUTHTYPE, "FACEPAY");
params.put(BaseUtils.APPID, "wxefawffetat56");
params.put(BaseUtils.MCH_ID, "11534ef6121");
params.put(BaseUtils.STORE_ID, "IMG001");
//订单号 ,注意后后面支付pay中的订单号要一致
out_trade_no = "" +(System.currentTimeMillis() / 100000);
params.put(BaseUtils.OUT_TRADE_NO, out_trade_no);
//订单金额
params.put(BaseUtils.TOTAL_FEE, "1");
//第三步中获取的AuthInfo
params.put(BaseUtils.AUTHINFO, mAuthInfo);
WxPayFace.getInstance().getWxpayfaceCode(params, new IWxPayfaceCallback() {
@Override
public void response(final Map info) throws RemoteException {
// if (!isSuccessInfo(info)) {
// return;
// }
Log.d(TAG, "response | getWxpayfaceCode" );
final String code = (String)info.get(BaseUtils.RETURN_CODE);
// open id , face code
runOnUiThread(new Runnable() {
@Override
public void run() {
//请求成功
if (TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_SUCCESS)) {
//获取openid 和 face_code
face_code = (String)info.get("face_code");
openID = (String)info.get("openid");
ip = "174.207.250.66";
try {
Thread.sleep(2000);
//第5步 支付
pay(openID,face_code,ip);
} catch (Exception e) {
}
} else if (TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_USER_CANCEL)) {
Log.d(TAG, "run: 用户取消");
} else if (TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_SCAN_PAYMENT)) {
Log.d(TAG, "run: 扫码支付");
} else if (TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_ERROR)) {
Log.d(TAG, "run: 发生错误");
}
}
});
}
});
}
这个过程没什么说的,就是用第三步的AuthInfo ,还有就是订单号 要和后面第五步支付过程的订单号要保持一致。
支付 同样是一个后端server接口 后端server接口
接口地址 :https://api.mch.weixin.qq.com/pay/facepay
请求方式 POST、XML
参数 | 必填 | 类型 | 说明 |
---|---|---|---|
appid | 是 | string | 商户号绑定的公众号/小程序 appid |
mch_id | 是 | string | 商户号 |
sub_appid | 否 | string(32) | 子商户绑定的公众号/小程序 appid(可不填) |
sub_mch_id | 否 | string(32) | 子商户号(非服务商模式不填) |
device_info | 否 | string(32) | 终端设备号(商户自定义,如门店编号)。 |
out_trade_no | 是 | string(32) | 商户系统内部的订单号,32个字符内、可包含字母;更换授权码必须要换新的商户订单号 其他说明见商户订单号 |
total_fee | 是 | string | 订单金额(数字), 单位分. 该字段在在face_code_type 为"1"时可不填,为"0"时必填 |
nonce_str | 是 | string(32) | 随机字符串,不长于32位。推荐随机数生成算法 |
sign | 是 | string(32) | 参数签名。详见微信支付签名算法 |
body | 是 | String(128) | 商品或支付单简要描述,格式要求:门店品牌名-城市分店名-实际商品名称 |
total_fee | 是 | Int | 订单总金额,单位为分,只能为整数,详见支付金额 |
spbill_create_ip | 是 | String(16) | 调用微信支付API的机器IP |
openid | 是 | String(128) | 用户在商户appid 下的唯一标识 |
face_code | 是 | String(128) | 人脸凭证,用于刷脸支付 |
更多不必要参数可查看文档。
举例如下:
<xml>
<appid>wx2421b1c4370ec43bappid>
<attach>订单额外描述attach>
<body>刷卡支付测试body>
<device_info>1000device_info>
<goods_tag>goods_tag>
<mch_id>10000100mch_id>
<nonce_str>8aaee146b1dee7cec9100add9b96cbe2nonce_str>
<out_trade_no>1415757673out_trade_no>
<spbill_create_ip>14.17.22.52spbill_create_ip>
<time_expire>time_expire>
<total_fee>1total_fee>
<openid>openid>
<face_code>Qpoqwhsdjhfausrhqieofnq90w=w8233wdwjdjiwqface_code>
<sign>C29DB7DB1FD4136B84AE35604756362Csign>
xml>
参数 | 必填 | 类型 | 说明 |
---|---|---|---|
appid | 是 | string | 商户绑定的公众号/小程序 appid |
mch_id | 是 | string | 商户号 |
store_id | 是 | string | 门店编号 |
authinfo | 是 | string | 调用凭证。获取方式参见: get_wxpayface_authinfo |
payresult | 是 | string | 支付结果。可取值:SUCCESS : 支付成功ERROR : 支付失败 |
参数 | 必填 | 类型 | 说明 |
---|---|---|---|
return_code | 是 | string | 错误码。公共定义见 公共错误码 |
return_msg | 是 | string(128) | 对错误码的描述 |
private void updateWxpayfacePayResult() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("appid", "填您的公众号"); // 公众号,必填
map.put("mch_id", "填您的商户号"); // 商户号,必填
map.put("store_id", "填您的门店编号"); // 门店编号,必填
map.put("authinfo", "填您的调用凭证"); // 调用凭证,必填
map.put("payresult", "SUCCESS"); // 支付结果,SUCCESS:支付成功 ERROR:支付失败 必填
WxPayFace.getInstance().updateWxpayfacePayResult(map, 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"); // 错误码描述
if (code == null || !code.equals("SUCCESS")) {
new RuntimeException("调用返回非成功信息,return_msg:" + msg + " ").printStackTrace();
return ;
}
/*
在这里处理您自己的业务逻辑:
执行到这里说明用户已经确认支付结果且成功了,此时刷脸支付界面关闭,您可以在这里选择跳转到其它界面
*/
}
});
}
最后第五步整体过程代码:
private void pay(String openID,String faceCode,String ipAddress){
Log.d(TAG, "-------------start pay-----------");
String a ="appid=APPID&body=刷脸支付测试&device_info=DEV001&face_code="+faceCode+"&mch_id=MCH_ID&nonce_str=V37ZHZVf2OrwsUV7kXTjTguP74c0byvE&openid="+openID+"&out_trade_no="+out_trade_no+"&spbill_create_ip="+ipAddress+"&total_fee=1";
String stringSignTemp=a+"&key=32KEY";//注:key为商户平台设置的密钥key
String sign= md5(stringSignTemp).toUpperCase();
String finalStr = "\n" +
" APPID \n" +
" 刷脸支付测试\n" +
" DEV001 \n"+
" " +faceCode+"\n"+
" MCHID \n" +
" V37ZHZVf2OrwsUV7kXTjTguP74c0byvE \n" +
" " +openID+"\n"+
" " +out_trade_no+"\n"+
" " +ipAddress+"\n" +
" 1 \n"+
" " +sign+"\n" +
"";
OkHttpClient client = new OkHttpClient.Builder()
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.build();
RequestBody body=RequestBody.create(MediaType.parse("application/xml"),finalStr);
Request request = new Request.Builder()
.url("https://api.mch.weixin.qq.com/pay/facepay")
.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, "支付结果 ====\n" + bodyStr);
String xmlStr = "\n"+bodyStr;
// String retrunCode = ReturnXMLParser.parseCodeXML( new ByteArrayInputStream(xmlStr.getBytes()),"return_code");
String result_code = ReturnXMLParser.parseCodeXML( new ByteArrayInputStream(xmlStr.getBytes()),"result_code");
Map map =new HashMap();
map.put("appid","APPID应用ID");
map.put("mch_id",商户号);
map.put("store_id","IMG001");
map.put("authinfo",mAuthInfo);
boolean resultStatus = TextUtils.equals(result_code, WxfacePayCommonCode.VAL_RSP_PARAMS_SUCCESS);
if (result_code!=null && resultStatus ){
map.put("payresult","SUCCESS");
}else {
map.put("payresult","ERROR");
}
//更新支付状态
WxPayFace.getInstance().updateWxpayfacePayResult(map, new IWxPayfaceCallback() {
@Override
public void response(Map info) throws RemoteException {
Log.d(TAG, "-------------更新支付状态 -----------"+info.get("return_code"));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
请求成功后一定要 调用更新支付结果的方法(不然一直卡在 支付等待界面),调用后会根据结果提示支付成功或者失败。 至此整个支付流程就完事了。