接入华为支付

1、华为支付相对微信支付又复杂点,同样包名,签名,appId都必须正确,不能修改
2、配置内容也多点,不过基本按照文档说明认真配置也是没问题的
3、华为支付是没demo源码参考,只有文档的示例代码,其他第三方一般都会有demo的

官方开发文档

https://developer.huawei.com/consumer/cn/service/hms/catalog/huaweiiap_oversea.html?page=hmssdk_huaweiiap_devprepare_oversea

按照文档一步步来,千万不要跳步骤,要不出问题不好排查
可能出问题的地方
在清单的application节点下增加APPID

<meta-data  
    android:name="com.huawei.hms.client.appid"  
    <!-- value的值“xxx”用实际申请的应用ID替换,来源于开发者联盟网站应用的服务详情。-->  
    android:value="appid=xxx">  
</meta-data> 

记住里面是appid=xxx而不是去掉xxx

初始化Agent

1.在Application类中的onCreate方法中初始化HMS Agent套件

public class MyApplication extends Application {
    @Override
    public void onCreate() {        

            super.onCreate();
            HMSAgent.init(this);
    }} 

2.请务必在应用启动后的首个activity的onCreate方法中调用connect接口,确保HMS SDK和HMS APK的连接

HMSAgent.connect(this, new ConnectHandler() {
@Override
public void onConnect(int rst) {
    showLog("HMS connect end:" + rst);
}});    

注意是启动的第一个activity,要不会上架审核不成功的

调起华为支付

关键方法HuaweiPay.HuaweiPayApi.pay();
只好建个中间的Activity来集成,因为支付返回结果都是在onActivityResult获取。如果只有一个界面有华为支付的,就没必要了。
中间华为支付activity
示例代码:

public  abstract class HuaWeiActivity<T extends BaseBuyIView,P extends BaseBuyPresenter<T>> extends BaseActivity<T,P> implements ISetHw {
private static final String TAG = HuaWeiActivity.class.getName();
public HuaweiApiClient client;
private String outTradeNo;

//启动参数,区分startactivityforresult的处理结果
private final int REQ_CODE_PAY = 4001;
//作用同startactivityforresult方法中的requestcode
private static final int REQUEST_HMS_RESOLVE_ERROR = 1000;
private ShowPayDialog showPayDialog;

//初始化init华为支付
private void initClient() {

    //-------------------华为支付----------------------
    if(null == client){
        client = new HuaweiApiClient.Builder(this)
                .addApi(HuaweiPay.PAY_API)
                .addOnConnectionFailedListener(this)
                .addConnectionCallbacks(this)
                .build();
    }

    client.connect(this);
}


@Override
public void onResume() {
    super.onResume();
    initClient();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (null != client) {
        client.disconnect();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    LogUtils.i("reCode", requestCode + " " + resultCode + "");
    if (requestCode == REQUEST_HMS_RESOLVE_ERROR) {
        if (resultCode == Activity.RESULT_OK) {
            int result = data.getIntExtra(EXTRA_RESULT, 0);
            if (result == ConnectionResult.SUCCESS) {
                LogUtils.i(TAG, "错误成功解决");
                if (!client.isConnecting() && !client.isConnected()) {
                    client.connect(this);
                }
            } else if (result == ConnectionResult.CANCELED) {
                LogUtils.i(TAG, "解决错误过程被用户取消");
            } else if (result == ConnectionResult.INTERNAL_ERROR) {
                LogUtils.i(TAG, "发生内部错误,重试可以解决");
                //CP可以在此处重试连接华为移动服务等操作,导致失败的原因可能是网络原因等
            } else {
                LogUtils.i(TAG, "未知返回码");
            }
        } else {
            LogUtils.i(TAG, "调用解决方案发生错误");
        }
    } else if (requestCode == REQ_CODE_PAY) {
        //当返回值是-1的时候表明用户支付调用成功
        if (resultCode == Activity.RESULT_OK) {
            //获取支付完成信息
            PayResultInfo payResultInfo = HuaweiPay.HuaweiPayApi.getPayResultInfoFromIntent(data);
            if (payResultInfo != null) {
                Map<String, Object> paramsa = new HashMap<String, Object>();
                if (PayStatusCodes.PAY_STATE_SUCCESS == payResultInfo.getReturnCode()) {
                    

                    //mHuaWeiPayPresenter.getHwPayNotify(b_tag, payResultInfo.getRequestId(), payResultInfo.getReturnCode(), success);
                    payHwSuccessNotify(payResultInfo.getReturnCode());
                    //paySuccess();
                    closeDialog();

                } else if (PayStatusCodes.PAY_STATE_CANCEL == payResultInfo.getReturnCode()) {
                    //支付失败,原因是用户取消了支付,可能是用户取消登录,或者取消支付
                    Log.i(TAG, "支付失败:用户取消" + payResultInfo.getErrMsg());
                    payHwFailNotify();

                } else {
                    //支付失败,其他一些原因
                    Log.i(TAG, "支付失败:" + payResultInfo.getErrMsg() + payResultInfo.getReturnCode());
                    payHwFailNotify();

                }
            } else {
                //支付失败
                payHwFailNotify();
            }
        } else {
            //当resultCode 为0的时候表明用户未登录,则CP可以处理用户不登录事件
            Log.i(TAG, "resultCode为0, 用户未登录 CP可以处理用户不登录事件");

        }
    }
}

protected abstract void payHwSuccessNotify(int payCode);
protected abstract void payHwFailNotify();

@Override
public void onConnected() {

}

@Override
public void onConnectionSuspended(int i) {
    //HuaweiApiClient异常断开连接, if 括号里的条件可以根据需要修改
    if (!this.isDestroyed() && !this.isFinishing()) {
        client.connect(this);
    }
    LogUtils.i(TAG, "HuaweiApiClient 连接异常断开成功");
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
    LogUtils.i(TAG, "HuaweiApiClient连接失败,错误码:" + connectionResult.getErrorCode());
    if (HuaweiApiAvailability.getInstance().isUserResolvableError(connectionResult.getErrorCode())) {
        final int errorCode = connectionResult.getErrorCode();
        new Handler(getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                // 此方法必须在主线程调用, xxxxxx.this 为当前界面的activity
                HuaweiApiAvailability.getInstance().resolveError(HuaWeiActivity.this, errorCode, REQUEST_HMS_RESOLVE_ERROR);
            }
        });
    } else {
        //其他错误码请参见API文档
    }
}

/**
 * 发起支付流程,开发者可以直接参照该方法写法
 */
protected void hwPay(HashMap params, String sign) {
    if (!client.isConnected()) {
        LogUtils.i(TAG, "支付失败,原因:HuaweiApiClient未连接");
        client.connect(this);
        return;
    }
    PendingResult<PayResult> payResult = HuaweiPay.HuaweiPayApi.pay(client, HwPayUtils.createPayReq(params, sign));
    payResult.setResultCallback(new PayResultCallback());
}

/**
 * 弹框相关
 *
 * @param showPayDialog
 */
public void setShowDialog(ShowPayDialog showPayDialog) {
    this.showPayDialog = showPayDialog;
}

/**
 * 支付接口调用的回调处理
 * 只有当处理结果中的返回码为 PayStatusCodes.PAY_STATE_SUCCESS的时候,CP需要继续调用支付
 * 否则就需要处理支付失败结果
 */
private class PayResultCallback implements ResultCallback<PayResult> {

    @Override
    public void onResult(PayResult result) {
        //支付鉴权结果,处理result.getStatus()
        Status status = result.getStatus();
        if (PayStatusCodes.PAY_STATE_SUCCESS == status.getStatusCode()) {
            //当支付回调 返回码为0的时候,表明支付流程正确,CP需要调用startResolutionForResult接口来进行后续处理
            //支付会先判断华为帐号是否登录,如果未登录,会先提示用户登录帐号。之后才会进行支付流程
            try {
                status.startResolutionForResult(HuaWeiActivity.this, REQ_CODE_PAY);

            } catch (IntentSender.SendIntentException e) {
                LogUtils.i(TAG, "启动支付失败" + e.getMessage());

            }
        } else {
            LogUtils.i(TAG, "支付失败,原因 :" + status.getStatusCode());

        }
    }
}



public void closeDialog() {
    if (null != showPayDialog) {
        showPayDialog.dialogDismiss();
    }
}}

仅仅是示例,把不用的删掉即可,
其中HuaweiPay.HuaweiPayApi.pay是调起华为支付,
PayResultCallback是是否能正常调起华为支付,
onActivityResult是调起后支付成功还是失败

HwPayUtils类

public class HwPayUtils {

private static final String TAG = "HwPayUtils";

/**
 * 获取华为的appId
 *
 * @param context
 * @return
 */
public static String getAppId(Context context) {
    String value = "";
    try {
        ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(),
                PackageManager.GET_META_DATA);
        value = appInfo.metaData.getString("com.huawei.hms.client.appid");
        String[] appidValue = value.split("=");
        return appidValue[appidValue.length-1];
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return value;
}

/**
 * 生成支付信息map 包含
 * HwPayConstant.KEY_MERCHANTID  必选参数 商户id,开发者联盟网站生成的支付ID
 * HwPayConstant.KEY_APPLICATIONID 必选参数 应用的appid,开发者联盟网站生成
 * HwPayConstant.KEY_AMOUNT 必选参数 支付金额 string类型,精确到小数点后2位 比如 20.00
 * HwPayConstant.KEY_PRODUCTNAME 必选参数 商品名称 此名称将会在支付时显示给用户确认 注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:) \ < > ,
 * HwPayConstant.KEY_PRODUCTDESC 必选参数 商品描述 注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:) \ < > , |
 * HwPayConstant.KEY_REQUESTID  必选参数 请求订单号。其值由商户定义生成,用于标识一次支付请求,每次请求需唯一,不可重复。
 * 支付平台在服务器回调接口中会原样返回requestId的值。注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:) \ < > , .以及中文字符
 * HwPayConstant.KEY_SDKCHANNEL 必选参数 渠道信息。 取值如下:0 代表自有应用,无渠道 1 代表应用市场渠道 2 代表预装渠道 3 代表游戏中心渠道
 * HwPayConstant.KEY_URLVER 可选参数  回调接口版本号。如果传值则必须传2, 额外回调信息,具体参考接口文档
 * HwPayConstant.KEY_URL 可选参数 支付结果回调URL. 华为服务器收到后检查该应用有无在开发者联盟配置回调URL,如果配置了则使用应用配置的URL,否则使用此url
 * 作为该次支付的回调URL,建议直接 以配置在 华为开发者联盟的回调URL为准
 * HwPayConstant.KEY_COUNTRY 可选参数 国家码.建议无特殊需要,不传
 * HwPayConstant.KEY_CURRENCY 可选参数 币种 选填.建议无特殊需要不传此参数。目前仅支持CNY,默认CNY
 */
public static HashMap getPayInfo(PayHwOrderResult result) {
    HashMap params = new HashMap<String, Object>();
    PayHwOrderResult.DataBean obj = result.getData();

    params.put(HwPayConstant.KEY_MERCHANTID, obj.getMerchantId());
    params.put(HwPayConstant.KEY_APPLICATIONID, obj.getAppId());

    params.put(HwPayConstant.KEY_AMOUNT, obj.getAmount());
    params.put(HwPayConstant.KEY_PRODUCTNAME, obj.getProductName());
    params.put(HwPayConstant.KEY_PRODUCTDESC, obj.getProductDesc());

    params.put(HwPayConstant.KEY_REQUESTID, obj.getOutTradeNo());
    params.put(HwPayConstant.KEY_SDKCHANNEL, obj.getSdkChannel());
    params.put(HwPayConstant.KEY_URLVER, obj.getUrlver());
    params.put(HwPayConstant.KEY_URL, obj.getUrl());

    //不需要签名参数
    params.put(HwPayConstant.KEY_MERCHANTNAME, obj.getMerchantName());
    params.put(HwPayConstant.KEY_SERVICECATALOG, obj.getServiceCatalog());
    params.put(HwPayConstant.KEY_EXTRESERVED, obj.getExtReserved());
    return params;
}


/**
 * 封装json参数给后台
 *
 * @param params
 * @return
 */
public static String paramJson(HashMap params) {
    String value = "";
    try {
        JSONObject mJsonobjData = new JSONObject();
        mJsonobjData.put(HwPayConstant.KEY_MERCHANTID, params.get(HwPayConstant.KEY_MERCHANTID));
        mJsonobjData.put(HwPayConstant.KEY_APPLICATIONID, params.get(HwPayConstant.KEY_APPLICATIONID));
        mJsonobjData.put(HwPayConstant.KEY_AMOUNT, params.get(HwPayConstant.KEY_AMOUNT));
        mJsonobjData.put(HwPayConstant.KEY_PRODUCTNAME, params.get(HwPayConstant.KEY_PRODUCTNAME));
        mJsonobjData.put(HwPayConstant.KEY_PRODUCTDESC, params.get(HwPayConstant.KEY_PRODUCTDESC));

        mJsonobjData.put(HwPayConstant.KEY_REQUESTID, params.get(HwPayConstant.KEY_REQUESTID));
        mJsonobjData.put(HwPayConstant.KEY_SDKCHANNEL, params.get(HwPayConstant.KEY_SDKCHANNEL));
        mJsonobjData.put(HwPayConstant.KEY_URLVER, params.get(HwPayConstant.KEY_URLVER));
        mJsonobjData.put(HwPayConstant.KEY_URL, params.get(HwPayConstant.KEY_URL));

        value = mJsonobjData.toString();

    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }

    return value;
}

/**
 * 生成PayReq对象,用来在进行支付请求的时候携带支付相关信息
 * payReq订单参数需要商户使用在华为开发者联盟申请的RSA私钥进行签名,强烈建议将签名操作在商户服务端处理,避免私钥泄露
 */
public static PayReq createPayReq(HashMap params, String sign) {

    PayReq payReq = new PayReq();

    //商品名称
    payReq.productName = (String) params.get(HwPayConstant.KEY_PRODUCTNAME);
    //商品描述
    payReq.productDesc = (String) params.get(HwPayConstant.KEY_PRODUCTDESC);
    // 商户ID:来源于开发者联盟的“支付ID”
    payReq.merchantId = (String) params.get(HwPayConstant.KEY_MERCHANTID);
    // 应用ID
    payReq.applicationID = (String) params.get(HwPayConstant.KEY_APPLICATIONID);
    // 支付金额
    payReq.amount = (String) params.get(HwPayConstant.KEY_AMOUNT);
    // 商户订单号:开发者在支付前生成,用来唯一标识一次支付请求
    payReq.requestId = (String) params.get(HwPayConstant.KEY_REQUESTID);
    // 渠道号
    payReq.sdkChannel = (Integer) params.get(HwPayConstant.KEY_SDKCHANNEL);
    // 回调接口版本号
    payReq.urlVer = (String) params.get(HwPayConstant.KEY_URLVER);
    payReq.url = (String) params.get(HwPayConstant.KEY_URL);
    LogUtils.i("hwPayUrl",payReq.url);
    LogUtils.i(TAG, payReq.productName + " " + payReq.productDesc + " " + payReq.merchantId + " " + payReq.applicationID + " "
            + payReq.amount + " " + payReq.requestId + " " + payReq.sdkChannel + " " + payReq.getUrlVer());
    //以上信息按照一定规则进行签名,建议CP在服务器端储存签名私钥,并在服务器端进行签名操作。

    payReq.sign = sign;

    // 商户名称,必填,不参与签名。开发者注册的公司名
    payReq.merchantName = (String) params.get(HwPayConstant.KEY_MERCHANTNAME);

    //分类,必填,不参与签名。该字段会影响风控策略
    // X4:主题,X5:应用商店,  X6:游戏,X7:天际通,X8:云空间,X9:电子书,X10:华为学习,X11:音乐,X12 视频,
    // X31 话费充值,X32 机票/酒店,X33 电影票,X34 团购,X35 手机预购,X36 公共缴费,X39 流量充值
    payReq.serviceCatalog = (String) params.get(HwPayConstant.KEY_SERVICECATALOG);
    //商户保留信息,选填不参与签名,支付成功后会华为支付平台会原样 回调CP服务端
    payReq.extReserved = (String) params.get(HwPayConstant.KEY_EXTRESERVED);

    return payReq;
}


/**
 * 将商户id,应用id, 商品名称,商品说明,支付金额,订单号,渠道号,回调地址版本号等信息按照key值升序排列后
 * 以key=value并以&的方式连接起来生成待签名的字符串
 *
 * @return
 */
public static String getNoSign(Map<String, Object> params) {
    //对参数按照key做升序排序,对map的所有value进行处理,转化成string类型
    //拼接成key=value&key=value&....格式的字符串
    StringBuffer content = new StringBuffer();
    // 按照key做排序
    List<String> keys = new ArrayList<String>(params.keySet());
    Collections.sort(keys);
    String value = null;
    Object object = null;
    for (int i = 0; i < keys.size(); i++) {
        String key = (String) keys.get(i);
        object = params.get(key);
        if (object instanceof String) {
            value = (String) object;
        } else {
            value = String.valueOf(object);
        }

        if (value != null) {
            content.append((i == 0 ? "" : "&") + key + "=" + value);
        } else {
            continue;
        }
    }

    //待签名的字符串
    String signOri = content.toString();
    return signOri;
}


/**
 * 使用开发者联盟提供的支付公钥对支付成功结果中的签名信息进行验证
 * 如果签名验证成功,则表明支付流程正确
 * 如果签名验证不成功,那么支付已经成功,但是签名有误,CP需要到服务器上查询支付情况
 *
 * @param content
 * @param sign
 * @return
 */
public static boolean doCheck(String content, String sign) {
    //开发者联盟提供的支付公钥
    String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAje6cNmwjC9wY3QuZ/YTMzqwqvDFRzlswS3evaRsuUkz9PnAXqea8rG2/JHKj1zASfbAXkdGVnojmlGuhiUTleYgZK6Hq7crrm77WtM25TgWQ15vBZp6inuqnqwdGiXrJdqSng5LGkIyrMIC1PLdSeYtb76dW/Y0fAAz277X+WK10nzL2RnVSZAAjZnnZVTaDJhWCy3uY90YsiLbaxXVghuNRuBJ+vSave61Ut4yOA5AGK1QwQPq0/c91MLQgkLPwmncz0BGK3+H7pYIN/ceQ1033tL7WZE3hHj2lgwRu0KTfk7d96AC0WMG2lk9BKHAZCxMswC4bXKjDa55MPb8JwQIDAQAB";
    //使用加密算法规则
    String SIGN_ALGORITHMS = "SHA256WithRSA";

    try {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] encodedKey = Base64.decode(publicKey, Base64.DEFAULT);
        PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

        java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);

        signature.initVerify(pubKey);
        signature.update(content.getBytes("utf-8"));

        boolean bverify = signature.verify(Base64.decode(sign, Base64.DEFAULT));
        return bverify;

    } catch (NoSuchAlgorithmException e) {
        Log.e(TAG, "doCheck NoSuchAlgorithmException" + e);
    } catch (InvalidKeySpecException e) {
        Log.e(TAG, "doCheck InvalidKeySpecException" + e);
    } catch (InvalidKeyException e) {
        Log.e(TAG, "doCheck InvalidKeyException" + e);
    } catch (SignatureException e) {
        Log.e(TAG, "doCheck SignatureException" + e);
    } catch (UnsupportedEncodingException e) {
        Log.e(TAG, "doCheck UnsupportedEncodingException" + e);
    }
    return false;
}}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容