注意:下面红色字体步骤需要游戏接入。
1. 游戏客户端调用 SuperSDK 框架 Login 接口。
2. 弹出渠道登录界面,渠道 SDK 内部向渠道服务端发起登录请求验证。
3. 用户登录成功,渠道 SDK 获取到渠道服务端返回的 token。
4. SuperSDK 客户端将从渠道 SDK 得到的 token,提交给 SuperSDK 服务端进行验证。
5. SuperSDK 服务端接收到 SuperSDK 客户端请求的数据后将去渠道服务端进行用户登录验证。
6. SuperSDK 服务端接收渠道服务端的验证结果。
7. SuperSDK 服务端将渠道返回信息和用户信息进行封装(osdk_ticket)。
8. SuperSDK 服务端将封装好的数据(osdk_ticket)返回 SuperSDK 客户端。
9. SuperSDK 客户端将收到的加密数据(osdk_ticket)抛给游戏客户端。
10. 游戏客户端转发加密数据(osdk_ticket)给游戏服务端校验。
11. 游戏服务端对加密数据进行解析校验,取出需要的参数用。
12. 游戏服务端返回游戏客户端登录成功。
1. 将得到的 osdk_ticket 进行 base64 解码(校验 osdk_ticket 的生成时间,建议不超过 3 分钟)。
2. 得到 sign,user_id 等值,将 sign 与通过签名算法得到的签名进行验证,key 值获取方式。(参考:接入前准备)
3. 签名算法请见下方:签名算法。
php代码范例:
function getOsdkTicket($ticket,$key){
$data = json_decode(base64_decode($ticket),true);
$sign=$data['sign'];
unset($data['sign']);
ksort($data);
$str = array();
foreach ($data as $k => $v) {
$str[] = $k . "=" . urldecode($v);
}
$strData = implode("&", $str) . $key;
$strSign = md5($strData);
if($sign==$strSign){
echo "校验成功,osdkTicket数据是:".json_encode($data);
}else{
echo "校验失败";
}
}
golang代码范例:
func getOsdkTicket(ticket string, key string) {
jsonData, err := base64.StdEncoding.DecodeString(ticket)
if err != nil {
fmt.Println("base64解析错误")
return
}
data := make(map[string]interface{})
err = json.Unmarshal(jsonData, &data)
if err != nil {
fmt.Println("json解析错误")
return
}
var keys []string
for k := range data {
if k != "sign" {
keys = append(keys, k)
}
}
sort.Strings(keys)
var dataParams []string
for _, k := range keys {
v := ""
switch t := data[k].(type) {
case string:
v = data[k].(string)
case float64:
v = fmt.Sprintf("%d", int64(data[k].(float64)))
default:
fmt.Printf("Unexpected type %T\n", t)
return
}
v, _ = url.QueryUnescape(v)
dataParams = append(dataParams, k+"="+v)
}
h := md5.New()
h.Write([]byte(strings.Join(dataParams, "&") + key))
if hex.EncodeToString(h.Sum(nil)) == data["sign"] {
fmt.Println("校验成功,osdkTicket数据是:", data)
} else {
fmt.Println("校验失败")
}
}
参数名称 | 类型 | 说明 |
---|---|---|
osdk_game_id | string | SuperSDK 游戏编号 |
user_id | string | 第三方联运商用户标识 |
login_sdk_name | string | 第三方联运商名称 |
account_system_id | string | 用户账号系统编号(用户体系按联运平台划分,每个联运平台一个用户帐号系统编号) |
osdk_user_id | string | 用户唯一标识,格式:osdk_user_id = account_system_id + _ + user_id |
channel_id | string | 渠道分包编号 |
extend | string | 打包工具中的配置参数 |
country | string | 国家码,已废弃,为了兼容老版本,字段保留,值为 00 ,游戏如需获取国家码,请调用客户端相应方法 |
time | int | sign 生成的时间戳 |
ip | string | 客户端请求的 ip 地址 |
sign | string | 签名 |
参数范例:
{
"osdk_game_id": "132435",
"user_id": "837263",
"account_system_id": "0060001",
"osdk_user_id": "0060001_837263",
"login_sdk_name": "360",
"channel_id": "0",
"extend":"",
"country":"00",
"ip": "128.1.1.10",
"time": 149382731,
"sign": "21232f297a57a5a743894a0e4a801fc3"
}
注意:下面红色字体步骤需要游戏接入。
1. 游戏客户端调用 SuperSDK 客户端支付 pay 接口。
2. SuperSDK 客户端请求 SuperSDK 服务端下单接口。
3. SuperSDK 服务端返回本次交易订单号。
4. SuperSDK 客户端调用渠道 SDK 支付接口,渠道 SDK 请求渠道服务端发起支付。
5. 渠道服务端返回渠道 SDK 下单成功,渠道 SDK 调起支付页面等待玩家付款。
6. 渠道 SDK 请求渠道服务端,玩家付款成功。
7. 渠道 SDK 返回 SuperSDK 客户端支付成功。
8. SuperSDK客户端返回游戏客户端支付成功。(游戏不能以 SuperSDK 客户端返回的支付状态信息来判断是否支付成功,具体订单状态以 SuperSDK 服务端通知为准。)
9. 渠道服务端异步验证玩家支付结果。
10. 渠道服务端通知 SuperSDK 服务端玩家支付成功。
11. SuperSDK 服务端验证订单。
12. 给渠道服务端返回验证结果。
13. SuperSDK 服务端调用游戏发货接口(游戏研发提供)。
14. 游戏服务端验证订单信息.
15. 游戏服务端返回 SuperSDK 服务端验证结果。
16. 游戏服务端通知游戏客户端支付结果。
注意点(防止代充行为):
a. 请游戏服务端验证 osdk_user_id、server_id 和 game_role_id 三者关系的唯一性。
b. 请游戏服务端验证 product_id 和 amount 的一致性,如果游戏依赖扩展字段 sdk_pay_extend 发货,请同时校验 sdk_pay_extend 和 amount 的一致性。
1. 接口地址:游戏服务端提供
2. 通信协议:HTTP
3. 请求方法:POST
4. 消息主体编码方式:x-www-form-urlencoded
5. 字符编码:UTF-8
6. 请求参数:
参数名称 | 类型 | 是否一定传值 | 说明 |
---|---|---|---|
order_id | string | 是 | SuperSDK 订单号 |
coo_order_id | string | 是 | 第三方联运营商订单号 |
user_id | string | 是 | 第三方联运商用户标识 |
op_id | int | 是 | 联运平台编号 |
account_system_id | string | 是 | 用户账号体系编号(用户体系按联运平台划分,每个联运平台一个用户帐号系统编号) |
osdk_user_id | string | 是 | 用户唯一标识:osdk_user_id=account_system_id + _ + user_id |
amount | float | 是 | 玩家支付的总金额,如 0.99、1.99,如果游戏提供商品配置表,则为表中的金额 |
currency | string | 是 | 国内:没有该字段,默认 元;海外:如果游戏提供商品配置表,则为表中的货币单位 |
server_id | string | 是 | 游戏服务器 id |
product_id | string | 是 | 游戏商品 id |
product_name | string | 否 | 游戏商品名称,无值时为空字符串 |
game_id | int | 是 | 游戏 id |
game_role_id | string | 是 | 角色 id |
pay_status | int | 是 | 1:真实支付,0:沙盒支付,无论此字段为何值游戏均需给玩家发货,但为 0 时该笔订单不参与对账 |
pay_time | int | 是 | 联运商通知过来的支付 UNIX 时间戳(从格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒起至现在的总秒数 |
channel_id | string | 否 | 渠道分包 id,无值时为空字符串 |
sdk_pay_extend | string | 否 | 游戏侧透传参数,游戏客户端传入 payExtra 字段的值,无值时为空字符串 |
custom_data | string | 否 | 渠道侧扩展参数,比如订阅商品,打折等功能使用,无值时为空字符串 |
sign | string | 是 | 签名,参考下述签名算法,key 为游戏服 server_key,运营提供或者从开服系统获取 |
amount 和 currency 说明
1. 如果游戏提供了商品配置表,则 amount 和 currency 为表中的商品金额和货币单位,以配置表中的金额和货币单位为准。
2. 商品配置表由运营负责提供。
3. Google、AppStore 均会配置商品配置表。
4. 如果游戏未提供商品配置表,amount 和 currency 为渠道提供的支付金额和货币单位。
商品配置表模板说明
1. 配置表中映射游戏商品 id 和渠道商品 id 的原因:
a. 各渠道商品 id 命名规则不同
b. 游戏商品 id 的命名规则可能不满足渠道商品 id 的命名规则
2. SuperSDK 在调渠道支付前将游戏商品 id 转换成渠道的商品 id 方可支付。
3. SuperSDK 与游戏交互均采用游戏商品 id,游戏侧无需关心渠道商品 id。
打折功能发货说明
custom_data 传入 json,格式如下
{
"is_discount":1,
"discount_price":xxx,
"original_price":xxx
}
1. custom_data 可能为空字符串,可能为其他字符串值,只有在 custom_data 是 json 格式并且 json 里面的 is_discount=1 时表示打折。
2. orginal_price 是原价,discount_price 是减少的价格,amount 是折后价。
3. orginal_price = discount_price+amount,游戏可以做金额的强校验。
说明:游戏返回的 body 必须为 json ,json key 为 status 和 msg。
注意:键名区分大小写,Status 和 Msg 均认为失败。
示例:
{
"status":1, // 1 表示成功,非 1 表示失败
"msg":"支付成功" // 游戏可以修改 msg 里面的内容,msg 最大支持100字符
}
status | msg |
---|---|
1 | 支付成功 |
-1 | 签名信息错误 |
-2 | 商品信息错误 |
-3 | 角色信息错误 |
-4 | 服务器信息错误 |
-5 | 其它错误 |
说明:只有充值成功的订单,SuperSDK 服务端才会通知游戏服务端。游戏服务端收到通知后,根据处理结果进行响应。
注意:当且仅当网络请求失败或 status=-1(签名错误)时, SuperSDK 服务端才会尝试多次通知!!!
重复通知
1. 支付通知会重复通知,为防止因收到多次支付通知而给玩家多次加钱,游戏服务端必须使用 order_id 做去重处理。
2. 正常收到消息或收到重复通知时均需按成功返回,否则 SuperSDK服务端会认为未成功导致重复通知。
3. 重复通知间隔为:1 分钟,4 分钟,9 分钟,16 分钟,25 分钟,36 分钟,49 分钟,64 分钟,81 分钟,100 分钟共 10 次,直到中间有收到接收成功的反馈消息,或 10 次发送完,结束周期性发送。
1. 所有参数按照参数名字母升序排列,sign 不参与签名。
2. 将排序后的参数名与对应的参数值(非 urlencode 的值)用 & 拼接在一起,如:a=1,b=2,c=3 拼接得到 a=1&b=2&c=3
3. 然后在直接连接 key 值,做一次 md5 运算并转换成小写,得到签名 waitSign 值。
4. 将 waitSign 与参数中的 sign 对比,相同则验证成功。
5. 参数名与参数个数可能会有变动,建义不要把参数写死,可直接把获取到的所有参数除 sign 以外参与签名。
6. key 对应的 value 如果是空的,也要参与签名,见下方示例里面的 key:b
范例:收到数据为 UTF-8 格式,并已经使用 urlencode 编码:a=%e5%85%83%e5%ae%9d&c=1&b=&sign=5,而:key=k waitSign=md5(“a=元宝&b=&c=1k”)
注意:参与签名的参数不能写死,动态获取,以便后续有参数扩展。
php 代码范例:
function sign($data, $key)
{
ksort($data);
$str = array();
foreach ($data as $k => $v)
{
$str[] = $k . "=" . urldecode($v);
}
$strData = implode("&", $str) . $key;
return md5($strData);
}
golang 代码范例:
func sign(data map[string]string, key string) string {
var keys []string
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
var dataParams []string
for _, k := range keys {
v, _ := url.QueryUnescape(data[k])
dataParams = append(dataParams, k+"="+v)
}
h := md5.New()
h.Write([]byte(strings.Join(dataParams, "&") + key))
return hex.EncodeToString(h.Sum(nil))
}
1. 国内接口请求示例:
header:
Content-type: application/x-www-form-urlencoded
body:
account_system_id=0060000
amount=6.00
channel_id=0
coo_order_id=OS_VMUMYXGRY4JJ42IY3
custom_data=2150|1234|opgameid
game_id=360
game_role_id=68719487024
op_id=2150
order_id=OS_VMUMYXGRY4JJ42IY3
osdk_user_id=0060000_3507
pay_status=1
pay_time=1562071618
product_id=gold6
product_name=60元宝
sdk_pay_extend={"level":23,"opSid":"2150","server_id":"1652440001","role_id":68719487024,"roleCreateTime":1561962929,"server_name":"外网QA1服","opgameid":"opgameid","role_name":"rel1","vip_grade":0,"account":"006}
server_id=1652440001
user_id=3507
sign=d123454bf14026f55481123446ab39fd
2. 海外接口请求示例:
header:
Content-type: application/x-www-form-urlencoded
body:
account_system_id=0060015
amount=1.99
channel_id=0
coo_order_id=GPA.3312-3316-3534-41943
currency=USD
custom_data=2226|1234|2701
game_id=2010403
game_role_id=F8C8E3189D444A7EAB4C20939EBCBC75
op_id=2226
order_id=US_TQJZMG7YSG1C2XIVT
osdk_user_id=0060015_1582650493919871832481233
pay_status=1
pay_time=1586941839
product_id=com.yoozoogames.test
product_name=Advanced Adventurer Pack
sdk_pay_extend=202004_252842_DD63F750CCFC45A79A4170DF89A8BB2B
server_id=2701310008
user_id=1582650493919871832481233
sign=d123454bf14026f55481123446ab39fd