# Webshop を Unity ゲームに統合する

> 認証されたプレイヤー セッションで Unity ゲームから Webshop を開きます。

UnityゲームからWebshopを開き、プレイヤーがアプリ内課金(IAP)カタログから製品を購入できるようにします。

Web ショップは、すでに設定した IAP カタログと支払いプロバイダーを使用します。プレイヤーは、ゲーム内のストアで別途購入しなくても、ウェブで購入を完了できます。

ショップを開くには、ボタンなどのゲーム内アクションを追加します。これにより、プレイヤーの認証済みセッションがアタッチされたショップURLが開きます。セッションは Webshop に対してプレイヤーを識別します。

## 前提条件##prerequisites

開始する前に、以下の前提条件を満たしていることを確認します。

* [アプリ内課金](/iap.md)を統合するUnityプロジェクト。
* プロジェクト内の[Authentication](/authentication.md) SDK（バージョン3.7.1以降）。
* ターゲット環境内の Webshop。

ライブショップを開くには、Webshop を公開して `shop.unity.com/{studio}/game/{slug}` で利用できるようにします。ドラフトをテストするには、ターゲット環境に作成された Webshop だけが必要です。

詳細については、「 最初の Webshop の[作成と公開](./create-and-publish-your-first-webshop.md) 」を参照してください。

## ゲームからウェブショップを開く##open-the-webshop-from-your-game

### SDK ヘルパーの使用##use-the-sdk-helper

Unity Iap SDK には、必要なパラメータとコンプライアンスチェックを使用して Webshop を開くための`RedirectToWebshop`が用意されています。

支払いプロバイダーの`IPaymentProvidersExtendedPurchaseService`経由で`RedirectToWebshop`を呼び出します。`catalogListingId`を空のままにすると Webshop 正面ページが開きます。リスト ID を渡すとプロダクトページが直接開きます。

```csharp
// Open the front page
 UnityIAPServices.StoreController(PaymentProvider.Name).PaymentProvidersExtendedPurchaseService.RedirectToWebshop();

// Open a specific product page
 UnityIAPServices.StoreController(PaymentProvider.Name).PaymentProvidersExtendedPurchaseService.RedirectToWebshop(catalogListingId: "your-listing-id");
```

#### オープン前にコンプライアンス承認が必要##require-compliance-approval-before-opening

コンプライアンス チェックで Webshop をゲートするには、`RedirectToWebshop` を呼び出す前に `SetComplianceCheck` にコールバックを登録します。コールバックは各リダイレクトの前に実行されます。`false`を返すとリダイレクトがキャンセルされ、購入は`PurchasingUnavailable`で失敗します。

```csharp
StoreController(PaymentProvider.Name).PaymentProvidersExtendedPurchaseService
    .SetComplianceCheck(async context => await ShowComplianceDialog(context));
```

#### Webshop を開く方法をコントロール##control-how-the-webshop-opens

デフォルトでは、SDK は外部ブラウザーで Webshop を開きます。これを変更するには、リダイレクトする前に支払いプロバイダーの`IPaymentProvidersExtendedPurchaseService`で`SetWebshopPresentationMode(CheckoutPresentationMode)`を呼び出します。この設定は `SetCheckoutPresentationMode` とは無関係です。

### 手動での統合##manual-integration

SDK ヘルパーを使用できない場合は、Webshop URL を直接作成して開きます。

ボタンやその他のゲーム内アクションから Webshop を開くには、Webshop サービスからショップ URL を解決し、`Application.OpenURL`で URL を開きます。

ランタイムに URL を解決すると、同じビルドでパブリッシュされたショップまたは環境ドラフトプレビューを開くことができます。これにより、公開前にドラフトをテストできます。

URL を解決するには、`storefront-link`エンドポイントに`GET` リクエストを送信します。

```text
https://webshop.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/storefront-link
```

デフォルトでは、サービスは環境の状態に基づいて URL を自動的に返します。

* 公開された本番環境では、公開中のストアフロント URL が返されます。ライブ URL は匿名で解決します。
* 非本番環境または未公開の本番環境では、短期間の下書きプレビュー URL が返されます。

ライブショップの未公開の変更をプレビューするには、`source=draft`クエリパラメーターを追加します。これは、パブリッシュされた本番環境であってもドラフトプレビュー URL を返します。

```text
https://webshop.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/storefront-link?source=draft
```

インテグレーションは以下のAuthentication SDKトークンを使用します。

* アクセストークンは、ドラフトプレビューの解決時に`storefront-link`リクエストを承認します。`Authorization: Bearer` ヘッダーでアクセストークンを送信します。
* セッショントークンはブラウザーでプレイヤーを認証します。ショップを開く直前に、短期間の、1回限りの使用に制限されたトークンを`GenerateRestrictedTokenAsync`で除外し、そのセッショントークンを`sessionToken`クエリパラメーターとして解決済みURLに追加します。

URL を解決したら、セッショントークン、プロジェクト ID、および環境を追加して、正しいプレイヤー用のショップを開きます。

各クエリパラメーターの詳細と、パラメーターがない場合のショップの動作については、「 [ゲームから Webshop を開く](./deep-links.md#outbound-links) 」を参照してください。

```csharp
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Services.Authentication;
using UnityEngine;
using UnityEngine.Networking;

public class WebshopLauncher :MonoBehaviour
{
    // Replace these with the values from your Unity Cloud project and webshop configuration.
    const string ProjectId       = "<your-project-id>";
    const 文字列 EnvironmentId = "<your-environment-id>"; // API リクエストパスで使用
    const 文字列 EnvironmentName = "production"; // ショップ URL で使用

    const string StorefrontLinkEndpoint =
        "https://webshop.services.api.unity.com/v1/projects/{0}/environments/{1}/storefront-link";

    // draftPreview: request source=draft to preview unpublished changes even on a live env.
    public void OpenShop(string locale, string currency, bool draftPreview = false)
    {
        StartCoroutine(OpenShopRoutine(locale, currency, draftPreview));
    }

    IEnumerator OpenShopRoutine(string locale, string currency, bool draftPreview)
    {
        var signedIn = AuthenticationService.Instance.IsSignedIn;

        // The access token is required for draft previews; live storefronts open anonymously
        // and ignore any token sent.プレイヤーがサインインするたびに送信します。
        if (draftPreview && !signedIn)
        {
            Debug.LogWarning("WebshopLauncher: draft preview requires the player to be signed in.");
            yield break;
        }

        // 1.Webshop サービスにストアフロント URL を問い合わせます。
        var endpoint = string.Format(StorefrontLinkEndpoint, ProjectId, EnvironmentId);
        if (draftPreview)
            endpoint += "?source=draft";

        using var request = UnityWebRequest.Get(endpoint);
        request.SetRequestHeader("Accept", "application/json");
        if (signedIn)
            request.SetRequestHeader("Authorization", $"Bearer {AuthenticationService.Instance.AccessToken}");

        yield return request.SendWebRequest();

        if (request.result != UnityWebRequest.Result.Success)
        {
            デバッグ.LogError($"WebshopLauncher: storefront-linkリクエストが失敗しました" +
                           $"({request.responseCode}): {request.error}");
            yield break;
        }

        var link = JsonUtility.FromJson<StorefrontLinkResponse>(request.downloadHandler.text);
        if (link == null || string.IsNullOrEmpty(link.storefrontUrl))
        {
            Debug.LogError("WebshopLauncher: storefront-link response did not contain a storefrontUrl.");
            yield break;
        }

        // 2.Webshop リダイレクト用の短期間の、1 回限りの使用制限付きトークンを最小にします。
        var tokenOptions = new RestrictedTokenOptions
        {
            Services ＝ new リスト<string> ｛ "no-svc" ｝， // ID トークンは実際のサービスに対して使用できません
            SingleUse = true、 // 最初の更新時に Webshop によって消費されます
            TtlSeconds = 60, // リダイレクト直前に発生
        };

        var tokenTask = AuthenticationService.Instance.GenerateRestrictedTokenAsync(tokenOptions);
        yield return new WaitUntil() => tokenTask.IsCompleted);

        if (tokenTask.IsFaulted)
        {
            Debug.LogError($"WebshopLauncher: failed to generate restricted token: {tokenTask.Exception}");
            yield break;
        }

        // 3.店を開けろ
        var sessionToken = tokenTask.Result.SessionToken;
        var shopUrl = BuildShopUrl(link.storefrontUrl, sessionToken, locale, currency);

        Application.OpenURL(shopUrl);
    }

    静的文字列 BuildShopUrl(文字列 storefrontUrl、文字列 sessionToken、文字列ロケール、文字列通貨)
    {
        var url = storefrontUrl;
        url = AppendParam(url, "sessionToken", sessionToken);
        url = AppendParam(url, "projectId", ProjectId);
        url = AppendParam(url, "environment", EnvironmentName);
        url = AppendParam(url, "locale", locale);
        url = AppendParam(url, "currency", currency);
        return url;
    }

    静的 文字列 AppendParam(文字列 URL, 文字列 キー, 文字列 value)
    {
        if (string.IsNullOrEmpty(value))
            return url;

        var separator = url.Contains("?") ? '&' : '?';
        return $"{url}{separator}{key}={UnityWebRequest.EscapeURL(value)}";
    }

    Serializable
    class StorefrontLinkResponse
    {
        public string storefrontUrl;
        public bool live;
    }
}
```

ショップを開いた後、URL からこれらのパラメータを削除し、ブラウザーでプレイヤーのセッションを維持します。セッションと持続性の詳細については、[ゲームから Webshop を開く](./deep-links.md#authenticate-players-and-persist-sessions) を参照してください。

> **Warning:**
>
> 子供のプライバシー児童オンラインプライバシー保護法 (COPPA) をはじめとする児童データ保護法では、制限年齢に満たないユーザー (適用法に応じて 13 歳、16 歳、または 18 歳未満の児童など) からのデータの収集とその使用に関して、各種の制限が設けられています。[Unity のサービス規約](https://unity.com/legal/terms-of-service) で説明されている適用法を遵守しない限り、年齢制限ユーザーに属する「個人情報」を Unity に送信することはできません。

渡すロケールまたは通貨がない場合は、これらのパラメーターを省略します。デフォルトでは、IAP カタログはプレイヤーのブラウザーロケールを使用し、デフォルトは米ドル (USD) です。カタログのロケール処理の詳細については、「 [カタログと Web ショップ](./catalog-and-payments.md)での支払い 」を参照してください。

## 着信ディープリンクのハンドル##handle-inbound-deep-links

Webshop に **Deeplink URL** を設定すると、ショップはそのカスタム URL スキームを通じてプレイヤーをゲームに戻します。ショップは、購入後、およびプレイヤーが未認証のランディングページで**Connect to game**(ゲームに接続)を選択した場合に、返品ディープリンクを使用します。

リターン ディープ リンクを受信するには、デバイスにカスタム URL スキームを登録し、ランタイムに着信リンクをハンドルします。ショップが戻り URL を構築する方法の詳細については、「 [ゲームから Webshop を開く](./deep-links.md#post-purchase-return-to-the-game) 」を参照してください。

> **Note:**
>
> **Connect to game** フローでは、Unity Iap SDK によって Webshop が自動的に開き直されます。このリンクは自分でハンドルする必要はありません。
>
> 以下の要件が満たされていることを確認します。
>
> * プレイヤーは IAP を初期化する前に Unity Authentication にサインインします。
> * PaymentProvider ストアが接続されています。
>
> 購入後の返品処理には、標準のプラットフォーム カスタムURLスキーム処理が使用され、Unity固有のSDKは必要ありません。

> **Note:**
>
> ディープリンクを返します。Unity エディターや WebGL では解決されず、ビルドされたプレイヤーでのみ解決されます。エディタはカスタムスキームのハンドラーとして登録されていないため、ブラウザーから開いたリンクは再生モードにならず、WebGL の構築はカスタムスキームではなくページ URL を使用します。デバイス (iOS または Android) またはスタンドアロンビルドでリターンフローをテストします。プラットフォーム サポートと登録の詳細については、Unityの[ディープ リンク](https://docs.unity3d.com/Manual/deep-linking.html)マニュアルを参照してください。

### URL スキームの登録##register-the-url-scheme

ダッシュボードの **Deeplink URL** フィールドで設定したのと同じスキームを宣言します。オペレーティング システムは、このスキームを使用してリンクをゲームにルートします。

* **iOS と macOS**:**編集> プロジェクト設定> プレイヤー> その他の設定> サポートされる URL スキーム** でスキームを追加します。Unity は、ビルド時にそれをビルドしたアプリケーションの`Info.plist` (`CFBundleURLTypes`) に書き込みます。ビルドごとに再生成される生成された`Info.plist`を手動で編集するよりも優先してください。
* **Android**カスタムのメインマニフェストまたは Gradle マニフェストテンプレートを使用して、`<data android:scheme="mygame" />`エントリーを持つ`intent-filter`をアクティビティに追加します。

スキームは **Deeplink URL** 値と正確に一致する必要があります。Android マニフェストを変更したら、ゲームを再インストールして OS に新しいスキームを適用します。

### ランタイムのリンクのハンドル##handle-the-link-at-runtime

ゲームの実行中に到着するリンクの `Application.deepLinkActivated` をサブスクライブします。起動時に、ディープリンクがゲームを起動するコールドスタートのハンドルにも`Application.absoluteURL`します。

```csharp
void Awake()
{
    // Links that arrive while the game is running.
    Application.deepLinkActivated += OnReturnFromWebshop;

    // Cold start: the deep link launched the game.
    if (!string.IsNullOrEmpty(Application.absoluteURL))
        OnReturnFromWebshop(Application.absoluteURL);
}

void OnReturnFromWebshop(string url)
{
    // Handle the post-purchase return: the shop appends ?status=success
    // (and playerId when available) after a completed purchase.
    if (new Uri(url).Query.Contains("status=success"))
    {
        // Purchase completed on the web — refresh the player's entitlements.
    }
    // The Connect to game sign-in link is reopened by the SDK automatically,
    // so it needs no handling here.
}
```

ショップは`status=success`しか送信しないため、その場合はここでハンドルします。前の注で説明したように、SDK は **Connect to game** sign-in (ゲームサインインに接続) リンクを自動的に開きます。

## テストオープン##test-opening-the-shop

公開する前にドラフトプレビューをテストし、公開中のショップに対してテストを繰り返します。ドラフトをテストするには、`draftPreview: true` を使用して `OpenShop` を呼び出し、プレイヤーがサインインしていることを確認します。非本番環境をターゲットにするか、`source=draft`を使用して本番環境で未公開の変更をプレビューできます。

インテグレーションを検証するには、次の手順に従います。

1. モバイルデバイスにゲームをビルドおよびインストールします。
2. `OpenShop`を呼び出してデバイスのシステムブラウザーを起動するゲーム内ボタンをトリガー。
3. ブラウザーで解決済みのストアフロントURLが開き、URLにロケールと通貨が入力されていることを確認します。下書きにすると、環境スコープのプレビュー URL が開きます。ライブ ショップが`shop.unity.com/{studio}/game/{slug}`オープンします。
4. ショップが、未認証のランディングページではなく、認証済み製品リストを開くことを確認します。
5. サンドボックスの購入を完了します。テスト認証情報については、該当する IAP 支払いプロバイダーのサンドボックスのドキュメントを参照してください。
6. ドラフトが期待通りに機能したら、`draftPreview: false` で`OpenShop`を呼び出し、公開中のショップに対してテストを繰り返します。

プロダクト リストではなく未認証のランディング ページにショップが開いた場合は、ゲームにURLの有効な`sessionToken`と`projectId`が渡されたことを確認してください。[Webshop のトラブルシューティング](./troubleshooting.md) を参照してください。
