> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Money2 トランザクションアクション

検証/消費/入手の各トランザクションアクションの仕様




## アクションの組み合わせと同時実行

すべてのサービスに共通する前提は [トランザクションアクションの組み合わせ]() にまとめています。先にそちらを読んでください。この節の残りは GS2-Money2 固有の内容です。

GS2-Money2 のトランザクションアクションは、ネームスペース・ユーザー・スロットの組で決まる 1 つのウォレットを対象にします。ウォレットは有償残高と無償残高をまとめて保持します。

無償残高だけを動かす更新は増分として書き込まれるため合算されます。有償残高が動く更新はウォレット全体を書き換えます。

操作 | 同じ行を重ねたとき | 入れ子越し | 別の対象になる境界 | 直列実行モード有効時
--- | --- | --- | --- | ---
無償通貨だけが動く入金・出金<br>`DepositByUserId` `WithdrawByUserId` | 混在してよい。統合され、金額が合算される | 合算される | ネームスペース・ユーザー・スロット | 変わらない。もともと合算されて成立する
有償通貨が動く入金・出金<br>`DepositByUserId` `WithdrawByUserId` | 入金は入金トランザクションが 1 つのリストにまとめられ、出金は金額が合算される。入金と出金の同居は失敗する | 失敗する | ネームスペース・ユーザー・スロット | 入金と出金の同居も含めて、入れ子越しでも成立する。各アクションが直前の書き込みを反映したウォレットを読み直してから書き戻すため、後から実行される方にはすでに先の書き込みが反映されている
レシートの検証<br>`VerifyReceiptByUserId` | 失敗する | 同じ取引 ID に解決されるなら失敗する | ネームスペース・ユーザー（行そのものは取引 ID 単位） | 同じ取引 ID での入れ子越しの衝突は成立する。記録はまだ存在しない行にだけ書き込まれるため、内側と外側の記録が同じ1件に統合されるようになる。同じトランザクション内の拒否は変わらない。これはどのアクションも実行される前、発行時に働くため

無償通貨の更新は増分なので、**無償の入金と出金を 1 つのトランザクションに入れても両方が適用されます。** 有償残高が動く場合はウォレット全体が書き換わるため、2 つの行を同居させられません。**直列実行モード（`enableSequentialExecution` または `TransactionSettingV2`）を有効にすると、2 行目についてもこの制約はなくなります。入金と出金のうち後から実行される方が、先に実行された方が残したとおりのウォレットを読むため、有償残高が動く場合でも 1 つのトランザクションに入れられるようになります。**

無償だけでは足りない出金は有償残高に食い込むので、有償を使うつもりがなくても 2 行目の扱いになります。

**次の場合はウォレット全体を書き換える経路に落ち**、2 行目と同じ挙動になります。有償ロットが動く場合、ウォレットが古い保存形式で保存されている場合、無償ロットがちょうど 1 本でない場合、summary が記録されたロットと辻褄が合っていない場合です。後ろの 2 つはインポートで持ち込まれたデータでしか起こりません。

`sharedFreeCurrency` を有効にしている場合、無償残高は全スロットぶんがスロット 0 の 1 行に保持されます。スロット 0 への操作は増分の経路に乗りますが、それ以外のスロットに対して発行された無償の操作は全体を書き換える形になります。

残高は統合後の合計に対して判定されます。単独なら足りている出金でも、他の出金と合わさると弾かれることがあります。

**有償残高からのみ引く出金と通常の出金は、同じスロットに並べられません。** どちらの残高から引くかが食い違ったまま合算すると、意図しない残高から引かれてしまうため、`paidOnly` が違う出金を同じウォレットに指定すると発行時にエラーになります。分けて指定してください。

`VerifyReceiptByUserId` は名前こそ検証ですが、種別としては消費アクションです。ウォレットは触りませんが、レシートを記録する専用の行は作ります。消費アクションの並びに置いてください。トランザクション開始時点の状態を見るため、同じトランザクションで記録されたレシートを検証することはできません。

**同じユーザーへのレシートの検証どうしは、レシートが違っていても発行時にエラーになります。** レシートは取引 ID を経由して行にたどり着きますが、取引 ID の取り出し方はストアごとに違います。Google Play では送った `transactionId` は一度も見られず、ペイロード内側の `orderId` が行を決めます。つまり見た目の違うレシートが同じ課金を指していることも、見た目の同じレシートが別の課金であることもあります。推測で畳まず、1 つのトランザクションに 1 件だけ置いてください。レシートを検証する陳列商品を数量 2 以上で購入した場合も、アクションが個数ぶん積まれるためこれに当たります。

この検証を入金と同じトランザクションに置けば、レシートが正当であることを確認したうえで入金する、という組み方ができます。二重計上を防ぐにはこの形にしてください。

### 入れ子になったトランザクションに注意

無償通貨の更新は入れ子越しでも安全です。どちらの経路から届いても 1 回の更新にまとまります。

有償残高が動く場合はウォレット全体が書き換わるため、内側と外側の両方から触るとトランザクションが失敗します。通貨はコストとして使われることが多いので、この状況は起こりやすくなります。外側でウォレットから支払う購入と、内側でもウォレットから支払う景品が同居すると通りません。

直列実行モードを有効にすると、この失敗も起こらなくなります。内側のトランザクションは外側と同じ直列実行の区間で実行されるようになり、各アクションが直前のアクションの結果を反映したウォレットを書き換えるため、両方の経路から触られたウォレットは衝突する代わりに統合されます。有償残高のみの出金と通常の出金を並べる場合は別の話です。同じトランザクションに並べて書いた出金の `paidOnly` が食い違うと、実行モードによらず発行時にエラーになります。この検査は同時に発行された出金しか見ないため、片方が入れ子から届く場合には働きません。また、直列実行モードでも解消しない組み合わせが 1 つあります。無償通貨だけが動く入出金は無償残高への増分として書き込まれ、有償残高が動く入出金はウォレット全体を書き換えるため、この 2 つを同じウォレットに対して 1 つのトランザクションに入れることはできません (400)。

**同じ金額の入金が内側と外側から届いた場合は失敗せず、1 件にまとめられます。** 100 の入金が 2 つあっても入るのは 100 だけで、エラーは出ません。これはウォレット全体を書き換える 2 行目での話で、金額が違えば失敗するので、静かに合わないのは金額が一致したときだけです。

### 制限を回避したい場合

ウォレットへの入金は入手アクション、出金とレシートの検証は消費アクションです。入金どうしの衝突は `acquireActionUseJobQueue` を有効にすれば解消できますが、入金と出金の同居は `enableAtomicCommit` を無効にしないと解消しません。通貨を扱う以上、この判断は慎重に行ってください。

レシートの検証に抜け道はありません。検査はどちらの設定よりも前に走りますし、レシートは 1 件の課金であって同じ課金を二重に検証してはいけないので、有効にしても何も変わりません。トランザクションを分け、レシートを検証する陳列商品は 1 個ずつ購入してください。

### 同時実行とリトライ

無償通貨の更新は、結果が範囲に収まるかぎり、同時実行のリクエストが何本重なってもコンフリクトしません。増分で書く利点がここに出ます。クエストの報酬・ログインボーナス・ミッション報酬のように無償通貨を配るものどうしが、互いに衝突しなくなります。

有償残高が動く場合はリビジョンの照合を伴って書き込まれるため、後から確定した側がコンフリクト (409) になります。リクエストの内容に問題があるわけではないので、リトライすれば成功します。リトライ時には最新の残高で判定し直されます。

スロットが違えば別のウォレットなので、同じユーザーでもスロットが違えばコンフリクトしません。例外は `sharedFreeCurrency` で、この場合は全スロットの無償残高が 1 行に載ります。

---



## Consume Action

消費アクション

### Gs2Money2:WithdrawByUserId

ユーザーIDを指定してウォレットから残高を消費<br>

指定されたユーザーのウォレットから指定量の通貨を消費します。<br>
paidOnly が false の場合、無償通貨が先に消費され、次に有償通貨が消費されます。

**数量指定可能なアクション：はい**

**反転可能なアクション：はい**

|  | 型 | 有効化条件 | 必須 | デフォルト | 値の制限 | 説明 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128文字 | ネームスペース名<br>ネームスペース固有の名前。英数字および -(ハイフン) _(アンダースコア) .(ピリオド)で指定します。 |
| userId | string |  | ✓|  |  ~ 128文字 | ユーザーID<br>`#{userId}` と設定することでログイン中のユーザーIDに置換されます。 |
| slot | int |  | ✓|  | 0 ~ 100000000 | スロット番号<br>ウォレットスロットを識別します。プラットフォーム間の残高共有が許可されていない場合、異なるスロットを使用してプラットフォームごとに通貨を分けて管理できます（例：iOS用とAndroid用）。 |
| withdrawCount | int |  | ✓|  | 1 ~ 2147483646 | 消費する課金通貨の数量 |
| paidOnly | bool |  | | false |  | 有償通貨のみを対象とするか |
| timeOffsetToken | string |  | |  |  ~ 1024文字 | タイムオフセットトークン |




**JSON**
```json
{
    "action": "Gs2Money2:WithdrawByUserId",
    "request": {
        "namespaceName": "[string]ネームスペース名",
        "userId": "[string]ユーザーID",
        "slot": "[int]スロット番号",
        "withdrawCount": "[int]消費する課金通貨の数量",
        "paidOnly": "[bool]有償通貨のみを対象とするか",
        "timeOffsetToken": "[string]タイムオフセットトークン"
    }
}
```

**YAML**
```yaml

action: Gs2Money2:WithdrawByUserId
request:
  namespaceName: "[string]ネームスペース名"
  userId: "[string]ユーザーID"
  slot: "[int]スロット番号"
  withdrawCount: "[int]消費する課金通貨の数量"
  paidOnly: "[bool]有償通貨のみを対象とするか"
  timeOffsetToken: "[string]タイムオフセットトークン"
```

**GS2-Script**
```lua

transaction.service("money2").consume.withdraw_by_user_id({
    namespaceName="[string]ネームスペース名",
    userId="[string]ユーザーID",
    slot="[int]スロット番号",
    withdrawCount="[int]消費する課金通貨の数量",
    paidOnly="[bool]有償通貨のみを対象とするか",
    timeOffsetToken="[string]タイムオフセットトークン",
})
```


---

### Gs2Money2:VerifyReceiptByUserId

ユーザーIDを指定してレシートを使用済み化<br>

指定されたユーザーの購入レシートを検証し、使用済みとして記録します。<br>
イベントログが記録され、ネームスペースに設定されたレシート検証スクリプトが実行されます。

**数量指定可能なアクション：いいえ**

**反転可能なアクション：いいえ**

|  | 型 | 有効化条件 | 必須 | デフォルト | 値の制限 | 説明 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128文字 | ネームスペース名<br>ネームスペース固有の名前。英数字および -(ハイフン) _(アンダースコア) .(ピリオド)で指定します。 |
| userId | string |  | ✓|  |  ~ 128文字 | ユーザーID<br>`#{userId}` と設定することでログイン中のユーザーIDに置換されます。 |
| contentName | string |  | ✓|  |  ~ 128文字 | ストアコンテンツモデル名 |
| receipt | [Receipt](../sdk/#receipt) |  | ✓|  |  | レシート |
| timeOffsetToken | string |  | |  |  ~ 1024文字 | タイムオフセットトークン |




**JSON**
```json
{
    "action": "Gs2Money2:VerifyReceiptByUserId",
    "request": {
        "namespaceName": "[string]ネームスペース名",
        "userId": "[string]ユーザーID",
        "contentName": "[string]ストアコンテンツモデル名",
        "receipt": {
            "Store": "[string]ストア",
            "TransactionID": "[string]トランザクションID",
            "Payload": "[string]ペイロード"
        },
        "timeOffsetToken": "[string]タイムオフセットトークン"
    }
}
```

**YAML**
```yaml

action: Gs2Money2:VerifyReceiptByUserId
request:
  namespaceName: "[string]ネームスペース名"
  userId: "[string]ユーザーID"
  contentName: "[string]ストアコンテンツモデル名"
  receipt: 
    Store: "[string]ストア"
    TransactionID: "[string]トランザクションID"
    Payload: "[string]ペイロード"
  timeOffsetToken: "[string]タイムオフセットトークン"
```

**GS2-Script**
```lua

transaction.service("money2").consume.verify_receipt_by_user_id({
    namespaceName="[string]ネームスペース名",
    userId="[string]ユーザーID",
    contentName="[string]ストアコンテンツモデル名",
    receipt={
        store="[string]ストア",
        transactionID="[string]トランザクションID",
        payload="[string]ペイロード"
    },
    timeOffsetToken="[string]タイムオフセットトークン",
})
```


---

## Acquire Action

入手アクション

### Gs2Money2:DepositByUserId

ユーザーIDを指定してウォレットの残高に加算<br>

指定されたユーザーのウォレットに指定された入金トランザクションを追加します。<br>
各入金トランザクションに対してイベントログが記録されます。

**数量指定可能なアクション：はい**

**反転可能なアクション：はい**

|  | 型 | 有効化条件 | 必須 | デフォルト | 値の制限 | 説明 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128文字 | ネームスペース名<br>ネームスペース固有の名前。英数字および -(ハイフン) _(アンダースコア) .(ピリオド)で指定します。 |
| userId | string |  | ✓|  |  ~ 128文字 | ユーザーID<br>`#{userId}` と設定することでログイン中のユーザーIDに置換されます。 |
| slot | int |  | ✓|  | 0 ~ 100000000 | スロット番号<br>ウォレットスロットを識別します。プラットフォーム間の残高共有が許可されていない場合、異なるスロットを使用してプラットフォームごとに通貨を分けて管理できます（例：iOS用とAndroid用）。 |
| depositTransactions | [List&lt;DepositTransaction&gt;](../sdk/#deposittransaction) |  | ✓|  | 1 ~ 1000 items | 入金トランザクションリスト |
| timeOffsetToken | string |  | |  |  ~ 1024文字 | タイムオフセットトークン |




**JSON**
```json
{
    "action": "Gs2Money2:DepositByUserId",
    "request": {
        "namespaceName": "[string]ネームスペース名",
        "userId": "[string]ユーザーID",
        "slot": "[int]スロット番号",
        "depositTransactions": [
            {
                "price": "[double]購入価格",
                "currency": "[string]通貨コード",
                "count": "[int]課金通貨の数量",
                "depositedAt": "[long]入金日時"
            }
        ],
        "timeOffsetToken": "[string]タイムオフセットトークン"
    }
}
```

**YAML**
```yaml

action: Gs2Money2:DepositByUserId
request:
  namespaceName: "[string]ネームスペース名"
  userId: "[string]ユーザーID"
  slot: "[int]スロット番号"
  depositTransactions: 
    - price: "[double]購入価格"
      currency: "[string]通貨コード"
      count: "[int]課金通貨の数量"
      depositedAt: "[long]入金日時"
  timeOffsetToken: "[string]タイムオフセットトークン"
```

**GS2-Script**
```lua

transaction.service("money2").acquire.deposit_by_user_id({
    namespaceName="[string]ネームスペース名",
    userId="[string]ユーザーID",
    slot="[int]スロット番号",
    depositTransactions={
        {
            price="[double]購入価格",
            currency="[string]通貨コード",
            count="[int]課金通貨の数量",
            depositedAt="[long]入金日時"
        }
    },
    timeOffsetToken="[string]タイムオフセットトークン",
})
```


---



