TV&盒子游戏内支付接入指南

快速访问:


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

在继续看文档之前,建议您先把随本文档一起分发的Demo程序安装到盒子或TV上,(MiSdkDemoForAppPayment.apk),这个程序完整演示了小米支付的工作流程,有助于您快速理解我们SDK支付的整个流程。单机游戏或网络游戏也可以参考SampleForMiSdkOffline、SampleForMiSdkOnline


一、接入前准备

1、小米游戏介绍

1.1 小米游戏平台介绍

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

————————————————————————

1.2 小米账户介绍

小米账户是小米公司为所有小米网、MIUI、米聊而开发的小米通行证账户系统,目前注册用户已经超过7000万,小米账户主要采取绑定手机号码或邮箱的方式,用户注册了小米账户同时会享受网盘、小米云存储、语音助理、米聊、游戏中心等多项优质免费服务。

小米账户即可使用小米ID登录,也可使用手机号码、邮箱作为用户名登录,十分方便快捷。

若没有项目账号,可以访问 https://account.xiaomi.com/ 进行注册。

————————————————————————

1.3米币系统介绍

米币是小米公司针对虚拟商品支付而发行的一种通用代币。具有可在小米公司所有虚拟物品平台流通的特性。用户只要通过小米账号进行充值(用人民币购买米币),就可以在小米公司旗下各移动产品或合作产品平台中支付使用。

1元=1米币,米币最小单位是1分,即可定价为0.01米币。

开发者将所在应用中用户消费的米币数额作为应用收入的结算依据。

目前给米币充值的方式包括:支付宝、财付通、银行卡、信用卡、话费充值卡、手机短信充值等。 用户可以在MIUI手机里内置的‘米币中心’进行充值,也可以访问 http://mibi.xiaomi.com/ 进行充值。

2、配置商品代码

2.1 在小米开发者站注册帐号,地址:http://dev.xiaomi.com/账号注册流程详解

2.2 在电视应用中创建应用:

enter image description here

2.3 获得AppId、AppKey、AppSecret,并配置支付的商品:

注意:AppId、AppKey用来初始化SDK、AppSecret在服务器与服务器通信签名生成signature时使用,游戏PackageName必须以“.mitv”结尾。

enter image description here

添加商品,保存即可 注意:如果是按金额付费必须配置回调地址,按计费代码付费可选。

enter image description here enter image description here

3、联运合同签署

开发者可根据以下项完成计费SDK的合同签署(接入与合同签署可以同步进行):

合同模板下载:电视盒子游戏联运合同模板V2.0 合同签署联系人QQ:911931627;E-mail:chenxiaocong@xiaomi.com


二、接入流程与实现

1、快速开始

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


2、产品设计及实现说明

2.1 登录及支付

2.1.1 登录流程概述

1)单机游戏:单机游戏开发者在支付前要调用miLogin()函数进行登陆,登陆成功后才可以调用miUniPay()函数进行支付 2)网络游戏:网络游戏开发者需要管理用户登录,并且需要记录用户登录状态,用户每次启动游戏时,必须调用miLogin()来判断用户会话是否超时,在游戏过程中如果需要充值可以调用miUniPay() 3)应用:用户每次启动应用时,必须调用miLogin()来判断用户会话是否超时,在应用运行过程中,如果需要充值可以调用miUniPay()。 4)登录实现请参考『3.2.2.2小米账户登录调用代码』,注意:调取miLogin()登录前需要:

  • 在开发者站申请并绑定有效的appId/appKey;
  • 配置应用内支付模块,否则会报告 -102 错误。

5)用户开户及登录流程

enter image description here

6)注意:

  • UID不是小米ID,但与小米ID有对照关系;
  • 请开发者一定要使用这个UID作为用户标识,不要使用本机的IMEI、IMSI或Mac地址,我们的审核团队会严格测试。

2.1.2 支付流程概述

1)计费方式:

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

2)按金额付费流程

enter image description here

2.2 SDK调用方法

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

2.2.1初始化

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

enter image description here

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

  • 在小米开发者站后台配置好AppId/AppKey和包名(包名必须以.mitv结尾);
  • 检查AndroidManifest.xml里面所设置的package必须与开发者站配置的一致;
  • 必须配置应用内消费,否则无法成功登陆(会返回1515);

3)然后在Application.onCreate中调用以下初始化方法 ———————————————————————— MiAppInfo appInfo = new MiAppInfo(); appInfo.setAppId("请申请获得"); appInfo.setAppKey("请申请获得"); MiCommplatform.Init( this, appInfo ); ————————————————————————

4)SDK所需要的权限 ———————————————————————— < uses-permission android:name="android.permission.GET_TASKS" /> < uses-permission android:name="com.xiaomi.sdk.permission.PAYMENT" /> < uses-permission android:name="android.permission.READ_PHONE_STATE" /> ————————————————————————

2.2.2 小米账户登录调用代码

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

> 
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(请参考『2.3.3流程校验Session有效性』)
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;
}
}
} );

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米币对应的虚拟币.

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

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

2.3 服务器接口

2.3.1 订单支付结果通知接口

2.3.1.1 流程说明

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

enter image description here

注:由于是异步通知模型,(3)和(4)不一定是按序号产生。因此(4)和(5)需要进行轮询处理或者使用接口进行支付结果查询。 相比后面提到的开发者主动查询订单的模式,我们推荐使用此模式。

2.3.1.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| 必须 |签名,签名方法见后面说明|

注意:如果开发者允许使用游戏礼券则必须使用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)

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

2.3.2 订单支付结果通知接口

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

2.3.2.1流程说明

enter image description here

2.3.2.2接口及参数说明:

————————————————————————

请求参数说明:

参数名称 重要性 说明
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 可选 错误信息

2.3.3用户session验证接口

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

2.3.3.1流程说明

enter image description here

2.3.3.2接口及参数说明

————————————————————————

请求参数说明:

参数名称 重要性 说明
appId 必须 游戏ID
session 必须 用户sessionID
uid 必须 用户ID
signature 必须 签名,签名方法见后面说明

例如: http://mis.migc.xiaomi.com/api/biz/service/verifySession.do?appId=2882303761517239138&session=1nlfxuAGmZk9IR2L&uid=100010&signature=b560b14efb18ee2eb8f85e51c5f7c11f697abcfc

————————————————————————

返回参数说明:

参数名称 重要性 说明
errcode 必须 状态码200 验证正确1515 appId 错误1516 uid 错误1520 session 错误1525 signature 错误
errMsg 可选 错误信息

例如: { “errcode”: 200 }

2.3.4 接口格式说明

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

2.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)哈希算法的实现请参考『3.2服务器签名函数』。


3、常见问题

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

3.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();
    }
}