Documentation index for AI agents

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

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

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

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

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

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

操作同じ行を重ねたとき入れ子越し別の対象になる境界
無償通貨だけが動く入金・出金
DepositByUserId WithdrawByUserId
混在してよい。統合され、金額が合算される合算されるネームスペース・ユーザー・スロット
有償通貨が動く入金・出金
DepositByUserId WithdrawByUserId
入金は入金トランザクションが 1 つのリストにまとめられ、出金は金額が合算される。入金と出金の同居は失敗する失敗するネームスペース・ユーザー・スロット
レシートの検証
VerifyReceiptByUserId
失敗する同じ取引 ID に解決されるなら失敗するネームスペース・ユーザー(行そのものは取引 ID 単位)

無償通貨の更新は増分なので、無償の入金と出金を 1 つのトランザクションに入れても両方が適用されます。 有償残高が動く場合はウォレット全体が書き換わるため、2 つの行を同居させられません。

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

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

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

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

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

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

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

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

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

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

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

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

制限を回避したい場合

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

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

同時実行とリトライ

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

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

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


Consume Action

消費アクション

Gs2Money2:WithdrawByUserId

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

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

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

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

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

Gs2Money2:VerifyReceiptByUserId

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

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

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

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

有効化条件必須デフォルト値の制限説明
namespaceNamestring
~ 128文字ネームスペース名
ネームスペース固有の名前。英数字および -(ハイフン) _(アンダースコア) .(ピリオド)で指定します。
userIdstring
~ 128文字ユーザーID
#{userId} と設定することでログイン中のユーザーIDに置換されます。
contentNamestring
~ 128文字ストアコンテンツモデル名
receiptReceipt
レシート
timeOffsetTokenstring~ 1024文字タイムオフセットトークン
{
    "action": "Gs2Money2:VerifyReceiptByUserId",
    "request": {
        "namespaceName": "[string]ネームスペース名",
        "userId": "[string]ユーザーID",
        "contentName": "[string]ストアコンテンツモデル名",
        "receipt": {
            "Store": "[string]ストア",
            "TransactionID": "[string]トランザクションID",
            "Payload": "[string]ペイロード"
        },
        "timeOffsetToken": "[string]タイムオフセットトークン"
    }
}
action: Gs2Money2:VerifyReceiptByUserId
request:
  namespaceName: "[string]ネームスペース名"
  userId: "[string]ユーザーID"
  contentName: "[string]ストアコンテンツモデル名"
  receipt: 
    Store: "[string]ストア"
    TransactionID: "[string]トランザクションID"
    Payload: "[string]ペイロード"
  timeOffsetToken: "[string]タイムオフセットトークン"
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を指定してウォレットの残高に加算

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

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

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

有効化条件必須デフォルト値の制限説明
namespaceNamestring
~ 128文字ネームスペース名
ネームスペース固有の名前。英数字および -(ハイフン) _(アンダースコア) .(ピリオド)で指定します。
userIdstring
~ 128文字ユーザーID
#{userId} と設定することでログイン中のユーザーIDに置換されます。
slotint
0 ~ 100000000スロット番号
ウォレットスロットを識別します。プラットフォーム間の残高共有が許可されていない場合、異なるスロットを使用してプラットフォームごとに通貨を分けて管理できます(例:iOS用とAndroid用)。
depositTransactionsList<DepositTransaction>
1 ~ 1000 items入金トランザクションリスト
timeOffsetTokenstring~ 1024文字タイムオフセットトークン
{
    "action": "Gs2Money2:DepositByUserId",
    "request": {
        "namespaceName": "[string]ネームスペース名",
        "userId": "[string]ユーザーID",
        "slot": "[int]スロット番号",
        "depositTransactions": [
            {
                "price": "[double]購入価格",
                "currency": "[string]通貨コード",
                "count": "[int]課金通貨の数量",
                "depositedAt": "[long]入金日時"
            }
        ],
        "timeOffsetToken": "[string]タイムオフセットトークン"
    }
}
action: Gs2Money2:DepositByUserId
request:
  namespaceName: "[string]ネームスペース名"
  userId: "[string]ユーザーID"
  slot: "[int]スロット番号"
  depositTransactions: 
    - price: "[double]購入価格"
      currency: "[string]通貨コード"
      count: "[int]課金通貨の数量"
      depositedAt: "[long]入金日時"
  timeOffsetToken: "[string]タイムオフセットトークン"
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]タイムオフセットトークン",
})