# 自己管理通貨

> ゲーム内報酬コールバックを設定し、サーバーでトランザクションを処理することで、Tapjoy Offerwall に自己管理ゲーム内通貨を設定します。

自己管理通貨を使用すると、自社のサーバー上でユーザーのゲーム内通貨を直接管理できます。この方法は、Tapjoy 管理通貨と比べてコントロールを強化できますが、通貨残高の保存や更新など、すべてのバックエンド処理を自分で行う必要があります。

Tapjoy 管理通貨とは異なり、自己管理通貨では `getCurrencyBalance`、`awardCurrency`、`spendCurrency` などの機能はサポートされません。また、Tapjoy は自己管理通貨に関するクライアント側またはアプリ側への通知を提供しません。Tapjoy がコールバックサーバーに通知してきた際にアプリとユーザーに通知する責任は皆さんにあります。

自己管理通貨には、以下の独自固有の要件があります。

* 報酬の遅延: Tapjoy はできる限り迅速にユーザーに報酬を与えるよう最善を尽くしていますが、即時の配布は保証できません。残高を正確に更新するため、アプリの起動時、再開時、レベル遷移時、動画広告の完了時、ストアの操作前など、定期的に残高の変化を確認してください。ユーザーには、オファーを完了してから報酬が表示されるまでに時間がかかる場合があることを伝えてください。
* プラットフォーム固有の通貨: 自己管理の場合でも、各プラットフォーム (iOS、Android) では独自の通貨設定が必要です。

## コールバック URL##callback-url

ユーザーがオファーを介して通貨を獲得すると、Tapjoy は指定されたコールバック URL に HTTP GET リクエストを送信します。リクエストには、以下のパラメーターが含まれます。

* `snuid`: ユーザー ID。
* `currency`: ユーザーのアカウントに加えるゲーム内通貨の額。
* `mac_address`: ユーザーの Wi-Fi MAC アドレス (利用可能な場合)。

以下のコールバック例を参照してください。

```java
  <callback_url>?snuid=<user_id>&currency=<currency>&mac_address=<mac_address>

  // Example
  http://www.sampledoman.com/payments/offers/tapjoy?&amp;snuid=42&amp;currency=50&amp;mac_address=00-16-41-34-2C-A6
```

## サーバー応答の要件##server-response-requirements

サーバーは以下の HTTP ステータスコードのいずれかで応答する必要があります。

| ステータスコード        | 原因                                                                           |
| --------------- | ---------------------------------------------------------------------------- |
| `200 OK`        | ユーザーがゲーム内通貨を受け取りました。                                                         |
| `403 Forbidden` | * verifier パラメーターが期待される値と一致しません。
* snuid パラメーターが不明です。
* 再試行を妨げるその他のエラーがあります。 |

Tapjoy は、以下の場合にリクエストを再試行します。

* Tapjoy が `200` または `403` の応答を受信していない場合は、リクエストを 5 分ごとに最大 4 日間再試行します。
* サーバーの応答に 5 秒以上かかる場合、Tapjoy はリクエストを失敗とみなして再試行します。
* 応答は UTF-8 でエンコードされている必要があります。これ以外の場合は、サーバーが `200` を返しても Tapjoy は再試行します。

> **Note:**
>
> サーバーが 200 以外の応答を返した場合は、ユーザーにゲーム内通貨を付与しないでください。Tapjoy は再試行を続けるため、報酬の重複につながる可能性があります。

## 不正の検出/防止パラメーター##fraud-detection-or-prevention-parameters

ゲーム内通貨の秘密鍵を使用して、セキュリティを強化できます。秘密鍵を設定するには、Tapjoy ダッシュボードで **Monetize** (収益化) > **Virtual Currency** (ゲーム内通貨) > **Create/Edit** (作成/編集) の順に移動します。このキーはアプリケーション SDK キーとは異なり、コールバックの署名に使用されます。

ゲーム内通貨秘密鍵が設定されている通貨には、以下の追加セキュリティパラメーターがあります。

* ID: ゲーム内報酬リクエストの一意の識別子 (`currency_id` とは異なる)
* 検証子: ID、snuid、通貨、および秘密鍵の MD5 ハッシュ。

### 監視する必要がある不正シナリオ##fraud-scenarios-to-monitor

ゲーム内通貨を管理する際は、以下の不正シナリオを考慮してください。

* ID の再利用や検証子の不一致が発生した場合は、コールバック URL が Tapjoy から発信されていない可能性があり、不正と判断できます。
* ユーザー ID に整数のみが含まれている場合は、snuid を検証して改ざんを防止してください。例えば、Tapjoy では `001234` と `1234` は別々のユーザー ID と見なされます。ただし、バックエンドサーバーのロジックではこの限りではない場合があります。

## 検証子の計算##verifier-calculation

検証子は、以下の形式の MD5 ハッシュです。

```java
Digest::MD5.hexdigest("#{id}:#{snuid}:#{currency}:#{secret_key}")
```

サーバーは検証子を再計算し、リクエストが一致しない場合は `403 Forbidden` の応答を返す必要があります。アプリケーションごとに固有の秘密鍵を使用して、アプリケーション間でゲーム内報酬の問題が発生するのを防ぎます。

アプリケーションごとに個別の秘密鍵が必要です。すべてのアプリケーションに同じ秘密鍵を使用しないでください。これを行うと、ユーザーに対してゲーム内報酬が複数のアプリケーションにわたって同時に付与される可能性があります。

## 改良されたコールバック URL##improved-callback-url

Tapjoy サポートを通じて、より安全なコールバック形式を利用できます。このバージョンでは、JSON ペイロードを利用する POST リクエストと、SHA256 のハッシュベースメッセージ認証コード (HMAC) を使用します。この機能を有効化するには、アカウントマネージャーまたはサポートチームにお問い合わせください。

ユーザーがゲーム内通貨を獲得すると、Tapjoy は指定された URL に POST リクエストを送信します。Tapjoy はリクエスト本文と共有秘密鍵 (Tapjoy ダッシュボードにある) から検証子を生成し、リクエストヘッダーに加えます。

Tapjoy は以下の例のような構造体を使用して、リクエストに署名します。

```javascript
{
   "cp": "some_string",
   "currency": {
      "currency_sale": "1.0",
      "id": "reward_id",
      "reward": 100
   },
   "id": "some_id",
   "offer": {
      "advertiser_app_name": "a_cool_app",
      "currency": {
         "max_reward_value": 1360
      },
      "expires_at": 1768175428,
      "icon_url": "some_URL/icon.jpg",
      "name": "eye_catching_headline",
      "task":{
         "is_iap": false,
         "name": "event_name"
      },
      "type": ""
   },
   "placement":{
      "content_type": "offerwall",
      "name": "placement_name"
   },
   "rev": 100,
   "timestamp": 1762993750,
   "user": {
      "id": "user_id"
   }
}
```

| パラメーター&#xA;型&#xA;説明 |

| `id`&#xA;string&#xA;このリクエストの一意のゲーム内報酬 ID。 |

| `rev`&#xA;double&#xA;獲得した収益 (ドル単位)。 |

| `cp`&#xA;string&#xA;setCustomParameter メソッドを介して SDK に渡されたカスタムパラメーター |

| `currency.id`&#xA;string&#xA;この通貨の一意の識別子。 |

| `currency.reward`&#xA;integer&#xA;ユーザーに与えられる通貨額。 |

| `currency.currency_sale`&#xA;float&#xA;通貨セール乗数 (該当する場合)。 |

| `currency.max_reward_value`&#xA;integer&#xA;ユーザーがこのオファーのすべてのタスクを完了した場合に獲得できる最大通貨。 |

| `offer.name`&#xA;string&#xA;広告主オファーの名前。 |

| `offer.type`&#xA;string&#xA;これは現在サポートされていません。 |

| `offer.icon_url`&#xA;string&#xA;オファーのアイコンの URL。 |

| `offer.advertiser_app_name`&#xA;string&#xA;広告主のアプリ名。 |

| `offer.task.name`&#xA;string&#xA;ユーザーが報酬を受け取る完了したタスクの名前または説明。 |

| `offer.task.is_iap`&#xA;boolean&#xA;報酬が付与されるイベントが IAP イベントの場合は true。 |

| `offer.expires_at`&#xA;timestamp&#xA;ユーザーがこのオファーの報酬を獲得できなくなるまでのタイムスタンプ (秒単位)。 |

| `placement.name`&#xA;string&#xA;このコンバージョンに関連付けられたプレースメント。 |

| `placement.content_type`&#xA;string&#xA;使用するプレースメントのタイプ。 |

| `user.id`&#xA;string&#xA;setUserID メソッドを介して SDK に渡されたユーザー ID。 |

| `timestamp`&#xA;timestamp&#xA;このトランザクションのタイムスタンプ。 |

Tapjoy は以下の形式を使用してリクエストに署名します。

```java
HMAC_SHA-256(<Request-Body>,<Secret-Key>)
```

以下の例は、署名付き Tapjoy ヘッダーを示しています。

```java
X-Tapjoy-Signature => 7205ccfdfa1fe28cd05a1b56a9508d898cc938aa555a6c18848097fe4ee0975b
```

## ユーザー ID の設定##setting-the-user-id

自己管理通貨の場合、Tapjoy とのインタラクション前に一意の `user_id` を設定することが重要です。これにより、ユーザーがゲーム内報酬を確実に受け取ることができます。この値は、コールバック URL 内の snuid となります。初回接続時、コンテンツがリクエストされる前に、接続フラグを使用して `user_id` を設定します。正しく設定しないと、ユーザーは報酬を受け取れず、自分も収益を獲得できません。

通貨の `user_id` を設定するときは、以下の要件を参照してください。

* 一意の数値 ID (最大 190 文字) を使用します。
* GDPR に準拠するため、識別可能な情報 (例えば、ユーザー名、本名、E メールアドレス) を使用しないようにします。
* セキュリティや不正検出のため、各ユーザーの生存期間を通じて一貫した ID を保持します。

設定しなかった場合、Tapjoy はデフォルトでデバイス ID (通常は広告 ID) を使用しますが、これは SDK バージョン、デバイス、OS によって異なる場合があります。

以下のコードサンプルは、接続フラグの使用方法と、必要に応じて setUserID API を (接続後に) 直接呼び出す方法を示しています。API を直接呼び出す場合は、コールバックを使用して ID が正しく設定されていることを確認します。推奨されるベストプラクティスは、可能な限り接続フラグを使用することです。

1. **iOS**

   ```objc title="Objective-C"
   // Recommended approach using connect flag
   NSDictionary *connectFlags = @{TJC_OPTION_USER_ID : @"<USER_ID_HERE>"};
   [Tapjoy connect:@"SDK_KEY_GOES_HERE" options:connectFlags];

   // Setting the user id directly
   [Tapjoy setUserIDWithCompletion:@"<USER_ID_HERE>" completion:^(BOOL success, NSError *error) {

   }];
   ```

2. **Android**

   ```java title="Java" 
   // Recommended approach using connect flag 
   Hashtable<string, object>connectFlags = new Hashtable<string, object>();
   connectFlags.put(TapjoyConnectFlag.USER_ID, "<USER_ID_HERE>");	// Important for self-managed currency

   Tapjoy.connect(getApplicationContext(), "SDK_KEY_GOES_HERE", connectFlags, new TJConnectListener() {...});

   // Setting the user id directly

   Tapjoy.setUserID("<USER_ID_HERE>", new TJSetUserIDListener() {
     @Override
     public void onSetUserIDSuccess() {
       
     }

     @Override
     public void onSetUserIDFailure(String error) {

     }
   });
   ```

3. **Unity**

   ```c# title="C-sharp"
   // Recommended approach using connect flag
   Dictionary&lt;string,string> connectFlags = new Dictionary&lt;string,string>();
   connectFlags.Add("TJC_OPTION_USER_ID", "<USER_ID_HERE>");

   #if UNITY_ANDROID
     Tapjoy.Connect("your_android_sdk_key", connectFlags);
   #elif UNITY_IOS
     Tapjoy.Connect("your_ios_sdk_key", connectFlags);
   #endif

   // Callbacks for SetUserID
   TJPlacement.OnSetUserIDSuccess += HandleOnSetUserIDSuccess;
   TJPlacement.OnSetUserIDFailure += HandleOnSetUserIDFailure;

   // Setting the user id directly
   Tapjoy.SetUserID("<USER_ID_HERE>")
   ```

4. **React Native**

   ```js title="Javascript"
   // Recommended approach using connect flag
   try {
     let flags: object = { TJC_OPTION_USER_ID: '<userId>' };
     await Tapjoy.connect('<sdk_key>', flags);
   } catch (error) {
     console.log(error);
   }

   // Setting the user id directly
   try {
     await Tapjoy.setUserId('<userId>');
   } catch (error) {
     console.log(error);
   }
   ```

各プラットフォームのクイックスタートページには、さらに多くの例が記載されてます。

## ユーザー残高の設定##setting-the-user-balance

プレースメントをリクエストするたび、コンテンツをロードする前に Tapjoy をユーザーの現在の残高で更新します。これにより、自己管理通貨を正確に追跡できます。

1. **iOS**

   ```objc title="Objective-C"
   TJPlacement *placement = [TJPlacement placementWithName:@"placementName" delegate:nil];
   [placement setBalance:100 forCurrencyId:@"1234" withCompletion:^(NSError * _Nullable error) {
       if (error != nil) {
           //Failure
           NSString *message = error.localizedDescription;
       } else {
           //Success
       }
   }];
   ```

2. **Android**

   ```java title="Java"
   TJPlacement placement = Tapjoy.getPlacement("placement", this);
   placement.setCurrencyBalance("1234", 100, new TJSetCurrencyBalanceListener() {
       @Override
       public void onSetCurrencyBalanceSuccess() {
           
       }

       @Override
       public void onSetCurrencyBalanceFailure(int code, String error) {

       }
   }); 
   ```

3. **Unity**

   ```c# title="C-sharp"
   TJPlacement placement = TJPlacement.CreatePlacement("placementName");
   placement.SetCurrencyBalance("[CURRENCY_ID]", 100);

   // Optional callbacks

   void OnEnable() 
   {	
       TJPlacement.OnSetCurrencyBalanceSuccess += HandleSetCurrencyBalanceSuccess;	
       TJPlacement.OnSetCurrencyBalanceFailure += HandleSetCurrencyBalanceFailure;	
   }

   void OnDisable() 
   {	
       TJPlacement.OnSetCurrencyBalanceSuccess -= HandleSetCurrencyBalanceSuccess;	
       TJPlacement.OnSetCurrencyBalanceFailure -= HandleSetCurrencyBalanceFailure;
   }

   public void HandleSetCurrencyBalanceSuccess(TJPlacement placement) 
   {

   }

   public void HandleSetCurrencyBalanceFailure(TJPlacement placement, int code, string error)
   {

   }
   ```

4. **React Native**

   ```js title="Javascript"
   let placement = new TJPlacement('placementName');
   try {
     await placement?.setCurrencyBalance('1234', 100);
   } catch (e: any) {
     let code = e.code;
     let message = e.message;
   }
   ```

## 必要な金額##required-amount

残高を設定する場合は、プレースメントに必要な金額の値も設定できます。

1. **iOS**

   ```objc title="Objective-C"
   TJPlacement* placement = [TJPlacement placementWithName:@"placementName" delegate:nil];
   placement setRequiredAmount:100 forCurrencyId:@"1234" withCompletion:^(NSError * _Nullable error) {
       if (error != nil) {
           //Failure
           NSString *message = error.localizedDescription;
       } else {
           //Success
       }
   } 
   ```

2. **Android**

   ```java title="Java"
   TJPlacement placement = Tapjoy.getPlacement("placement", this);
   placement.setCurrencyAmountRequired("1234", 100, new TJSetCurrencyAmountRequiredListener() {
       @Override
       public void onSetCurrencyAmountRequiredSuccess() {
           
       }

       @Override
       public void onSetCurrencyAmountRequiredFailure(int code, String error) {

       }
   }); 
   ```

3. **Unity**

   ```c# title="C-sharp"
   TJPlacement placement = TJPlacement.CreatePlacement("placementName");
   placement.SetRequiredAmount("[CURRENCY_ID]", 200); 

   // Optional callbacks

   void OnEnable() 
   {	
       TJPlacement.OnSetCurrencyAmountRequiredSuccess += HandleSetRequiredAmountSuccess;	
       TJPlacement.OnSetCurrencyAmountRequiredFailure += HandleSetRequiredAmountFailure;}

   void OnDisable() 
   {	
       TJPlacement.OnSetCurrencyAmountRequiredSuccess -= HandleSetRequiredAmountSuccess;	
       TJPlacement.OnSetCurrencyAmountRequiredFailure -= HandleSetRequiredAmountFailure;
   }

   public void HandleSetCurrencyBalanceSuccess(TJPlacement placement)
   {

   }

   public void HandleSetCurrencyBalanceFailure(TJPlacement placement, int code, string error)
   {

   }

   public void HandleSetRequiredAmountSuccess(TJPlacement placement)
   {

   }
   public void HandleSetRequiredAmountFailure(TJPlacement placement, int code, string error)
   {

   } 
   ```

4. **React Native**

   ```javascript title="Javascript"
   let placement = new TJPlacement('placementName');
   try {
   await offerwallPlacement?.setRequiredAmount(100, '100');
   } catch (e: any) {
     let code = e.code;
     let message = e.message;
   } 
   ```

## ゲーム内報酬コールバック IP 許可リスト##reward-callback-ip-allowlisting

コールバックサーバーでアクセス制限を行っている場合、以下の Tapjoy IP アドレスを許可リストに加えてください。

> **Note:**
>
> このリストは 2024 年 5 月 12 日 (日) 時点の情報です。

* `18.215.207.89`
* `18.235.142.165`
* `23.20.255.113`
* `23.23.134.165`
* `3.210.188.32`
* `3.215.42.140`
* `3.217.209.177`
* `3.218.95.35`
* `3.219.236.53`
* `3.231.137.161`

## トラブルシューティング##troubleshooting

コールバック URL で予期しない snuid を受け取った場合は、アプリを起動するたびに、接続呼び出し後、ユーザーがオファーにアクセスする前に `setUserID` を呼び出すようにします。

デバイスにユーザー ID が関連付けられていない場合、Tapjoy はデバイス ID を snuid としてコールバック URL に送信します。例えば、ユーザーがアプリを起動したものの、アプリが userID を送信する前に Tapjoy ウェブサイトに移動する場合があります。これを防ぐには、起動のたびに接続呼び出し後、サーバーで `setUserID` が呼び出されるようにする必要があります。

ユーザー ID を設定していない場合、システムは利用できる中で最も適切なデバイス ID を使用しようとします。通常、これはデバイスの広告 ID になります。ただし、SDK のバージョン、デバイスのモデル/バージョン、デバイスの OS バージョン、Google Play 開発者サービスによって、具体的な ID は異なる場合があります。その他の利用可能な値には、Android ID、udid、mac\_address があります。
