Skip to content

U8Server 接入新渠道

IMPORTANT

接入一个新渠道SDK,除了客户端代码版本的实现,还需要实现其服务器端部分。其中,一般包括两部分,登录认证协议和支付回调通知协议。部分渠道SDK,还需要实现其服务器端下单协议。

渠道SDK接入服务器端部分,总体来说,就两个部分:登录认证和支付回调。

1、 登录认证处理说明:

登录认证入口是u8-x-server模块中UserController.login方法。 客户端SDK登录成功之后,由U8SDK框架层像U8Server发起登录认证请求,U8Server再去渠道平台进行登录验证。首次登录认证成功之后,将会生成一条用户数据。这个数据中sdkUserID对应该渠道SDK的用户唯一ID。

此协议方法中,会根据传入的渠道号,从数据库中取出当前渠道SDK的脚本类路径(一个java类文件),然后通过反射完成实例化,并执行里面该渠道的登录认证接口。

每个渠道SDK处理登录认证的脚本类,在u8-x-server模块中com.u8.server.sdk包名下,每个渠道SDK对应一个子包名。每个子包名下,建立一个处理类,该类需要实现com.u8.server.sdk.ISDKScript接口。

java
public interface ISDKScript {

    SDKVerifyResult verify(U8Game game, U8Channel channel, String extension, String deviceID, String ip);

    String createOrder(U8Game game, U8Channel channel, U8User user, U8Order order);

}

1、verify方法: 实现该渠道SDK的登录认证逻辑,登录成功返回SDKVerifyResult对象;登录失败返回null。注意:此方法本身就在异步任务中,所以发送http请求等,不需要单独再开异步任务;
2、createOrder方法: 实现该渠道自己的下单或下单验证逻辑,返回扩展数据,扩展数据会返回给客户端。 比如部分渠道SDK,需要支付之前,服务器端去SDK服务器端下单,对应逻辑就写在该接口中。注意:此方法本身就在异步任务中,所以发送http请求等,不需要单独再开异步任务。

2、 支付回调处理说明

每个渠道SDK一个类处理文件。所有渠道SDK的支付回调处理类都位于u8-x-server模块com.u8.server.web.controllers.partner.pay包名下。

支付回调处理类的实现步骤都大同小异:

1、解析参数 2、参数校验和签名验证 3、修改订单状态,通知游戏服务器给玩家发游戏币 4、给渠道SDK服务器一个反馈(成功/失败)

以UC渠道的支付回调处理为例:

java
package com.u8.server.web.controllers.partner.pay;

import com.u8.server.common.contants.Consts;
import com.u8.server.common.logger.UGLogger;
import com.u8.server.common.utils.EncryptUtils;
import com.u8.server.common.utils.JsonUtils;
import com.u8.server.common.utils.StringUtils;
import com.u8.server.entities.U8Channel;
import com.u8.server.entities.U8Game;
import com.u8.server.entities.U8Order;
import com.u8.server.sdk.uc.PayCallbackResponse;
import com.u8.server.service.*;
import com.u8.server.service.common.I18nService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * UC/阿里游戏
 */
@Controller
@RequestMapping("/partner/pay/uc")
public class UCPayController {
    private final String TAG = "uc";
    private static final UGLogger logger = UGLogger.getLogger(UCPayController.class);

    private final UserService userService;

    private final OrderService orderService;

    private final ChannelService channelService;

    private final GameService gameService;

    private final OrderPlatformLogService platformLogService;

    private final I18nService i18nService;

    public UCPayController(UserService userService,
                           OrderService orderService,
                           ChannelService channelService,
                           GameService gameService,
                           OrderPlatformLogService platformLogService,
                           I18nService i18nService) {
        this.userService = userService;
        this.orderService = orderService;
        this.channelService = channelService;
        this.gameService = gameService;
        this.platformLogService = platformLogService;
        this.i18nService = i18nService;
    }

    private PayCallbackResponse parseData(HttpServletRequest request) throws IOException {
        BufferedReader br = request.getReader();
        String line;
        StringBuilder sb = new StringBuilder();
        while((line=br.readLine()) != null){
            sb.append(line).append("\r\n");
        }

        return JsonUtils.parse(sb.toString(), PayCallbackResponse.class);
    }

    @RequestMapping(value = "/callback", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String payCallback(HttpServletRequest request) throws IOException {

        PayCallbackResponse result = parseData(request);
        if(result == null) {
            logger.error("{} pay callback failed. data parse failed", TAG);
            return "FAILURE";
        }

        long orderID = Long.parseLong(result.getData().getCallbackInfo());
        U8Order order = orderService.getOrderByID(orderID);
        if (order == null) {
            // 订单不存在
            logger.error("{} pay callback failed:{}. order is not exists.", TAG, result.getData().getCallbackInfo());
            return "FAILURE";
        }

        if (order.getStatus() == Consts.OrderStatus.FINISH) {
            // 订单状态不是已支付
            logger.error("{} pay callback ignored:{}. status:{} is already finished", TAG, order.getId(), order.getStatus());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.status.finish"));
            return "SUCCESS";
        }

        U8Channel channel = this.channelService.getChannel(order.getChannelID());
        if (channel == null) {
            // 渠道不存在
            logger.error("{} pay callback failed:{}. channel:{} is not exists", TAG, order.getId(), order.getChannelID());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.channel.not.exists"));
            return "FAILURE";
        }

        final U8Game game = gameService.getGameByID(order.getAppID());
        if (game == null) {
            // 游戏不存在
            logger.error("{} pay callback failed:{}. game:{} is not exists", TAG, order.getId(), order.getAppID());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.game.not.exists"));
            return "FAILURE";
        }

        if (order.getNotifyStatus() == Consts.NotifyStatus.NotifySuccess) {
            // 订单已经通知过游戏服务器了
            logger.error("{} pay callback ignored:{}. order is already notified to game server", TAG, order.getId());
            platformLogService.addNotifyLog(order, true, i18nService.getMessage("err.pay.already.notify"));
            return "SUCCESS";
        }

        if (!"S".equalsIgnoreCase(result.getData().getOrderStatus())) {
            // 订单状态错误
            logger.error("{} pay callback failed:{}. OrderStatus is not 1:{}", TAG, order.getId(), result.getData().getOrderStatus());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.channel.status.failed"));
            return "FAILURE";
        }


        if (!isSignOK(order, channel, result)) {
            // 签名验证失败
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.sign.not.match"));
            return "FAILURE";
        }

        int realMoney = (int)(Float.parseFloat(result.getData().getAmount()) * 100f);
        if(realMoney < order.getPrice()){
            // 金额验证错误
            logger.error("{} pay callback failed:{}. price:{} is not matched with OrderMoney:{}", TAG, order.getId(), order.getPrice(), realMoney);
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.price.not.match"));
            return "FAILURE";
        }

        // 完成订单,并通知给游戏服务器
        orderService.completeOrder(game, order, result.getData().getOrderId(), realMoney);

        return "SUCCESS";

    }

    /***
     * 验证支付
     * @param channel
     * @param rsp
     * @return
     */
    public boolean isSignOK(U8Order order, U8Channel channel, PayCallbackResponse rsp){

        String signSource= "accountId="+rsp.getData().getAccountId()+"amount="+rsp.getData().getAmount()+"callbackInfo="+rsp.getData().getCallbackInfo();
        if(rsp.getData().getCpOrderId() != null && rsp.getData().getCpOrderId().length() > 0){
            signSource += "cpOrderId="+rsp.getData().getCpOrderId();
        }
        signSource = signSource+"creator="+rsp.getData().getCreator()+"failedDesc="+rsp.getData().getFailedDesc()+"gameId="+rsp.getData().getGameId()
                +"orderId="+rsp.getData().getOrderId()+"orderStatus="+rsp.getData().getOrderStatus()
                +"payWay="+rsp.getData().getPayWay()
                +channel.getCpAppKey();

        String sign = EncryptUtils.md5(signSource).toLowerCase();
        boolean valid = sign.equals(rsp.getSign());
        if (!valid) {
            logger.error("{} pay callback failed:{}. sign not match. sign:{}; sign local:{}; sign string:{}", TAG, order.getId(), rsp.getSign(), sign, signSource);
        }
        return valid;

    }


    private static class PayResult {
        private String out_order_no;        //:cp订单编号
        private String order_no;        //:订单编号
        private String amount;      //:支付金额 单位元
        private String role_id;     //:玩家角色id
        private String pay_time;        //:支付时间(YY-mm-dd HH:ii:ss) 2017-12-31 12:22:22
        private String cp_game_id;      //:游戏id(对接群获取)
        private String sign;        //:签名

        public String getOut_order_no() {
            return out_order_no;
        }

        public void setOut_order_no(String out_order_no) {
            this.out_order_no = out_order_no;
        }

        public String getOrder_no() {
            return order_no;
        }

        public void setOrder_no(String order_no) {
            this.order_no = order_no;
        }

        public String getAmount() {
            return amount;
        }

        public void setAmount(String amount) {
            this.amount = amount;
        }

        public String getRole_id() {
            return role_id;
        }

        public void setRole_id(String role_id) {
            this.role_id = role_id;
        }

        public String getPay_time() {
            return pay_time;
        }

        public void setPay_time(String pay_time) {
            this.pay_time = pay_time;
        }

        public String getCp_game_id() {
            return cp_game_id;
        }

        public void setCp_game_id(String cp_game_id) {
            this.cp_game_id = cp_game_id;
        }

        public String getSign() {
            return sign;
        }

        public void setSign(String sign) {
            this.sign = sign;
        }
    }
}


1、@RequestMapping("/partner/pay/uc") :类上面加此注解,指定该渠道SDK的支付回调处理地址的相对路径。 注意: 回调格式必须以【/partner/pay/渠道SDK标识】命名。
2、@RequestMapping(value = "/callback") :方法上加此注解,一般固定名称/callback。 这样UC渠道完整的回调地址就是: {{u8server地址}}/partner/pay/uc/callback
3、isSignOK:此方法中进行签名校验
4、处理成功,调用orderService.completeOrder(game, order, result.getData().getOrderId(), realMoney);方法完成订单,并通知游戏服务器给玩家发货。
5return "SUCCESS" 或者 "FAILURE",按渠道要求给渠道服务器反馈

假设我们现在接入一个渠道SDK——vivo渠道, 服务器端需要实现登录认证协议和支付回调协议。

登录认证

通过看vivo的文档,我们得知,vivo的登录认证需要使用http post请求其登录认证地址,将客户端SDK登录成功获取到的token进行验证。

1、建立渠道SDK脚本子目录和脚本类

在com.u8.server.sdk下面,新建一个包名:vivo。 然后在com.u8.server.sdk.vivo包名下,建立一个VivoSDK脚本类。类内容如下:

java
public class VivoSDK implements ISDKScript{
    @Override
    public void verify(final UChannel channel, String extension, final ISDKVerifyListener callback) {

        
    }

    @Override
    public void onGetOrderID(UUser user, UOrder order, ISDKOrderListener callback) {

    }
}

2、从extension中,解析需要的参数。

登录认证协议这里, 是去验证客户端获取到的SDK的参数。客户端登录成功,每个渠道SDK返回的参数都是不同的。所以登录认证的时候,我们将该渠道SDK需要的参数,按照一定的格式组合,最终生成一个字符串,传到u8server。 这里我们的规则,一般是:一个参数直接传;多个参数,用json格式。这个参数,传到u8server这边,就对应verify接口中的extension字段了。

WARNING

Android客户端接入渠道SDK,登录成功触发U8SDK.getInstance().onLoginResult(String ext)方法时,传入的ext就会完整的对应服务器端这里的extension。iOS接入新渠道,登录成功触发[self eventUserLogin:data];方法时,传入的data也会完整的对应这里的extension。

这里参数的格式,服务器端同学和客户端接入的同学协商好。多个字段json格式,各个字段的key是什么。可以服务器端来指定,也可以客户端来指定。

VivoSDK这里,需要三个参数,客户端登录成功,获取到的openid,name和token。以json格式封装。

java
JSONObject json = JSONObject.parseObject(extension);
final String openid = json.getString("openid");
final String name = json.getString("name");
final String token = json.getString("token");

解析参数之后,我们根据vivo 服务器端文档中登录认证协议,进行http post请求。 访问http请求, 我们已经封装好了接口。直接调用UHttpAgent.getInstance().post或者UHttpAgent.getInstance().get方法进行post或者get方式的请求。

参数默认是application/x-www-form-urlencoded类型,可以直接将参数放到Map<String,String>中传入。

如果渠道SDK要求参数按照application/json格式传入,那么需要设置对应的请求头和参数传入方式,比如:

java
String jsonData = JsonUtils.encodeJson(params);


Map<String,String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json");

httpClient.post(channel.getChannelAuthUrl(), headers, new ByteArrayEntity(jsonData.getBytes(Charset.forName("UTF-8"))), new UHttpFutureCallback() {
	//......
});

vivo sdk这里参数是按照application/x-www-form-urlencoded方式进行传递:

java
public SDKVerifyResult verify(U8Game game, U8Channel channel, String extension, String deviceID, String ip) {

        extension = extension.replace("\n", "%0A");//替换回车符,vivo子帐号登录成功返回的token多了回车,导致解析json出错。@小抛同学发现

        JSONObject json = JSONObject.parseObject(extension);
        final String openid = json.getString("openid");
        final String name = json.getString("name");
        final String token = json.getString("token");

        Map<String, String> params = new HashMap<String, String>();
        params.put("authtoken", token);

        String result = UHttpAgent.getInstance().post(channel.getAuthUrl(), params);
        logger.debug("channelName:{}_channelID:{} login verify result:{}", channel.getMasterName(), channel.getChannelID(), result);

        if (StringUtils.isEmpty(result)) {
            logger.error("channelName:{}_channelID:{} login verify failed:{}", channel.getMasterName(), channel.getChannelID(), result);
            return null;
        }

        JSONObject rjson = JSONObject.parseObject(result);
        if (rjson.containsKey("retcode") && rjson.getIntValue("retcode") == 0) {

            return new SDKVerifyResult(true, openid, name, "");
        }

        return null;
}

1、post请求成功之后,我们从content中解析返回的数据。并验证登录认证协议结果是否正确。如果正确, 返回SDKVerifyResult对象
2、失败的情况,我们直接return null
3new SDKVerifyResult(true, openid, name, "")。 该成功回调的用户对象,有四个参数。第一个固定为true。第二个为渠道SDK用户唯一ID(必须传入),第三个为渠道SDK用户名(没有则传入空字符串),第四个一般传入""
4、登录认证url地址,是在后台管理中,渠道商里面配置。 这里我们直接通过 channel.getChannelAuthUrl()接口获取。 怎么配置,在下面会讲到。

有些时候,部分小渠道可能没有登录认证这一步。那我们怎么做呢?

对于没有登录认证协议的渠道SDK,我们依然要写登录认证逻辑。 让客户端传入渠道SDK用户唯一ID,然后直接构造并返回SDKVerifyResult对象

java
@Override
public void verify(final UChannel channel, String extension, final ISDKVerifyListener callback) {


    JSONObject json = JSONObject.parseObject(extension);
    final String uid = json.getString("uid");

    return new SDKVerifyResult(true, uid, "", ""));

}

支付回调

渠道SDK支付回调,是SDK支付成功之后, 渠道SDK服务器会异步发送一个http请求到指定的处理接口。每个渠道SDK的支付回调处理类,在com.u8.server.web.controllers.partner.pay包名下。

1、所有支付回调类的命名以PayCallbackAction结尾,当然只是一个约定,并不是强制。 2、所有渠道SDK支付回调地址的相对url,是/pay/渠道SDK标识/payCallback。 这里对应@Namespace注解和@Action注解中字符串的结合。比如下面vivo这里,支付回调相对地址就是: /pay/vivo/payCallback。这个相对地址,回头也需要在后台管理系统-》渠道商管理中配置。 3、支付回调协议中,需要根据渠道SDK文档中的参数说明进行解析。 4、客户端调用渠道SDK的支付接口时,需要将PayParams中的orderID放到渠道SDK异步通知会原样返回u8server的某个字段。u8server支付回调处理类中,从该参数中读取出订单号,并从数据库中查出订单信息。

有些渠道SDK回调协议中Content-Type是application/x-www-form-urlencoded类型, 有些渠道SDK回调协议中Content-Type,可能是application/json或者text/xml等。 不同格式的回调协议,我们在处理的时候,需要注意一下。不过接入的时候,可以查看其它已经实现的渠道代码中,各种格式的都有对应的已实现案例,可以参考。

这里还是以vivo sdk为例,这里他的Content-Type为application/x-www-form-urlencoded,大部分渠道的回调协议格式都是这个。 我们直接定义一个PayResult对象, 当成参数传入callback。 springboot会自动帮我们解析参数到对象中的各个字段。

java
package com.u8.server.web.controllers.partner.pay;

import com.u8.server.common.contants.Consts;
import com.u8.server.common.logger.UGLogger;
import com.u8.server.common.utils.EncryptUtils;
import com.u8.server.common.utils.StringUtils;
import com.u8.server.entities.U8Channel;
import com.u8.server.entities.U8Game;
import com.u8.server.entities.U8Order;
import com.u8.server.service.*;
import com.u8.server.service.common.I18nService;
import org.apache.http.util.TextUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * VIVO
 */
@Controller
@RequestMapping("/partner/pay/vivo")
public class ViVoPayController {
    private final String TAG = "vivo";
    private static final UGLogger logger = UGLogger.getLogger(ViVoPayController.class);

    private final UserService userService;

    private final OrderService orderService;

    private final ChannelService channelService;

    private final GameService gameService;

    private final OrderPlatformLogService platformLogService;

    private final I18nService i18nService;

    public ViVoPayController(UserService userService,
                             OrderService orderService,
                             ChannelService channelService,
                             GameService gameService,
                             OrderPlatformLogService platformLogService,
                             I18nService i18nService) {
        this.userService = userService;
        this.orderService = orderService;
        this.channelService = channelService;
        this.gameService = gameService;
        this.platformLogService = platformLogService;
        this.i18nService = i18nService;
    }

    @RequestMapping(value = "/callback", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String payCallback(PayResult result) {

        long orderID = Long.parseLong(result.cpOrderNumber);
        U8Order order = orderService.getOrderByID(orderID);
        if (order == null) {
            // 订单不存在
            logger.error("{} pay callback failed:{}. order is not exists.", TAG, result.cpOrderNumber);
            return "fail";
        }

        if (order.getStatus() == Consts.OrderStatus.FINISH) {
            // 订单状态不是已支付
            logger.error("{} pay callback ignored:{}. status:{} is already finished", TAG, order.getId(), order.getStatus());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.status.finish"));
            return "success";
        }

        U8Channel channel = this.channelService.getChannel(order.getChannelID());
        if (channel == null) {
            // 渠道不存在
            logger.error("{} pay callback failed:{}. channel:{} is not exists", TAG, order.getId(), order.getChannelID());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.channel.not.exists"));
            return "fail";
        }

        final U8Game game = gameService.getGameByID(order.getAppID());
        if (game == null) {
            // 游戏不存在
            logger.error("{} pay callback failed:{}. game:{} is not exists", TAG, order.getId(), order.getAppID());
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.game.not.exists"));
            return "fail";
        }

        if (order.getNotifyStatus() == Consts.NotifyStatus.NotifySuccess) {
            // 订单已经通知过游戏服务器了
            logger.error("{} pay callback ignored:{}. order is already notified to game server", TAG, order.getId());
            platformLogService.addNotifyLog(order, true, i18nService.getMessage("err.pay.already.notify"));
            return "success";
        }

        if (!isSignOK(order, channel, result)) {
            // 签名验证失败
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.sign.not.match"));
            return "fail";
        }

        int realMoney = Integer.parseInt(result.orderAmount);
        if(realMoney < order.getPrice()){
            // 金额验证错误
            logger.error("{} pay callback failed:{}. price:{} is not matched with OrderMoney:{}", TAG, order.getId(), order.getPrice(), realMoney);
            platformLogService.addNotifyLog(order, false, i18nService.getMessage("err.pay.price.not.match"));
            return "fail";
        }

        // 完成订单,并通知给游戏服务器
        orderService.completeOrder(game, order, result.orderNumber, realMoney);

        return "success";

    }


    // 校验签名
    private boolean isSignOK(U8Order order, U8Channel channel, PayResult rsp) {

        StringBuilder sb = new StringBuilder();
        sb.append("appId=").append(rsp.appId).append("&");

        if(!StringUtils.isEmpty(rsp.channelInfo)){
            sb.append("channelInfo=").append(rsp.channelInfo).append("&");
        }
        sb.append("cpId=").append(rsp.cpId).append("&");

        sb.append("cpOrderNumber=").append(rsp.cpOrderNumber).append("&");


        if(!TextUtils.isEmpty(rsp.extInfo)){
            sb.append("extInfo=").append(rsp.extInfo).append("&");
        }

        sb.append("orderAmount=").append(rsp.orderAmount).append("&")
                .append("orderNumber=").append(rsp.orderNumber).append("&")
                .append("payTime=").append(rsp.payTime).append("&")
                .append("respCode=").append(rsp.respCode).append("&");

        if(!TextUtils.isEmpty(rsp.respMsg)){
            sb.append("respMsg=").append(rsp.respMsg).append("&");
        }

        sb.append("tradeStatus=").append(rsp.tradeStatus).append("&")
                .append("tradeType=").append(rsp.tradeType);

        if(!TextUtils.isEmpty(rsp.uid)){
            sb.append("&").append("uid=").append(rsp.uid);
        }

        sb.append("&").append(EncryptUtils.md5(channel.getCpAppKey()).toLowerCase());

        String signLocal = EncryptUtils.md5(sb.toString());

        boolean valid = signLocal.equalsIgnoreCase(rsp.signature);
        if (!valid) {
            logger.error("{} pay callback failed:{}. sign not match. sign:{}; sign local:{}; sign string:{}", TAG, order.getId(), rsp.signature, signLocal, sb.toString());
        }
        return valid;
    }


    private static class PayResult {
        private String respCode     ;                   //响应码   200
        private String respMsg      ;                   //响应信息  交易完成
        private String signMethod   ;                   //签名方法  对关键信息进行签名的算法名称:MD5
        private String signature    ;                   //签名信息  对关键信息签名后得到的字符串1,用于商户验签签名规则请参考附录 2.6.3 消息签名
        private String tradeType    ;                   //交易种类  目前固定01
        private String tradeStatus  ;                   //交易状态  0000,代表支付成功
        private String cpId         ;                   //Cp-id 定长20位数字,由vivo分发的唯一识别码
        private String appId        ;                   //appId 应用ID
        private String uid          ;                   //用户在vivo这边的唯一标识
        private String cpOrderNumber;                   //商户自定义的订单号 商户自定义,最长 64 位字母、数字和下划线组成
        private String orderNumber  ;                   //交易流水号 vivo订单号
        private String orderAmount  ;                   //交易金额  单位:分,币种:人民币,为长整型,如:101,10000
        private String extInfo      ;                   //商户透传参数    64位
        private String payTime      ;                   //交易时间  yyyyMMddHHmmss
        private String channelInfo  ;                   //广告渠道号

        public String getRespCode() {
            return respCode;
        }

        public void setRespCode(String respCode) {
            this.respCode = respCode;
        }

        public String getRespMsg() {
            return respMsg;
        }

        public void setRespMsg(String respMsg) {
            this.respMsg = respMsg;
        }

        public String getSignMethod() {
            return signMethod;
        }

        public void setSignMethod(String signMethod) {
            this.signMethod = signMethod;
        }

        public String getSignature() {
            return signature;
        }

        public void setSignature(String signature) {
            this.signature = signature;
        }

        public String getTradeType() {
            return tradeType;
        }

        public void setTradeType(String tradeType) {
            this.tradeType = tradeType;
        }

        public String getTradeStatus() {
            return tradeStatus;
        }

        public void setTradeStatus(String tradeStatus) {
            this.tradeStatus = tradeStatus;
        }

        public String getCpId() {
            return cpId;
        }

        public void setCpId(String cpId) {
            this.cpId = cpId;
        }

        public String getAppId() {
            return appId;
        }

        public void setAppId(String appId) {
            this.appId = appId;
        }

        public String getUid() {
            return uid;
        }

        public void setUid(String uid) {
            this.uid = uid;
        }

        public String getCpOrderNumber() {
            return cpOrderNumber;
        }

        public void setCpOrderNumber(String cpOrderNumber) {
            this.cpOrderNumber = cpOrderNumber;
        }

        public String getOrderNumber() {
            return orderNumber;
        }

        public void setOrderNumber(String orderNumber) {
            this.orderNumber = orderNumber;
        }

        public String getOrderAmount() {
            return orderAmount;
        }

        public void setOrderAmount(String orderAmount) {
            this.orderAmount = orderAmount;
        }

        public String getExtInfo() {
            return extInfo;
        }

        public void setExtInfo(String extInfo) {
            this.extInfo = extInfo;
        }

        public String getPayTime() {
            return payTime;
        }

        public void setPayTime(String payTime) {
            this.payTime = payTime;
        }

        public String getChannelInfo() {
            return channelInfo;
        }

        public void setChannelInfo(String channelInfo) {
            this.channelInfo = channelInfo;
        }
    }
}

下单逻辑

VIVO渠道SDK的支付回调地址,是需要在客户端调用vivo支付接口的时候传入进去,而不是直接配置在vivo后台。 所以,我们可以在vivo的下单逻辑中,将u8server中vivo的渠道回调地址返回给客户端, 而不是让客户端直接写死。 如下:

java
@Override
public String createOrder(U8Game game, U8Channel channel, U8User user, U8Order order) {

    // 将渠道的支付回调地址放extension中返回给客户端, 客户端传给渠道那边
    return channel.getPayCallbackUrl();
}

后台添加配置

通过上面的步骤,该渠道SDK的登录认证和支付回调逻辑,我们都已经实现了。接下来,我们需要去u8server后台进行配置。以下几点须知:

1、渠道商管理中,添加一条渠道商信息。 如果接客户端部分的时候,已经添加了,这里就不需要再次添加了。只需要将其中服务端相关的配置修改一下即可。渠道商里面主要配置该渠道SDK登录认证地址,下单地址,支付回调相对地址,以及该渠道SDK登录认证处理类脚本类的完整路径等信息。

2、渠道管理中,添加一条渠道信息。如果接客户端部分的时候,已经添加了,这里就不需要再次添加了。配置该渠道所属的游戏,所属的渠道商,以及登录认证和支付回调中需要用到的渠道参数。

3、渠道商和渠道配置的关系,这里要区分下。 比如百度这个渠道, 渠道商就一个,即百度。但是每个游戏都会有一个对应的百度渠道配置记录。比如ABC三款游戏,他们每个都会对应一个百度的渠道配置记录,用来配置百度分配给他们的appID、appKey等参数信息。

4、登录认证和支付回调中,有时候需要用到渠道参数,进行签名或者签名验证。渠道参数配置在,渠道管理中该渠道的cpID,cpAppID,cpAppKey,cpPayID,cpPayPrivateKey,cpPayKey等几个字段中。这几个参数字段,可以理解为参数1,参数2,参数3...。 所以,比如vivo的渠道,你如果将appkey配置到cpAppKey字段中,那么代码中,你需要用到appkey时,只需要调用channel.getCpAppKey()接口来获取。同理,你如果配置到cpPayID字段中,那么就调用channel.getCpPayID()接口来获取。

如果你还不知道如何添加和配置渠道商和渠道,请看视频教程:U8SDK管理后台使用

版权所有© 2021-2030 上海丞诺网络科技有限公司