手机单机游戏SDK集成指南

小米游戏中心手机单机游戏联运接入包括服务器端和客户端两部分,其中服务器端接入是可选的(取决于单机游戏是否有服务器端)。

下载最新SDK,请访问:手机游戏SDK错误代码技术FAQ运营FAQ

有任何技术问题,欢迎到游戏开发者论坛吐槽,寻求帮助。

赶快适配小米平板吧! 小米平板接入最佳实践

目前版本本文内容可以完全被更详细的《应用内支付接入指南》所替代,请阅读以上文档。

修订记录

1.单机游戏流程概述

单机游戏开发者在支付前要调用miLogin()函数进行登陆,登陆成功后才可以调用miUniPayOffline()函数进行支付,SDK会自己处理用户登录、注册、余额不足、已购买过等逻辑最后返回结果。对于开发者来说,只需要按以下操作引入代码即可完成单机游戏支付流程。 注意:在调用miLogin()函数之前,需要:1)在开发者站申请并绑定有效的appId/appKey、2)配置应用内支付模块。 2.SDK调用方法 2.1初始化

在小米开发者创建应用并获取 AppId 、AppKey和AppSecretKey,创建应用时packageName必须以“.mi”为后缀。

将 SDK 包中的 MiGameCenterSDKService.apk 放到应用工程的的 assets 目录下, SDK 的 jar 包放到工程的 libs 下,在 buildpath 中引用,然后对 SDK 进行初始化。

danjiSDK

注意: 需要检查下面的一致性,如果不一致会导致调用登录和其它 SDK 接口失败

1.游戏的包名是否与提供给小米后台数据配置的包名一致;

2.AppId与 AppKey是否与申请的一致。

然后在Application.onCreate中调用以下初始化方法

MiAppInfo appInfo = new MiAppInfo();
appInfo.setAppId("请申请获得");
appInfo.setAppKey("请申请获得"); 
appInfo.setAppType(MiGameType.offline); // 单机游戏
MiCommplatform.Init( this, appInfo );

SDK 所需要的权限

<uses-permission android:name="android.permission.GET_TASKS"  />
<uses-permission android:name="com.xiaomi.sdk.permission.PAYMENT" />

2.2 调用支付

2.2.1小米帐户登陆调用代码

MiCommplatform.getInstance().miLogin(context,newOnLoginProcessListener(){
    @Override
    public void finishLoginProcess( int code , MiAccountInfo arg1 ) {
        switch( code ) {
        case MiErrorCode.MI_XIAOMI_GAMECENTER_SUCCESS: 
        // 登陆成功
        //获取用户的登陆后的 UID(即用户唯一标识)
        long uid = arg1.getUid();
        //若没有登录返回 null
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_LOGIN_FAIL: 
        // 登陆失败
        break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_CANCEL :
        // 取消登录
        break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_ACTION_EXECUTED:
        // 登录操作正在进行中 
        break;
        default :
        // 登录失败
        break ;
        } 
    }
});

2.2.2 可消耗商品(例如:血瓶,法瓶等可重复购买的商品)

MiBuyInfoOffline offline = new MiBuyInfoOffline();
offline.setCpOrderId( UUID.randomUUID ().toString() );//订单号唯一(不为空) 
offline.setProductCode( "productCode" );//商品代码,开发者 申请获得(不为空) 
offline.setCount( 3 );//购买数量 (商品数量最大 9999,最小 1)(不为空)
MiCommplatform.getInstance ().miUniPayOffline( activiy, offline, 
    new OnPayProcessListener(){
    @Override
    public void finishPayProcess( int code ) { 
        switch( code ) {
        case MiErrorCode.MI_XIAOMI_GAMECENTER_SUCCESS: 
            //购买成功 ,请处理发货
            break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_PAY_CANCEL: 
            //取消购买
            break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_PAY_FAILURE:
            //购买失败 
            break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_ACTION_EXECUTED: 
            //操作正在执行
            break; 
        default:
            //购买失败 
            break;
        } 
    }
});

2.2.3非消耗商品(例如:关卡等不可重复购买的商品)

MiBuyInfoOffline offline = new MiBuyInfoOffline();
offline.setCpOrderId( UUID.randomUUID ().toString() );//订单号唯一(不为空) 
offline.setProductCode( “productCode ” );//商品代码,开发者申请获得(不为空) 
offline.setCount( 1 );//购买数量 (只能为 1)(不为空)
MiCommplatform.getInstance().miUniPayOffline(activiy, offline,
    new OnPayProcessListener(){
    @Override
    public void finishPayProcess( int code ) { 
        switch( code ) {
        case MiErrorCode.MI_XIAOMI_GAMECENTER_SUCCESS: 
        //购买成功,请处理发货
        break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_PAY_CANCEL: 
        //取消购买
        break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_PAY_FAILURE:
        //购买失败
        break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_PAY_REPEAT:
        //已购买过,无需购买,可直接使用
        break;
        case MiErrorCode.MI_XIAOMI_GAMECENTER_ERROR_ACTION_EXECUTED:
        //操作正在执行 break;
        default: 
        //购买失败
        break; 
        }
    } 
});

参数说明

参数名 用途 备注
cpOrderId 开发方订单号 20~100字符以内,开发方生成,要求不能重复,可根据开发方规则自行生成。
productCode 商品编号 规则要求:可由数字 0-9,字母 a-zA-Z 以及特殊字符”_”,”.”,”-”组成,长度8~40位,同一款 游戏内 productCode要求唯一,区分字母大小写。建议使用com.xiaomi.migc.xxx 的格式命名。调用时请不要弄混非消耗类商品和可消耗类商品的productCode。
count 购买数量 非消耗类商品,取值=1 可消耗类商品,取值>=1

3. 服务器接口

单机游戏如果需要,也可以提供接收订单支付结果通知的地址(可选)。

3.1订单支付结果通知接口

此接口由开发者负责开发并在游戏上线前提交给游戏平台进行配置。 在订单支付成功后,小米游戏平台服务器会将支付结果通知给开发者预先提供的服务器上。若开发者所提供的服务器地址不可用,在一定时间段内游戏平台服务器会按照周期进行轮询(前 10 次,每分钟通知 1 次;10次后每小时通知1次)。具体流程如下:

1.1.6.1.1

注:由于是异步通知模型,(3)和(4)不一定是按序号产生。因此(4)和(5)需要进行轮询处理或者使用接口进行支付结果查询。

3.2接口及参数说明:

接口地址:各开发者服务器的通知地址(提前申请,在游戏平台进行配置)

请求参数:

参数名称 重要性 说明
appId 必须 游戏ID
cpOrderId 必须 开发商订单ID
cpUserInfo 可选 开发商透传信息
uid 必须 用户ID
orderId 必须 游戏平台订单ID
orderStatus 必须 订单状态,TRADE_SUCCESS 代表成功
payFee 必须 支付金额,单位为分,即0.01 米币。
productCode 必须 商品代码
productName 必须 商品名称
productCount 必须 商品数量
payTime 必须 支付时间,格式 yyyy-MM-dd HH:mm:ss
orderConsumeType 可选 订单类型:
10:普通订单
11:直充直消订单
partnerGiftConsume 可选 使用游戏券金额 (如果订单使用游戏券则有,long型),如果有则参与签名
signature 必须 签名,签名方法见后面说明

返回参数说明:

参数名称 重要性 说明
errcode 必须 状态码,200 成功,
1506 cpOrderId 错误,
1515appId 错误,
1516 uid 错误,
1525 signature错误
errMsg 可选 错误信息

注意:对于同一个订单号的多次通知,开发商要自己保证只处理一次发货。

3.3接口格式说明:

请求方式:采用 HTTP GET 方式 输入参数: ?参数1=值1&参数2=值2&….&参数n=值n,如果遇到文本参数值,需要 根据情况对参数值做 UrlEncode。 返回参数:采用 json 格式,如:{“返回参数 1″:”返回值 1″,”返回参数 2″:”返回值 2″,….” 返回参数 n”:” 返回值 n”}

3.4签名方法说明:

3.4.1 生成带签名字符串 表中各参数按字母顺序排序(不包含 signature),如果第一个字母相同,按第二个 字母排序,依次类推。排序后拼接成 par1=val1&par2=val2&par3=val3 的格式,所生成 的字符串即为待签名的字符串。没有值的参数请不要参与签名。由于有些数据根据 HTTP 协议需求,需要进行 URLencoding,这样接收方才可以接收到正确的参数,但如果这个参数参 与签名,那么待签名字符串必须是字符串原值而非 URLencoding 的值。 3.4.2 签名算法 以 AppSecretKey 作为 key,使用 hmac-sha1 带密钥(secret)的哈希算法对代签字符串进行 签名计算。签名的结果由 16 进制表示。hmac-sha1 带密钥(secret)哈希算法的实现参考附录。

4.FAQ

4.1APK打包及发布

需要注意,SDK 包是以 jar 包提供给开发者,此 jar 包本身已为混淆状态,您在混淆 自己游戏的 APK 包时,需要在 proguard.cfg 里加入,以避免二次混淆.

-keep public class com.xiaomi.gamecenter.sdk.ui.actlayout.ViewAliPayWeb$PayObject{*;} -keepclasseswithmembers class * {
public (...);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

4.2服务器签名函数

Hmac-SHA1 算法 java 实现参考:

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

   public class HmacSHA1Encryption {
    private static final String MAC_NAME = "HmacSHA1" ;
    private static final String ENCODING = "UTF-8";
    /**
     * 使用 HMAC-SHA1 签名方法对对 encryptText 进行签名
     * @param encryptText 被签名的字符串
     * @param encryptKey 密钥
     * @return 返回被加密后的字符串
     * @throws Exception
     */
    public static String HmacSHA1Encrypt( String encryptText, String encryptKey ) throws Exception{
        byte[] data = encryptKey.getBytes( ENCODING );
        SecretKey secretKey = new SecretKeySpec( data, MAC_NAME );
        Mac mac = Mac.getInstance ( MAC_NAME );
        mac.init( secretKey );
        byte[] text = encryptText.getBytes( ENCODING );
        byte[] digest = mac.doFinal( text );
        StringBuilder sBuilder = bytesToHexString ( digest );
        return sBuilder.toString();
    }
    /**
     * 转换成Hex
     * @param bytesArray
     */
    public static StringBuilder bytesToHexString( byte[] bytesArray ){
        if ( bytesArray == null ){
            return null;
        }
        StringBuilder sBuilder = new StringBuilder();
        for ( byte b : bytesArray ){
            String hv = String.format("%02x", b);
            sBuilder.append( hv );
        }
        return sBuilder;
    }
    /**
     * 使用 HMAC-SHA1 签名方法对对 encryptText 进行签名
     * @param encryptData 被签名的字符串
     * @param encryptKey 密钥
     * @return 返回被加密后的字符串
     * @throws Exception
     */
     public static String HmacSHA1Encrypt( byte[] encryptData, String encryptKey ) throws Exception{
         byte[] data = encryptKey.getBytes( ENCODING );
         SecretKey secretKey = new SecretKeySpec( data, MAC_NAME );
         Mac mac = Mac.getInstance ( MAC_NAME );
         mac.init( secretKey );
         byte[] digest = mac.doFinal( encryptData );
         StringBuilder sBuilder = bytesToHexString ( digest );
         return sBuilder.toString();
     }
}

其他参考