Skip to content

伺服器端接入介面說明

如果您的遊戲擁有自己的遊戲伺服器,且遊戲玩家資料由您的伺服器儲存和管理,則需要進行伺服器端接入:

模組說明接入建議
使用者系統您需要將 Jogos 平台使用者系統與您伺服器的使用者進行關聯,則需要由伺服器端接入使用者系統☑️ 必要接入
內購系統如果您的遊戲有內購項目,則需要接入 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 條「支付成功通知」一樣。
  • 建議開發者根據退款訂單的資訊,酌情扣減或限制玩家之前充值所得的獎勵道具。