GS2-Money

課金通貨管理機能

ゲーム内のリソースのうち、現金相当の価値を持つリソースを扱う機能です。 日本の資金決済法の前払い式支払い手段(自家型)に該当する資産を取り扱う場合は必ずこの機能を利用してください。

残高

GS2-Money はプレイヤーのもつ課金通貨の残高を単純な数量では管理せず、購入時の価値ごとに数量を管理します。

例えば、100円で100個の課金通貨を購入した場合は課金通貨1個の価値は1円相当となります。 同時に1000円で1200個の課金通貨を購入できるとしましょう。あくまで課金通貨の単価は1円とし、200個はおまけとして無料で処理するのも一つの手段ですし、1000円で購入した場合は単価を 0.8334円 として異なる単価で扱うのも一つの手段です。

後者のような方式を採用した場合、GS2-Money は「単価1円の課金通貨の残高」「単価0.8334円の課金通貨の残高」をそれぞれ分けて管理する機能を持っています。

graph TD
  Wallet["ウォレット<br/>(スロット番号で区別)"]
  Wallet --> Paid["有償通貨<br/>(paid)"]
  Wallet --> Free["無償通貨<br/>(free)"]
  Paid --> Detail1["単価1円の残高"]
  Paid --> Detail2["単価0.8334円の残高"]
  Paid --> Detail3["...購入価値ごと"]

後者を選択するメリットは?

明らかに後者は会計処理が複雑になり、メリットがないように感じるかもしれません。 サービス提供側としては全くのその通りです。しかし、立場を変えてゲームプレイヤーの立場で考えてみましょう。

ゲームの中には運営によって配られた現金相当の価値が0円の課金通貨(通称 無償通貨)があります。 プレイヤーに課金通貨を購入してもらうために、有償で購入した課金通貨(通称 有償通貨)でしか購入できない魅力的な商品を課金通貨300個で販売したとしましょう。

1000円 で 1200個の課金通貨を購入した際に、1000個の有償通貨と200個の無償通貨 を付与するように処理した場合 プレイヤーは有償通貨300個で購入できる商品を3回しか購入できません。 一方で、単価を0.8334円として1200個全てを有償通貨として取り扱う方法であればプレイヤーは4回購入できます。 この差はプレイヤー心理に多少なりとも影響を与えます。

会計上の都合を優先するか、プレイヤーの利益を優先するか 慎重に検討するべき仕様でしょう。

スロット

GS2-Money ではウォレットを複数持つことができます。 その複数のウォレットを区別するためのキーがスロットです。

この機能は他のプラットフォームで購入した課金通貨を持ち込ませないようにしているプラットフォーマーが存在するため、そのガイドラインを遵守するために存在する機能です。

しかし、これらのガイドラインは有償通貨にのみ適用されるため、無償通貨については全てのスロットで共有できる機能があります。 ネームスペースの shareFreetrue に設定すると、無償通貨は全スロット共通の残高として扱われます。

消費優先度

プレイヤーが課金通貨を消費する時に、無償通貨を優先して消費するか、有償通貨を優先して消費するかを選択できます。 一般的に無償通貨を優先して消費する仕様が採用されますが、会計上の都合がある場合は有償通貨を優先できます。 有償通貨を消費する場合は、より単価の高い通貨から優先して消費されます。

ネームスペースの priority で以下のいずれかを指定します。

priority 説明
free 無償通貨を優先して消費
paid 有償通貨(単価の高いものから)を優先して消費

無償・有償問わず、あるいは有償通貨の中で入手した順番に消費したいというニーズがあることを我々は理解しています。 ただし、現在はその機能は実装されていません。この要件が重要なプロジェクトを検討中の場合は開発チームにご連絡ください。

レシートの検証

ゲームの配信プラットフォームで追加コンテンツの購入時に得られるレシートの検証機能を有しています。 レシートを検証し、正しくプラットフォーマーによって発行された内容であることを確認するとともに、過去にゲーム内で使用したことがないかも確認します。

この機能を利用することで、不正なレシートを利用して課金通貨を入手しようとする攻撃を回避できます。

レシート検証を有効化するには、ネームスペースに以下の認証情報を登録します。

  • appleKey: Apple App Store のバンドルID
  • googleKey: Google Play の公開鍵
  • enableFakeReceipt: 開発時にダミーレシートを許可するかどうか

トランザクションログ

レシートの検証履歴はもちろんですが、課金通貨の加算・減算履歴も全て記録されます。 そして、毎日数回 現在ゲーム内には未使用の課金通貨が現金相当額でいくらプールされているかを集計します。 集計結果はネームスペースの balance フィールドに反映されます。

状況に応じて未使用残高の一部を第三者機関に供託する対応が必要になることがありますが、その時にこの計算結果を利用できます。

法的手続き

GS2-Money は各種法的手続きをとるために必要なデータを収集し、APIによってアクセス可能な状態で保持しますが、 法的手続き自体はGS2の利用者である あなた/あなたが所属する組織 が実行する必要があります。

どのような手続きが必要となるかは、GS2では責任を持つことができないためアドバイスもできません。顧問弁護士に相談するようにしてください。

スクリプトトリガー

ネームスペースに各種スクリプト設定を登録すると、入出金処理やウォレット作成のタイミングでカスタムスクリプトを実行できます。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

  • createWalletScript: ウォレット作成時
  • depositScript: 入金処理時
  • withdrawScript: 出金処理時

トランザクションアクション

GS2-Money では以下のトランザクションアクションを提供しています。

  • 消費アクション: 残高の消費、レシートの記録
  • 入手アクション: 残高の加算、レシート記録の削除

「残高の加算」を入手アクションとして利用することで、特定のイベントクリア時や、ガチャの「おまけ」として直接課金通貨(有償・無償)を付与するといった処理を、トランザクション内で安全に実行できます。これにより、課金とゲーム内報酬を組み合わせた柔軟な施策が可能になります。

実装例

残高を取得

ウォレットのスロットを指定して、現在の有償通貨残高(paid)、無償通貨残高(free)、購入単価別の内訳(detail)を取得します。

    var item = await gs2.Money.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    ).ModelAsync();
    const auto Domain = Gs2->Money->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Wallet(
        0 // slot
    );
    const auto Item = Domain->Model();

残高を加算

残高を加算する処理はゲームエンジン用の SDK では処理できません。

GS2-Showcase の購入時の報酬として残高を加算する、あるいは GS2-Money のレシート検証 API をサーバー間通信で呼び出して付与するといった方法で実装してください。

残高を消費

このAPIで直接残高の消費処理を行うことは推奨していません。 GS2-Showcase といったサービスを通して課金通貨の消費を行うことで、消費と引き換えに商品を付与する処理を一連のトランザクションとして安全に扱うことができます。

paidOnlytrue に指定すると、有償通貨のみを消費対象とすることができます。 プラットフォーマーのガイドラインに準拠する必要がある場面で利用してください。

    var result = await gs2.Money.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    ).WithdrawAsync(
        count: 50,
        paidOnly: false
    );
    var item = await result.ModelAsync();
    var price = result.Price;
    const auto Future = Gs2->Money->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Wallet(
        0 // slot
    )->Withdraw(
        50,
        nullptr // paidOnly
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

詳細なリファレンス