Unity用SDK / ペイステーションとFirebase認証の組み合わせ使用について
  ドキュメントに戻る

Unity用SDK

ペイステーションとFirebase認証の組み合わせ使用について

Firebaseを使用してアプリケーションにユーザー認証を実装済みの場合、Firebase側で決済トークンを生成し、アプリケーションのクライアント側に渡して決済UIを開くことができます。

この統合オプションを使用すると、購入代金を支払うユーザーの国と通貨を決定するロジックを独自に実装する必要があります。

統合フロー:

  1. プロジェクトを作成します
  1. アドミンページに新規登録して、新しいプロジェクトを作成します。今後の手順で作成したプロジェクトのIDが必要です。

  1. カタログをセットアップします:
    • エクソーラ側のアイテムカタログを作成します。アイテムを手動で追加するか、Google PlayまたはPlayFabからインポートすることができます。
    • SDKを使用して、アプリケーションのクライアント側でカタログを取得して表示する機能を実装します。

  1. アイテム購入をセットアップします
    • アプリケーションのクライアント側で、Firebase Cloud Functionsを使って、ユーザーとアイテムのデータを含む注文を作成します。
    • SDKを使用して、アプリケーションのクライアント側で決済UIを開くように実装します。

  1. 注文状況の追跡をセットアップします

注意

統合を完了し、実際の支払いを受け付けるようにするには、エクソーラとライセンス契約を結ぶ必要があります。

ライセンス契約はいつでも統合のどのステップでも署名できますが、審査プロセスには最大で3営業日かかる可能性があることを覚えておいてください。

ペイステーションとFirebase認証の組み合わせ使用例として、サンプルウェブアプリケーションを使用してください。サンプルアプリケーションのソースコードはGitHubで利用可能です。

プロジェクトを作成する

アドミンページの新規登録

アドミンページは、エクソーラの機能を構成し、アナリティクスや取引を処理するための主要なツールです。

登録時に指定された会社とアプリケーションに関するデータは、エクソーラとのライセンス契約のドラフトを作成し、あなたに適したソリューションを提案するために使用されます。データは後で変更することができますが、新規登録時に正しいデータを提供することで、ライセンス契約締結までのプロセスが迅速化されます。

新規登録するには、アドミンページにアクセスし、アカウントを作成してください。

お知らせ

アドミンページのパスワードはラテン文字、数字、特殊文字で構成でき、少なくとも以下を含む必要があります:

  • 8文字以上
  • 1桁
  • 大文字1つ
  • 小文字1つ

パスワードのセキュリティを確保するために、以下をことを推奨します:

  • 少なくとも90日に1回はパスワードを変更する
  • アカウントの過去4回のパスワードと一致しない新しいパスワードを使用する
  • 他の場所で使用されているパスワードと一致しない固有のパスワードを使用する
  • 簡単にアクセスできる場所にパスワードを保存しない
  • パスワードマネージャーを使用してパスワードを保存する

アドミンページでは2要素認証が使用され、認証を試行するたびに確認コードが送信されます。

アドミンページでのプロジェクト作成

複数のアプリケーションを所有している場合は、それぞれのアプリケーションに対して個別のプロジェクトを作成することをお勧めします。プロジェクト作成時に指定したデータに基づいて、エクソーラはあなたに適したソリューションを提案します。

新たしいプロジェクトを作成するには:

  1. アドミンページを開きます。
  2. サイドメニューで、「プロジェクトの作成」をクリックします。

  1. プロジェクト名を英語で入力してください(必須)。

お知らせ
プロジェクトを作成した後、「プロジェクト設定」セクションで言語やローカライズされたプロジェクト名を追加することができます。

  1. ゲームのリリースプラットフォームを1つまたは複数選択します(必須)。
  2. ゲームへのリンクを追加します。ゲームがまだウェブサイトを持っていない場合は、ゲームに関する情報を含むソースへのリンクを追加します(必須)。
  3. ゲームエンジンを選択します。
  4. 使用している、または使用する予定の収益化オプションを選択します。
  5. ゲームがすでにリリースされているかどうかを指定します。ゲームがまだリリースされていない場合は、予定公開日を指定します。
  6. プロジェクトを作成する」をクリックします。おすすめのエクソーラ製品のページが表示されます。

統合プロセスでは、アドミンページでプロジェクト名の横に表示されているプロジェクトIDを入力する必要があります。

カタログのセットアップ

アドミンページでのアイテム作成

注意

エクソーラ側でカタログを作成する必要があります。アイテムを手動で追加するか、Google PlayまたはPlayFabからインポートすることができます。Google Playからインポートする場合、一度に最大100個のアイテムをインポートできます。

これらの説明は、仮想アイテムの基本設定手順を提供します。後で、カタログに他のアイテム(仮想通貨、バンドル、ゲームキー)を追加したり、アイテムグループを作成したり、プロモーションキャンペーンや地域価格などを設定したりすることができます。

基本設定の仮想アイテムをカタログに追加するには:

  1. アドミンページでプロジェクトを開きます。
  2. サイドメニューの「ストア」をクリックします。
  3. 仮想アイテム」ペインで、「接続」をクリックします。
  4. ドロップダウンメニューで、「アイテムを作成する」を選択します。

  1. 以下のフィールドでアイテムの基本設定を行います:
    • イメージ(任意)
    • SKU(アイテムの一意のID)
    • アイテム名
    • 説明(任意)

  1. アイテム価格を指定します:
    1. 実際通貨での価格」のトグルを「オン」に設定します。
    2. デフォルト通貨」フィールドで通貨を変更し(任意)、アイテム価格を指定します。
    3. デフォルト通貨」フィールドで通貨を変更した場合は、「実際通貨での価格」フィールドで同じ通貨を選択します。

お知らせ
カタログを取得するためのAPIコールが正しく機能するようにするには、デフォルト通貨と価格が指定されている通貨リストがすべてのアイテムで一致していることを確認してください。

  1. アイテムのステータスを「利用可能」に変更します。

  1. アイテムを作成」をクリックします。

アプリケーションのクライアント側にカタログの表示

  1. CDNまたはGitHubからSDKをダウンロードします。
  2. パッケージを解凍します。
  3. メインメニューで、Assets > Import Package > Custom Packageに移動し、ダウンロードしたSDKを選択します。
  4. メインメニューで、Window > Xsolla > Edit Settingsに移動します。
  5. Inspectorパネルに移動します。Project IDフィールドで、プロジェクト名の横のアドミンページにあるプロジェクトIDを指定します。

  1. アプリケーションのクライアント側で、製品カタログを表示するUIを追加します。
  2. エクソーラサーバーからのアイテムカタログのリクエストを実装します。

お知らせ

仮想アイテムのリストを取得するには、GetCatalog SDKメソッドを使用します。また、その他のSDKメソッドを使用して、カタログアイテムに関する情報を取得することもできます。

カタログページの作成手順については、アイテムカタログの表示を参照してください。

アイテム購入のセットアップ

Cloud Functionsを利用して注文を作成する

エクソーラ側でユーザーとアイテムのデータを使用して注文を作成するには、購入用の決済トークンを作成するAPIコールを使用するCloud Functionsをプロジェクトに追加します。このコールは決済UIを開き、購入を行うために必要な決済トークンを返します。

制限事項:

  • 決済トークンをリクエストする際に、ユーザーの国かIPアドレスを渡す必要があります。
  • トークンに通貨を渡さなかった場合、通貨は国によって決定されます。
  • トークンに通貨を渡すと、ユーザーはこの通貨で支払います。

注意
まず、Firebaseプロジェクトを作成して初期化し、Firebaseを使用してユーザー認証を有効にする必要があります。これらの手順の詳細については、以下のFirebaseの説明を参照してください:

プロジェクトにCloud Functionsを追加するには:

  1. Firebase CLI(コマンドラインインターフェース)をインストールします。これを行うには、CLIコマンドを実行します:

Copy
Full screen
Small screen
    npm install -g firebase-tools
    

    1. プロジェクトをFirebaseプロジェクトにリンクするには、CLIコマンドを実行してFirebaseプロジェクトを初期化します:

    Copy
    Full screen
    Small screen
      firebase init functions
      

      1. インストーラーの指示に従って設定を構成します:
        1. 既存のコードベースを選択します。
        2. Cloud Functionsを作成する言語としてJavaScriptを指定します。
        3. 依存関係をインストールします。

      1. functions/index.jsを開いて修正します:

      Copy
      Full screen
      Small screen
      // The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.
      const functions = require('firebase-functions/v1');
      
      const projectId = <projectId>;
      const apiKey = <apiKey>;
      
      exports.getXsollaPaymentToken = functions.https.onRequest((req, res) => {
      
        const requestBody = req.body;
          if (!requestBody) {
            res.status(400).send('Request body is missing');
            return;
          }
      
        const userId = requestBody.data.uid;
        const email = requestBody.data.email;
        const sku = requestBody.data.sku;
        const returnUrl = requestBody.data.returnUrl;
      
        const payload = {
          user: {
            id: {value: userId},
            name: {
              value: email
            },
            email: {
              value: email
            },
            country: {
              value: 'US',
              allow_modify: false
            }
          },
          purchase: {
            items: [
              {
                sku: sku,
                quantity: 1
              }
            ]
          },
          sandbox: true,
          settings: {
            language: 'en',
            currency: 'USD',
            return_url: returnUrl,
            ui: {
              theme: '63295aab2e47fab76f7708e3'
            }
          }
        }
      
        let url = "https://store.xsolla.com/api/v3/project/" + projectId.toString() + "/admin/payment/token";
      
        fetch(
          url,
          {
            method: "POST",
            headers: {
              'Content-Type': 'application/json',
              Authorization: 'Basic ' + btoa(`${projectId}:${apiKey}`)
            },
            body: JSON.stringify(payload)
          },
        )
        .then(xsollaRes => {
          // Handle the response data
          if (xsollaRes.ok) {
            return xsollaRes.json();
          } else {
            throw new Error(`HTTP request failed with status ${xsollaRes.status} and statusText: ${xsollaRes.statusText}`)
          }
        })
        .then(data => {
          res.send(JSON.stringify(data));
        })
        .catch(error => {
          res.send("Error = " + error);
        });
      });
      
      exports.webhookFakeResponse = functions.https.onRequest((request, response) => {
        response.status(200).send()
      })
      

      1. スクリプトの中で、変数の値を指定指定します:
        • projectId — アドミンページのプロジェクト名の横にあるプロジェクトID。

        • apiKey ― APIキー。作成時に一度だけアドミンページに表示され、ユーザー側に保存する必要があります。次のセクションで新しいキーを作成できます:
          • 会社設定 > APIキー
          • プロジェクト設定 > APIキー

      1. エミュレータでCloud Functionsをテストするには、CLIコマンドを実行します:

      Copy
      Full screen
      Small screen
        firebase emulators:start
        

        1. Cloud Functionsを実行した後、アプリケーションのクライアント側で以下のメソッドを呼び出すことができます:
          • getXsollaPaymentToken — 決済インターフェースを開くための決済トークンを返します。
          • webhookFakeResponse支払いウェブフックへの応答として、HTTPコード200を送信します。このメソッドには購入検証ロジック(テスト用のみ)は含まれていません。ウェブフックの全リストと、ウェブフックの操作に関する一般的な情報については、ウェブフックのドキュメンテーションを参照してください。

        1. メソッドをローカルで呼び出すには、https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentTokenhttps://localhost:5001/{firebase-project-id}/us-central1/webhookFakeResponseのURLを使用します。{firebase-project-id}はFirebaseプロジェクトID(Firebaseコンソール > プロジェクト設定 > プロジェクトID)です。

        1. 本番環境にCloud Functionsをデプロイするには、CLIコマンドを実行します:

        Copy
        Full screen
        Small screen
          firebase deploy --only functions
          

          1. 本番環境にデプロイされた場合、https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentTokenhttps://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponseのURLからメソッドを呼び出すことができます。{firebase-project-id}はFirebaseのプロジェクトIDです(Firebaseコンソール > プロジェクト設定 > プロジェクトID)。本番環境での機能実行の詳細については、Firebaseのドキュメンテーションを参照してください。

          Unityプロジェクトで注文を作成し、決済UIを開く

          1. Unityプロジェクトを開きます。
          2. ページコントローラーのスクリプトを変更します:
            1. MakeCloudFunctionRequestメソッドを追加して、Cloud Functionsを呼び出します。getXsollaPaymentTokenメソッドを呼び出すには、次のURLのいずれかを指定します。そこで、{firebase-project-id}はFirebase プロジェクトID(Firebaseコンソール > プロジェクト設定 > プロジェクトID):

              • ローカルアクセス用 — https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken
              • 本番環境でのアクセス用 — https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentToken

          Copy
          Full screen
          Small screen
          IEnumerator MakeCloudFunctionRequest(string sku)
             {
                 string url = "https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken";
          
                 using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
                 {
                     var userData = new UserData()
                     {
                         data = new UserData.Data() {
                             uid = user.UserId,
                             email = user.Email,
                             sku = sku,
                             returnUrl = "app://xpayment.com.xsolla.unitysample"
                         }
                     };
          
                     byte[] data = System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(userData, true));
                     UploadHandlerRaw upHandler = new UploadHandlerRaw(data);
                     upHandler.contentType = "application/json";
                     webRequest.uploadHandler = upHandler;
                     webRequest.method = "POST";
                     yield return webRequest.SendWebRequest();
          
                     if (webRequest.result != UnityWebRequest.Result.Success)
                     {
                         Debug.LogError("Error: " + webRequest.error);
                     }
                     else
                     {
                         var paymentToken = "";
                         XsollaWebBrowser.OpenPurchaseUI(
                                 paymentToken,
                                 false);
                         Debug.Log("Response: " + webRequest.downloadHandler.text);
                     }
                 }
             }
          

            1. 購入ボタンのクリック時にCloud Functionsのコールバックを追加します:

          Copy
          Full screen
          Small screen
          private void OnItemsRequestSuccess(StoreItems storeItems)
              {
                  foreach (var storeItem in storeItems.items)
                  {
                      var widgetGo = Instantiate(WidgetPrefab, WidgetsContainer, false);
                      var widget = widgetGo.GetComponent<StoreItemWidget>();
          
                      widget.BuyButton.onClick.AddListener(() =>
                      {
                          StartCoroutine(MakeCloudFunctionRequest(storeItem.sku));
                      });
          
                      widget.NameText.text = storeItem.name;
                      widget.DescriptionText.text = storeItem.description;
          
                      if (storeItem.price != null)
                      {
                          var realMoneyPrice = storeItem.price;
                          widget.PriceText.text = $"{realMoneyPrice.amount} {realMoneyPrice.currency}";
                      }
          
                      ImageLoader.LoadSprite(storeItem.image_url, sprite => widget.IconImage.sprite = sprite);
                  }
              }
          

          テストプロジェクトを実装例として使用できます。UnityプロジェクトのソースコードはGitHubで公開されています。

          ページコントローラースクリプトの例:

          Copy
          Full screen
          Small screen
          using Firebase.Extensions;
          using System;
          using System.Collections;
          using UnityEngine;
          using UnityEngine.Networking;
          using UnityEngine.UI;
          using Xsolla.Catalog;
          using Xsolla.Core;
          
          [Serializable]
          public class UserData
          {
              public Data data;
          
              [Serializable]
              public class Data
              {
                  public string uid;
                  public string email;
                  public string sku;
                  public string returnUrl;
              }
          }
          
          public class FirebaseExamplePage : MonoBehaviour
          {
              public GameObject LoginContainer;
              public GameObject StoreItemsContainer;
          
              public InputField EmailInputField;
              public InputField PasswordInputField;
              public Button LoginButton;
              public Button RegisterButton;
          
              public Transform WidgetsContainer;
              public GameObject WidgetPrefab;
          
              protected Firebase.Auth.FirebaseAuth auth;
              Firebase.Auth.FirebaseUser user = null;
          
              Firebase.DependencyStatus dependencyStatus = Firebase.DependencyStatus.UnavailableOther;
          
              public virtual void Start()
              {
                  Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
                      dependencyStatus = task.Result;
                      if (dependencyStatus == Firebase.DependencyStatus.Available)
                      {
                          InitializeFirebase();
                      }
                      else
                      {
                          Debug.LogError(
                            "Could not resolve all Firebase dependencies: " + dependencyStatus);
                      }
                  });
              }
          
              protected void InitializeFirebase()
              {
                  StoreItemsContainer.SetActive(false);
          
                  Debug.Log("Setting up Firebase Auth");
                  auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
                  auth.StateChanged += AuthStateChanged;
                  RegisterButton.onClick.AddListener(() =>
                  {
                      auth.CreateUserWithEmailAndPasswordAsync(EmailInputField.text, PasswordInputField.text).ContinueWith(task =>
                      {
                          if (task.IsCanceled)
                          {
                              Debug.LogError("CreateUserWithEmailAndPasswordAsync was canceled.");
                              return;
                          }
                          if (task.IsFaulted)
                          {
                              Debug.LogError("CreateUserWithEmailAndPasswordAsync encountered an error: " + task.Exception);
                              return;
                          }
          
                          Firebase.Auth.AuthResult result = task.Result;
                          Debug.LogFormat("Firebase user created successfully: {0} ({1})",
                              result.User.DisplayName, result.User.UserId);
                      });
                  });
          
                  LoginButton.onClick.AddListener(() =>
                  {
                      auth.SignInWithEmailAndPasswordAsync(EmailInputField.text, PasswordInputField.text).ContinueWith(task =>
                      {
                          if (task.IsCanceled)
                          {
                              Debug.LogError("SignInWithEmailAndPasswordAsync was canceled.");
                              return;
                          }
                          if (task.IsFaulted)
                          {
                              Debug.LogError("SignInWithEmailAndPasswordAsync encountered an error: " + task.Exception);
                              return;
                          }
          
                          Firebase.Auth.AuthResult result = task.Result;
                          Debug.LogFormat("Firebase user logged in successfully: {0} ({1})",
                              result.User.DisplayName, result.User.UserId);
                      });
                  });
              }
          
              void AuthStateChanged(object sender, System.EventArgs eventArgs)
              {
                  Firebase.Auth.FirebaseAuth senderAuth = sender as Firebase.Auth.FirebaseAuth;
                  if (senderAuth == auth && senderAuth.CurrentUser != user)
                  {
                      bool signedIn = user != senderAuth.CurrentUser && senderAuth.CurrentUser != null;
                      if (!signedIn && user != null)
                      {
                          Debug.Log("Signed out " + user.UserId);
                      }
                      user = senderAuth.CurrentUser;
                      if (signedIn)
                      {
                          Debug.Log("AuthStateChanged Signed in " + user.UserId);
                          LoadCatalog();
                      }
                  }
              }
          
              void OnDestroy()
              {
                  if (auth != null)
                  {
                      auth.SignOut();
                      auth.StateChanged -= AuthStateChanged;
                      auth = null;
                  }
              }
              private void LoadCatalog()
              {
                  LoginContainer.SetActive(false);
                  StoreItemsContainer.SetActive(true);
                  XsollaCatalog.GetCatalog(OnItemsRequestSuccess, OnError);
              }
          
              private void OnItemsRequestSuccess(StoreItems storeItems)
              {
          
                  foreach (var storeItem in storeItems.items)
                  {
                      var widgetGo = Instantiate(WidgetPrefab, WidgetsContainer, false);
                      var widget = widgetGo.GetComponent<StoreItemWidget>();
          
                      if(widget != null)
                      {
                          widget.NameText.text = storeItem.name;
                          widget.DescriptionText.text = storeItem.description;
          
                          widget.BuyButton.onClick.AddListener(() =>
                          {
                              StartCoroutine(MakeCloudFunctionRequest(storeItem.sku));
                          });
          
                          if (storeItem.price != null)
                          {
                              var realMoneyPrice = storeItem.price;
                              widget.PriceText.text = $"{realMoneyPrice.amount} {realMoneyPrice.currency}";
                          }
          
                          ImageLoader.LoadSprite(storeItem.image_url, sprite => widget.IconImage.sprite = sprite);
                      }
                  }
              }
              IEnumerator MakeCloudFunctionRequest(string sku)
              {
                  string url = "https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken";
          
                  using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
                  {
                      var userData = new UserData()
                      {
                          data = new UserData.Data() {
                              uid = user.UserId,
                              email = user.Email,
                              sku = sku,
                              returnUrl = "app://xpayment.com.xsolla.unitysample"
                          }
                      };
          
                      byte[] data = System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(userData, true));
                      UploadHandlerRaw upHandler = new UploadHandlerRaw(data);
                      upHandler.contentType = "application/json";
                      webRequest.uploadHandler = upHandler;
                      webRequest.method = "POST";
                      yield return webRequest.SendWebRequest();
          
                      if (webRequest.result != UnityWebRequest.Result.Success)
                      {
                          Debug.LogError("Error: " + webRequest.error);
                      }
                      else
                      {
                          string responseJson = webRequest.downloadHandler.text;
                          var responseData = JsonUtility.FromJson<OrderData>(responseJson);
          
                          var paymentToken = responseData.token;
                          int orderId = responseData.order_id;
          
                          XsollaWebBrowser.OpenPurchaseUI(
                                  paymentToken,
                                  false);
                          Debug.Log("Response: " + webRequest.downloadHandler.text);
                      }
                  }
              }
          
              private void OnError(Error error)
              {
                  Debug.LogError($"Error: {error.errorMessage}");
              }
          }
          

          注文状況追跡のセットアップ

          注文状況の追跡は、支払いが成功したことを確認し、ユーザーにアイテムを付与するために必要です。

          クライアント側で注文状況の取得

          注文追跡ロジックはGetXsollaPaymentTokenメソッドに含まれています。成功した購入を処理するには、注文ステータスがdoneに変わったときに呼び出される関数を渡す必要があります。

          AddOrderForTrackingSDKメソッドは追跡に使用されます。このメソッドがどのように動作するかの詳細については、注文状況の追跡を参照してください。

          サーバー側で注文状況の取得

          注意

          SDKはアプリケーションのクライアント側で注文状況を追跡することを可能にします。ただし、完了した購入の追加の検証を実装するために、アプリケーションのバックエンドで支払いウェブフックハンドラを設定することをお勧めします。これにより、注文情報を受け取ることができます

          ウェブフックの完全なリストと、ウェブフックの操作に関する一般情報については、ウェブフックのドキュメンテーションを参照してください。

          エクソーラ側でウェブフックを構成するには:

          1. アドミンページでプロジェクトを開きます。
          2. サイドメニューの「プロジェクト設定」をクリックし、「ウェブフック」セクションに移動します。
          3. ウェブフックサーバー」フィールドに、エクソーラがウェブフックを送信するURLを入力します。

          お知らせ

          テストの場合は、https://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponseを指定できます。ここで、{firebase-project-id}はFirebaseプロジェクトIDです(Firebaseコンソール > プロジェクト設定 > プロジェクトID)。この場合、Firebaseはウェブフックの処理が成功したかのようにシミュレートします。実際のプロジェクトでは、購入の検証ロジックを追加する必要があります。

          ウェブフックをテストするには、webhook.siteなどの専門サイトや、ngrokなどのプラットフォームを選択することもできます。

          1. 秘密鍵」フィールドの値をコピーして保存します。このキーはデフォルトで生成され、ウェブフックの署名に使用されます。変更したい場合は、更新アイコンをクリックします。
          2. ウェブフックを有効にする」をクリックします。

          この記事は役に立ちましたか?
          ありがとうございます!
          改善できることはありますか? メッセージ
          申し訳ありません
          この記事が参考にならなかった理由を説明してください。 メッセージ
          ご意見ありがとうございました!
          あなたのメッセージを確認し、体験を向上させるために利用させていただきます。
          このページを評価する
          このページを評価する
          改善できることはありますか?

          答えたくない

          ご意見ありがとうございました!
          最終更新日: 2024年4月5日

          誤字脱字などのテキストエラーを見つけましたか? テキストを選択し、Ctrl+Enterを押します。

          問題を報告する
          当社は常にコンテンツを見直しています。お客様のご意見は改善に役立ちます。
          フォローアップ用のメールをご提供してください
          ご意見ありがとうございました!