Skip to content

Описание интерфейсов для интеграции на стороне сервера

Если ваша игра имеет собственный игровой сервер и данные игроков хранятся и управляются вашим сервером, требуется интеграция на стороне сервера:

МодульОписаниеРекомендация по интеграции
Система пользователейВам необходимо связать систему пользователей платформы Jogos с пользователями вашего сервера. Требуется интеграция системы пользователей на стороне сервера.☑️ Обязательная интеграция
Система внутриигровых покупокЕсли ваша игра имеет внутриигровые покупки, необходимо интегрировать систему внутриигровых покупок Jogos для обеспечения безопасности платежей и данных пользователей.☑️ Обязательна для игр с покупками

Интеграция системы пользователей

1 Получение токена пользователя на стороне клиента

2 Обзор

После получения токена пользователя (token) и открытого ключа (publickey) от Jogos, они передаются на игровой сервер для проверки и получения информации о пользователе. Важно: при каждой проверке токена необходимо получать актуальный открытый ключ (publickey), так как он может измениться в любой момент.

3 Логика проверки

  • Разработчик игры получает токен пользователя от Jogos и передает его на свой сервер.
  • Сервер получает открытый ключ (publickey) (через https://www.jogos.com/publicKey.json).
  • Получает RSA публичный ключ из строки publickey, закодированной в Base64.
  • Расшифровывает JWT токен с помощью RSA публичного ключа. Декодирование токена можно проверить на 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) {
		/**
		 * Получите publickey и токен пользователя из jogos sdk и передайте их на сервер, как показано ниже для publickey:
		  
		  -----BEGIN PUBLIC KEY-----
		  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhyUrr/W8bSuC+HK8Rk++BDOGDefGMEBa9jekVwVE3oeqi7QbVzZPAuqb1K3GPJjDBfi44IWzb3w8Xa0P1ZeO2Cnbg1LltanlzFv/EcKseCIkOd8Qo78ARPfmlf4WP6MznYGKwNVGFh/s5Y6ar8QgWX1ttkcwYzHu/gUroO+nPOZkU6bfxHjRrJxk3lSQBZWfTSFd2JFwntq3h45UHymjQZiMtW47G2C4VTtTTt1Iz0VQ9rdtWie/+REqQYoFUm04Yns9jyG3TZzio9vsRrxESLQbBIRxto77cQNtEe9j/2EXNwQabRkiS6zo6i2TIN4O6uthWBVud5WXsBWdiyIOHQIDAQAB
		  -----END PUBLIC KEY-----

		и токен пользователя:
		  eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIxOTY3NDIzNTM3NjE1NTgxMTg0Iiwic3ViIjoie1wicHJvZmlsZVBpY3R1cmVVcmxcIjpcImh0dHBzOi8vaW1hZ2Uuam9nb3NwcmUuY29tLzBlZTkyZjViZTRhMzQ4NzBhYjY0YWU1N2FjN2I1YmE0LmpwZ1wiLFwiZ2FtZUlkXCI�XCIxMjIwXCIsXCJ1c2VySWRcIjpcIjI1N1wiLFwidXNlcm5hbWVcIjpcIlBsYXllcjI1N1wifSIsImlzcyI6ImNvbTpqb2dvczpzZGsiLCJpYXQiOjE3NTc5MDUyOTgsImV4cCI6MTc2MTUwNTI5OH0.MKBab0XC1o5AYGosbq1l5m9xCLN1-4xfcr7_dHn-_C8Eh2moTuq9TbQutIajB3dViFW0e1KyIg7UhxNk00rdhT2b5UrvO23tyFsgg9FYyAyCZABURxHyI0lTW9V8YA9k4faycK_gCUMXH_IubseDMz1P7cYmPpo8WxJXZq3R-mL8OhhuKCn8DlpP5BVsd0_gYSTvDUD0gjdINLUNTVrZVkETcDVWW8OXQVzJxdTH0VVlDs4cGvIyko8TJ6g1Bvz4VWj4qy1XoQFfRBH8sgBl3oGJtUodhq3b4bOx4Cr_o-2tK54CetDdmmCPbtcbmmDcBqK5EQ51A2Kws-1cYtsFxg
		     
		  Примечание: Получайте ключ publickey при каждой проверке токена, так как он может измениться.
		 */
		
		String publickey ="Ваш publickey";
		String token = "Ваш токен пользователя";
		
		System.out.println("publickey:["+publickey+"]");
		System.out.println("token:["+token+"]");
		Map<String, Object> userMap = verifyToken(token,publickey);
		// Если токен успешно распарсен, userMap не будет пустым
        if (userMap != null) {
        	System.out.println("Проверка пройдена, информация о пользователе:");
        	System.out.println(JSONObject.toJSONString(userMap));
        }else {
        	System.out.println("JWT проверка не пройдена");
        }
	}
	
	
	/**
	 * Проверить токен
	 * @param token
	 * @return
	 */
	public static Map<String, Object> verifyToken(String token,String publickey){  
		
		// Попытаться распарсить токен, в случае неудачи вернуть null
		Map<String, Object> map = null;
        try {
        	map = parseToken(token,publickey);
        } catch (Exception e) {
            
        }
        return map;
         
    }
	
	/**
	 * Расшифровать токен
	 * @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;
    }
	
	 /**
     * Получить RSA публичный ключ из строки, закодированной в Base64
     * @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 (рабочая среда); URL-адреса POST-запросов, упомянутые ниже, необходимо изменить на соответствующие адреса. (Например, 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 timestamp)
    payTimelongНетВремя успешной оплаты заказа (Unix timestamp)
    userIdintНетID пользователя
    refundStatusintДаСтатус возврата, не пуст при возврате: 0 - возврат обрабатывается, 1 - возврат успешен; 2 - возврат не удался; 3 - возврат отклонен
    refundTimelongДаВремя возврата (Unix timestamp), не пусто при возврате

    Пример тела запроса:

    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); // Пропустить "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 timestamp)
    productIdStringНетID товара
    userIdIntegerНетID пользователя
    refundStatusIntegerДаСтатус возврата, не пуст при возврате: 0 - возврат обрабатывается, 1 - возврат успешен, 2 - возврат не удался, 3 - возврат отклонен
    refundTimelongДаВремя возврата (Unix timestamp), не пусто при возврате
    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 отправит информацию о successfully refunded order этого пользователя на настроенный вами адрес обратного вызова для уведомлений.
  • Тело запроса такое же, как в пункте 1 «Уведомление об успешной оплате».
  • Разработчикам рекомендуется на основе информации о заказе на возвратПри необходимости вычитается или ограничивается вознаграждение, полученное игроком до пополнения.. (Сократить или ограничить награды/предметы, полученные игроком за предыдущие пополнения, по своему усмотрению на основе информации о заказе на возврат).