337SDK集成文档

快速集成

引入依赖工程

337SDK Android版本以library工程的形式提供,使用时必须引入核心Lib工程。

_images/includelib.jpg

用户模块涉及到Facebook登录部分,所以还需要引入Facebook SDK。下载地址:https://developers.facebook.com/docs/android/

添加权限

需要声明的权限如下:

<!-- 基本权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>

<!-- 涉及GoogelPlay内购功能 -->
<uses-permission android:name="com.android.vending.BILLING" />

<!-- 涉及手机短信付款功能 -->
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />

<uses-permission android:name="com.tmoney.vending.INBILLING"/>
<permission android:name="com.tmoney.vending.INBILLING"/>

添加Activity、MetaData和其他内容

需要添加的内容如下:

<activity
    android:name="com.web337.android.widget.PopuView"
    android:theme="@style/Theme.Translucent"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:windowSoftInputMode="adjustResize">
<activity
    android:name="com.web337.android.pay.PayCoreMobileActivity"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:theme="@style/mobile337transparent">
</activity>
<activity
    android:name="com.web337.android.pay.PayShowPackagesActivity"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:theme="@style/mobile337transparent">
</activity>
<activity
    android:name="com.web337.android.ticket.TicketCoreActivity"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:windowSoftInputMode="adjustResize" >
</activity>
<activity
    android:name="com.fortumo.android.FortumoActivity"
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >
</activity>
<activity
    android:name="com.web337.android.pay.fortumo.FortumoActivity"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >
</activity>
<activity
    android:name="com.web337.android.widget.Web"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:launchMode="singleTask" >
</activity>
<activity
    android:name="com.web337.android.user.UserPage"
    android:theme="@style/mobile337user" >
</activity>
<activity
    android:name="com.web337.android.user.GoogleAcountLogin"
    android:configChanges="orientation|keyboardHidden|screenSize" >
</activity>
<activity
    android:name="com.facebook.LoginActivity"
    android:theme="@android:style/Theme.Translucent" />
<activity
    android:name="com.skplanet.dodo.IapWeb"
    android:configChanges="orientation|keyboardHidden|locale|screenSize|layoutDirection"
    android:excludeFromRecents="true"
    android:windowSoftInputMode="stateHidden"/>
<receiver
    android:name="com.web337.android.Tracker"
    android:exported="true" >
    <intent-filter>
        <action android:name="com.android.vending.INSTALL_REFERRER" />
    </intent-filter>
</receiver>
<receiver android:name="com.fortumo.android.BillingSMSReceiver" >
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" >
        </action>
    </intent-filter>
</receiver>

<service android:name="com.fortumo.android.FortumoService" />
<service android:name="com.fortumo.android.StatusUpdateService" />
<uses-feature android:name="android.hardware.telephony" android:required="false"></uses-feature>
<meta-data
    android:name="com.facebook.sdk.ApplicationId"
    android:value="\ 220782057940018" />
<meta-data android:name="iap:api_version" android:value="1"/>

screenSize添加时如果出现错误,请更改targetSdkVersion为13以上即可

com.facebook.sdk.ApplicationId如果不使用默认的220782057940018,可以替换为游戏自己的ID,需要事先将应用secret配置在337的后台

如果需要添加的INSTALL_REFERRER的receiver不止一个,可以单独设立一个统一的入口,然后转发给com.web337.android.Tracker

添加第三方广告推广平台SDK

具体配置见第三方广告SDK条目

SDK初始化以及重载关键方法

在主Activity的onCreate中调用:

FuncCore.onCreate(this);

在主Activity的代码中,重载以下方法:

@Override
protected void onDestroy() {
        FuncCore.onDestroy(this);
        /*your code*/
        super.onDestroy();
}

@Override
protected void onStart() {
        FuncCore.onStart(this);
        /*your code*/
        super.onStart();
}

@Override
protected void onStop() {
        FuncCore.onStop(this);
        /*your code*/
        super.onStop();
}

@Override
public void onBackPressed() {
        if (FuncCore.onBackPressed(this)) {
                return;
        } else {
                /*your code*/
                super.onBackPressed();
        }
}

设置支付回调

FuncCore.setPayCallback(new FuncCore.PayCallback() {

        @Override
        public void onInitFinish(Msg msg) {
                if(msg.isSuccess()){
                        /*初始化成功*/
                }else{
                        /*初始化失败*/
                }
        }

        @Override
        public void onComplete(Order o) {
                /*付款成功*/
        }
        @Override
        public void onCancel() {
                /*取消支付*/
        }
        @Override
        public void onFailed(Msg msg) {
                /*支付失败*/
        }
});

用户登录

  • 实例化一个回调对象:

    final FuncCore.LoginCallback callback = new FuncCore.LoginCallback(){
            @Override
            public void onLoginSuccess(User u, boolean isRegist) {
                    if(isRegist){
                            /*注册成功*/
                    }else{
                            /*登录成功*/
                    }
            }
    
            @Override
            public void onCancel() {
                    /*取消登录*/
            }
    };
    
  • 调用登录方法:

    FuncCore.goLoginAndInit(Context c, FuncCore.LoginCallback callback,final boolean priorityLogin);
    

可以在进入游戏主页面后直接调用,Context传递当前的activity即可,LoginCallback传递上一步创建的callback对象

调用该方法后只需要关心callback中的两个回调方法即可,若当前无登录用户,则会弹出登录或注册页面,用户登录或注册完成后,会回调。若已经有登录的用户,则直接回调

设置角色和服信息

在获取到角色的信息和所在服之后,设置一下相应的信息:

com.web337.android.id.Zone.getInstance().clear();
com.web337.android.id.Zone.getInstance().setRole_id("roleid00001");
com.web337.android.id.Zone.getInstance().setRole_name("wangxiaoming");
com.web337.android.id.Zone.getInstance().setServer_id("1");
com.web337.android.id.Zone.getInstance().setServer_name("ServerName");

加入行云统计

用户登录完成后,设置完角色信息即可调用:

com.web337.android.sdks.XA.send(Context c);

Context传递当前Activity即可

打开浮动窗口

进入游戏主面板后,打开337的浮动窗口:

FuncCore.showFloatWindow(activity);

直接发起支付

Order o = new Order();
o.setAmount(游戏币数量);
o.setDescription(商品描述);
o.setGross(商品金额);
o.setCurrency(货币类型);
o.setProductId(商品代码);
PayCore.beginPay(activity, o);

展示支付套餐

需要如下三个步骤:

//展示套餐,此处套餐均在支付平台后台配置
PayCore.show();

show()方法支持传递一个自定义字符串,该值最终会作为custom_data参数回调给游戏服务器

至此337 SDK接入完成,更多的使用方法可以参考各个模块的文档

用户登录模块集成

在应用中引入lib工程

请参照快速集成中(引入337lib工程)

在Manifest文件添加必要的声明

需要添加内容入下:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>

<activity
android:name="com.web337.android.user.UserPage"
android:theme="@style/mobile337user"/>

<activity
android:name="com.facebook.LoginActivity"
android:theme="@android:style/Theme.Translucent"/>

<activity
android:name="com.web337.android.user.GoogleAcountLogin"
android:configChanges="orientation|keyboardHidden"/>

<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="\ 156198457906087"/>

<activity
android:name="com.web337.android.widget.Web"
android:configChanges="orientation|keyboardHidden|screenSize"
android:launchMode="singleTask"/>

获取用户信息

应用可以通过直接调用 UserCore.checkLogin(Context c,UserLoginCallback callback) 然后在 UserLoginCallback 中获取用户信息。而不需要关心是否注册等细节。在 UserLoginCallback 接口中有两个方法

  • onLoginSuccess(User u,boolean isRegist)

通过此回调回去用户信息,如果isRegist为true则此用户为注册用户,如果为false则此用户为登录用户

  • public void onCancel()

如果用户在弹出的登录-注册界面没有登录或者注册,则回调此方法

自定义用户登录界面

如果您希望使用个性化的登录界面,你有以下两种选择:

  • 直接更改SDK提供的登录界面布局文件 /res/layout/mobilev2_337_user_login.xml ,需要注意的是您可以任意更改此布局文件的样式,但是请务必保证每个组件的功能和ID不要变更。
  • 完全使用自己的布局文件。然后调用SDK中的接口来使用相应功能,可以调用的方法请参照下一节

其他方法

  1. 获取当前登录用户:

    UserCore.getLoginUser();

  2. 退出登录:

    UserCore.logout();

  3. 获取用户登录状态:

    UserCore.isLogin();

  4. 获取最后一次登录的用户名:

    UserCore.lastLoginUsername();

  5. 找回用户密码:

    UserCore.getPassword(Context c);

  6. 使用用户名和密码登陆:

    使用前需要先调用setContext(Context c)方法

    login(final String username,final String password,final UserSelfLoginCallback callback);

  7. 注册用户:

    使用前需要先调用setContext(Context c)方法

    register(final String username,final String password,final String email,final UserSelfRegisterCallback callback);

  8. 检查用户是否合法:

    check(final User u,final UserSelfCheckCallback callback);

  9. 个人信息页:

调用方法打开页面:

UserCore.showUserInfo(Activity);

当前无337登录用户时,方法返回false,当前有337登录用户时,方法返回true,并打开用户个人信息页。玩家可以在此页面进行更改个人信息、更改密码、切换账户等操作。

如果玩家在个人信息页切换了账户,当玩家关闭个人信息页时,SDK会将新的用户回调给开发者。回调方法的设置如下:

UserCore.setOnChangeUserListener(new com.web337.android.user.UserCore.OnChangeUserListener(){
 @Override
 public void onChange(User u) {
        if(u != null){
                alert("更改用户:"+u.getUsername());
        }else{
                alert("退出登录");
        }
}});

移动支付集成

玩家支付流程

托管模式下,玩家点击购买按钮,触发支付行为,SDK会根据玩家支付的币种动态选择出相应的支付渠道列表和从服务器端设置好的物品包价格,玩家可以选择自己需要的物品包并通过合适的渠道进行支付,玩家付款之后,支付平台会向应用提供的回调地址发送通知,同时前端获取支付结果。

代理模式下,由应用构造出订单传递给SDK,SDK为玩家弹出付款页面,玩家付款之后,支付平台会向应用提供的回调地址发送通知,同时前端获取支付结果。

准备工作

在准备计入之前首先需要从运营方获获取appid,appid作为接入系统的标识,是一个单独的统计单元,且都有各自的配置。一个项目下允许有多个appid。

应用需要提供一个回调地址接收支付结果的通知,对于单机版无服务端的应用可以忽略这项配置。但对于需要联网的应用建议都采用服务端对服务端的通知模式,以确保交易安全。具体的回调地址规则请参见服务端回调验证文档。

当您获取了appid,并设置了回调地址之后就可以开始集成支付组件了。

在应用中引入lib工程

请参照快速集成中(引入337lib工程)

将demo中提供的com.android.vending.billing包放入工程src目录,在gen文件夹中生成IInAppBillingService.java文件即表示成功

在Manifest文件添加必要的声明

需要添加内容入下:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-feature android:name="android.hardware.telephony" android:required="false"></uses-feature>

<activity
android:name="com.web337.android.pay.PayCoreMobileActivity"
android:configChanges="orientation|keyboardHidden|screenSize"/>

<activity
android:name="com.web337.android.pay.PayShowPackagesActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />

<activity
android:name="com.fortumo.android.FortumoActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

<activity
android:name="com.web337.android.pay.fortumo.FortumoActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:configChanges="orientation|keyboardHidden|screenSize" />

<receiver android:name="com.fortumo.android.BillingSMSReceiver" >
        <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" >
                </action>
        </intent-filter>
</receiver>

<service android:name="com.fortumo.android.FortumoService" />
<service android:name="com.fortumo.android.StatusUpdateService" />

初始化PayCore

通过调用 PayCore.init(Context context,String appid,PayCallback back) 方法来进行初始化。

第一个参数Context,传递当前的activity即可。

第二个参数appid,要传应用获得的appid。

第三个参数PayCallback,这是一个前端回调类,因为SDK初始化和支付是异步进行的,所以并不能立即获取相应结果。应用需要监听这些回调方法,并作出相应的处理。该接口共有四个方法,分别如下:

onInitFinish(Msg msg) 初始化完成

onComplete(Order o) 完成订单

onCancel() 订单被取消

onFailed(Msg msg) 支付失败

支付准备

必须确保当前已经为支付模块设置过uid才能发起支付。

设置uid有以下几种方法:

  1. 使用Settings.setCommonUid,该方法需要在支付初始化之前设置,设置之后,支付模块在初始化时会使用该方法设置的uid。(在引入IdZone之后这个方法已经过时,只用于兼容旧版代码和单独接入支付时使用)
  2. 使用337用户id,若未使用Commonuid,若在支付初始化之前已经登录了337uid,则支付模块在初始化时会使用用户的337uid。
  3. 发起支付前设置,使用PayCore.setUid(id)方法,可以在发起支付前设置uid。

设置uid之后,还可以设置角色id,PayCore.setRoleId(roleid)方法可以单独设置角色id,若未设置,则角色id将和uid相同。

IdZone设置: 2.0.4版本后增加了IdZone的概念,使用IdZone的前提是使用337登录模块。在登录完成之后,手动调用IdZone的方法设置角色名、角色ID、服名称和服ID,设置完成后,支付系统会默认使用用户337uid和角色id,不再支持自定义uid。客服系统也将使用IdZone中的角色名和服名称

设置方法如下:

com.web337.android.id.Zone.getInstance().clear();
com.web337.android.id.Zone.getInstance().setRole_id("12345");
com.web337.android.id.Zone.getInstance().setRole_name("王小明");
com.web337.android.id.Zone.getInstance().setServer_id("1");
com.web337.android.id.Zone.getInstance().setServer_name("琉璃仙境");

发起支付

  • 代理模式:

在代理模式下,SDK只负责按照应用内预定的方式引导玩家到支付渠道进行付款,并及时反馈给客户端支付结果。

代理模式下,部分第三方的支付方式需要手动添加,比如台湾大哥大和GooglePlay应用内购

添加台湾大哥大

代码如下:

if (PayCore.add(PayCore.SDK_TWM)) {
        PayCore.twm.bind("应用在大哥大处申请到的支付代码", "当前购买的商品在应用内部的id");
        PayCore.twm.init(Context context);
}

添加GooglePlay内购

代码如下:

if (PayCore.add(PayCore.SDK_GOOGLEPLAY)) {
        PayCore.googleplay.bindSKU("应用在google申请的内购代码", "当前购买的商品在应用内部的id");
        PayCore.googleplay.init(new initGooglePlayListener() {
                @Override
                public void initSuccess() {
                }

                @Override
                public void initFailed(String msg) {
                }
        });
}

发起支付:

beginPay(Context c,Order o)

第一个参数传递当前的activity即可。

第二个参数order需要是com.web337.android.model.Order的实例,发起支付时必需的属性如下:

amount,传递商品数量。

description,传递商品描述,比如10个元宝、100枚金币等,会显示在第三方渠道的支付页面上。

gross,要支付的金额。对于Google Play内购支付来说,玩家的真实花费和该值无关系,支付平台会回调的金额是所传的金额,而对于第三方支付比如paypal,真实花费就是所传的金额。举例说明,一件商品在Google Play上的内购价格为0.99美元,发起支付时gross设置为0.99,当香港玩家使用Google Play内购时,所花费的是0.99美元换算成港币的金额,而使用paypal支付时,必须要去支付0.99美元。之后支付平台会回调的金额还是0.99美元。

currency,支付的货币类型,该值和gross共同起作用,使用ISO-4217标准货币代码,如USD(美元)、TWD(新台币)等。

productId,应用自定义的商品代码,通常应用对于特定的商品都会有特定的代码,比如一个关卡、一组金币、一个新功能等,这个值是为了方便游戏识别用户所购商品,在使用Google Play内购支付时,需要将商品代码和应用在Google Play内购代码进行绑定,SDK会根据所传的productId来获取真正的内购代码,这样应用在发起支付时,就无需区分是用Google Play内购还是第三方渠道进行支付了,同时应用的服务端接受回调时,也无需区分,只需要识别productId即可。

customData,自定义参数,应用可以随意传递任何数据,长度为200。支付平台会将该值原样回调。应用可以自行决定如何使用该值。该值不能为空字符串。

  • 托管模式

在托管模式中不在需要自己手动添加和绑定台湾大哥大和Google Play两个支付渠道,SDK会根据后台提供的配置自己进行初始化和绑定工作。

应用直接调用以下方法即可发起支付

PayCore.show();

SDK会直接展示在后台预设好的物品包金额,从而方便用户进行快速支付。

可以使用以下方法单独调用手机渠道支付(fortumo、mozca等):

PayCore.mobileShow();

两种支付模式是并列的,不能同时使用。代理模式一般需要将需要购买的物品金额等信息配置在应用内部,然后作为参数进行支付,而托管模式全部参数都在服务器端配置,可以灵活的调整物品包的金额种类等

其他说明

单机版无服务端的应用可以通过PayCallback来获取支付结果,这部分的回调可能会有一些延迟。

337移动支付服务端回调及验证说明

概述

在用户支付完成之后,支付平台会将支付结果通知到应用提供的回调地址,应用接收到回调后,应该根据结果给用户提供相应的商品,并给支付平台返回一个事先约定好的结果。至此一笔交易在支付平台才算完成。

当应用没有返回要求的结果时,支付平台最多会回调3次,3次不成功之后,当前交易会被设置为失败交易,之后会有客服介入,手动处理失败交易。

为了保证应用方收到的数据是由支付平台发送而非第三方伪造,支付平台提供了二次验证的方式,应用服务端在收到回调通知之后,可以拿收到的数据到支付平台提供的接口进行验证,若验证通过,则说明该通知真实可信。

回调环节

应用需要提供一个回调地址来接收回调参数

支付平台会使用post方式发送以下参数:

  • trans_id:交易流水号,该交易在支付平台生成的唯一id
  • amount:道具数量,该值由前端传递而来
  • user_id:完成支付的玩家id,该值由前端传递而来
  • timestamp:时间戳
  • gross:交易金额
  • currency:货币类型代码,比如USD(美元)、CNY(人民币)、BRL(巴西雷亚尔)
  • channel:渠道名称,比如alipay、paypal、mycard
  • item:商品描述,该值由前端传递而来
  • custom_data:应用方的自定义字段,由前端传递而来
  • product_id:应用方定义的商品id,由前端传递而来
  • pay_type:交易类型,移动端该值为mobile
  • role_id:角色id

支付平台需要应用服务端返回以下规则的字符串:

  • 3,null:表示游戏方处理失败
  • 3,94a0acb127ef8ee8c925e3944941ce5e:表示游戏方不认识这个玩家id
  • 3,user_id:表示处理成功,比如回调时,user_id为123456,那么游戏方的返回值应该为3,123456

如果3次回调后应用服务端未按规则返回,则该交易失败,等待客服处理。

安全验证

安全验证机制可以保证应用服务端收到的回调确实是由支付平台发起而非第三方伪造。

IP验证

需要对请求作IP验证,只有IP地址:174.37.255.60,173.192.195.130发送过来的请求才能通过

验证参数

当接收到支付系统的回调之后,应请求地址 VERIFY URL 验证交易信息以保证交易信息的正确性。如果交易信息正确则智明星通支付系统将返回 ‘OK’。:

[VERIFY URL]?trans_id=[TRANS_ID]&amount=[AMOUNT]&user_id=[USER_ID]&timestamp=[TIMESTAMP]&gross=[GROSS]&currency=[CURRENCY]&channel=[CHANNEL]

验证返回

  • OK:交易信息正确
  • 否则交易信息不可信,不能发游戏币

样例代码

注意:php版本的代码需要 下载证书文件 ,请将这个文件放到和回调php文件同样的目录。

php

<?php
$trans_id = $_REQUEST ["trans_id"];
$user_id = $_REQUEST ["user_id"];
$amount = $_REQUEST['amount'];
$gross = $_REQUEST['gross'];
$currency = $_REQUEST['currency'];
$channel = $_REQUEST['channel'];
$timestamp = $_REQUEST['timestamp'];

ob_clean();
//To check if the transaction exists in db, if it does. means the transactions has been successfully processed. Just return OK status
$exist = is_trans_exist($trans_id);
if($exist) {
                echo '3,'.$user_id;
                return;
}

//to verify the transaction towards payelex server.
$res = check_payelex_transaction($trans_id, $user_id, $amount, $gross, $currency, $channel);
if(!$res) {
                echo "3,null";
                return;
}

//retrieve the user from db.
$user = find_user_from_db();
if ($user == null) {
                echo '3,94a0acb127ef8ee8c925e3944941ce5e';
                return;
}

//recharge the user with the deserved game coins.
if(add_coins($_REQUEST)) {
                echo '3,'.$user_id;
                return;
}
echo "3,null";
function check_payelex_transaction($trans_id, $user_id, $amount, $gross, $currency, $channel) {
                $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
        //verisign_ca.crt is the public certificate from VeriSign(It is the biggest Certificate Authority which issue ELEX client certificate)
        //verisign_ca.crt must be located at the same directory as this PHP code are.
        curl_setopt($ch, CURLOPT_CAINFO, 'verisign_ca.crt');
        curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/x-www-form-urlencoded"));
        curl_setopt($ch, CURLOPT_URL, 'https://pay.337.com/payelex/api/callback/verify.php');
        curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                $params = array(
                                'trans_id'=>$trans_id,
                                'user_id'=>$user_id,
                                'amount'=>$amount,
                                'gross'=>$gross,
                                'currency'=>$currency,
                                'channel'=>$channel,
                                'timestamp'=>$timestamp
                );
                curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
        $result = curl_exec($ch);
        curl_close($ch);
        $result = trim($result);
        if ($result === 'OK') return true;
        return false;
}

java

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class PayelexTransactionServlet extends HttpServlet {

                private static final long serialVersionUID = -2108375440169533437L;
                private static final String VERIFY_URL = "https://pay.337.com/payelex/api/callback/verify.php";
                private static final String VERISIGN_CA =
                                "-----BEGIN CERTIFICATE-----\n"+
                                "MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE\n"+
                                "BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO\n"+
                                "ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk\n"+
                                "IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp\n"+
                                "ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB\n"+
                                "yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln\n"+
                                "biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh\n"+
                                "dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt\n"+
                                "YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n"+
                                "ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz\n"+
                                "j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD\n"+
                                "Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/\n"+
                                "Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r\n"+
                                "fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/\n"+
                                "BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv\n"+
                                "Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy\n"+
                                "aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG\n"+
                                "SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+\n"+
                                "X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE\n"+
                                "KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC\n"+
                                "Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE\n"+
                                "ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq\n"+
                                "-----END CERTIFICATE-----";

                @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                                throws ServletException, IOException {
                                doPost(request, response);
                }

                protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
                                String transId=request.getParameter("trans_id");
                                String userId=request.getParameter("user_id");
                                String amount=request.getParameter("amount");
                                String gross=request.getParameter("gross");
                                String currency=request.getParameter("currency");
                                String channel=request.getParameter("channel");
                                String timestamp=request.getParameter("timestamp");
                                boolean flag=check(transId,userId,amount,gross,currency,channel,timestamp);
                                response.setContentType("application/json; charset=UTF-8");
                                response.setStatus(HttpServletResponse.SC_OK);

                                if(flag==true){
                                                //TODO:检查该uid在游戏中是否真实存在,如果不存在的话返回3,94a0acb127ef8ee8c925e3944941ce5e
                                                boolean isUserExists=checkUserIdExists(userId);
                                                if(isUserExists==false){
                                                                response.getWriter().write("3,94a0acb127ef8ee8c925e3944941ce5e");
                                                }else{
                                                                response.getWriter().write("3,"+userId);
                                                }
                                }else{
                                                response.getWriter().write("3,null");
                                }
                }
                public boolean checkUserIdExists(String userId){
                                //TODO:判断该玩家是否真实存在,需要开发者自行扩展该方法
                                return false;
                }
                public static final boolean check(String transId, String userId, String amount, String gross, String currency, String channel,String timestamp) {
                                try {
                                                StringBuilder buffer = new StringBuilder();
                                                buffer.append("trans_id=").append(URLEncoder.encode(transId, "UTF-8")).append("&")
                                                                .append("user_id=").append(URLEncoder.encode(userId, "UTF-8")).append("&")
                                                                .append("amount=").append(URLEncoder.encode(amount, "UTF-8")).append("&")
                                                                .append("gross=").append(URLEncoder.encode(gross, "UTF-8")).append("&")
                                                                .append("currency=").append(URLEncoder.encode(currency, "UTF-8")).append("&")
                                                                .append("channel=").append(URLEncoder.encode(channel, "UTF-8")).append("&")
                                                                .append("timestamp").append(URLEncoder.encode(timestamp, "UTF-8"));

                                                TrustManager[] trustAllCerts = new TrustManager[]{
                                        new X509TrustManager() {
                                                                public X509Certificate[] getAcceptedIssuers() {
                                                                        return null;
                                                                }
                                                                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                                                                }
                                                                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                                                                                                InputStream is = new ByteArrayInputStream(VERISIGN_CA.getBytes());
                                                                                                try {
                                                                                                                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                                                                                                                Certificate publicCert = cf.generateCertificate(is);
                                                                                                                PublicKey publicKey = publicCert.getPublicKey();
                                                                                                                boolean validSignature = false;
                                                                                                                for (int i = 0; i < certs.length; i++) {
                                                                                                                                try {
                                                                                                                                                certs[i].verify(publicKey);
                                                                                                                                                validSignature = true;
                                                                                                                                                break;
                                                                                                                                } catch (SignatureException e) {}
                                                                                                                }
                                                                                                                if (!validSignature) {
                                                                                                                                throw new SignatureException();
                                                                                                                }
                                                                                                } catch (InvalidKeyException e) {
                                                                                                                throw new RuntimeException(e);
                                                                                                } catch (CertificateException e) {
                                                                                                                throw new RuntimeException(e);
                                                                                                } catch (NoSuchAlgorithmException e) {
                                                                                                                throw new RuntimeException(e);
                                                                                                } catch (NoSuchProviderException e) {
                                                                                                                throw new RuntimeException(e);
                                                                                                } catch (SignatureException e) {
                                                                                                                throw new RuntimeException(e);
                                                                                                }
                                                                }
                                                        }
                                                };
                                                SSLContext sc = SSLContext.getInstance("SSL");
                                        sc.init(null, trustAllCerts, new java.security.SecureRandom());
                                        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

                                                URL serverUrl = new URL(VERIFY_URL);
                                                HttpURLConnection conn = (HttpURLConnection) serverUrl.openConnection();
                                                conn.setConnectTimeout(10000);
                                                conn.setReadTimeout(10000);
                                                conn.setRequestMethod("POST");
                                                conn.setDoOutput(true);
                                                conn.connect();

                                                conn.getOutputStream().write(buffer.toString().getBytes("UTF-8"));
                                                if (conn.getResponseCode() == HttpURLConnection.HTTP_OK || conn.getResponseCode() == HttpURLConnection.HTTP_CREATED) {
                                                                String res = toString(conn.getInputStream(), "UTF-8");
                                                                if (res != null && res != "" && res.trim().equals("OK")) return true;
                                }
                                                return false;
                                } catch (Exception e) {
                                                return false;
                                }
                }
                private static String toString(InputStream is, String encoding) throws IOException {
                                InputStreamReader in = new InputStreamReader(is, encoding);
                                StringWriter sw = new StringWriter();
                                char[] b = new char[1024 * 4];
                int n = 0;
                while (-1 != (n = in.read(b))) {
                                sw.write(b, 0, n);
                }
                                return sw.toString();
                }
                public static void main(String[] args) {
                                if (check("elex337c1f4d6a5c520c02cd0ccd43712a3b23e", "elex337_24319771", "4500.0", "30.14", "TRY", "elex337")) {
                                                System.out.println("check OK");
                                } else {
                                                System.out.println("check Failed");
                                }
                }
}

第三方广告及统计SDK

已经集成的SDK

目前SDK中集成了一下6个第三方SDK

  • Tapjoy
  • Facebook
  • Chartboost
  • Google Analytics
  • hasoffers
  • FIKSU

之后会逐渐添加更多的第三方SDK。

在应用中引入lib工程

请参照快速集成中(引入337lib工程)

开始集成

  1. 在OnCreate方法中调用

    SdkCore.initAll(Activity);
    
  2. 在onStart方法中调用

    SdkCore.onStart(Activity);
    
  3. 在onStop方法中调用

    SdkCore.onStop(Activity);
    
  4. 在onDestroy方法中调用

    SdkCore.onDestroy(Activity);
    
  5. 在onBackPressd调用

    SdkCore.onBackPressed();
    
3-6参数一般使用this来传递当前activity即可

集成Tapjoy

添加Activitys声明:

<activity
android:name="com.tapjoy.TJCOffersWebView"
android:configChanges="orientation|keyboardHidden|screenSize" />
<activity
android:name="com.tapjoy.TapjoyFullScreenAdWebView"
android:configChanges="orientation|keyboardHidden|screenSize" />
<activity
android:name="com.tapjoy.TapjoyDailyRewardAdWebView"
android:configChanges="orientation|keyboardHidden|screenSize" />
<activity
android:name="com.tapjoy.TapjoyVideoView"
android:configChanges="orientation|keyboardHidden|screenSize" />
<activity
android:name="com.tapjoy.TJAdUnitView"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true" />
<activity
android:name="com.tapjoy.mraid.view.ActionHandler"
android:configChanges="orientation|keyboardHidden|screenSize" />
<activity
android:name="com.tapjoy.mraid.view.Browser"
android:configChanges="orientation|keyboardHidden|screenSize" />

添加meta-data声明:

<meta-data android:value="请填写您获得的Tapjoy的AppID"
android:name="337_TAPJOY_APPID"></meta-data>
<meta-data android:value="请填写您获得的Tapjoy的Key"
android:name="337_TAPJOY_APPKEY"></meta-data>

引入 tapjoyconnectlibrary.jar 文件

集成Facebook

添加meta-data声明:

<meta-data android:value="请填写您获得的Facebook的AppID"
android:name="337_FACEBOOK_APPID"></meta-data>

引入 FacebookSDK lib 工程

集成Chartboost

添加meta-data声明:

<meta-data android:value="请填写您获得的Chartboost的AppID"
android:name="337_CHARTBOOST_APPID"></meta-data>
<meta-data android:value="请填写您获得的Charboost的Key"
android:name="337_CHARTBOOST_APPKEY"></meta-data>

引入 chartboost.jar

集成Google Analytics

在工程中/res/values/目录下,新建一个analytics.xml文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="TypographyDashes">
        <!--Replace placeholder ID with your tracking ID-->
        <string name="ga_trackingId">请填写Google统计的ID</string>
        <!--Enable automatic activity tracking-->
        <bool name="ga_autoActivityTracking">true</bool>
        <!--Enable automatic exception tracking-->
        <bool name="ga_reportUncaughtExceptions">true</bool>
        <bool name="ga_debug">true</bool>
</resources>

引入 libGoogleAnalyticsV2.jar

集成hasoffers

添加meta-data声明:

<meta-dataandroid:value="请填写您获得的Hasoffers的Advertiser ID"
android:name="337_HASOFFERS_APPID"></meta-data>
<meta-data android:value="请填写您获得的Hasoffers的Key"
android:name="337_HASOFFERS_APPKEY"></meta-data\

引入 MobileAppTracker.jar

添加receiver声明:

<receiver android:name="com.mobileapptracker.Tracker"
android:exported="true">
        <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
        </intent-filter>
</receiver>

集成FIKSU

添加meta-data声明:

<meta-data android:value="on" android:name="337_FIKSU_ON"></meta-data>

添加receiver声明:

<receiver android:name="com.fiksu.asotracking.InstallTracking"
android:exported="true">
        <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
        </intent-filter>
</receiver>

引入 FiksuAndroidSDK_2.0.2.jar

注意若需要额外的receiver,需要在fiksu的receiver中加入如下格式的meta-data:

<meta-data android:name="forward.1" android:value="receiver类" />

比如同时接入hasoffers和FIKSU,需要将hasoffers的receiver以meta-data方式添加::

<receiver android:name="com.fiksu.asotracking.InstallTracking"
android:exported="true">
        <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
        </intent-filter>
<meta-data android:name="forward.1" android:value="com.mobileapptracker.Tracker" />
<!-- 其他的receiver -->
<meta-data android:name="forward.2" android:value="com.yourpackage.SomeReceiver" />
</receiver>

自定义初始化

若应用不想采用在AndroidManifest.xml文件中加meta-data的方式进行统一初始化,则可以调用SDK提供的自定义初始化方法, SDK提供了initXXXX的方法来方便应用调用,比如

SdkCore.initTapjoy(Context context,String appID,String secretKey);

传递相应的参数后即可进行初始化。 应用可以自由决定如何使用该类方法

其他方法

  • 部分第三方SDK提供一些事件统计,337SDK整合了部分方法

注册事件统计::

SdkCore.registerAction(context);

购买事件统计::

SdkCore.Purchases(context, user, gross, currency);

user为购买人,gross为购买金额,currency为货币类型

  • 获取Chartboost:

    SdkCore.getChartboost();
    
  • 获取MobileAppTracker:

    SdkCore.getMobileAppTracker():
    
  • 是否初始化完成:

    SdkCore.isInitFinish();
    

用户反馈

*用户反馈功能依赖于用户系统,即只有接入了用户登录模块且用户登录之后才能使用用户反馈功能*

在应用中引入lib工程

请参照快速集成中(引入337lib工程)

在Manifest文件添加必要的声明

需要添加内容入下:

<uses-permission android:name="android.permission.INTERNET"/>

<activity
        android:name="com.web337.android.ticket.TicketCoreActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:windowSoftInputMode="adjustResize" >
</activity>

使用用户反馈功能

  • 如果IdZone中已经设置了用户相关的游戏信息可以跳过这一步。调用``SupportCore.setUserInfo(String role_id, String role_name,String server_id, String server_name);``设置用户游戏角色服务器等相关信息。
  • 调用``SupportCore.openTicket(Context context, boolean addTicket);``即可直接使用用户反馈功能。如果返回true则正常使用反馈功能。如果返回false说明用户游戏角色的相关信息没有设置。

附录

游戏内客服系统实现的最佳方式 Step by Step

  • 概述

    因为咱们使用的客服系统Freshdesk与用户的往来最好的方式是基于邮件,故而在此基础上设计了这份客服最佳实践

  • 为游戏注册一个客户服务邮箱

    前期大家可以自行注册,@337.com, @elex-tech.com 后缀的邮箱找IT开通即可。 计划中可能会开启一个@support.337.com的邮箱后缀。游戏也可自行注册@Gmail.com后缀的邮箱来用于接收用户发来的客服邮件

  • 在客服系统中设置邮箱及转发

    客服人员可以在Freshdesk中创建一个产品,并未此产品指定若干个来信邮箱。在添加上一步注册好的客服邮箱之后,Freshdesk会提供一个自己的邮件地址用于接收转发过来的邮件,这时,去我们第一步注册的邮箱里,设置收到的邮件转发到提供的这个地址即可。

    部分邮件服务商在做转发的时候,会向目标邮箱发一个验证,这个验证可以直接在Freshdesk中收到并点击其中的连接进行认证即可。

  • 在游戏中提供按钮让用户方便的提交问题

    游戏中可以提供一个联系客服按钮,点击这个按钮时,我们可以预先吧收件人(也就是我们之前注册的邮箱)等信息填好,游戏也可以把对解决用户问题有帮助的内容(用户UID,系统版本号,游戏版本号等)预先写在邮件正文中,并提示用户这些信息不要删除。

    用户可以自行填写邮件标题,并在正文中附上自己问题的描述后发送邮件。 这封邮件就会以tickets形式出现在Freshdesk后台。

    后续客服的回复和用户的问题补充就都通过邮箱系统了。

  • 按钮实现方式

    Android:

    Intent intent = new Intent(Intent.ACTION_SENDTO,Uri.parse(MailTo.MAILTO_SCHEME));
    intent.putExtra(Intent.EXTRA_SUBJECT, subject);//主题
    intent.putExtra(Intent.EXTRA_TEXT, text);//正文
    intent.putExtra(Intent.EXTRA_EMAIL, to);//收件人
    startActivity(Intent.createChooser(intent, "发送邮件"));
    

    系统会自动提示玩家选择相应的邮件客户端去发送。剩下的事就可以不用管了,发送完邮件后会自动返回游戏。

    iOS:

    Coming Soon.