最近项目中集成了华为账号登录与支付的功能,把踩过的坑和过程记录下来。
先看下支付效果图:
支付价格0.01请忽略,因为这是为了测试用的。
刚开始接到这个项目的时候我很奇怪,为什么要集成华为支付呢,原有的微信和支付宝已经能够满足项目需求,难道还有客户偏偏要用华为支付的?看下华为联运人员发来的邮件说明
原来公司是想在华为平台为项目做推广,然后华为的联运应用基础必须要集成华为的账号、支付。然后与我们公司收益三七分成...
ok,那就搞吧。
先说下几个坑点:
1、集成Agent必须保持代码的包和路径的结构不变
2、登录华为账号必须是正式包签名,HMS服务才会起作用(换而言之就是正式包签名下才能登录华为账号)
至于debug模式下如何打正式包,可以看下我这篇博客https://blog.csdn.net/yun382657988/article/details/83928748
3、华为支付的sign签名有可能验证通过不了
这里就需要配合后台开发验证了
4、调试华为推送push,最好在华为手机上调试,其他手机有可能推送不成功(LZ小米5sp手机推送不成功)
一、注册成为华为开发者
接入准备,创建应用、申请HMS服务等巴拉巴拉的一堆就不细说了,可以看详细的华为联运应用文档(注:文档说明,接入华为支付服务的前提,必须成为企业开发者,所以个人的,咱只好歇菜了)给上链接
https://developer.huawei.com/consumer/cn/service/hms/catalog/HwJointOperationApp.html?page=hmssdk_jointOper_app_prepare
然后还需要完成appid,支付公钥,支付私钥等,这个很好弄,按文档操作
二、集成sdk
集成sdk用Gradle+maven集成方式,依旧看文档操作。这里主要说下集成Agent,通过里面的脚本操作,选择需要的代码拷贝到项目中,这里有个坑,需要注意一下:
保持HMSAgent代码的包的路径和结构不变,什么意思呢?看下图
必须要这样的结构才行,不然会报错!!!!!!
三、配置manifest文件、配置混淆、配置签名等
这里也不做过多说明,根据需求按照文档操作
四、华为账号登录
private void signIn(boolean forceLogin) {HMSAgent.Hwid.signIn(forceLogin, (rtnCode, signInResult) -> {if (rtnCode == HMSAgent.AgentResultCode.HMSAGENT_SUCCESS && signInResult != null) {Map<String, String> params = new HashMap<>();params.put("username", signInResult.getDisplayName());if((signInResult.getPhotoUrl()).equals("")){params.put("head_photo", "https://pan.baidu.com/s/17ZznCBxsde53WfWqFgU2Wg");}else {params.put("head_photo", signInResult.getPhotoUrl());}params.put("openid", signInResult.getOpenId());JLogger.d("head_photo:"+signInResult.getPhotoUrl());uploadUserInfo(params);} else {JLogger.e("signIn---error: " + rtnCode);}});}
代码很简单,调用HMSAgent.Hwid.signIn接口即可。这里主要是获取用户的OpenId(应用内用户的唯一标识)、昵称和头像
uploadUserInfo(params)方法是把用户的信息上传到自己项目的服务器上,这里不做过多说明。
非华为手机调试需要安装华为移动服务
全部点击同意。这里建议用华为的手机调试(华为push的集成就不在这说了,网上有很多例子,要用华为的手机,lz用的小米的push推送老是不成功哦,这个是我踩的坑二,哈哈)
五、华为支付
支付的代码也不多,主要是调用HMSAgent.Pay.pay接口,贴上代码
package com.jm.ec.main.pay;import android.app.Activity;
import android.content.SharedPreferences;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.huawei.android.hms.agent.HMSAgent;
import com.huawei.android.hms.agent.pay.PaySignUtil;
import com.huawei.hms.support.api.entity.pay.OrderRequest;
import com.huawei.hms.support.api.entity.pay.PayReq;
import com.huawei.hms.support.api.entity.pay.PayStatusCodes;
import com.huawei.hms.support.api.pay.PayResultInfo;
import com.jm.core.delegates.JumeiDelegate;
import com.jm.core.net.RestClient;
import com.jm.core.util.log.JLogger;
import com.jm.ec.constant.JConstants;
import com.jm.ec.event.StartOrderEvent;
import com.jm.ec.main.design.IEvnValues;import org.greenrobot.eventbus.EventBus;import java.security.SecureRandom;
import java.text.DateFormat;
import java.util.Date;import static com.jm.core.app.Jumei.getActivity;public class HuaWeiPay {private Activity mActivity = null;private static final String pay_priv_key = IEvnValues.pay_priv_key;private static final String pay_pub_key = IEvnValues.pay_pub_key;private static final String appId = IEvnValues.appId;private static final String cpId = IEvnValues.cpId;private static int RESULT ;private static String AMOUNT = null;private static String ORDERID = null;private static String REQUESTID = null;private static String NOTIFYTIME = null;private static String USERNAME= null;private static String PAYNAME = "";private HuaWeiPay(JumeiDelegate delegate) {this.mActivity = delegate.getProxyActivity();}public static HuaWeiPay create(JumeiDelegate delegate) {return new HuaWeiPay(delegate);}/*** 普通支付示例*/public static void pay(float amount, String orderNumber, String name) {PayReq payReq = createPayReq(amount,orderNumber,name);HMSAgent.Pay.pay(payReq, (int retCode, PayResultInfo payInfo) -> {if (retCode == HMSAgent.AgentResultCode.HMSAGENT_SUCCESS && payInfo != null) {boolean checkRst = PaySignUtil.checkSign(payInfo, pay_pub_key);JLogger.d("pay: onResult: pay success and checksign=" + checkRst);if (checkRst) {// 支付成功并且验签成功,发放商品RESULT = payInfo.getReturnCode();AMOUNT = payInfo.getAmount();ORDERID = payInfo.getOrderID();REQUESTID = orderNumber;NOTIFYTIME = payInfo.getTime();USERNAME = payInfo.getUserName();PAYNAME = name;RestClient.builder().url("这个主要是获取华为sign的接口,因为上传payInfo.getSign()获取的sign,后台那边死活通过不了,于是写了个接口获取华为sign").params("result",payInfo.getReturnCode()).params("amount",payInfo.getAmount()).params("orderId",payInfo.getOrderID()).params("requestId",orderNumber).params("notifyTime",payInfo.getTime()).params("userName",payInfo.getUserName()).params("productName",name).params("payType",4).params("sign",payInfo.getSign()).success(response -> handleResultHuawei(response)).build().post();JLogger.d("PayResultInfo================="+payInfo.getSign());} else {// 签名失败,需要查询订单状态:对于没有服务器的单机应用,调用查询订单接口查询;其他应用到开发者服务器查询订单状态。getPayDetail(orderNumber);}} else if (retCode == HMSAgent.AgentResultCode.ON_ACTIVITY_RESULT_ERROR|| retCode == PayStatusCodes.PAY_STATE_TIME_OUT|| retCode == PayStatusCodes.PAY_STATE_NET_ERROR) {// 需要查询订单状态:对于没有服务器的单机应用,调用查询订单接口查询;其他应用到开发者服务器查询订单状态。getPayDetail(orderNumber);} else {JLogger.d("pay: onResult: pay fail=" + retCode);// 其他错误码意义参照支付api参考}});// 将requestid缓存,供查询订单addRequestIdToCache(payReq.getRequestId());}private static void handleResultHuawei(String response) {final JSONObject jsonObject = JSON.parseObject(response);if (JConstants.OK.equals(jsonObject.getString("code"))) {JSONObject data = jsonObject.getJSONObject("data");final String sign = data.getString("sign");JLogger.d(sign);RestClient.builder().url("这个接口是验证获取的华为singn").params("result",RESULT).params("amount",AMOUNT).params("orderId",ORDERID).params("requestId",REQUESTID).params("notifyTime",NOTIFYTIME).params("userName",USERNAME).params("productName",PAYNAME).params("payType",4).params("appSign",sign)//就是这个appSign咯.success(response1 -> handleResultSign(response1)).build().post();}}private static void handleResultSign(String response) {JLogger.json(response);final JSONObject jsonObject = JSON.parseObject(response);if (JConstants.OK.equals(jsonObject.getString("code"))) {updatePaySuccessUI();}}private static void addRequestIdToCache(String requestId) {SharedPreferences sp = getActivity().getSharedPreferences("pay_request_ids", 0);sp.edit().putBoolean(requestId, false).commit();}/*** 创建普通支付请求对象* @param totalAmount 要支付的金额* @param orderNumber* @return 普通支付请求对象*/private static PayReq createPayReq(float totalAmount, String orderNumber, String name) {PayReq payReq = new PayReq();/*** 生成requestId*/DateFormat format = new java.text.SimpleDateFormat("yyyyMMddhhmmssSSS");int random= new SecureRandom().nextInt() % 100000;random = random < 0 ? -random : random;String requestId = format.format(new Date());requestId = String.format("%s%05d", requestId, random);/*** 生成总金额 | Generate Total Amount*/String amount = String.format("%.2f", totalAmount);//商品名称payReq.productName = name;//商品描述payReq.productDesc = "发型设计、形象设计、脸型分析";// 商户ID,来源于开发者联盟,也叫“支付id”payReq.merchantId = cpId;// 应用ID,来源于开发者联盟payReq.applicationID = appId;// 支付金额 | Amount paidpayReq.amount = amount;// 支付订单号payReq.requestId = orderNumber;// 国家码 | Country codepayReq.country = "CN";//币种payReq.currency = "CNY";// 渠道号payReq.sdkChannel = 1;// 回调接口版本号payReq.urlVer = "2";// 商户名称,必填,不参与签名。会显示在支付结果页面payReq.merchantName = "合肥聚美网络科技有限公司";//分类,必填,不参与签名。该字段会影响风控策略// X4:主题,X5:应用商店, X6:游戏,X7:天际通,X8:云空间,X9:电子书,X10:华为学习,X11:音乐,X12 视频,// X31 话费充值,X32 机票/酒店,X33 电影票,X34 团购,X35 手机预购,X36 公共缴费,X39 流量充值payReq.serviceCatalog = "X5";//商户保留信息,选填不参与签名,支付成功后会华为支付平台会原样 回调CP服务端payReq.extReserved = "Here to fill in the Merchant reservation information";//对单机应用可以直接调用此方法对请求信息签名,非单机应用一定要在服务器端储存签名私钥,并在服务器端进行签名操作。// 在服务端进行签名的cp可以将getStringForSign返回的待签名字符串传给服务端进行签名payReq.sign = PaySignUtil.rsaSign(PaySignUtil.getStringForSign(payReq), pay_priv_key);return payReq;}private static void getPayDetail(final String reqId) {OrderRequest or = new OrderRequest();JLogger.d("checkPay: begin=" + reqId);or.setRequestId(reqId);or.setTime(String.valueOf(System.currentTimeMillis()));or.setKeyType("1");or.setMerchantId(cpId);or.sign = PaySignUtil.rsaSign(PaySignUtil.getStringForSign(or), pay_priv_key);HMSAgent.Pay.getOrderDetail(or, (retCode, checkPayResult) -> {JLogger.d("checkPay: requId="+reqId+" retCode=" + retCode);if (checkPayResult != null && checkPayResult.getReturnCode() == retCode) {// 处理支付业务返回码 | Processing Payment Business return codeif (retCode == HMSAgent.AgentResultCode.HMSAGENT_SUCCESS) {boolean checkRst = PaySignUtil.checkSign(checkPayResult, pay_pub_key);if (checkRst) {// 支付成功,发放对应商品JLogger.d("checkPay: Pay successfully, distribution of goods");} else {// 验签失败,当支付失败处理JLogger.d("checkPay: Failed to verify signature, pay failed");}// 不需要再查询 | No more queriesremoveCacheRequestId(checkPayResult.getRequestId());} else if (retCode == PayStatusCodes.ORDER_STATUS_HANDLING|| retCode == PayStatusCodes.ORDER_STATUS_UNTREATED|| retCode == PayStatusCodes.PAY_STATE_TIME_OUT) {// 未处理完,需要重新查询。如30分钟后再次查询。超过24小时当支付失败处理JLogger.d("checkPay: Pay failed. errorCode="+retCode+" errMsg=" + checkPayResult.getReturnDesc());} else if (retCode == PayStatusCodes.PAY_STATE_NET_ERROR) {// 网络失败,需要重新查询JLogger.d("checkPay: A network problem caused the payment to fail. errorCode="+retCode+" errMsg=" + checkPayResult.getReturnDesc());} else {// 支付失败,不需要再查询JLogger.d("checkPay: Pay failed. errorCode="+retCode+" errMsg=" + checkPayResult.getReturnDesc());removeCacheRequestId(reqId);}} else {// 没有结果回来,需要重新查询。如30分钟后再次查询。超过24小时当支付失败处理JLogger.d("checkPay: Pay failed. errorCode="+retCode);}});}private static void removeCacheRequestId(String reqId) {SharedPreferences sp = getActivity().getSharedPreferences("pay_request_ids", 0);sp.edit().remove(reqId).commit();}private static void updatePaySuccessUI() {EventBus.getDefault().post(new StartOrderEvent());//发送消息给主线程,跳转到订单详情页}
}
上面的支付代码,在华为demo里也有,建议,先把私钥、公钥、AppID、cpid放到demo里看能否运行起来,再接入到项目中,省时省力。其实华为文档上建议把私钥放在开发者的服务器端,客户端请求,私钥生成这些处理放在服务器端处理,这样是为了安全起见哦。我们这边后端没写,只能放里面了。
这里支付做了简单的封装处理,需要支付的时候HuaWeiPay.pay(Float.parseFloat(price),orderNumber,name);写上这行代码,传进来的参数分别是价格、订单编号、和支付产品的名字
以上即是华为账号登录与华为支付了,这里总结起来容易,前几天弄的时候可是踩了不少坑,哈哈。