《插件自助开发——支付类插件》

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

类名及初始化

支付系统中,单机版的类一般命名为"IAP+插件名", 网游版的类一般命名为"IAPOnline+插件名"
以心动为例

 @interface IAPOnlineXindong : NSObject <InterfaceIAP>

- (void)configDeveloperInfo:(NSMutableDictionary *)cpInfo;
在init方法里调用configDeveloperInfo:方法,执行SDK的初始化操作。
cpInfo参数为字典,需要调用PluginHelper类的getParamsInfo方法,从打包工具里获取到。
以心动为例:

- (id)init {
  if ([super init]){
     [self configDeveloperInfo:[PluginHelper getParamsInfo]];
     _orderID = @"";
   }
  return self;
}

- (void)configDeveloperInfo:(NSMutableDictionary *)cpInfo {
   [[XindongWrapper getInstance] initSDK:self info:cpInfo];
}

支付系统的初始化回调有两种返回值: PAYRESULT_INIT_SUCCESS:初始化成功 PAYRESULT_INIT_FAIL:初始化失败

InterfaceIAP

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

- (void)payForProduct:(NSMutableDictionary *)productInfo;//支付,购买商品
- (NSString *)getOrderId;                    //获取订单 id
- (NSString *)getPluginId;           //获取插件 ID
- (void)setDebugMode:(BOOL)debug;    //设置调试模式,已废弃
- (NSString *)getSDKVersion;                   //获取SDK 版本号
- (NSString *)getPluginVersion;                //获取插件版本号

支付

支付流程描述

下载页面

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方法判断,返回值为NO时表示网络不可用,支付流程结束,回调PAYRESULT_PRODUCTIONINFOR_INCOMPLETE。
3.\是否登录:大部分渠道类(包括用户和支付等) SDK 要求 支付前用户必须已登录,所以对于这类 SDK,支付前必须判定登录状态:如果已登录,请求订单;如果未登录,先调用登录,登录成功后,请求订单,登录失败,支付流程结束,回调PAYRESULT_FAIL。
以心动为例: :

- (void)payForProduct:(NSMutableDictionary *)productInfo {
 OUTPUT_LOG(@"payForProduct %@ invoked.\n",productInfo);

 if (![[XindongWrapper getInstance] bIsInited]) {
    [self onPayResult:PAYRESULT_FAIL msg:@"init failed"];
    return;
}

 if (![PluginHelper networkReachable]) {
    [self onPayResult:PAYRESULT_NETWORK_ERROR msg:@"network is unreachable"];
    return;
}

 _productInfo = [productInfo mutableCopy];
if (!_productInfo || [_productInfo count] < 1) {
    [self onPayResult:PAYRESULT_FAIL msg:@"product is null"];
    return;
}

if (![[XindongWrapper getInstance] isLogined]) {
    [self userLogin];
} else {
    [self getPayOrderId:_productInfo];
 }
}

获取订单

- (void)getPayOrderId:(NSMutableDictionary *)productInfo;

getPayOrderId:方法用一个字典传递多个参数,其中包括从用户传入的参数中获取对应值,字段名如下:

参数 是否必传 参数说明 对应传入值
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

此外,还需要增加一个插件ID字段,key为plugin_id,值为插件ID,是必传字段。
自定义参数:自定义参数是某些特殊情况下,插件客户端与插件服务端交互时需要的特殊字段,根据具体需求而定。
获取订单的回调带有一个NSData类型的参数,插件需要在回调方法里将data转成相应的格式,并且解析。
如果解析出来状态有异常,需要回调异常,如果没有异常,需要把订单号保存下来。
以心动为例:

- (void)getPayOrderId:(NSMutableDictionary *)productInfo {
    NSMutableDictionary *orderInfo = [IAPWrapper getOrderInfo:productInfo userID:  [[XindongWrapper getInstance] userID]];
    [orderInfo setObject:[self getPluginId] forKey:@"plugin_id"];
    [IAPWrapper getPayOrderId:orderInfo target:self action:@selector(onGetPayOrderId:)];
}
- (void)onGetPayOrderId:(NSData *)data {
    NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
   OUTPUT_LOG(@"onGetPayOrderId %@",message);
   if ([message isEqualToString:@"ERROR" ]) {
       [self onPayResult:PAYRESULT_FAIL msg:@"onGetPayOrderId error! message: ERROR"];
       return ;
    }

    NSError *error = nil;
    NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];

    if (jsonObject) {
       NSString *status = [jsonObject objectForKey:@"status"];
       if (status && [status isEqualToString:@"ok"]) {
          NSMutableDictionary *data = [jsonObject objectForKey:@"data"];
          if (data) {
             _orderID = [data objectForKey:@"order_id"];
             [self payInSDK];
          } else {
             [self onPayResult:PAYRESULT_FAIL msg:@"data is nil"];
          }
       } else {
         [self onPayResult:PAYRESULT_FAIL msg:@"onGetPayOrderId status error"];
       }
    } else {
       OUTPUT_LOG(@"parse jsondata failed!!error:%@",error);
       [self onPayResult:PAYRESULT_FAIL msg:@"parse jsondata failed!"];
    }
}

SDK 支付

获取订单号后,调用 SDK 的支付接口,进行支付,之后完成后处理收到同步通知。 具体需要传递哪些参数给SDK,就需要参考SDK的技术文档。有的SDK会有一些比较特殊的要求。
注意:当回调结果无法明确判定是成功还是失败时,客户端回调支付成功,以服务端的异步通知为准。
以心动为例

- (void)payInSDK {
  // get the params used by the SDK
   float price = [[_productInfo objectForKey:@"Product_Price"]  floatValue];
   price *= 100;
   NSString *priceStr = [NSString stringWithFormat:@"%f",price > 1.0f ? price : 1.0f];

   int count = [[_productInfo objectForKey:@"Product_Count"] intValue];
   NSString *countStr = [NSString stringWithFormat:@"%d",count <  0 ? 1 : count];


   NSMutableDictionary *product = [NSMutableDictionary dictionary];
   [product setObject:_orderID forKey:@"OrderId"];
   [product setObject:priceStr forKey:@"Product_Price"];
   [product setObject:countStr forKey:@"Product_Count"];
   [product setObject:[_productInfo objectForKey:@"Product_Id"] forKey:@"Product_Id"];
   [product setObject:[_productInfo objectForKey:@"Role_Id"] forKey:@"Role_Id"];
   [product setObject:[_productInfo objectForKey:@"Server_Id"] forKey:@"Sid"];
   OUTPUT_LOG(@"payInSDK params: \n");
   OUTPUT_LOG(@"%@",product);

   // call payment function supported by the SDK
   [[XDCore getInstance] payForProduct:product];
}

说明:
心动SDK要求传递的金额单位必须为分,所以获取到以元为单位的单价后,需要乘以100。

支付的回调

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

获取用户ID

- (NSString *)getUserID; 获取用户 ID,登录成功后才能获取到正确的值。用户ID是SDK中用户的唯一标志,具体名称可能不一致,如 uID,openID 等。
如果SDK有相应的接口,则以SDK的为准,如果SDK没有提供相应的接口,则需要插件在登录的时候把UserID保存。
以心动为例

- (NSString *)getUserID {
   OUTPUT_LOG(@"getUserID invoked!\n");
   return [[XindongWrapper getInstance] userID];
}

获取插件ID

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

设置Debug模式

- (void)setDebugMode:(BOOL)debug;
已废弃,实现方式参照模板。

获取SDK版本号

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

获取插件版本号

- (NSString *)getPluginVersion;
格式『版本序号_SDK版本号』,版本序号从『2.0.0』开始,每次更新+0.0.1