デジタル商品API

コミュニティグループドラフトレポート

このバージョン:
https://wicg.github.io/digital-goods/
課題追跡:
GitHub
編集者:
(Google)
(Google)
参加方法:
GitHub WICG/digital-goods新しい課題オープン課題
テスト:
web-platform-tests digital-goods/進行中の作業

概要

デジタル商品APIは、ウェブアプリケーションが自分のデジタル商品とデジタルストアによって管理されているユーザーの購入情報を取得できるようにします。ユーザーエージェントはストアへの接続を抽象化し、購入にはPayment Request APIが使用されます。

この文書の位置付け

この仕様はWeb Platform Incubator Community Groupによって公開されました。 これはW3C標準でもなく、W3C標準化の進行状況にも含まれていません。 W3C Community Contributor License Agreement (CLA)に基づき限定的なオプトアウトや他の条件が適用されることにご注意ください。 W3Cコミュニティおよびビジネスグループについて詳しくはこちら。

1. 利用例

注: このセクションは規範的ではありません。

1.1. サービスインスタンスの取得

APIの使用は window.getDigitalGoodsService() の呼び出しから始まります。このメソッドは特定のコンテキスト(例:HTTPS、アプリ、ブラウザ、OS)でのみ利用可能な場合があります。 利用できる場合、メソッドはサービスプロバイダーのURLを指定して呼び出すことができます。 指定したサービスプロバイダーが利用不可の場合、このメソッドはPromiseをリジェクトします。
if (window.getDigitalGoodsService === undefined) {
  // このコンテキストではデジタル商品APIはサポートされていません。
  return;
}
try {
  const digitalGoodsService = await
      window.getDigitalGoodsService("https://example.com/billing");
  // ここでサービスを利用します。
  ...
} catch (error) {
  // 希望するサービスプロバイダーが利用できませんでした。
  // 通常のウェブベースの決済フローを利用してください。
  console.error("サービスの取得に失敗:", error.message);
  return;
}

1.2. アイテム情報の取得

const details = await digitalGoodsService
    .getDetails(['shiny_sword', 'gem', 'monthly_subscription']);
for (item of details) {
  const priceStr = new Intl.NumberFormat(
      locale,
      {style: 'currency', currency: item.price.currency}
    ).format(item.price.value);
  AddShopMenuItem(item.itemId, item.title, priceStr, item.description);
}

getDetails() メソッドは特定のアイテムのサーバー側情報を返します。これはユーザーに購入オプションや価格を表示するためのもので、購入フローを実行することなくメニューで表示できます。

返される ItemDetails シーケンスの順序は任意であり、入力リストと出力が1:1で対応しない場合(サーバにアイテムが無い場合は含まれません)もあります。

アイテムIDはストアサーバで設定された主キーを表す文字列です。アイテムID一覧を取得する関数は無いため、クライアント側でハードコードするか、開発者自身のサーバから取得する必要があります。

アイテムの価格は PaymentCurrencyAmount であり、ユーザーの現在の地域・通貨での価格が格納されます。ユーザーのロケールに合わせてIntl.NumberFormatでフォーマットできます(上記例参照)。

ItemDetails オブジェクトの各フィールドについては、下記[ItemDetails 辞書]セクションを参照してください。

1.3. Payment Request APIでの購入

const details = await digitalGoodsService.getDetails(['monthly_subscription']);
const item = details[0];
new PaymentRequest(
  [{supportedMethods: 'https://example.com/billing',
    data: {itemId: item.itemId}}]);

購入フロー自体にはPayment Request API を利用します。ここでは完全な支払いリクエストのコードは省略していますが、ユーザーが購入を選択したアイテムのIDは、メソッドデータ内のdataフィールドにストアごとの形式で渡すことができます。

1.4. 既存購入の確認

purchases = await digitalGoodsService.listPurchases();
for (p of purchases) {
  VerifyOnBackendAndGrantEntitlement(p.itemId, p.purchaseToken);
}

listPurchases() メソッドは、ユーザーが現在所有・購入しているアイテムの一覧を取得するためのものです。エンタイトルメント(例:サブスクリプションやプロモコード、有料アップグレードの有効性確認)や、途中で通信エラーが発生した場合の復旧(例:購入済みだがバックエンド未確認の場合)などに必要となる場合があります。メソッドはアイテムIDと購入トークンを返し、エンタイトルメント付与前に開発者-プロバイダー間のAPIで検証されるのが一般的です。

1.5. 過去の購入履歴の確認

const purchaseHistory = await digitalGoodsService.listPurchaseHistory();
for (p of purchaseHistory) {
  DisplayPreviousPurchase(p.itemId);
}

listPurchaseHistory() メソッドは、ユーザーがこれまで購入した各アイテム種別の最新購入を一覧取得するためのものです。期限切れまたは消費済み購入も含まれることがあります。ストアによっては購入履歴を保存しない場合もあり、その場合はlistPurchases() と同じデータになります。

1.6. 購入の消費

digitalGoodsService.consume(purchaseToken);

複数回購入可能な商品は、再度購入する前に「消費済み」にする必要があります。例として、ゲーム内でプレイヤーを一時的に強化する消耗アイテムなどがあります。これはconsume() メソッドで実現できます。

より確実に消費を保証するために、可能な場合は開発者-プロバイダー間のAPIで消費処理することが推奨されます。

1.7. サブドメインiframeとの利用

<iframe
  src="https://sub.origin.example"
  allow="payment">
</iframe>

サブドメインのiframeからデジタル商品APIを利用可能にするには、iframe要素のallow属性に"payment"キーワードを指定します。クロスオリジンiframeでは本APIの呼び出しはできません。詳細や例についてはPermissions Policy仕様を参照してください。

2. API定義

2.1. Windowインターフェースの拡張

partial interface Window {
  [SecureContext] Promise<DigitalGoodsService> getDigitalGoodsService(
      DOMString serviceProvider);
};

Window オブジェクトは getDigitalGoodsService() メソッドを公開してもよい(MAY)。デジタル商品をサポートしないユーザーエージェントはgetDigitalGoodsService()Window インターフェース上に公開すべきではありません(SHOULD NOT)。

注: 上記の記述は機能検出を許容するためのものです。getDigitalGoodsService() が存在すれば、少なくとも1つのサービスプロバイダーで動作することが期待されます。

2.1.1. getDigitalGoodsService() メソッド

注: getDigitalGoodsService() メソッドは指定したserviceProvider が現在のコンテキストでサポートされているかどうかを判定するために呼び出されます。メソッドは DigitalGoodsService オブジェクトでPromiseを解決(resolve)しますが、serviceProviderが非対応や何らかのエラーの場合は例外でリジェクト(reject)します。 serviceProvider には通常URLベースの決済手段識別子を指定します。

getDigitalGoodsService(serviceProvider) メソッドが呼ばれたとき、以下の手順を実行する:
  1. document現在の設定オブジェクト関連グローバルオブジェクト関連付けられた Documentとする。

  2. もし document完全にアクティブ でなければ、a promise rejected with を返し、 "InvalidStateError" DOMExceptionとする。

  3. もし documentオリジントップレベルオリジンと同一オリジンでなければ、 a promise rejected with を返し、"NotAllowedError" DOMExceptionとする。

  4. もし document"payment" パーミッションを利用可能でなければ、 a promise rejected with を返し、"NotAllowedError" DOMExceptionとする。

  5. もし serviceProvider が undefined か null か空文字列なら、a promise rejected with を返し、TypeErrorとする。

  6. result を、デジタル商品サービス作成可否アルゴリズムserviceProviderdocumentを渡す)を実行した結果とする。

  7. result が false なら a promise rejected with を返し、 OperationError とする。

  8. a promise resolved with 新しい DigitalGoodsServiceを返す。

2.1.2. デジタル商品サービス作成可否アルゴリズム

デジタル商品サービス作成可否アルゴリズムは、ユーザーエージェント が指定された serviceProvider および document コンテキストをサポートするかどうかを判定する。
  1. ユーザーエージェントserviceProviderdocument、あるいは外部要因に応じて true または false を返してよい(MAY)。

注: これによりユーザーエージェントは異なるコンテキストで異なるサービスプロバイダーをサポートできます。

2.2. DigitalGoodsService インターフェース

[Exposed=Window, SecureContext] interface DigitalGoodsService {

  Promise<sequence<ItemDetails>> getDetails(sequence<DOMString> itemIds);

  Promise<sequence<PurchaseDetails>> listPurchases();

  Promise<sequence<PurchaseDetails>> listPurchaseHistory();

  Promise<undefined> consume(DOMString purchaseToken);
};

dictionary ItemDetails {
  required DOMString itemId;
  required DOMString title;
  required PaymentCurrencyAmount price;
  ItemType type;
  DOMString description;
  sequence<DOMString> iconURLs;
  DOMString subscriptionPeriod;
  DOMString freeTrialPeriod;
  PaymentCurrencyAmount introductoryPrice;
  DOMString introductoryPricePeriod;
  [EnforceRange] unsigned long long introductoryPriceCycles;
};

enum ItemType {
  "product",
  "subscription",
};

dictionary PurchaseDetails {
  required DOMString itemId;
  required DOMString purchaseToken;
};

2.2.1. getDetails() メソッド

getDetails(itemIds) メソッドが呼び出されたとき、次の手順を実行する:
  1. itemIds が空の場合、a promise rejected with を返し、 TypeError とする。

  2. result をデジタル商品サービスから指定された itemIds の情報をリクエストした結果とする。

注: これによりユーザーエージェント内でサービスプロバイダーごとの動作が可能になる。

  1. result がエラーの場合、 a promise rejected with を返し、OperationError とする。

  2. result 内の各 itemDetails について:

    1. itemDetails.itemId は空文字列であってはならない(SHOULD NOT)。

    2. itemIdsitemDetails.itemId を 含むべき(SHOULD)。

    3. itemDetails.title は空文字列であってはならない(SHOULD NOT)。

    4. itemDetails.price は 正規 PaymentCurrencyAmount でなければならない(MUST)。

    5. 存在する場合、itemDetails.subscriptionPeriod は iso-8601 期間でなければならない(MUST)。

    6. 存在する場合、itemDetails.freeTrialPeriod はiso-8601 期間でなければならない(MUST)。

    7. 存在する場合、itemDetails.introductoryPrice は 正規 PaymentCurrencyAmount でなければならない(MUST)。

    8. 存在する場合、itemDetails.introductoryPricePeriod は iso-8601 期間でなければならない(MUST)。

  3. a promise resolved with result を返す。

注: result の項目の順序が itemIds の順序と一致する必要はない。これは無効・欠落したアイテムを出力リストから省略可能にするため。

2.2.2. listPurchases() メソッド

listPurchases() メソッドが呼び出されたとき、次の手順を実行する:
  1. result をデジタル商品サービスからユーザーの購入情報をリクエストした結果とする。

注: これによりユーザーエージェント内でサービスプロバイダーごとの動作が可能になる。

  1. result がエラーの場合、 a promise rejected with を返し、OperationError とする。

  2. result 内の各 itemDetails について:

    1. itemDetails.itemId は空文字列であってはならない(SHOULD NOT)。

    2. itemDetails.purchaseToken は空文字列であってはならない(SHOULD NOT)。

  3. a promise resolved with result を返す。

2.2.3. listPurchaseHistory() メソッド

listPurchaseHistory() メソッドが呼び出されたとき、次の手順を実行する:
  1. result をユーザーがこれまで購入した各アイテム種別の最新購入情報のリクエスト結果とする。

  2. result がエラーの場合、 a promise rejected with を返し、OperationError とする。

  3. result 内の各 itemDetails について:

    1. itemDetails.itemId は空文字列であってはならない(SHOULD NOT)。

    2. itemDetails.purchaseToken は空文字列であってはならない(SHOULD NOT)。

  4. a promise resolved with result を返す。

2.2.4. consume() メソッド

注: この文脈での「消費」は購入を使い切ることを意味します。消費後はユーザーにその購入品の権利はなくなると想定されます。

consume(purchaseToken) メソッドが呼び出されたとき、次の手順を実行する:
  1. purchaseToken が空文字列の場合、 a promise rejected with を返し、 TypeError とする。

  2. result をデジタル商品サービスに purchaseToken を消費として記録するようリクエストした結果とする。

注: これによりユーザーエージェント内でサービスプロバイダーごとの動作が可能になる。

  1. result がエラーの場合、 a promise rejected with を返し、OperationError とする。

  2. a promise resolved with undefined を返す。

2.3. ItemDetails 辞書

このセクションは規範的ではありません。

ItemDetails 辞書は、serviceProvider から取得したデジタルアイテムの情報を表します。

2.4. PurchaseDetails 辞書

このセクションは規範的ではありません。

PurchaseDetails 辞書は、ユーザーが何らかの時点で購入した serviceProvider のデジタルアイテムについての情報を表します。

3. Permissions Policy との統合

本仕様は、"ポリシー制御機能" を "payment" という文字列で定義します。その デフォルト許可リストは 'self' です。

注: documentpermissions policy により、そのドキュメント内のコンテンツが DigitalGoodsService インスタンスを取得できるか決まります。どこかで無効化されていれば、その document 内のいかなるコンテンツも allowed to use getDigitalGoodsService() メソッドは利用できません(呼び出すと例外がスローされます)。

4. 追加定義

"payment" パーミッションは [permissions-policy] のフィーチャーであり、 payment-request仕様で定義されています

正規化された PaymentCurrencyAmount とは、PaymentCurrencyAmountamount正規化(check and canonicalize amount)の手順を実行してもエラーにも変更も発生しないものです。

iso-8601 は日付・時刻表現の標準規格です。

適合性

文書規約

適合要件は、記述的主張とRFC 2119用語の組み合わせで表現されます。 本仕様の規範的な部分で使われるキーワード "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", "OPTIONAL" はRFC 2119で定義される意味で解釈してください。 ただし、読みやすさのため、これらの単語はこの仕様書内ですべて大文字にはなっていません。

本仕様のテキストは、明示的に非規範的とされたセクション、例、および注釈を除いてすべて規範的です。 [RFC2119]

この仕様で例示は "for example" という語で始まるか、または class="example" として 本文から明示的に区別されます。例えば:

これは情報提供的な例です。

情報提供的な注釈は「Note」で始まり、 class="note" で明示的に区別されます。例えば:

Note, これは情報提供的な注釈です。

適合アルゴリズム

アルゴリズム内で命令形で書かれている要件(例えば「先頭スペース文字を除去する」や「false を返してこれらの手順を中断する」など)は、 そのアルゴリズム導入句で使われるキーワード("must", "should", "may"など)に従って解釈されます。

アルゴリズムや特定ステップとして表現される適合要件は、最終的な結果が同等である限り、どのような実装でもかまいません。 とくに本仕様で定義されるアルゴリズムは分かりやすさを重視しており、性能最適化を意図したものではありません。 実装者は最適化を推奨します。

索引

この仕様で定義された用語

参照によって定義される用語

参考文献

規範的な参考文献

[HTML]
Anne van Kesteren 他. HTML Standard. Living Standard.URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard.Living Standard.URL: https://infra.spec.whatwg.org/
[PAYMENT-REQUEST]
Marcos Caceres; Rouslan Solomakhin; Ian Jacobs. Payment Request API.URL: https://w3c.github.io/payment-request/
[PERMISSIONS-POLICY]
Ian Clelland. Permissions Policy.URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. RFCで要求レベルを表すキーワード.1997年3月.Best Current Practice.URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard.Living Standard.URL: https://webidl.spec.whatwg.org/

参考情報

[DOM]
Anne van Kesteren. DOM Standard.Living Standard. URL: https://dom.spec.whatwg.org/
[PAYMENT-METHOD-ID]
Marcos Caceres. Payment Method Identifiers.URL: https://w3c.github.io/payment-method-id/

IDL索引

partial interface Window {
  [SecureContext] Promise<DigitalGoodsService> getDigitalGoodsService(
      DOMString serviceProvider);
};

[Exposed=Window, SecureContext] interface DigitalGoodsService {

  Promise<sequence<ItemDetails>> getDetails(sequence<DOMString> itemIds);

  Promise<sequence<PurchaseDetails>> listPurchases();

  Promise<sequence<PurchaseDetails>> listPurchaseHistory();

  Promise<undefined> consume(DOMString purchaseToken);
};

dictionary ItemDetails {
  required DOMString itemId;
  required DOMString title;
  required PaymentCurrencyAmount price;
  ItemType type;
  DOMString description;
  sequence<DOMString> iconURLs;
  DOMString subscriptionPeriod;
  DOMString freeTrialPeriod;
  PaymentCurrencyAmount introductoryPrice;
  DOMString introductoryPricePeriod;
  [EnforceRange] unsigned long long introductoryPriceCycles;
};

enum ItemType {
  "product",
  "subscription",
};

dictionary PurchaseDetails {
  required DOMString itemId;
  required DOMString purchaseToken;
};