Skip to content

服务端接入接口说明

如果您的游戏有自己的游戏服务器。并且游戏玩家数据由你的服务器保存和管理。则需要服务端进行接入:

模块说明接入建议
用户系统你需要将Jogos平台用户系统与您服务器的用户进行关联。则需要由服务端接入用户系统K☑️必要接入
内购系统你的游戏有内购项目,则需要接入Jogos内购系统,以保障用户付费和数据安全☑️内购游戏必要接入

接入用户系统

1 由客户端获取用户令牌

2 概述

从jogos 获取用户token及publickey后,回传到游戏服务端,进行验证用获取用户信息,注意:每次验证令牌时都获取密钥publickey,因为它随时可能会改变。

3 验证逻辑

  • 游戏开发者从jogos获取用户token回传到游戏开发者服务端。
  • 由服务端获取publickey后(通过https://www.jogos.com/publicKey.json 获取)
  • 根据Base64编码的publickey字符串获取RSA公钥。
  • 根据RSA公钥进行JWT解密token。返回的令牌解码可以在 jwt.io 上进行测试。
  • 解码后获取用户信息,与你的游戏服务器的用户经行绑定。
  • 如解密不成功,则返回空的用户信息。

4 代码示例

工程中导入jwt:

  <!--JWT-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.2</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.2</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.2</version>
    </dependency>
java
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import com.alibaba.fastjson.JSONObject;

public class JwtUtils {

	
	public static void main(String[] args) {
		/**
		 * 从jogos sdk 获取publickey和用户token,传到服务端,如以下的publickey:
		  
		  -----BEGIN PUBLIC KEY-----
		  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhyUrr/W8bSuC+HK8Rk++BDOGDefGMEBa9jekVwVE3oeqi7QbVzZPAuqb1K3GPJjDBfi44IWzb3w8Xa0P1ZeO2Cnbg1LltanlzFv/EcKseCIkOd8Qo78ARPfmlf4WP6MznYGKwNVGFh/s5Y6ar8QgWX1ttkcwYzHu/gUroO+nPOZkU6bfxHjRrJxk3lSQBZWfTSFd2JFwntq3h45UHymjQZiMtW47G2C4VTtTTt1Iz0VQ9rdtWie/+REqQYoFUm04Yns9jyG3TZzio9vsRrxESLQbBIRxto77cQNtEe9j/2EXNwQabRkiS6zo6i2TIN4O6uthWBVud5WXsBWdiyIOHQIDAQAB
		  -----END PUBLIC KEY-----

		和用户token:
		  eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIxOTY3NDIzNTM3NjE1NTgxMTg0Iiwic3ViIjoie1wicHJvZmlsZVBpY3R1cmVVcmxcIjpcImh0dHBzOi8vaW1hZ2Uuam9nb3NwcmUuY29tLzBlZTkyZjViZTRhMzQ4NzBhYjY0YWU1N2FjN2I1YmE0LmpwZ1wiLFwiZ2FtZUlkXCI6XCIxMjIwXCIsXCJ1c2VySWRcIjpcIjI1N1wiLFwidXNlcm5hbWVcIjpcIlBsYXllcjI1N1wifSIsImlzcyI6ImNvbTpqb2dvczpzZGsiLCJpYXQiOjE3NTc5MDUyOTgsImV4cCI6MTc2MTUwNTI5OH0.MKBab0XC1o5AYGosbq1l5m9xCLN1-4xfcr7_dHn-_C8Eh2moTuq9TbQutIajB3dViFW0e1KyIg7UhxNk00rdhT2b5UrvO23tyFsgg9FYyAyCZABURxHyI0lTW9V8YA9k4faycK_gCUMXH_IubseDMz1P7cYmPpo8WxJXZq3R-mL8OhhuKCn8DlpP5BVsd0_gYSTvDUD0gjdINLUNTVrZVkETcDVWW8OXQVzJxdTH0VVlDs4cGvIyko8TJ6g1Bvz4VWj4qy1XoQFfRBH8sgBl3oGJtUodhq3b4bOx4Cr_o-2tK54CetDdmmCPbtcbmmDcBqK5EQ51A2Kws-1cYtsFxg
		     
		  注:每次验证令牌时都获取密钥publickey,因为它可能会更改  
		 */
		
		String publickey ="您的publickey";
		String token = "您的用户token";
		
		System.out.println("publickey:["+publickey+"]");
		System.out.println("token:["+token+"]");
		Map<String, Object> userMap = verifyToken(token,publickey);
		// 如果Token解析成功,userMap不为空
        if (userMap != null) {
        	System.out.println("验证通过,用户信息为:");
        	System.out.println(JSONObject.toJSONString(userMap));
        }else {
        	System.out.println("jwt验证不通过");
        }
	}
	
	
	/**
	 * 验证token
	 * @param token
	 * @return
	 */
	public static Map<String, Object> verifyToken(String token,String publickey){  
		
		// 尝试解析Token,如果解析失败则直接返回null
		Map<String, Object> map = null;
        try {
        	map = parseToken(token,publickey);
        } catch (Exception e) {
            
        }
        return map;
         
    }
	
	/**
	 * 解密token
	 * @param token
	 * @return
	 */
	public static Map<String,Object> parseToken(String token,String publickey){
		//去除开头和结尾部分 
		if(publickey.startsWith("-----BEGIN PUBLIC KEY-----")) {
			publickey = publickey.substring(27,publickey.length()-25);
		}
		Claims claims = (Claims) Jwts.parser()
	                .setSigningKey(getPublicKey(publickey))
	                .parse(token)
	                .getBody();
        String parseToken = claims.getSubject();
        
        if (parseToken != null) {
        	Map<String, Object> map = JSONObject.parseObject(parseToken, Map.class);
			return map;
 
 
        }
        return null;
    }
	
	 /**
     * 根据Base64编码的字符串获取RSA公钥
     * @param key Base64编码的公钥字符串
     * @return 转换后的RSA公钥
     * <p>
     * 注意:该方法使用Base64解码器将输入的字符串解码为字节数组,然后使用KeyFactory和X509EncodedKeySpec
     * 将字节数组转换为PublicKey对象。如果转换过程中发生异常,将抛出RuntimeException。
     */
    private static PublicKey getPublicKey(String key) {
        byte[] decode = Base64.getDecoder().decode(key);
        PublicKey publicKey;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decode);
            publicKey = keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
 
            throw new RuntimeException(e);
        }
        return publicKey;
    }
}

接入内购系统

开始接入前,请先确认:

  • 您当前使用是 www.jogospre.com(测试环境)或者 www.jogos.com(正式环境);下文所提及的 POST 请求 URL 需要改成 对应地址
    (例如测试环境到货 API 为:https://api.jogospre.com/api/gamepay/webhook/arrivedorder)

  • 确保您已经在开发者平台创建游戏应用,并在“游戏参数”中已经生成好您的游戏的 共享密钥

  • 确保您的游戏选项中,已勾选“使用应用内购买”模式,并输入您服务器接收 回调通知地址

  • 通信中采用 SHA-1 加密方式,具体用法如下:

    javascript
    private String sha1(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] hashInBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
    
            StringBuilder sb = new StringBuilder();
            for (byte b : hashInBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-1 algorithm is not available", e);
        }
    }

客户端前置接入:

开始服务端接入:

1. 用户在游戏中购买商品成功后,Jogos 平台发送支付成功的通知

  • Jogos 平台会发送支付成功回调信息到您配置的 回调通知地址

  • 请求头:

    字段名类型可否为空描述
    Authorizationstring验证头,sha1(body+秘钥),其中秘钥为该游戏共享秘钥,在游戏参数中设置
  • 请求体包含以下字段:

    字段名类型可否为空描述
    paytypeString通知类型 pay:支付成功通知;refund:退款成功通知
    gameIdint游戏 ID
    orderIdString订单编号
    arrivedStatusint到货状态: 0 未到货,1 已到货
    productIdString产品 ID
    createTimelong订单创建时间(Unix 时间戳)
    payTimelong订单支付成功时间(Unix 时间戳)
    userIdint用户 ID
    refundStatusint退款状态,发生退款时不为空 0:退款中,1:退款成功; 2:退款失败;3:拒绝退款
    refundTimelong退款时间(Unix 时间戳),发生退款时不为空

    请求体示例:

    javascript
    {
        "gameId"9968,
        "orderId""zXhKYRy3genKKh9TgndBpu1gp5tBpA",
        "arrivedStatus"0,
        "productId""1002",
        "createTime"1755657098,
        "payTime"1755657115,
        "userId"278,
        "refundStatus"null,
        "refundTime"null
    }
    • 返回的 JSON 对象包含以下字段:(code:200 代表成功)
    {
        "code": "200",
        "msg": "Success"
    }
  • 错误码:

    • 305 必填参数为空
    • 40001 未找到对应的游戏
    • 41007 验证不通过
    • 500 系统异常
    *一般提醒*
    • 当游戏服务端未返回 200 时,支付服务将 3 小时后重试多次发送,最多 5 次。
    • 收到支付服务端通知时,需要对参数进行验证,验证方式如下:
    javascript
    public boolean verifySignature(String body, String authorizationHeader) throws Exception
    {
        if (authorizationHeader == null || !authorizationHeader.startsWith("Signature "))
        {
            throw new Exception("\"Authorization\" header not found or invalid in Jogos webhook request.");
        }
    
        String clientSignature = authorizationHeader.substring(10); // Skip "Signature "
        String serverSignature = sha1(body + your_key);//your_key为游戏特定共享秘钥,在游戏参数中设置
    
        return clientSignature.equals(serverSignature);
    }

2. 游戏服务端发货完成后,需调用“到货状态更新”接口,将订单标识为已发货状态

  • 请求 URL:[POST] https://api.jogos.com/api/gamepay/webhook/arrivedorder

  • 请求头:

    字段名类型可否为空描述
    Authorizationstring验证头,sha1(body+秘钥),其中秘钥为该游戏共享秘钥,在游戏参数中设置
  • 请求体:

    字段名类型可否为空描述
    gameIdint游戏 ID
    orderIdint订单编号

    请求体示例:

    javascript
    {
        "gameId"9943,
        "orderId":"gHpGwweTlucXwcM6yIeSMcgKkhFmoO"
    }
  • 返回的 JSON 对象包含以下字段:(code:200 代表成功)

    {
        "code": "200",
        "msg": "Success"
    }
  • 错误码:

    • 305 必填参数为空
    • 40001 未找到对应的游戏
    • 41007 验证不通过
    • 500 系统异常

3. 游戏服务端查询订单列表,同步订单及到货状态

  • 请求 URL:[POST] https://api.jogos.com/api/gamepay/getOrders

  • 请求头:

    字段名类型可否为空描述
    Authorizationstring验证头,sha1(body+秘钥),其中秘钥为该游戏共享秘钥,在游戏参数中设置
  • 请求体:

    字段名类型可否为空描述
    gameIdint游戏 ID
    pageNoint页码
    pageSizeint分页数

    请求体示例 :

    javascript
    {
        "gameId"9943,
        "pageNo":1
        "pageSize":20
    }
  • 返回的 JSON 对象包含以下字段:(code:200 代表成功)

    字段名类型可否为空描述
    codeString状态码,表示请求的结果 (例如: SUCCESS)
    messageString提示信息 (例如: 登出成功)
    totalInteger总数
    pageSizeInteger每页数量
    currentPageInteger当前页
    totalPageInteger总页数
    orderIdString订单编号
    arrivedStatusInteger到货状态;0 未到货,1 已到货
    payTimelong支付时间(Unix 时间戳)
    productIdString产品 ID
    userIdInteger用户 id
    refundStatusInteger退款状态,发生退款时不为空:0:退款中,1:退款成功,2 退款失败 ,3 拒绝退款
    refundTimelong退款时间(Unix 时间戳),发生退款时不为空
    json
    {
      "code": "200",
      "msg": "Success",
      "page": {
        "total": 31,
        "totalPage": 7,
        "currentPage": 1,
        "pageSize": 5,
        "content": [
          {
            "orderId": "gHpGwweTlucXwcM6yIeSMcgKkhFmoO",
            "arrivedStatus": 1,
            "payTime": 1112211221,
            "productId": "game_pro1",
            "userId": 278,
            "refundStatus": null,
            "refundTime": null
          }
        ]
      }
    }
  • 错误码:

    • 305 必填参数为空
    • 40001 未找到对应的游戏
    • 41007 验证不通过
    • 500 系统异常

4. 当用户发生退款时,向游戏服务端发送退款成功通知

  • Jogos 平台会发送该用户申请退款成功的订单回调信息到您配置的 回调通知地址
  • 请求体与第 1 条“支付成功通知”一样。
  • 建议开发者根据退款订单的信息,酌情扣减或限制玩家之前充值所得的奖励道具。