《插件自助开发——支付系统》

支付系统包含支付相关的接口,需要实现框架接口InterfaceIAP,并通过框架的IAPWrapper类实现获取点单、回调等。

构造函数和初始化

支付系统中,单机版的类一般命名为"IAP+插件名", 网游版的类一般命名为"IAPOnline+插件名",构造函数参数为Context,是主 Activity 的Context,很多 SDK接口需要该参数。在构造函数中实现初始化SDK等。
百度游戏支付类(网游版)的构造函数如下:

  public IAPOnlineBDYouxi(Context context)
{
    mActivity = (Activity)context;
    mIAPOnline = this;
    configDeveloperInfo(PluginHelper.getParamsInfo());
}

说明:

客户端打包时,通过插件 config.xml中参数列表,显示并配置的参数通过框架接口PluginHelper.getParamsInfo获取。
支付系统的初始化回调有两种返回值:

InterfaceIAP

支付类插件需要实现接口InterfaceIAP,在InterfaceIAP接口中,有如下函数:

public void payForProduct(Hashtable<String, String> paramHashtable);//支付,购买商品        
public String getOrderId();                      //获取订单 id    
public abstract String getPluginId();            //获取插件 ID     
public void setDebugMode(boolean paramBoolean);  //设置调试模式,已废弃     
public String getSDKVersion();                   //获取SDK 版本号     
public String getPluginVersion();                //获取插件版本号

payForProduct(Hashtable paramHashtable)

支付流程描述

下载页面
1)游戏客户端APP调用AnySDK框架支付接口请求订单号
这个过程可以拆分为3步:1.用户调用 AnySDK框架的 payForProduct 函数;2.AnySDK框架调用插件的 payForProduct 函数; 3.插件 payForProduct 函数中调用框架函数IAPWrapper.getPayOrderId请求订单。
2)AnySDK框架获取订单号
AnySDK框架处理网络,插件内通过IAPWrapper.getPayOrderId接口注册的监听获取网络返回数据。
3)AnySDK框架通过 SDK 的客户端支付接口向SDK服务器请求支付
插件处理:
4)支付成功后,渠道平台服务器同步通知AnySDK框架(游戏客户端)
SDK服务端处理:
5)渠道平台服务器会异步通知AnySDK服务器
插件服务端处理:
6)AnySDK服务器响应渠道平台服务器
插件服务端处理:
7)AnySDK服务器将支付结果通知到游戏服务器
AnySDK服务端处理
8)游戏服务器响应AnySDK支付通知
游戏服务端处理
9)游戏服务器验证支付通知并发放道具
游戏服务端处理

用户参数

接口中参数paramHashtable是用户调用支付时传入的参数,参数字段如下:

参数 是否必传 参数说明
Product_Id Y 商品id(联想、七匣子、酷派等商品id要与在渠道后台配置的商品id一致)
Product_Name Y 商品名
Product_Price Y 商品价格(整型,元)
Product_Count Y 商品份数(除非游戏需要支持一次购买多份商品,否则传1即可)
Role_Id Y 游戏角色id
Role_Name Y 游戏角色名
Role_Grade Y 游戏角色等级
Role_Balance Y Y 用户游戏内虚拟币余额,如元宝,金币,符石
Server_Id Y 服务器id,若无填“1”
EXT N 扩展字段

检查条件

在请求订单之前,需要检查:
1. 初始化是否成功:如果初始化不成功,支付流程结束,回调PAYRESULT_FAIL。
2. 网络是否链接:调用框架接口PluginHelper.networkReachable判断,返回值为 false 时表示网络不可用,支付流程结束,回调PAYRESULT_PRODUCTIONINFOR_INCOMPLETE。
3.\是否登录:大部分渠道类(包括用户和支付等) SDK 要求 支付前用户必须已登录,所以对于这类 SDK,支付前必须判定登录状态:如果已登录,请求订单;如果未登录,先调用登录,登录成功后,请求订单,登录失败,支付流程结束,回调PAYRESULT_FAIL。
360的检查代码如下:

@Override
  public void payForProduct(Hashtable<String, String> info) {
    LogD("payForProduct(" + info.toString() + ")invoked!");
    curProductInfo = info;
    PluginWrapper.runOnMainThread(new Runnable() {
        @Override
        public void run() {
            if (!QH360Wrapper.getInstance().SDKInited()) {
                payResult(IAPWrapper.PAYRESULT_FAIL, "inited fialed!");
                return;
            }
            if (!PluginHelper.networkReachable((Activity)mContext)) {
                payResult(IAPWrapper.PAYRESULT_NETWORK_ERROR, "Network not available!");
                return;
            }

            if (!QH360Wrapper.getInstance().isLogined()) {
                userLogin();
            } else {
                getOrderId(curProductInfo);
            }
        }
    });
}

public void userLogin() {
    QH360Wrapper.getInstance().userLogin(mContext, ProtocolConfigs.FUNC_CODE_LOGIN, new ILoginCallback() {
        @Override
        public void onSuccessed(int code, String msg) {
            getOrderId(curProductInfo);
        }

        @Override
        public void onFailed(int code, String msg) {
            if (null == msg) {
                payResult(IAPWrapper.PAYRESULT_FAIL, "login failed");
            } else {
                payResult(IAPWrapper.PAYRESULT_FAIL, msg);
            }
        }
    });
}

获取订单

框架获取订单接口:

public static void getPayOrderId(Context paramContext, Hashtable<String, String> paramHashtable, SdkHttpListener paramSdkHttpListener)

参数:
* paramContext:主 Activity 的 Context * paramHashtable:订单参数,单独参数有三部分:固定参数(用户传入参数),插件 ID,插件客户端与插件服务端自定义参数。 固定参数是从用户传入的参数中获取对应值,字段名如下:

参数 是否必传 参数说明 对应传入值
product_id Y 商品id Product_Id
product_name Y 商品名 Product_Name
money Y 支付总价格 Product_Price*Product_Count
coin_num Y 商品份数 Product_Count
game_user_id Y 游戏角色id Role_Id
game_server_id Y 服务器id Server_Id
user_id Y 用户 ID 有用户系统时为用户的uid,无用户系统时为传入的Role_Id
private_data N 扩展字段 EXT

注意:用户传入参数与订单参数的转换关系固定,所以框架提供了从传入参数转换为订单参数的接口如下,其中,paramHashtable为用户传入参数,paramString为user_id的值。

public static Hashtable<String, String> getOrderInfo(Hashtable<String, String> paramHashtable, String paramString)

插件 ID:字段名为 plugin_id,值为插件ID,是必传字段。
自定义参数:自定义参数是某些特殊情况下,插件客户端与插件服务端交互时需要的特殊字段,根据具体需求而定。下面的酷派示例代码中,"notifyurl","ver"均为自定义参数。
* paramSdkHttpListener:网络请求的监听。 酷派获取订单的代码如下:

 private void getPayOrderId(final Hashtable<String, String> info) {
Hashtable<String, String> orderInfo = IAPWrapper.getOrderInfo(
        info, CoolpadWrapper.getInstance().getUserID());
if (orderInfo == null) {
    payResult(IAPWrapper.PAYRESULT_PRODUCTIONINFOR_INCOMPLETE, "something is null");
}
orderInfo.put("plugin_id", getPluginId());
String notifyurl=IAPWrapper.replaceNotifyURL(mIAPOnline.getClass(), notifyUrl);
orderInfo.put("notifyurl", notifyurl);
orderInfo.put("ver", CoolpadWrapper.getInstance().getPluginVersion());

LogD("orderInfo:" + orderInfo.toString());

IAPWrapper.getPayOrderId(mActivity, orderInfo,
        new SdkHttpListener() {
    @Override
    public void onResponse(String response) {
        LogD("getOrderIdResponse:" + response);
        try {
            JSONObject json = new JSONObject(response);
            String status = json.getString("status");
            if (status.equals("ok")) {
                JSONObject jsonData = json.getJSONObject("data");
                mOrderId = jsonData.getString("order_id");

                JSONObject expand = jsonData.getJSONObject("order_expand");
                String exp_status = expand.getString("status");
                if(exp_status.equals("ok")){
                    String transid = expand.getString("transid");
                    payOnline(info,transid);

                }else{
                    String err_msg = expand.getString("err_msg");
                    payResult(IAPWrapper.PAYRESULT_FAIL, err_msg);
                }

            } else {
                payResult(IAPWrapper.PAYRESULT_FAIL, "status error");
            }
        } catch (JSONException e) {
            LogE("getPayOrderId response error", e);
            payResult(IAPWrapper.PAYRESULT_FAIL, "getPayOrderId onResponse error");
        }
    }

    @Override
    public void onError() {
        payResult(IAPWrapper.PAYRESULT_FAIL, "getPayOrderId onError");
    }   
  });       
}

说明:
1. 酷派采用的是服务端下单模式,SDK 支付示例代码的说明中详述。 2. 酷派渠道通知地址采用向酷派服务端下单是传入的方式,即自定义参数notifyurl。为了实现热备,所有渠道通知地址在使用前都需要调用服务端接口IAPWrapper.replaceNotifyURL来处理。

SDK 支付

获取订单号后,调用 SDK 的支付接口,进行支付,之后完成后处理收到同步通知。
注意:当回调结果无法明确判定是成功还是失败时,客户端回调支付成功,以服务端的异步通知为准。

private void payOnline(Hashtable<String, String> payOnlineInfo,String transid ) {

//拼装帐号信息
String coolPadAuthToken = CoolpadWrapper.getInstance().getToken();
String coolPadUAppid = CoolpadWrapper.getInstance().getUAppID();
String coolPadOpenID =CoolpadWrapper.getInstance().getUserID();
final AccountBean account = CoolPadPay.buildAccount(mActivity, coolPadAuthToken, coolPadUAppid, coolPadOpenID);

//订单信息
String coolPadAppid = CoolpadWrapper.getInstance().getAppID();
final String genUrl = "transid=" + transid + "&appid=" + coolPadAppid;
LogD(genUrl);

PluginWrapper.runOnMainThread(new Runnable() {
    @Override
    public void run() {
        //支付
        CoolPadPay.startPay(mActivity, genUrl, account,new IPayResultCallback() {
            @Override
        public void onPayResult(int resultCode, String signvalue,String resultInfo) {
            switch (resultCode) {
            case CoolPadPay.PAY_SUCCESS:
                payResult(IAPWrapper.PAYRESULT_SUCCESS,"pay success");
                break;
            case CoolPadPay.PAY_CANCEL:
                payResult(IAPWrapper.PAYRESULT_CANCEL, resultInfo);
                break;
            default:
                payResult(IAPWrapper.PAYRESULT_FAIL, resultInfo);
                break;
            }
        }       
        });
    }
  });
}

说明:
1. 酷派采用的是服务端下单模式,所以客户端支付时没有使用获取订单时得到的 AnySDK 订单号 (mOrderID),因为服务端插件在向酷派服务端请求订单时,已经传入了AnySDK 订单号;非服务端下单时,SDK 客户端接口通常会有用户订单号或者扩展字段的参数,需要传AnySDK 订单号。当支付完成后,渠道发送支付通知给 AnySDK 服务端,服务端根据AnySDK 订单号来验签并发送给游戏服务端。

支付的回调

支付的回调有五种返回值:

getOrderId()

获取支付订单号。支付订单号只有支付流程的第二步返回后,才能得到正确的订单号。

getPluginId()

插件的唯一标志,多支付时判断支付方式的标识。

setDebugMode(boolean paramBoolean)

已废弃,实现方式参照模板。

getSDKVersion()

SDK版本是指第三方SDK的版本号,格式可能都不一样,如6.0、1.0.3、2.1.0.4等,这个版本号格式不需要任何改动。

getPluginVersion()

格式『版本序号_SDK版本号』,版本序号从『2.0.0』开始,每次更新+0.0.1