应用内支付接入指南

如果您在集成过程中遇到任何问题,都可以添加QQ群:480416669,群中会有工程师解答您的问题。

支付SDK的推荐接入版本是4.6.11。

注:米聊社交游戏的接入SDK版本是4.1.0(beta),增加了米聊社交关系相关接口,非社交游戏及应用无需接入。

快速访问:下载SDK错误代码运营FAQSDK更新记录

有任何技术问题,欢迎到游戏开发者论坛吐槽,寻求帮助。 赶快适配小米平板吧! 小米平板接入最佳实践 强烈建议使用小米手机来测试。

若您是游戏开发者,以下信息需要注意:

在继续看文档之前,建议您先把随本文档一起分发的Demo程序安装到手机上,(MiSdkDemoForAppPayment.apk,上面SDK中已包含),这个程序完整演示了应用内支付SDK的工作流程,有助于您快速理解我们SDK支付的整个流程。如遇错误请在log里过滤tag然后在MiGameSDK里找一下errcode,得到errcode后请在本文档中的‘错误代码’里对照。

1.新版v4.6.11修订记录

1). 支付方式增加微信支付
2). 修复jar包版本低于3.0.0时,调用SDK service时会失败
3). 修复帐户相关bug
4). 修复米币支付过程中的bug
5). 修复isMiAccountLogin接口在某些情况下会崩溃
6). 修复在android 6.0中使用联通短信支付时候会崩溃
7). 修复在某些机型上无法调起支付页面

2.使用说明

本指南用于指导开发者在[小米游戏中心]及[小米应用商店]中接入游戏及应用内计费的技术流程和实现。本指南中:

3.接入前准备:提供了接入应用内支付的流程及需要准备的事项。

4.小米游戏:提供了小米游戏及各主要组件功能介绍。

5.接入流程与实现:提供了游戏及应用的接入流程和技术实现,适合研发工程师阅读。

6.常见问题:提供了游戏及应用的接入过程中遇到的绝大部分通用问题的解决方案及最佳实践,适合研发工程师阅读。

3.接入前准备

开发者可根据以下项做好接入前的准备工作: 1.准备小米手机、平板设备。设备规格:http://dev.xiaomi.com/doc/?p=246 2.注册开发者站帐号。流程:http://dev.xiaomi.com/doc/?p=90 3.在开发者站创建应用并申请AppId、AppKey、AppSecret,AppId、AppKey用来初始化SDK、AppSecret在服务器与服务器通信签名生成signature时使用,如果是游戏PackageName必须以“.mi”结尾。流程:http://dev.xiaomi.com/doc/?p=129 。 4.开发者站配置应用内支付的流程界面说明如下: (在申请完appId、appkey、appsecret之后在开发者站上配置应用内支付) 应用内支付详细流程: (1) 请在游戏里面创建

enter image description here

(2) 选择计费方式 —【PS:按计费代码的请看(3),按金额的请直接看(6)】

enter image description here

(3) 接计费代码的配置方式---点击添加

enter image description here

(4)填写好以下计费点点击保存 —【PS:1、‘不可重复计费’请谨慎填写,一旦选择一个小米账号只能购买一次。2、计费点代码按照后面给的格式自定义填写*】

enter image description here

(5)保存之后 如下的回调地址可填可不填,之后按保存 计费代码就配置好啦—【PS:单机有服务器可填,单机无服务器可不填,网游必须填】

enter image description here

(6)金额计费请将以下计费信息填好按保存 金额的就配置好啦—【PS:回调地址如果是ip形式 请在前面加上http:// 后面加上/ 如果是URL方式仅在前加上http:// 】

enter image description here

(7)用配置好的参数信息来接入SDK

5.在开发者站配置应用内支付,如果是游戏必须选择‘手机游戏’否则上线审核将会被拒绝,并配置商品代码,如果是按金额付费必须配置回调地址,按计费代码付费可选。

4.小米游戏

4.1小米游戏平台介绍

小米游戏平台是小米公司推出的面向所有小米手机、MIUI用户的游戏平台,整个游戏平台整合了MIUI系统、开发SDK、互联网网站、小米论坛等各方面优质资源,力求打造一个良好的安卓游戏生态系统。 小米游戏平台提供游戏下载、游戏搜索、游戏计费(支持单机游戏和网络游戏)等多种平台支撑能力,接入简单快捷,是众多游戏开发商最优的选择。

4.2小米账户

小米账户是小米公司为所有小米网、MIUI、米聊而开发的小米通行证账户系统,小米账户主要采取绑定手机号码或邮箱的方式,用户注册了小米账户同时会享受小米云服务、小米社区、米聊、游戏中心等多项优质免费服务。 小米账户即可使用小米ID登录,也可使用手机号码、邮箱作为用户名登录,十分方便快捷。 若没有项目账号,可以访问 https://account.xiaomi.com/ 进行注册。

4.3米币系统

米币是小米公司针对虚拟商品支付而发行的一种通用代币。具有可在小米公司所有虚拟物品平台流通的特性。用户只要通过小米账号进行充值(用人民币购买米币),就可以在小米公司旗下各移动产品或合作产品平台中支付使用。 1元=1米币,米币最小单位是1分,即可定价为0.01米币。 开发者将所在应用中用户消费的米币数额作为应用收入的结算依据。 目前给米币充值的方式包括:支付宝、财付通、银行卡、信用卡、话费充值卡、手机短信充值等。 用户可以在MIUI手机里内置的‘米币中心’进行充值,也可以访问 http://mibi.xiaomi.com/ 进行充值。

5.接入流程与实现

5.1快速开始

在继续看文档之前,建议您先把随本文档一起分发的Demo程序安装到手机或安卓平板上,(MiSdkDemoForAppPayment.apk),这个程序完整演示了小米应用支付的工作流程,有助于您快速理解我们SDK支付的整个流程。单机游戏或网络游戏也可以参考SampleForMiSdkOffline、SampleForMiSdkOnline 应用内支付SDK由两部分组成,其一是SDK_TY_3.2.0.jar(位于sdk_lib目录下),需要将此jar包放入游戏App的工程中,我们已将要使用的接口封装好,而另一部分是MiGameCenterSDKService.apk(位于sdk_service目录下),这个apk对jar提供了登录、支付等各方面的服务,此apk程序负责完成与小米服务器的数据交互,MIUI手机里内置了这个apk程序,但其他安卓手机也可以安装并运行此apk。 enter image description here

5.2产品设计及实现说明

5.2.1登录及支付

5.2.1.1登录流程概述

1)单机游戏:单机游戏开发者在支付前要调用miLogin()函数进行登陆,登陆成功后才可以调用miUniPay()函数进行支付
2)网络游戏:网络游戏开发者需要管理用户登录,并且需要记录用户登录状态,用户每次启动游戏时,必须调用miLogin()来判断用户会话是否超时,在游戏过程中如果需要充值可以调用miUniPay()
3)应用:用户每次启动应用时,必须调用miLogin()来判断用户会话是否超时,在应用运行过程中,如果需要充值可以调用miUniPay()。 登录实现请参考5.2.2.2小米账户登录调用代码,注意:调取miLogin()登录前需要:1)在开发者站申请并绑定有效的appId/appKey、2)配置应用内支付模块,否则会报告 -102 错误。

用户开户及登录流程 enter image description here 注意: 1)UID不是小米ID,但与小米ID有对照关系;
2)请开发者一定要使用这个UID作为用户标识,不要使用本机的IMEI、IMSI或Mac地址,我们的审核团队会严格测试。
3)非社交游戏(米聊游戏)请在demo中忽略米聊登陆。

5.2.1.2支付流程概述

计费方式: 1)按金额付费:必须配置回调地址,推荐网游使用 2)按计费代码付费:支持可消耗类商品代码与不可消耗类商品代码,回调地址配置可选,推荐单机和应用使用。

按金额付费流程 enter image description here

5.2.2 SDK调用方法

对于开发者来说只需要引入以下代码即可完成应用内支付

5.2.2.1初始化

在小米开发者创建应用并获取 AppId 、AppKey和AppSecret(注意AppSecret是用于服务器端签名,不要在客户端里使用),创建应用时如果是游戏PackageName必须以“.mi”为后缀。 将 zip 包中sdk_service目录下的 MiGameCenterSDKService.apk 放到应用工程的的 assets 目录下, zip sdk_lib目录下SDK_TY_x.x.x.jar 包放到工程的 libs 下,在 buildpath 中引用,然后对 SDK 进行初始化。 enter image description here

注意: 需要检查下面的一致性,如果不一致会导致调用登录和其它 SDK 接口失败 1.在小米开发者站后台配置好AppId/AppKey和包名(包名必须以.mi结尾); 2.检查AndroidManifest.xml里面所设置的package必须与开发者站配置的一致; 3.必须配置应用内消费,否则无法成功登陆(会返回1515); 然后在Application.onCreate中调用以下初始化方法

MiAppInfo appInfo = new MiAppInfo();
appInfo.setAppId("请申请获得");
appInfo.setAppKey("请申请获得");
MiCommplatform.Init( this, appInfo );

SDK所需要的权限

< uses-permission android:name="android.permission.GET_TASKS"  />//必须添加
< uses-permission android:name="com.xiaomi.sdk.permission.PAYMENT" />//必须添加
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> //新增接口    isMiAccountLogin和下载付费相关功能需要,如果不使用此功能不需要加此权限

5.2.2.2小米账户登录调用代码

MiCommplatform.getInstance().miLogin( context, 
                new OnLoginProcessListener()
{
        @Override
        public void finishLoginProcess( int code ,MiAccountInfo arg1)
        {
            switch( code )
            {
                  case MiErrorCode.MI_XIAOMI_PAYMENT_SUCCESS:
                           // 登陆成功
       //获取用户的登陆后的UID(即用户唯一标识)
       long uid = arg1.getUid();
       **/\**以下为获取session并校验流程,如果是网络游戏必须校验,如果是单机游戏或应用可选\**/**
       //获取用户的登陆的Session(请参考[5.3.3流程校验Session有效性](#8))
       String session = arg1.getSessionId();
       //请开发者完成将uid和session提交给开发者自己服务器进行session验证
                        break;
                case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_LOGIN_FAIL:
                        // 登陆失败
                        break;
                case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_CANCEL:
                          // 取消登录
                          break;
  case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_ACTION_EXECUTED: 
      //登录操作正在进行中
      break;        
  default:
                        // 登录失败
              break;
        }
    }
} );

可以通过实现OnLoginProcessListener接口来捕获登录结果。

5.2.2.3按金额付费调用

MiBuyInfo miBuyInfo= new MiBuyInfo();
miBuyInfo.setCpOrderId(UUID.randomUUID().toString());//订单号唯一(不为空)
miBuyInfo.setCpUserInfo( “cpUserInfo” ); //此参数在用户支付成功后会透传给CP的服务器
miBuyInfo.setAmount( 10 ); //必须是大于1的整数,10代表10米币,即10元人民币(不为空)
**//用户信息,网游必须设置、单机游戏或应用可选**
Bundle mBundle = new Bundle();
mBundle.putString( GameInfoField.GAME_USER_BALANCE, "1000" );   //用户余额
mBundle.putString( GameInfoField.GAME_USER_GAMER_VIP, "vip0" );  //vip等级
mBundle.putString( GameInfoField.GAME_USER_LV, "20" );           //角色等级
mBundle.putString( GameInfoField.GAME_USER_PARTY_NAME, "猎人" );  //工会,帮派
mBundle.putString( GameInfoField.GAME_USER_ROLE_NAME, "meteor" ); //角色名称
mBundle.putString( GameInfoField.GAME_USER_ROLEID, "123456" );    //角色id
mBundle.putString( GameInfoField.GAME_USER_SERVER_NAME, "峡谷" );  //所在服务器
miBuyInfo.setExtraInfo( mBundle ); //设置用户信息
MiCommplatform.getInstance().miUniPay(activity, miBuyInfo, 
new OnPayProcessListener()
{
@Override
    public void finishPayProcess( int code ) {
        switch( code ) {
        case MiErrorCode.MI_XIAOMI_PAYMENT_SUCCESS:
       //购买成功
            break;
        case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_PAY_CANCEL:
            //取消购买
               break;
        case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_PAY_FAILURE:
            //购买失败
               break;       
case  MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_ACTION_EXECUTED:  
   //操作正在进行中
   break;       
        default:
              //购买失败
            break;
        }
    }
});

参数说明:

参数名称 用途 备注
cpOrderId 开发方订单号 必填参数。20~100字符以内,要求必须由开发者的业务服务器生成,因订单支付成功后游戏平台服务器会直接将支付结果通知给开发者的业务服务器,通知参数的cpOrderId是重要信息。
cpUserInfo 给网游透传参数 必填参数。用于透传用户信息,当用户支付成功后我们会将此参数透传给开发者业务服务器(不能为null或“”)
amount 金额 必填参数。且数量是int型,即最少只能购买1米币对应的虚拟币.

5.2.2.4按计费代码购买可消耗商品(例如:血瓶,法瓶等可重复购买的商品)

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

5.2.2.5 按计费代码购买非可消耗商品(例如:关卡等不可重复购买的商品)

MiBuyInfo miBuyInfo = new MiBuyInfo();
miBuyInfo.setCpOrderId( UUID.randomUUID().toString() );//订单号唯一(不为空)
miBuyInfo.setProductCode( “productCode” );//商品代码,开发者申请获得(不为空)
miBuyInfo.setCount( 1 );//购买数量(只能为1)(不为空)
MiCommplatform.getInstance().miUniPay(activiy, miBuyInfo, 
new OnPayProcessListener()
{
@Override
    public void finishPayProcess( int code ) {
        switch( code ) {
        case MiErrorCode.MI_XIAOMI_PAYMENT_SUCCESS:
         //购买成功,请处理发货
                 break;
        case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_PAY_CANCEL:
                //取消购买
                break;
        case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_PAY_FAILURE:
                //购买失败
                break;
        case MiErrorCode.MI_XIAOMI_PAYMENT_ERROR_PAY_REPEAT:
                //已购买过,无需购买,可直接使用
                break;
case MiErrorCode.MI_XIAOMI_PAYMENT_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

5.2.2.6 查询登录状态(此为耗时操作接口,请勿在UI线程中调用)

boolean islogin = MiCommplatform.getInstance().isMiAccountLogin();
If(islogin)
{
  //已登录
}else{
  //未登陆
}

5.2.2.7 退出接口 (游戏在执行退出操作时,需调用此接口,待接口返回结果后再执行退出游戏操作)

MiCommplatform.getInstance().miAppExit(MainActivity.this,new OnExitListner() {          
                @Override
                public void onExit(int code) {
                    Log.e("errorCode===",code+"");
                    if(code==MiErrorCode.MI_XIAOMI_EXIT){
                        //执行退出的一些操作
                    }
                }
            });

5.3服务器接口 1)按金额付费,以小米应用支付服务器通知的结果为准为用户进行虚拟币充值。所以必须提供接收订单支付结果通知的地址(必选)。 2)按计费代码计费,也可以提供接收订单支付结果通知的地址(可选)。 3)服务器代码示例下载地址。 包含签名实现,登录验证,订单查询,回调接口,有php和java的示例。

5.3.1订单支付结果通知接口

5.3.1.1流程说明

此接口由开发者负责开发并在开发者后台进行配置。在订单支付成功后,小米应用支付服务器会将支付结果通知给开发者预先提供的服务器上。若开发者所提供的服务器地址不可用,在一定时间段内游戏平台服务器会按照周期进行轮询(前10次,每分钟通知1次;10次后每小时通知1次)。 具体流程如下: enter image description here 注:由于是异步通知模型,(3)和(4)不一定是按序号产生。因此(4)和(5)需要进行轮询处理或者使用接口进行支付结果查询。 相比后面提到的开发者主动查询订单的模式,我们推荐使用此模式。

5.3.1.2接口及参数说明

1)接口地址:各开发者服务器的通知地址(提前申请,在小米开发者站进行配置) 2)请求方法:GET 请求参数说明:

参数名称 重要性 说明
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 必须 签名,签名方法见后面说明

注意:如果开发者允许使用游戏礼券则必须使用partnerGiftConsume参数,否则使用游戏礼券的消费订单会出现掉单情况。

例如:

http://ccc.com/notify.do?appId=2882303761517239138&cpOrderId=9786bffc-996d-4553-aa33-f7e92c0b29d5&orderConsumeType=10&orderId=21140990160359583390&orderStatus=TRADE_SUCCESS&payFee=1&payTime=2014-09-05%2015:20:27&productCode=com.demo_1&productCount=1&productName=%E9%93%B6%E5%AD%901%E4%B8%A4&uid=100010&signature=1388720d978021c20aa885d9b3e1b70cec751496

返回参数说明:

参数名称 重要性 说明
errcode 必须 状态码,200 成功1506 cpOrderId 错误1515 appId 错误1516 uid 错误1525 signature 错误3515 订单信息不一致,用于和 CP 的订单校验
errMsg 可选 错误信息
注意:对于同一个订单号的多次通知,开发商要自己保证只处理一次发货。

例如:

{“errcode”:200}

服务器IP地址

42.62.48.246

223.202.68.237

42.62.103.0/26(42.62.103.1 ~ 42.62.103.62)

120.134.34.0/26(120.134.34.1 ~ 120.134.34.62)

58.83.200.0/24​(58.83.200.0~58.83.200.255)

请开发商服务器开发人员加到ip白名单内,以免因为ip限制造成回调不成功。

5.3.2主动查询订单支付状态接口

此接口由小米为开发者提供。

5.3.2.1流程说明 enter image description here

5.3.2.2接口及参数说明:

1)接口地址:http://mis.migc.xiaomi.com/api/biz/service/queryOrder.do 2)请求方法:GET 请求参数说明:

参数名称 重要性 说明
appId 必须 游戏ID
cpOrderId 必须 开发商订单ID
uid 必须 用户ID
signature 必须 签名,签名方法见后面说明

例如:

http://mis.migc.xiaomi.com/api/biz/service/queryOrder.do?appId=2882303761517239138&cpOrderId=9786bffc-996d-4553-aa33-f7e92c0b29d5&uid=100010&signature=e91d68b864b272b6ec03f35ee5a640423d01a0a1

正确返回参数说明:

参数名称 重要性 说明
appId 必须 游戏ID
cpOrderId 必须 开发商订单ID
cpUserInfo 可选 开发商透传信息
uid 必须 用户ID
orderId 必须 游戏平台订单ID
orderStatus 必须 订单状态,TRADE_SUCCESS 代表成功WAIT_BUYER_PAY 代表未支付REPEAT_PURCHASE 订购关系已经存在
payFee 必须 支付金额,单位为分,即0.01 米币。
productCode 必须 商品代码
productName 必须 商品名称
productCount 必须 商品数量
payTime 必须 支付时间,格式 yyyy-MM-dd HH:mm:ss
orderConsumeType 可选 订单类型:10:普通订单11:直充直消订单
signature 必须 签名,签名方法见后面说明
例如:
{
"signature": "eb30240cff8c66f856ec0e48354aa670b8cf037f",
"uid": "100010",
"appId": 2882303761517239300,
"cpOrderId": "9786bffc-996d-4553-aa33-f7e92c0b29d5",
"productCode": "com.demo_1",
"orderStatus": "TRADE_SUCCESS",
"productName": "%E9%93%B6%E5%AD%901%E4%B8%A4",
"productCount": 1,
"orderConsumeType": "10",
"orderId": "21140990160359583390",
"payFee": 1,
"payTime": "2014-09-05 15:20:27"
}

错误返回参数说明:

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

5.3.3用户session验证接口

此接口由小米应用支付为开发者提供,用于验证登录账户的有效性。 注意:用户的唯一标识是通过SDK获得的uid,而不是Session,Session用于校验登录验证的有效性,必须经过SDK、游戏中心服务器、开发者服务器进行三方验证,如果Session失效,需要重新调用miLogin()进行登录。

5.3.3.1流程说明 enter image description here 5.3.3.2接口及参数说明

1)接口地址:http://mis.migc.xiaomi.com/api/biz/service/loginvalidate 2)请求方法:POST 请求参数说明:

参数名称 重要性 说明
appId 必须 游戏ID
session 必须 用户sessionID
uid 必须 用户ID
signature 必须 签名,签名方法见后面说明
例如:
http://mis.migc.xiaomi.com/api/biz/service/loginvalidate
appId=2882303761517239138&session=1nlfxuAGmZk9IR2L&uid=100010&signature=b560b14efb18ee2eb8f85e51c5f7c11f697abcfc

返回参数说明:

参数名称 重要性 说明
errcode 必须 状态码200 验证正确1515 appId 错误1516 uid 错误1520 session 错误1525 signature 错误
errMsg 可选 错误信息
adult 可选 用户实名标识。 407:实名认证通过,年龄大于18岁;408:实名认证通过,年龄小于18岁;409:未进行实名认证
例如:
{
“errcode”: 200,“adult”: 409
}

5.3.4接口格式说明

输入参数: ?参数1=值1&参数2=值2&….&参数n=值n,如果遇到文本参数值,需要根据情况对参数值做 UrlEncode。 返回参数:在回调的时候,是 http get方式发送请求 ,参数拼接在url后面,你们的服务器返回的数据 要求是json格式的,如:{“errcode”:200}这种格式

5.3.5 signature签名方法说明

1、生成带签名字符串 表中各参数按字母顺序排序(不包含signature),如果第一个字母相同,按第二个字母排序,依次类推。排序后拼接成par1=val1&par2=val2&par3=val3的格式,所生成的字符串即为待签名的字符串。没有值的参数请不要参与签名。由于有些数据根据HTTP协议需求,需要进行URLencoding,这样接收方才可以接收到正确的参数,但如果这个参数参与签名,那么待签名字符串必须是字符串原值而非URLencoding的值。 例如: 在订单通知接口收到的回调信息如下:

appId=2882303761517239138&cpOrderId=9786bffc-996d-4553-aa33-f7e92c0b29d5&orderConsumeType=10&orderId=21140990160359583390&orderStatus=TRADE_SUCCESS&payFee=1&payTime=2014-09-05%2015:20:27&productCode=com.demo_1&productCount=1&productName=%E9%93%B6%E5%AD%901%E4%B8%A4&uid=100010&signature=1388720d978021c20aa885d9b3e1b70cec751496

这时候需要对每个参数的值进行URLdecode,decode后的字符如下:

appId=2882303761517239138&cpOrderId=9786bffc-996d-4553-aa33-f7e92c0b29d5&orderConsumeType=10&orderId=21140990160359583390&orderStatus=TRADE_SUCCESS&payFee=1&payTime=2014-09-05 15:20:27&productCode=com.demo_1&productCount=1&productName=银子1两&uid=100010&signature=1388720d978021c20aa885d9b3e1b70cec751496

而需要进行签名的字符串为:

appId=2882303761517239138&cpOrderId=9786bffc-996d-4553-aa33-f7e92c0b29d5&orderConsumeType=10&orderId=21140990160359583390&orderStatus=TRADE_SUCCESS&payFee=1&payTime=2014-09-05 15:20:27&productCode=com.demo_1&productCount=1&productName=银子1两&uid=100010

2、签名算法 以AppSecret作为key,使用hmac-sha1带密钥(secret)的哈希算法对代签字符串进行签名计算。签名的结果由16进制表示。hmac-sha1 带密钥(secret)哈希算法的实现请参考6.2服务器签名函数

6.常见问题

6.1 APK打包及发布

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

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 *;
}

6.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 mac = Mac.getInstance( MAC_NAME );
        // 用给定密钥初始化 Mac 对象
        mac.init( secretKey );
        byte[] text = encryptText.getBytes( ENCODING );
        // 完成 Mac 操作
        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 mac = Mac.getInstance( MAC_NAME );
            // 用给定密钥初始化 Mac 对象
        mac.init( secretKey );
            // 完成 Mac 操作
        byte[] digest = mac.doFinal( encryptData );
        StringBuilder sBuilder = bytesToHexString( digest );
        return sBuilder.toString();
    }
}