小米微信WAP支付SDK接入指南

一、 接入前准备

1、下载小米微信wap支付sdk_v1.3.1版本

2、准备小米手机、平板设备。设备规格:http://dev.xiaomi.com/doc/?p=246

3、注册开发者站帐号。流程:http://dev.xiaomi.com/doc/?p=90

4、在开发者站创建应用并申请AppId、AppKey、AppSecret

enter image description here

enter image description here

5、 配置应用内支付,计费方式分为两种“金额购买”和“计费点购买”,下面以计费点购买为例:

enter image description here

6、添加自己服务器的回调地址和计费单位,并保存

enter image description here

二、程序接入

1、 添加jar包

  将libs中的jar包添加至自己的工程中。

2、AndroidManifest.xml清单文件修改

   清单中添加如下:

    <activity android:name="com.xiaomi.gamecenter.wxwap.HyWxWappayActivity"
     android:theme="@android:style/Theme.Translucent.NoTitleBar"
     android:exported="true"
     android:configChanges="orientation|screenSize"         />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name=“android.permission.READ_PHONE_STATE”/>

3、调用方法: 在Application中初始化,传入appid和appkey:

public class HyApplication extends Application {
@Override
public void onCreate() {
    super.onCreate();
    //appid和appkey
    HyWxWapPay.init(this,"2882303761517****35","5151740****35");
}

}

4、 调用方式:

 public void onClick(View v) {
    switch (v.getId()){
        case R.id.btn_wap:
            //购买可消耗计费点 请一定确认是可消耗计费点
            /**
             * 整个调用必须在主线程中进行,回调默认也在主线程中
             * 调用前请确认自己的游戏申请的时候是  “计费点计费方式”,还是“金额计费方式”
             * 金额方式只能以第三种方式调用,计费点方式只能以前两种方式调用,不然“创建订单失败”
             * **/


            WxRepeatPurchase purchase =new WxRepeatPurchase();
            purchase.setCpOrderId(System.currentTimeMillis()+"");//设置订单号
            purchase.setChargeCode("a1");//计费点名称设置
            //purchase.setAmout("2");//购买数量,不设置的话默认为1
            HyWxWapPay.getInstance().pay(this, purchase, new PayResultCallback() {
                @Override
                public void onError(int code, String msg) {
                    Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
                }

                @Override
                public void onSuccess(String cpOrderId) {
                    Toast.makeText(MainActivity.this, "支付成功", Toast.LENGTH_LONG).show();
                }
            });
            break;
        case R.id.btn_wap2:
            //购买不可消耗计费点,请一定确保是不可消耗计费点

            WxUnrepeatPurchase purchase1 =new WxUnrepeatPurchase();
            purchase1.setCpOrderId(System.currentTimeMillis()+"");
            purchase1.setChargeCode("a2");//设置计费点
            HyWxWapPay.getInstance().pay(this,purchase1, new PayResultCallback() {
                 @Override
                 public void onError(int code, String msg) {
                     Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
                 }

                 @Override
                 public void onSuccess(String cpOrderId) {
                     Toast.makeText(MainActivity.this, "支付成功", Toast.LENGTH_LONG).show();
                 }
             });
            break;
        case R.id.btn_wap3:
            //购买金额 最小值100,必须是100倍数,整形值,请不要加小数点,不然创建订单失败
            WxFeePurchase purchase3 =new WxFeePurchase();
            purchase3.setCpOrderId(System.currentTimeMillis()+"");
            purchase3.setFeeValue("100");//设置金额
            HyWxWapPay.getInstance().pay(this,purchase3, new PayResultCallback() {
                 @Override
                 public void onError(int code, String msg) {
                     Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
                 }

                 @Override
                 public void onSuccess(String cpOrderId) {
                     Toast.makeText(MainActivity.this, "支付成功", Toast.LENGTH_LONG).show();
                 }
             });
            break;
    }
}

三、常见问题:

 1、忘记在application中初始化,或者忘了写application:name
 2、计费代码填写错误,比如创建了可消耗计费代码类,却填写不可消耗的计费代码

四、服务器接口:

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

4.1订单支付结果通知接口

4.1.1流程说明

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

4.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)

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

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

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

4.2.1流程说明 enter image description here

4.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 可选 错误信息

4.3用户session验证接口

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

4.3.1流程说明 enter image description here 4.3.2接口及参数说明

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

参数名称 重要性 说明
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
}

4.4接口格式说明

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

4.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服务器签名函数

五、常见问题*

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

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