Appearance
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);方法完成订单,并通知游戏服务器给玩家发货。
5、return "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。
3、new 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管理后台使用