前言:公司需要做一个人脸识别项目。用户点击商品 》 选择支付方式 》 微信人脸支付或微信、支付宝二维码支付。但不是我们的后端来和我合作,我需要自己去微信官方拿rawdata和AuthInfo(需要注意的是微信的接口请求都是XML请求)
微信人脸支付
1.选择设备。微信人脸需要选择微信官方提供的设备
2.商户APP Demo。仔细查看demo中的人脸支付流程。(demo中的流程同样是不需要有后端的,需要的参数都需要自己去微信要)
商户APP Demo(查看的是商户APP Demo)
微信刷脸支付文档 # 刷脸支付场景说明
3.把wxfacepay-release加到libs中。
4.Application中微信人脸SDK初始化
private fun initWxpayface() {
val m1: Map<String?, String?> = HashMap()
// m1.put("ip", "192.168.1.1"); //若没有代理,则不需要此行
// m1.put("port", "8888");//若没有代理,则不需要此行
// m1.put("user", mEtnUser.getText().toString());//若没有代理,则不需要此行
// m1.put("passwd", mEtnPassword.getText().toString());//若没有代理,则不需要此行
// m1.put("proxy_type", 1 ); //若没有代理,则不需要此行
// m1.put("perform_mode", "LOW_PERFORM");//低性能表现,默认关闭美颜等
WxPayFace.getInstance().initWxpayface(this, m1, object : IWxPayfaceCallback() {
@Throws(RemoteException::class)
override fun response(info: Map<*, *>?) {
if (info == null) {
e("调用返回为空, 请查看日志")
RuntimeException("调用返回为空").printStackTrace()
return
}
val code = info["return_code"] as String?
val msg = info["return_msg"] as String?
e("初始化完成")
}
})
}
5.获取rawdata。getWxpayfaceRawdata
/**
* 获取rawdata
*/
WxPayFace.getInstance().getWxpayfaceRawdata(object : IWxPayfaceCallback() {
override fun response(info: Map<*, *>?) {
if (info == null) {
e("调用返回为空, 请查看日志")
java.lang.RuntimeException("调用返回为空").printStackTrace()
return
}
val code = info["return_code"] as String?
val msg = info["return_msg"] as String?
val rawData = info["rawdata"] as String?
e(rawData)
getAuthInfo(rawData)
}
})
6.获取AuthInfo。https://payapp.weixin.qq.com/face/get_wxpayface_authinfo
private fun getAuthInfo(rawData: String?) {
var map = mutableMapOf<String, String>()
map["rawdata"] = rawData!!
//门店编号
map["store_id"] = Constance.store_id
//门店名称
map["store_name"] = Constance.store_name
//终端设备编号,由商户定义。
map["device_id"] = Constance.device_id
//商户号绑定的公众号/小程序 appid
map["appid"] = orderInfoBean!!.data.wxappid
// 商户号
map["mch_id"] = orderInfoBean!!.data.wxmchid
//版本号:固定是1
map["version"] = "1"
var now = (System.currentTimeMillis() / 1000).toString()
map["now"] = now
map["sign_type"] = "MD5"
map["nonce_str"] = Constance.nonce_str
//按字典顺序排序
val infoIds: List<Map.Entry<String, String>> =
ArrayList(map.entries)
Collections.sort(infoIds, { o1, o2 -> o1.key.compareTo(o2.key) })
//使用&符号进行拼接
val sbR: String =
Tools.getStringBuffer(infoIds).toString() + "&key=" + orderInfoBean!!.data.wxmchkey
//进行MD5加密之后 转大写
val sign: String = Tools.encode(sbR).toUpperCase()
map["sign"] = sign
/*将map集合转为xml*/
val toXml = Tools.mapToXml(map)
// e("认证参数XMl$toXml")
val body = RequestBody.create(null, toXml)
e(toXml.toString())
OkGo.post<String>(UrlUtils.WECHAT)
.tag(UrlUtils.WECHAT)
.upRequestBody(body)
.execute(object : StringCallback() {
override fun onError(response: com.lzy.okgo.model.Response<String?>?) {
e("获取到的微信支付凭证${response!!.body()}")
}
override fun onSuccess(response: com.lzy.okgo.model.Response<String?>?) {
val payVoucher = response!!.body()!!
val headers = response.headers()!!
try {
var code = Tools.parseGetAuthInfoXML(payVoucher, "return_code")
if ("SUCCESS" == code) {
val authinfo: String = Tools.parseGetAuthInfoXML(payVoucher, "authinfo")
pay(authinfo)
} else {
val msg: String = Tools.parseGetAuthInfoXML(payVoucher, "return_msg")
e("获取微信凭证失败$msg")
}
} catch (e: Exception) {
e.printStackTrace()
e("获取微信凭证失败$e")
}
}
})
}
7.调起微信人脸识别。进行人脸识别getWxpayfaceCode(获取用户信息facecode)
private fun pay(mAuthInfo: String?) {
e("pay", "onClick | pay ")
var params: HashMap<String, String> = HashMap()
//FACEPAY: 人脸凭证,常用于人脸支付
params["face_authtype"] = "FACEPAY"
//商户号绑定的公众号/小程序 appid
params["appid"] = orderInfoBean!!.data.wxappid
// 商户号
params["mch_id"] = orderInfoBean!!.data.wxmchid
//门店编号
params["store_id"] = Constance.store_id
//目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付。如果不填写则默认为"0"
params["face_code_type"] = "1"
//支付成功页是否需要展示人脸识别授权项。 展示:1 不展示:0
params["ask_face_permit"] = "0"
//商户端是否对SDK返回支付结果,可选值:"0",返回支付结果,商户需在确认?付结果后调?[updateWxpayfacePayResult]通知SDK;"1"
params["ignore_update_pay_result"] = "1"
// 指定刷脸界面的运行屏幕,可选值:“0”, 运行在默认屏幕; "1", 运行在第二屏幕(副屏); "2", 自动选择屏幕; "3", 强制用presentation方式在第二屏幕(副屏)启动。
params["screen_index"] = "0"
//商户订单号,须与调用支付接口时字段一致
params["out_trade_no"] = orderInfoBean!!.data.order_number
//订单金额(数字)
params["total_fee"] = allPrice.times(100).toInt().toString()
params["authinfo"] = mAuthInfo.toString()
e("pay | single callback")
WxPayFace.getInstance().getWxpayfaceCode(params, object : IWxPayfaceCallback() {
@Throws(RemoteException::class)
override fun response(info: Map<*, *>?) {
if (info != null) {
e(info.toString())
getWxfaceCode(info)
} else
e("调用返回为空, 请查看日志")
}
}, object : IWxPayfaceCallback() {
override fun response(info: MutableMap<Any?, Any?>?) {
e(info.toString())
if (info == null) {
RuntimeException("调用返回为空").printStackTrace()
return
}
val code = info["return_code"] as String // 错误码
val msg = info["return_msg"] as String // 错误码描述
if (code == null || code != "SUCCESS") {
RuntimeException("调用返回非成功信息,return_msg:$msg ")
.printStackTrace()
return
}
}
})
}
private fun getWxfaceCode(info: Map<*, *>) {
val code = info["return_code"] as String?
val facecode = info["face_code"] as String?
when {
//用户确认支付
TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_SUCCESS) ->
doPay(facecode)
TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_USER_CANCEL) ->
e("用户取消")
TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_SCAN_PAYMENT) ->
e("扫码支付")
TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_ERROR) ->
e("发生错误")
TextUtils.equals(code, WxfacePayCommonCode.VAL_RSP_PARAMS_USER_QUERY_CANCEL) ->
e("请与商户确认支付结果")
}
}
8.进行发起订单支付micropay。接口地址:https://api.mch.weixin.qq.com/pay/micropay
private fun doPay(facecode: String?) {
var map = mutableMapOf<String, String>()
map["auth_code"] = facecode.toString()
map["spbill_create_ip"] = "192.168.0.143"
map["body"] = "捷视商品"
map["attach"] = "订单额外描述"
map["device_info"] = Constance.device_id
map["sign_type"] = "MD5"
map["total_fee"] = allPrice.times(100).toInt().toString()
map["out_trade_no"] = orderInfoBean!!.data.order_number
//商户号绑定的公众号/小程序 appid
map["appid"] = orderInfoBean!!.data.wxappid
// 商户号
map["mch_id"] = orderInfoBean!!.data.wxmchid
var now = (System.currentTimeMillis() / 1000).toString()
map["time_start"] = now
map["nonce_str"] = Constance.nonce_str
//按字典顺序排序
val infoIds: List<Map.Entry<String, String>> = ArrayList(map.entries)
Collections.sort(infoIds) { o1, o2 -> o1.key.compareTo(o2.key) }
//使用&符号进行拼接商户Key
val sbR: String =
Tools.getStringBuffer(infoIds).toString() + "&key=" + orderInfoBean!!.data.wxmchkey
//进行MD5加密之后 转大写
val sign: String = Tools.encode(sbR).toUpperCase()
map["sign"] = sign
e(map.toString())
/*将map集合转为xml*/
val toXml = Tools.mapToXml(map)
// e("认证参数XMl$toXml")
val body = RequestBody.create(null, toXml)
OkGo.post<String>("https://com.api.mch.weixin.qq.com/pay/micropay")
.tag(this)
.upRequestBody(body)
.execute(object : StringCallback() {
override fun onError(response: com.lzy.okgo.model.Response<String?>?) {
e("获取微信支付${response!!.body()}")
}
override fun onSuccess(response: com.lzy.okgo.model.Response<String?>?) {
val payVoucher = response!!.body()!!
val headers = response.headers()!!
e("微信支付${payVoucher}")
try {
var code = Tools.parseGetAuthInfoXML(payVoucher, "return_code")
var resultCode = Tools.parseGetAuthInfoXML(payVoucher, "result_code")
var msg = Tools.parseGetAuthInfoXML(payVoucher, "return_msg")
if ("SUCCESS" == code) {
if ("SUCCESS" == resultCode) {
//支付成功,返回,告诉后端我们支付成功了,把支付成功参数给后台
returServerCode(payVoucher)
} else {
queryPay()
}
} else {
e("微信支付失败$msg")
}
} catch (e: Exception) {
e.printStackTrace()
e("微信支付失败$e")
}
}
})
}
9.因为微信的人脸支付没有“notify_url”参数,所以需要我们主动去给后台消息说我们支付成功了。有时候微信给我们的订单支付成功消息不是SUCCESS,而是SYSTEMERROR(系统超时)(我有时候遇到了这种情况,用户明明付款成功了,但是返回的不是SUCCESS),所以我们要主动去查询订单一次
查询订单状态orderquery。接口地址:https://api.mch.weixin.qq.com/pay/orderquery
private fun returServerCode(payVoucher: String) {
//微信支付订单号
var transactionid = Tools.parseGetAuthInfoXML(payVoucher, "transaction_id")
var openid = Tools.parseGetAuthInfoXML(payVoucher, "openid")
var totalfee = Tools.parseGetAuthInfoXML(payVoucher, "total_fee")
var map = mutableMapOf<String, String>()
map["api_name"] = "wxFaceNotify"
map["shop_id"] = orderInfoBean!!.data.shop_id
map["order_id"] = orderInfoBean!!.data.order_id
map["order_number"] = orderInfoBean!!.data.order_number
map["transaction_id"] = transactionid
map["openid"] = openid
map["total_fee"] = (totalfee.toDouble() / 100).toString()
e(map.toString())
OkGo.post<String>(UrlUtils.SHOPLIST)
.tag(this)
.params(map)
.execute(object : StringCallback() {
override fun onError(response: com.lzy.okgo.model.Response<String?>?) {
ToastUtil.showTextToast("网络连接失败请重试!!")
}
override fun onSuccess(response: com.lzy.okgo.model.Response<String?>?) {
e(response!!.body()!!)
}
})
}
微信二维码(NATIVE支付)
获取微信二维码要开启NATIVE支付,我们要去统一下单中获取二维码
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
private fun showWeChatCodePay(payType: Int) {
var map = mutableMapOf<String, String>()
//商户号绑定的公众号/小程序 appid
map["appid"] = orderInfoBean!!.data.wxappid
// 商户号
map["mch_id"] = orderInfoBean!!.data.wxmchid
// 商户号
map["device_info"] = MyApplication.IMEI.toString()
// 商户系统内部订单号
map["out_trade_no"] = orderInfoBean!!.data.order_number
// 商户系统内部ID
map["product_id"] = orderInfoBean!!.data.order_id
//随机字符串
map["nonce_str"] = Constance.nonce_str
//商品描述
map["body"] = "捷视商品"
//订单金额(数字)
map["total_fee"] = allPrice.times(100).toInt().toString()
//终端IP
map["spbill_create_ip"] = "192.168.0.143"
//通知地址
map["notify_url"] = "http://www.jieshishop.com/Wxsite/Wxpay/wxf2fNotify"
//交易类型
map["trade_type"] = "NATIVE"
//按字典顺序排序
val infoIds: List<Map.Entry<String, String>> =
ArrayList(map.entries)
Collections.sort(infoIds) { o1, o2 -> o1.key.compareTo(o2.key) }
//使用&符号进行频率
val sbR: String =
Tools.getStringBuffer(infoIds).toString() + "&key=" + orderInfoBean!!.data.wxmchkey
//进行MD5加密之后 转大写
val sign: String = Tools.encode(sbR).toUpperCase()
map["sign"] = sign
e(map.toString())
/*将map集合转为xml*/
val toXml = Tools.mapToXml(map)
// e("认证参数XMl$toXml")
val body = RequestBody.create(null, toXml)
e(toXml.toString())
OkGo.post<String>("https://api.mch.weixin.qq.com/pay/unifiedorder")
.tag(this)
.upRequestBody(body)
.execute(object : StringCallback() {
override fun onError(response: com.lzy.okgo.model.Response<String?>?) {
e("微信二维码支付失败${response!!.body()}")
}
override fun onSuccess(response: com.lzy.okgo.model.Response<String?>?) {
val payVoucher = response!!.body()
try {
var msg = Tools.parseGetAuthInfoXML(payVoucher, "return_msg")
var code = Tools.parseGetAuthInfoXML(payVoucher, "return_code")
var resultCode = Tools.parseGetAuthInfoXML(payVoucher, "result_code")
var codeUrl = Tools.parseGetAuthInfoXML(payVoucher, "code_url")
if ("SUCCESS" == code) {
if ("SUCCESS" == resultCode) {
//支付成功
dialog = PayCodeDialog(mContext, payType, codeUrl)
dialog!!.show()
}
} else {
ToastUtil.showTextToast("微信二维码支付失败:$msg")
}
} catch (e: Exception) {
e.printStackTrace()
ToastUtil.showTextToast("微信二维码支付失败:$e")
}
}
})
}
Tools 工具类:
public class Tools {
/**
* * 将Map转换为XML格式的字符串
* *
* * @param data Map类型数据
* * @return XML格式的字符串
* * @throws Exception
*
*/
public static String mapToXml(Map<String, String> data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
/**
* 拼接
*
* @param infoIds
* @return
*/
@NonNull
public static String getStringBuffer(List<Map.Entry<String, Object>> infoIds) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < infoIds.size(); i++) {
Map.Entry<String, Object> stringStringEntry = infoIds.get(i);
if (stringStringEntry.getKey() == null) {
stringStringEntry.getKey();
}
String key = stringStringEntry.getKey();
Object val = stringStringEntry.getValue();
if (i != infoIds.size() - 1) {
if (val != null && !TextUtils.equals("", val.toString())) {
sb.append(key).append("=").append(val).append("&");
}
} else {
if (val != null && !TextUtils.equals("", val.toString())) {
sb.append(key).append("=").append(val);
}
}
}
return sb.toString();
}
/**
* 作 者: yzhg
* 历 史: (版本) 1.0
* 描 述: md5加密
*/
public static String encode(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] result = digest.digest(password.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : result) {
int number = b & 0xff;
String str = Integer.toHexString(number);
if (str.length() == 1) {
sb.append("0");
}
sb.append(str);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
public static String parseGetAuthInfoXML(String resultText, String indexText) throws Exception {
InputStream is = new ByteArrayInputStream(resultText.getBytes());
String result = null;
XmlPullParser parser = Xml.newPullParser();
parser.setInput(is, "UTF-8");
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
if (parser.getName().equals(indexText)) {
eventType = parser.next();
result = parser.getText();
}
}
eventType = parser.next();
}
return result;
}
}