Android项目中集成华为账号登录、支付

最近项目中集成了华为账号登录与支付的功能,把踩过的坑和过程记录下来。

先看下支付效果图:

 支付价格0.01请忽略,因为这是为了测试用的。

刚开始接到这个项目的时候我很奇怪,为什么要集成华为支付呢,原有的微信和支付宝已经能够满足项目需求,难道还有客户偏偏要用华为支付的?看下华为联运人员发来的邮件说明

 原来公司是想在华为平台为项目做推广,然后华为的联运应用基础必须要集成华为的账号、支付。然后与我们公司收益三七分成...

ok,那就搞吧。

先说下几个坑点:

1、集成Agent必须保持代码的包和路径的结构不变

2、登录华为账号必须是正式包签名,HMS服务才会起作用(换而言之就是正式包签名下才能登录华为账号)

扫描二维码关注公众号,回复: 4513473 查看本文章

至于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 paid
        payReq.amount = amount;
        // 支付订单号
        payReq.requestId = orderNumber;
        // 国家码 | Country code
        payReq.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 code
                if (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 queries
                    removeCacheRequestId(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);写上这行代码,传进来的参数分别是价格、订单编号、和支付产品的名字

以上即是华为账号登录与华为支付了,这里总结起来容易,前几天弄的时候可是踩了不少坑,哈哈。

猜你喜欢

转载自blog.csdn.net/yun382657988/article/details/84027758