目录

客户端开发

本文只介绍Android侧集成应用内支付的方法,以帮助您快速了解华为支付提供的客户端接口及其调用方法。开发者进行VR应用开发时需编写java中间件,将集成好HMS的module打成jar包或aar包放入3D开发引擎中。

对于使用自定义价格支付pay接口的您需要完成如下开发步骤:

  1. 连接HuaweiApiClient;
  2. 调用pay自定义价格支付接口完成支付;
  3. 处理支付结果。

另外对于有自己应用服务器的您需要完成支付回调服务器的开发工作,详细步骤参见服务端开发。

商品购买流程

当前版本的VR支付调用pay自定义价格支付接口完成支付,购买流程图如下:

连接HuaweiApiClient

调用支付接口之前需构建并连接HuaweiApiClient,确保HMS Core SDK和HMS Core APK的连接,方法如下:
Context context = this.getApplicationContext();
mClient = getApiClient(context);
if (mClient != null && !mClient.isConnected()) {
    startConnect();
}

private void startConnect() {
    Log.i("IAP", "startConnect");
    // 执行连接
    mClient.connect(MainActivity.this);
}
// 获取Api Client
private HuaweiApiClient getApiClient(Context context) {
    return new HuaweiApiClient.Builder(context)
        .addApi(HuaweiPay.PAY_API)
        .addConnectionCallbacks(new HuaweiApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected() {
                Log.i("IAP", "onConnected");
                onConnectResult(CONNECT_STATUS_SUCCUSS);
            }

            @Override
            public void onConnectionSuspended(int i) {
                Log.i("IAP", "onConnectionSuspended");
                onConnectResult(CONNECT_STATUS_SUSSPAND);
            }
        })
        .addOnConnectionFailedListener(new HuaweiApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {
                Log.i("IAP", "onConnectionFailed");
                onConnectResult(CONNECT_STATUS_FAILED);
            }
        })
        .build();
}

// 连接结果处理
private void onConnectResult(int connectStatus) {
    if (connectStatus == CONNECT_STATUS_SUCCUSS) {
        if (mClient != null && mClient.isConnected()) {
            Log.i("IAP", "connect success");
            // 连接成功,开始支付
            startPay();
        }
    } else if (connectStatus == CONNECT_STATUS_SUSSPAND) {
        Log.i("IAP", "connect suspend");
        startConnect();
    } else if (connectStatus == CONNECT_STATUS_FAILED) {
        Toast.makeText(this, "client connect failed", Toast.LENGTH_SHORT).show();
    }
}

封装PayReq支付请求参数

创建并封装PayReq对象。PayReq对象属性的详细信息请参见pay。

private PayReq createPayReq(String productName, String productDesc, String amount) {
    Map<String, Object> params = new HashMap<String, Object>();
    params.put(HwPayConstant.KEY_MERCHANTID, Constant.getMerchantID());
    params.put(HwPayConstant.KEY_APPLICATIONID, Constant.getAppID());
    params.put(HwPayConstant.KEY_PRODUCTNAME, productName);
    params.put(HwPayConstant.KEY_PRODUCTDESC, productDesc);
    requestID = Util.getRequestID();
    params.put(HwPayConstant.KEY_REQUESTID, requestID);
    params.put(HwPayConstant.KEY_SDKCHANNEL, 1);
    params.put(HwPayConstant.KEY_URLVER, "2");
    params.put(HwPayConstant.KEY_AMOUNT, Util.getAmount(amount));
    params.put(HwPayConstant.KEY_URL, "https://selfURL.cn");

    params.put(HwPayConstant.KEY_COUNTRY, "CN");
    params.put(HwPayConstant.KEY_CURRENCY, "CNY");

    // 强烈建议在商户服务端做签名处理,且私钥存储在服务端
    // PaySignUtil为自定义签名工具类,实现方法可参加示例代码
    String noSign = PaySignUtil.getSignData(params);
    String sign = PaySignUtil.rsaSign(noSign);

    PayReq payReq = new PayReq();
    // 商品名称,必填。此名称将会在支付时显示给用户确认。注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:) \ < > ,
    payReq.productName = (String) params.get(HwPayConstant.KEY_PRODUCTNAME);

    // 商品描述,必填。注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:) \ < > , |
     payReq.productDesc = (String) params.get(HwPayConstant.KEY_PRODUCTDESC);

    // 商户ID,必填。由华为开发者联盟分配
    payReq.merchantId = Constant.getMerchantID();

    // 应用ID,必填。由华为开发者联盟分配
    payReq.applicationID = (String) params.get(HwPayConstant.KEY_APPLICATIONID);

    // 待支付金额,必填。格式为:元.角分,最小金额为分, 例如:20.00,此金额将会在支付时显示给用户确认),保留到小数点后两位
    payReq.amount = (String) params.get(HwPayConstant.KEY_AMOUNT);

    // 请求订单号,必填。其值由商户定义生成,用于标识一次支付请求,每次请求需唯一,不可重复。
    // 支付平台在服务器回调接口中会原样返回requestId的值。
    // 注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:) \ < > , |
    payReq.requestId = (String) params.get(HwPayConstant.KEY_REQUESTID);

    // 支付结果回调URL,选填。华为服务器收到后检查该应用有无在开发者联盟配置回调URL,如果配置了则使用应用配置的URL,否则使用此url
    // 作为该次支付的回调URL
    // 建议直接以配置在华为开发者联盟的回调URL为准
    payReq.url = (String) params.get(HwPayConstant.KEY_URL);

    // 渠道信息,选填。取值如下:0代表自有应用,无渠道;1代表智汇云渠道;2代表预装渠道;3代表游戏
    payReq.sdkChannel = (Integer) params.get(HwPayConstant.KEY_SDKCHANNEL);

    // 回调接口版本号,选填。建议传值2,额外回调信息,具体参见接口文档
    payReq.urlVer = (String) params.get(HwPayConstant.KEY_URLVER);

    // 国家/地区码,选填。建议无特殊需要,不传
    payReq.country = (String) params.get(HwPayConstant.KEY_COUNTRY);
    // 币种,选填。建议无特殊需要不传此参数。目前仅支持CNY,默认CNY
    payReq.currency = (String) params.get(HwPayConstant.KEY_CURRENCY);

    /* 以上字段皆需要参与签名 * */

    // 签名字段 必填。采用华为开发者联盟分配的私钥进行签名,强烈建议在服务端进行签名 .
    // 【注意】以下参数不参与签名:
    // a)sign参数
    // b)参数说明中标识不参与签名的参数
    // c)没有值的参数,包括null和“”两种情况
    // 2、排序完成之后,再把所有参数名和参数值的键值对以“&”字符连接起来,得到的字符串即为待签名串。如:
    // amount=XXX&applicationID=XXX&country=XXX&currency=XXX&merchantId=XXX&productDesc=XXX&productName=XXX&requestId=XXX&
    //sdkChannel=XXX&url=XXX&urlver=XXX
    // 3、将待签名字符串使用RSA私钥进行签名,采用 SHA256WithRSA签名算法 得到的字符串即为sign参数的值
    payReq.sign = sign;

    // 商户名称,必填,不参与签名。会显示在支付结果页面
    payReq.merchantName = getString(R.string.merchantName);

    // 分类,选填,不参与签名。该字段会影响风控策略
    // X4:主题  X5:应用商店 X6:游戏 X7:天际通 X8:云空间 X9:电子书  X10:华为学习  X11:音乐  X12 视频
    // X31 话费充值   X32 机票/酒店   X33 电影票  X34 团购  X35 手机预购  X36 公共缴费   X39 流量充值
    payReq.serviceCatalog = "X6";

    return payReq;
}

对于有服务器的非单机应用,强烈建议您在自己的应用服务器端保存签名私钥,并在服务器端完成签名操作,避免私钥的泄露,参见服务端开发。但是对于没有服务器的单机应用,您可以本地封装方法获取支付签名信息,此时请您自行确保私钥的安全性,签名方法如下。

签名方法:

  1. 构造源串。

    将支付参数中需要参与签名的参数(参与签名的参数是指payReq中标识需要签名的参数,值为空<包括null和"">的不参与签名),按参数名的ASCII码的增序排序,若遇到相同首字母,则看第二个字母,以此类推。

    例如:BankId=JUNNETNET&amount=2.00&ifyTime=1353317037173&orderId=918370122485714G&payType=2&productName=珍珠项链&requestId=1352286825177&result=0&userName=lilan

  2. 生成签名值。
    1. 将源串使用RSA算法(SHA256WithRSA)进行签名。
    2. 将签名后的字符数组经过Base64.encode编码,生成签名值。

调用pay自定义价格支付接口

自定义价格pay支付接口仅限于国内开放,适用您对单个国家/地区提供的商品或服务自行定价或随时修改价格的场景,您无需使用华为PMS系统进行商品全球定价。可以帮助您快速实现应用内支付功能。

private void startPay() {
    PendingResult<PayResult> payResult = 
        HuaweiPay.HuaweiPayApi.pay(mClient, createPayReq("黄金会员", "开通会员服务", "0.01"));
    payResult.setResultCallback(new ResultCallback<PayResult>() {
        @Override
        public void onResult(PayResult payResult) {
            if (payResult == null) {
                Log.e("IAP", "payResult is null");
                return;
            }
            Status status = payResult.getStatus();
            if (status == null) {
                Log.e("IAP", "status is null");
                return;
            }
            int rstCode = status.getStatusCode();
            if (rstCode == PayStatusCodes.PAY_STATE_SUCCESS) {
                //启动支付流程
                try {
                    status.startResolutionForResult(MainActivity.this, REQUEST_PAY);
                } catch (IntentSender.SendIntentException e) {
                    Log.e("IAP", "SendIntentException");
                }
            } else {
                Log.e("IAP", "pay state failed");
            }
        }
    });
}

支付结果处理

在onActivityResult中获取支付结果,调用getPayResultInfoFromIntent接口解析支付结果。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_PAY) {
        if (resultCode == Activity.RESULT_OK) {
            // 获取支付完成信息
            PayResultInfo payResultInfo = HuaweiPay.HuaweiPayApi.getPayResultInfoFromIntent(data);
            dealPayResult(payResultInfo);
        }
    }
}

private void dealPayResult(PayResultInfo payResultInfo) {
    if (payResultInfo == null) {
        return;
    }
    int retuenCode = payResultInfo.getReturnCode();
    if (retuenCode == PayStatusCodes.PAY_STATE_SUCCESS) {
        Toast.makeText(this, "支付成功", Toast.LENGTH_SHORT).show();
        boolean checkRst = PaySignUtil.checkSign(payResultInfo, Constant.getPublicKey());
        if(checkRst){
            // 支付成功且验签成功,发放商品
            Log.i("IAP", "pay success");
        } else {
            // 签名失败,需要查询订单状态:对于没有服务器的单机应用,调用查询订单接口查询;其他应用到开发者服务器查询订单状态
            startQueryOrder();
        }
    } else {
        dealPayResultErrCode(retuenCode);
    }
}

/**
 * 支付失败处理
 * @param returnCode
 */
public void dealPayResultErrCode(int returnCode) {
    Log.e("IAP", "dealPayResultErrCode");
    switch (returnCode) {
        case PayStatusCodes.PAY_STATE_CANCEL:
            // 用户取消支付
            break;
        case PayStatusCodes.PAY_STATE_NET_ERROR:
            // 网络连接异常
            break;
        case PayStatusCodes.PAY_STATE_PARAM_ERROR:
            // 参数错误,包括无参
            break;
        case PayStatusCodes.PAY_STATE_TIME_OUT:
            // 支付结果查询超时(建议此时客户端去服务器查询订单是否支付成功)
            break;
        default:
            break;
    }
}

支付结果校验说明:

当出现以下情况时需要查询订单实际状态:

对于非单机应用(应用有自己的服务器端)由于支持支付结果回调自己的应用服务器,客户端需要访问自己的服务器确认是否支付成功的情况,具体开发者客户端和开发者服务器的支付验证接口协议,由开发者客户端和开发者服务器自行协定。

对于单机应用(应用无自己的服务器端)需要自己封装接口进行签名操作,封装方法可以参见服务端开发。

订单查询(单机应用)

对于一些单机应用,由于没有服务器的支持,在用户支付之后可能由于出现异常(网络错误、进程被杀等等)导致应用无法知道用户实际是否支付成功,即出现所谓的漏单情况。针对这种情况,华为支付提供了订单查询能力。在发起支付请求时,应用需持久保存requestId,在可能丢单的情况下,调用订单查询能力来查询实际支付结果。

封装OrderRequest订单请求参数,调用getOrderDetail接口查询订单:

private void startQueryOrder() {
    PendingResult<OrderResult> orderDetail = HuaweiPay.HuaweiPayApi.getOrderDetail(mClient, createOrderRequest(requestID));
    orderDetail.setResultCallback(new ResultCallback<OrderResult>() {
        @Override
        public void onResult(OrderResult orderResult) {
            Log.i("IAP", "startQueryOrder success");
            if (orderResult.getReturnCode() == OrderStatusCode.ORDER_STATE_SUCCESS) {
                // 订单查询成功
                boolean orderSignCheck = PaySignUtil.checkSign(orderResult, Constant.getPublicKey());
                if (orderSignCheck) {
                    // 支付成功且验签成功,发放商品
                } else {
                    // 订单验签失败
                    Log.i("IAP", "orderResult checkSign  failed");
                }
            } else {
                // 订单查询失败
                Log.i("IAP", "getOrderDetail failed");
            }

        }
    });
}

private static OrderRequest createOrderRequest(String requestId) {
    String merchantId = Constant.getMerchantID();
    String time = String.valueOf(System.currentTimeMillis());
    OrderRequest orderRequest = new OrderRequest();
    orderRequest.setRequestId(requestId);
    orderRequest.setKeyType("1");
    orderRequest.setMerchantId(merchantId);
    orderRequest.setTime(time);

    //以上信息按照一定规则进行签名,建议CP在服务器端储存签名私钥,并在服务器端进行签名操作。
    Map<String, Object> params = new HashMap<>();
    // 商户ID
    params.put(HwPayConstant.KEY_MERCHANTID, merchantId);
    // 商户订单号,支付前生成用来唯一标识一次支付请求
    params.put(HwPayConstant.KEY_REQUESTID, requestId);
    // 固定值,请填写:1 
    params.put("keyType", "1");
    params.put("time", time);
    String noSign = PaySignUtil.getSignData(params);
    String sign = PaySignUtil.rsaSign(noSign);
    orderRequest.setSign(sign);
    return orderRequest;
}