搭配BaaS授权使用游戏内商店

您可以搭配BaaS授权系统使用游戏内商店来销售游戏内商品。该方案的交互过程如下:

  1. 用户通过BaaS授权系统登录您的应用程序。
  2. BaaS服务通过传入用户ID向艾克索拉服务器请求用户Web令牌(JWT)。
  3. 艾克索拉服务器向BaaS服务返回用户JWT。
  4. BaaS服务将用户JWT传给应用程序。
  5. 应用程序使用API通过用户JWT与艾克索拉服务器交互。
注:
如果您未实现用户授权的逻辑,可集成艾克索拉登录管理器并在PlayFab或Firebase中设置用户数据的存储。这样您就可以使用Login API来认证用户身份并接收JWT来与其他艾克索拉产品的API交互。
要获取用户JWT:
  1. 在发布商帐户中,将标准登录管理器项目连接到您的项目
  2. 设置服务器OAuth 2.0客户端
  3. 按照FirebasePlayFab的说明将现成函数添加到您的项目。

设置服务器OAuth 2.0客户端

  1. 发布商帐户中打开您的项目,然后前往登录管理器部分。
  2. 在登录管理器项目面板中单击配置
  3. 前往安全性区块,然后选择OAuth 2.0部分。
  4. 单击添加OAuth 2.0
  5. 指定OAuth 2.0重定向URI
  6. 勾选服务器(服务器对服务器连接)复选框。
  7. 单击连接
  8. 复制并保存客户端ID和密钥。

向Firebase项目添加云函数

  1. 初始化您的Firebase项目
  2. 导入并配置用户JWT的接收函数,其中:
    • <ProjectID>是项目ID,可在您发布商帐户中项目名称的旁边找到。
    • <LoginID>是登录管理器ID,获取方式是打开发布商帐户,前往登录管理器 > 仪表板 > 您的登录管理器项目部分,然后单击登录管理器项目名称旁边的复制ID
    • <OAuthClientID>设置服务器OAuth 2.0客户端时收到的客户端ID。
    • <OAuthSecretKey>设置服务器OAuth 2.0客户端时收到的密钥。

接收用户JWT的函数代码:

Copy
Full screen
Small screen
const projectId = "<ProjectID>";
const loginProjectId = "<LoginID>";

const serverOauthClientId = <OAuthClientID>;
const serverOauthClientSecret = "<OAuthSecretKey>";

exports.getXsollaLoginToken = functions.https.onCall((data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError(
        "failed-precondition",
        "The function must be called while authenticated."
    );
  }

  const postData =
      "grant_type=client_credentials" +
      `&client_secret=${serverOauthClientSecret}`+
      `&client_id=${serverOauthClientId}`;

  const options = {
    hostname: "login.xsolla.com",
    port: 443,
    path: "/api/oauth2/token",
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      "Content-Length": postData.length,
    },
  };

  return new Promise( (resolve, reject) => {
    const req = https.request(options, (res) => {
      if (res.statusCode !== 200) {
        reject(
            new functions.https.HttpsError(
                "internal",
                "Server token not received"
            )
        );
      }
      let body = [];
      res.on("data", (d) => {
        body.push(d);
      });
      res.on("end", () => {
        try {
          body = JSON.parse(Buffer.concat(body).toString());
        } catch (e) {
          reject(
              new functions.https.HttpsError(
                  "internal",
                  "Malformed server token response"
              )
          );
        }
        getClientToken(context.auth.uid, body.access_token, resolve, reject);
      });
    });
    req.on("error", (e) => {
      reject(new functions.https.HttpsError(
          "internal",
          "Internal error while server token flow"
      ));
    });

    req.write(postData);
    req.end();
  });
});

// eslint-disable-next-line require-jsdoc
function getClientToken(userId, serverToken, resolve, reject) {
  const postData = JSON.stringify(
      {
        "server_custom_id": userId,
      }
  );

  const path =
      "/api/users/login/server_custom_id?" +
      `projectId=${loginProjectId}&` +
      `publisher_project_id=${projectId}`;

  const options = {
    hostname: "login.xsolla.com",
    port: 443,
    path: path,
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": postData.length,
      "X-Server-Authorization": serverToken,
    },
  };

  const req = https.request(options, (res) => {
    if (res.statusCode !== 200) {
      reject(
          new functions.https.HttpsError(
              "internal",
              "Client token not received"
          )
      );
    }
    let body = [];
    res.on("data", (d) => {
      body.push(d);
    });
    res.on("end", () => {
      try {
        body = JSON.parse(Buffer.concat(body).toString());
      } catch (e) {
        reject(
            new functions.https.HttpsError(
                "internal",
                "Malformed client token response"
            )
        );
      }
      resolve({
        "token": body.token,
      });
    });
  });
  req.on("error", (e) => {
    reject(new functions.https.HttpsError(
        "internal",
        "Internal error while client token flow"
    ));
  });

  req.write(postData);
  req.end();
}
  1. 参照此示例将该函数部署到生产环境。
  2. 添加客户端侧逻辑从您的应用程序调用函数。将getXsollaLoginToken指定为函数名称。传参为非必需。
  3. 在应用程序中,自行实现与API交互的方法或使用艾克索拉SDK

向PlayFab项目添加云脚本

  1. 创建包含用户JWT接收函数的JS文件,其中:
    • <ProjectID>是项目ID,可在您发布商帐户中项目名称的旁边找到。
    • <LoginID>是登录管理器ID,获取方式是打开发布商帐户,前往登录管理器 > 仪表板 > 您的登录管理器项目部分,然后单击登录管理器项目名称旁边的复制ID
    • <OAuthClientID>设置服务器OAuth 2.0客户端时收到的客户端ID。
    • <OAuthSecretKey>设置服务器OAuth 2.0客户端时收到的密钥。
注:
如果已在项目中使用了云脚本,请将接收用户JWT的函数添加到该代码的末尾。
接收用户JWT的函数代码:
Copy
Full screen
Small screen
handlers.GetXsollaLoginToken = function (args) {

    // TODO replace with production credentials
    const projectId = <ProjectID>;
    const loginProjectId = "<LoginID>";
    const serverOauthClientId = <OAuthClientID>;
    const serverOauthClientSecret = "<OAuthSecretKey>";

    const getServerTokenBody =
        "grant_type=client_credentials" +
        `&client_secret=${serverOauthClientSecret}` +
        `&client_id=${serverOauthClientId}`;

    const serverTokenResponse = JSON.parse(
        http.request(
            "https://login.xsolla.com/api/oauth2/token",
            "post",
            getServerTokenBody,
            "application/x-www-form-urlencoded",
            {})
    );

    let serverToken = ""
    if ('access_token' in serverTokenResponse) {
        serverToken = serverTokenResponse.access_token;
    } else {
        return {
            "error_message": "Server token not received"
        }
    }

    const getUserTokenHeaders = {
        "X-Server-Authorization": serverToken
    }

    const getUserTokenBody = JSON.stringify(
        {
            "server_custom_id": currentPlayerId,
        }
    );

    const getUserTokenPath =
        "/api/users/login/server_custom_id?" +
        `projectId=${loginProjectId}&` +
        `publisher_project_id=${projectId}`;

    const userTokenResponse = JSON.parse(
        http.request(
            "https://login.xsolla.com" + getUserTokenPath,
            "post",
            getUserTokenBody,
            "application/json",
            getUserTokenHeaders)
    );

    if ('token' in userTokenResponse) {
        return {
            "token": userTokenResponse.token
        }
    } else {
        return {
            "error_message": "User token not received"
        }
    }
}

  1. 前往PlayFab项目设置。
  2. 上传云脚本文件。
  3. 在生产环境中运行云脚本。
  4. 添加客户端侧逻辑从应用程序调用函数。将GetXsollaLoginToken指定为函数名。传参为非必需。

调用用户JWT接收函数的示例:

Copy
Full screen
Small screen

kotlin

  • kotlin
  • C#
  • C++
val tokenRequest = PlayFabClientModels.ExecuteCloudScriptRequest()
tokenRequest.FunctionName = "GetXsollaLoginToken"
val res = PlayFabClientAPI.ExecuteCloudScript(tokenRequest)
val result = res.Result.FunctionResult as Map<*, *>
val token = result["token"]
val errorMessage = result["error_message"]
var tokenRequest = new ExecuteCloudScriptRequest{
  FunctionName = "GetXsollaLoginToken"
};

PlayFabClientAPI.ExecuteCloudScript(
  tokenRequest,
  scriptResult =>
  {
     var functionResult = scriptResult.FunctionResult as Dictionary<string, string>;
     var token = functionResult["token"];
  },
  playfabError => { Debug.LogError($"GetXsollaAccessToken error: {playfabError.ErrorMessage}"); });

void UMyClass::GetXsollaToken()
{
    FClientExecuteCloudScriptRequest tokenRequest;
    tokenRequest.FunctionName = TEXT("GGetXsollaLoginToken");

    UPlayFabClientAPI::FDelegateOnSuccessExecuteCloudScript onSuccess;
    onSuccess.BindUFunction(this, "OnTokenRecieved");

    UPlayFabClientAPI::FDelegateOnFailurePlayFabError onFailure;
    onSuccess.BindUFunction(this, "OnError");

    UPlayFabClientAPI::ExecuteCloudScript(tokenRequest, onSuccess, onFailure, nullptr);
}

void UMyClass::OnTokenRecieved(FClientExecuteCloudScriptResult result, UObject* customData)
{
    const FString& token = result.FunctionResult->GetStringField(TEXT("token"));

    // do something with a token
}

void UMyClass::OnError(FPlayFabError error, UObject* customData)
{
    // handle errors
}
注:
本例中使用PlayFab SDK方法向云脚本发送请求。您也可以不在项目中添加PlayFab SDK,而是自行实现请求和响应过程。
  1. 在应用程序中,自行实现与API交互的方法或使用艾克索拉SDK
本文对您的有帮助吗?
谢谢!
我们还有其他可改进之处吗? 留言
非常抱歉
请说明为何本文没有帮助到您。 留言
感谢您的反馈!
我们会查看您的留言并运用它改进用户体验。

有用链接

上次更新时间: 2024年10月3日

发现了错别字或其他内容错误? 请选择文本,然后按Ctrl+Enter。

报告问题
我们非常重视内容质量。您的反馈将帮助我们做得更好。
请留下邮箱以便我们后续跟进
感谢您的反馈!