$30 off During Our Annual Pro Sale. View Details »

[DroidKaigi 2022] 詳解Google Playの新しい定期購入 ~オファーの活用や実装例を添えて~

syarihu
October 05, 2022

[DroidKaigi 2022] 詳解Google Playの新しい定期購入 ~オファーの活用や実装例を添えて~

DroidKaigi 2022で発表した「詳解Google Playの新しい定期購入 ~オファーの活用や実装例を添えて~」の発表資料です。
https://droidkaigi.jp/2022/timetable/363682

syarihu

October 05, 2022
Tweet

More Decks by syarihu

Other Decks in Technology

Transcript

  1. 詳解Google Playの新しい定期購入 ~オファーの活用や実装例を添えて~ Taichi Sato / @syarihu Giftmall, Inc. Android

    Engineer 1
  2. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 2
  3. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 3
  4. 従来の定期購入とその課題について • 機能が同じ定期購入でも提供したいオファーなどが違う場合 は、ひとつの商品(SKU)として追加しなければならない • SKUがどんどん増えていき、管理が複雑になってしまう • SKUを作成するたびにアプリをリリースしなければならない 4

  5. 従来の定期購入とその課題について 5 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日
  6. 従来の定期購入とその課題について 6 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日
  7. 従来の定期購入とその課題について 7 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日
  8. 従来の定期購入とその課題について 8 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 期間限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日、お試し価格: 5,000円
  9. 従来の定期購入とその課題について 9 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 限定オファー • 請求対象期間: 年間 • 価格: 5,000円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額プレミアムプラン 初回限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日、お試し価格: 4,500円
  10. 従来の定期購入とその課題について 10 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額プレミアムプラン • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 限定オファー • 請求対象期間: 年間 • 価格: 5,000円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額プレミアムプラン 初回限定 • 請求対象期間: 年間 • 価格: 5,000円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 学生限定 • 請求対象期間: 年間 • 価格: 4,000円 オファー • 無料期間: 30日
  11. 従来の定期購入とその課題について • このように、従来の方法だとSKUが増え続け複雑になってしま う • これらの課題を解決し、各定期購入の管理コストを下げ、より 柔軟にオファーを作成できるように、Googleは定期購入の販 売方法を再構築した 11

  12. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 12
  13. 13 https://support.google.com/googleplay/android-developer/answer/12124625 より引用

  14. 14 https://support.google.com/googleplay/android-developer/answer/12124625 より引用

  15. 15 https://support.google.com/googleplay/android-developer/answer/12124625 より引用

  16. 16 https://support.google.com/googleplay/android-developer/answer/12124625 より引用

  17. 17 https://support.google.com/googleplay/android-developer/answer/12124625 より引用

  18. 18 https://support.google.com/googleplay/android-developer/answer/12124625 より引用

  19. 19 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別

    • 価格: 500円 オファー • 無料期間: 30日 従来の定期購入
  20. 20 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別

    • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 従来の定期購入
  21. 21 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別

    • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 従来の定期購入
  22. 22 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別

    • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 Subscription • 商品名: PROプラン 従来の定期購入 新しい定期購入に再構築した例
  23. 23 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別

    • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 Subscription • 商品名: PROプラン Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 従来の定期購入 新しい定期購入に再構築した例
  24. 24 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別

    • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 Subscription • 商品名: PROプラン オファー 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 オファー 提供の条件: 新規ユーザー 段階: • 無料試用 60日間 従来の定期購入 新しい定期購入に再構築した例
  25. 25 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間

    • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 期間限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額プレミアムプラン 初回限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 学生限定 • 請求対象期間: 年間 • 価格: 4,000円 オファー • 無料期間: 30日 従来の定期購入
  26. 26 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間

    • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 期間限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額プレミアムプラン 初回限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 学生限定 • 請求対象期間: 年間 • 価格: 4,000円 オファー • 無料期間: 30日 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 期間限定 提供の条件: デベロッパー指定 段階: • 1年間 500円 固定割引 オファー - 無料試用 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円 固定割引 従来の定期購入 新しい定期購入に再構築した例 Subscription • 商品名: PROプラン オファー - 学生限定 提供の条件: デベロッパー指定 段階: • 無料試用 30日間 • 3年間 1,500円 固定割引
  27. 27 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間

    • 価格: 5,500円 オファー • 無料期間: 30日 Subscription • 商品名: PROプラン Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 無料試用 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 従来の定期購入 新しい定期購入に再構築した例
  28. 28 • Subscription (SKU) • 商品名: 年額PROプラン 期間限定 • 請求対象期間:

    年間 • 価格: 5,500円 オファー • お試し価格: 5,000円 Subscription • 商品名: PROプラン Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 期間限定 提供の条件: デベロッパー指定 段階: • 1年間 500円 固定割引 従来の定期購入 新しい定期購入に再構築した例
  29. 29 • Subscription (SKU) • 商品名: 年額PROプラン 初回限定 • 請求対象期間:

    年間 • 価格: 5,500円 オファー • 無料期間: 30日、お試し価格: 4,500円 Subscription • 商品名: PROプラン Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円 固定割引 従来の定期購入 新しい定期購入に再構築した例
  30. 30 • Subscription (SKU) • 商品名: 年額PROプラン 学生限定 • 請求対象期間:

    年間 • 価格: 4,000円 オファー • 無料期間: 30日 Subscription • 商品名: PROプラン Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 従来の定期購入 新しい定期購入に再構築した例 定期購入を解約するまで 割引価格で利用できてしまう 定期的な支払いで割引価格が提供で きるようになったので、 今回は例として3年間固定割引にして いる オファー - 学生限定 提供の条件: デベロッパー指定 段階: • 無料試用 30日間 • 3年間 1,500円 固定割引
  31. 31 • Subscription (SKU) • 商品名: 年額PROプラン • 請求対象期間: 年間

    • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 期間限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額プレミアムプラン 初回限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 学生限定 • 請求対象期間: 年間 • 価格: 4,000円 オファー • 無料期間: 30日 Base plan - 通常 • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 期間限定 提供の条件: デベロッパー指定 段階: • 1年間 500円 固定割引 オファー - 無料試用 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円 固定割引 従来の定期購入 新しい定期購入に再構築した例 Subscription • 商品名: PROプラン オファー - 学生限定 提供の条件: デベロッパー指定 段階: • 無料試用 30日間 • 3年間 1,500円 固定割引
  32. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 32
  33. 新しい定期購入の作り方 1. 定期購入を作成する 2. 定期購入の下に基本プランを追加する ◦ 更新期間や地域別の価格などを設定する 3. 基本プランの下にオファー(特典)を追加する ◦

    無料試用や特定の価格から割引きたい場合はオファーと して追加する 33
  34. BenefitsとOffer • 定期購入の詳細で編集できる「特典」 とこれから説明する基本プランに追 加する「特典」は別物なので注意 ◦ 定期購入の詳細では、定期購入を 購入することにより得られるメリット を記載する •

    定期購入の詳細の「特典」は英語で はBenefits、基本プランに追加する 「特典」はOfferとなっているため、本 発表では基本プランに追加する「特 典」はオファーとして扱う 34
  35. 1. 定期購入を作成する アイテムID • 定期購入を識別する一意なID • 実装ではProduct Id(旧SKU) 名前 •

    ユーザーにも表示される定期購入 名 35
  36. 2. 基本プランを追加する 基本プランID • 定期購入ごとに一意なID 更新の種類 • 自動更新 or 前払い

    • 種類ごとに、1つの定期購入に対し て同じ請求対象期間の基本プラン を複数作ることはできない ◦ 月額の基本プランを2つ追加 するなどはできない ◦ 無効にしても同じ期間の基本 プランは追加できないので注 意 36
  37. 2. 基本プランを追加する タグ • 実装時に基本プランやオファーを識別するため のタグ • アプリの実装では基本プランやオファーをタグ でしか判別できないため、必ず設定しておく •

    基本プランやオファーごとに最大 20個設定でき る • 基本プランに設定したタグは、オファーのタグ にも継承される ◦ Play Console上からは見えないが、 アプリのAPIからオファーのタグを取 得するときに基本プランに設定したタ グも含まれている 37
  38. 3. オファーを追加する • 追加する対象の基本プランを選択 して、オファーを追加する 38

  39. 3. オファーを追加する 特典ID • オファーを識別する一意な ID 基本プランと公開設定 • 基本プランは追加する対象の基本プラン が表示される

    • 公開設定はオファーを提供する提供地域 を選択する(デフォルトでは基本プランと 同じ提供地域になる) タグ • アプリでオファーを識別するタグ • 用途は基本プランのタグと同じ 39
  40. 3. オファーを追加する 提供の条件 • 新規ユーザーの獲得 • アップグレード • デベロッパー指定 40

  41. 3. オファーを追加する 提供の条件 • 新規ユーザーの獲得 この定期購入を利用したことがないか、 このアプリの定期購入をまったく利用し たことが無いユーザーを対象としてオ ファーを提供する このオファーの対象になるかは購入時

    にGoogle Playが判定する 41
  42. 3. オファーを追加する 提供の条件 • アップグレード 特定の定期購入と基本プランを購読中 のユーザーを対象としてオファーを提 供する このオファーの対象になるかは購入時 にGoogle

    Playが判定する 42
  43. 3. オファーを追加する 提供の条件 • デベロッパー指定 ユーザーがオファーの対象となるかどう かはデベロッパーがアプリ内で指定す る オファーをアプリ外で販売することはで きない

    43
  44. 3. オファーを追加する 提供の条件 • デベロッパー指定 たとえば、アプリが持つユーザー情報 を元にオファーを提供するのかを決め たい場合は、提供条件をデベロッパー 指定にする 44

  45. 3. オファーを追加する 提供の条件 • デベロッパー指定 また、ユーザーが利用できるオファーの 一覧をアプリで表示したい場合は、条 件をデベロッパー指定にする デベロッパー指定のオファーにタグを設 定して、そのタグが設定されたオファー

    のみを表示するなどの使い方ができる 45
  46. 3. オファーを追加する 購入フェーズ(段階) タイプ • 1回限りのお支払い • 定期的なお支払い • 無料試用

    46
  47. 3. オファーを追加する 購入フェーズ(段階) タイプ • 1回限りのお支払い 1回だけ固定額または割引した価格を支払 い、指定した期間だけ利用できる たとえば1年間だけ特別に割引したい場合 は12ヶ月に設定すると、12ヶ月間はその価

    格で利用できる その後、基本プランの価格での支払いが 始まる 47
  48. 3. オファーを追加する 購入フェーズ(段階) 1回限りのお支払い - 価格のオーバーライド • 固定額 ◦ ここで設定した価格に上書きする

    ◦ 通常価格が5,000円で、固定額が 4,000円なら4,000円になる • 割引率 ◦ 通常価格からの割引率( ~ %) ◦ 通常価格が5,000円で10%に設定し たら4,500円 • 割引額 ◦ 通常価格からの割引額( ~ 円 ) ◦ 通常価格が5,000円で500円に設定 したら4,500円 48
  49. 3. オファーを追加する 購入フェーズ(段階) タイプ • 定期的なお支払い 2 ~ 52回のいずれかの請求対象期間、固定 額または割引した価格を支払うことで利用で

    きる たとえば年間払いで請求対象期間を 2回に すると、2回目の支払いまでは固定額または 割引した価格で利用できる その後、基本プランの価格での支払いが始 まる 49
  50. 3. オファーを追加する 50 購入フェーズ(段階) 価格のオーバーライド • 固定額 ◦ ここで設定した価格に上書きする •

    割引率 ◦ 既存価格からの割引率( ~ %) ◦ 既存価格が5,000円で10%に設定し たら4,500円 • 割引額 ◦ 既存価格からの割引額( ~ 円 ) ◦ 既存価格が5,000円で500円に設定 したら4,500円
  51. 3. オファーを追加する 購入フェーズ(段階) タイプ • 無料試用 無料試用期間を設定する 既存の定期購入で提供していた無料期 間は購入フェーズの一部となっている ため、無料試用を設定したい場合はこ

    れを利用する 51
  52. 52 Subscription • 商品名: PROプラン Base plan - 初回限定 •

    更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円割引 新しい定期購入の作り方
  53. 53 Subscription • 商品名: PROプラン Base plan - 初回限定 •

    更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円割引 新しい定期購入の作り方
  54. 54 Subscription • 商品名: PROプラン Base plan - 初回限定 •

    更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円割引 新しい定期購入の作り方
  55. 55 Subscription • 商品名: PROプラン Base plan - 初回限定 •

    更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円割引 新しい定期購入の作り方
  56. 56 新しい定期購入の作り方 Subscription • 商品名: PROプラン Base plan - 初回限定

    • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円割引
  57. 57 新しい定期購入の作り方 Subscription • 商品名: PROプラン Base plan - 初回限定

    • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 オファー - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円割引
  58. 基本プランやオファーの反映について • 即時反映されるわけではなく、若干のラグがある • Playストアのアプリデータを消し飛ばすと再取得されることも あるので、デバッグ時に更新されないなと思ったらアプリデー タを消してみるのがいいかも 58

  59. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 59
  60. 自動移行された既存の定期購入 • 既存の定期購入は、新しい定期購入として自動移行されてい る • しかし、自動移行された定期購入は理想の定期購入の構成に はならない 60

  61. 61 自動移行された既存の定期購入 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日
  62. 62 自動移行された既存の定期購入 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 月別のプランはp1mの基本プランとして追加されている(半年なら p6m、年間ならp1yになる)
  63. 63 自動移行された既存の定期購入 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 無料期間は freetrial のオファーとして追加されている
  64. 64 自動移行された既存の定期購入 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間:

    月別 • 価格: 500円 オファー • 無料期間: 30日 自動移行されたプランは下位互換性のあるプランとしてマークされている 下位互換性のある基本プランは、 PBLv5で非推奨になっている querySkuDetailsAsync() メソッドに 返されるため、PBLv4以前の既存の実装でも問題なく購入できるようになっている
  65. 65 Subscription • 商品名: PROプラン Offer 提供の条件: 新規ユーザー 段階: •

    無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 • Subscription (SKU) • 商品名: 月額PROプラン • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 月額PROプラン 2ヶ月無料 • 請求対象期間: 月別 • 価格: 500円 オファー • 無料期間: 60日 Offer 提供の条件: 新規ユーザー 段階: • 無料試用 60日間 理想の定期購入の構成 - 月額
  66. 66 Subscription • 商品名: PROプラン Offer 提供の条件: 新規ユーザー 段階: •

    無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 Offer 提供の条件: 新規ユーザー 段階: • 無料試用 60日間 実際に自動移行された定期購入の構成 - 月額 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 Subscription • 商品名: PROプラン 2ヶ月無料
  67. 67 理想の定期購入の構成 - 年額 • Subscription (SKU) • 商品名: 年額PROプラン

    • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 期間限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額プレミアムプラン 初回限定 • 請求対象期間: 年間 • 価格: 5,500円 オファー • 無料期間: 30日 • Subscription (SKU) • 商品名: 年額PROプラン 学生限定 • 請求対象期間: 年間 • 価格: 4,000円 オファー • 無料期間: 30日 Base plan - 通常 • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 Offer - 期間限定 提供の条件: 新規ユーザー 段階: • 1年間 500円 固定割引 Offer - 無料試用 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 Offer - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円 固定割引 Subscription • 商品名: PROプラン Offer - 学生限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 3年間 1,500円 固定割引
  68. 68 実際に自動移行された定期購入の構成 - 年額 Base plan • 更新の種類: 自動更新 •

    請求対象期間 : 年間 • 価格: 5,500円 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 Subscription • 商品名: 年額PROプラン 期間限定 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 5,500円 Subscription • 商品名: 年額PROプラン Subscription • 商品名: 年額PROプラン 初回限定 Subscription • 商品名: 年額PROプラン 学生限定 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 年間 • 価格: 4,000円 Offer - 期間限定 提供の条件: 新規ユーザー 段階: • 1年間 500円 固定割引 Offer - 無料試用 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 Offer - 無料試用 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 Offer - 初回限定 提供の条件: 新規ユーザー 段階: • 無料試用 30日間 • 1年間 1,000円 固定割引
  69. 自動移行された既存の定期購入 • 新しい定期購入では、定期購入に基本プランを追加し、基本 プランにオファーを追加できるようになった ◦ すでに提供されている定期購入を新しい定期購入の構成 に移行しようと思うと、期間ごとに定期購入商品が違うた め、別の商品に買い直さなければならない ◦ 自動では移行できないのでこの構成になったのだと思わ

    れる 69
  70. 既存の定期購入を新しい定期購入にしようとすると… • 既存の定期購入を購読中のユーザーは勝手には移行できな いので、そのままにするか、新しい定期購入への切り替え手 順を用意して移行を促すしかない • それでも移行しないユーザーはいるので、単純に定期購入商 品が増えて売上管理や分析、実装などが面倒になる 70

  71. 既存の定期購入を新しい 定期購入にしようとすると … 既存の定期購入に基本プランを増やす こともできるが、一つの定期購入商品 に対して一つの請求対象期間のみだっ たのが、 71 Base plan

    • 請求対象期間 : 月間 Subscription 定期購入1 - 月額 Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年額 既存の定期購入の構成
  72. 既存の定期購入を新しい 定期購入にしようとすると … 新しく作成する一部の定期購入だけ一 つの定期購入に複数の請求対象期間 が含まれることになる 実装上も、定期購入に紐づく基本プラ ンをタグを元に探すというステップが必 要になり、既存の定期購入と実装方法 が異なるため、そういった点も考慮が必

    要になる 72 Base plan • 請求対象期間 : 月間 Base plan • 請求対象期間 : 年間 Subscription 定期購入1 新しい定期購入の形
  73. 自動移行された既存の定期購入をどうするか • 新しく作成する定期購入だけ新しい構成に則る • 新しい定期購入の構成に則るのを諦める • 全部新しい定期購入の構成に則るように頑張る ◦ これはデメリットが多すぎるのでおすすめしません 73

  74. 新しく作成する定期購入だけ新しい構成に則る 74 Base plan • 請求対象期間 : 月間 Subscription 定期購入1

    - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い 自動移行された既存の定期購入
  75. 新しく作成する定期購入だけ新しい構成に則る 75 Base plan • 請求対象期間 : 月間 Subscription 定期購入1

    - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い Base plan • 請求対象期間 : 月間 Subscription 新しい定期購入 Base plan • 請求対象期間 : 年間 自動移行された既存の定期購入 新しい定期購入だけ定期購入の下に 複数の期間を追加する
  76. 新しく作成する定期購入だけ新しい構成に則る 76 Base plan • 請求対象期間 : 月間 Subscription 定期購入1

    - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い Base plan • 請求対象期間 : 月間 Subscription 新しい定期購入 Base plan • 請求対象期間 : 年間 新しい定期購入だけ定期購入の下に 複数の期間を追加する 自動移行された既存の定期購入 • 自動移行された既存の定期購入は定期購入と 期間がイコールなのでシンプルだったが、一つ の定期購入に複数の基本プランが紐づく • 特定の期間(基本プラン)を購入しようとしたと き、定期購入に紐づく基本プランを探さなけれ ばならないので、既存と新しい定期購入で異な る実装をしなければならないため、実装が少し 複雑になる • 一部だけ違う構成になるので、構成が統一さ れておらず管理上も少しややこしくなる
  77. 新しく作成する定期購入だけ新しい構成に則る 77 Base plan • 請求対象期間 : 月間 Subscription 定期購入1

    - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い Base plan • 請求対象期間 : 半年 自動移行された既存の定期購入 • 既存の定期購入は定期購入と期間が 同じのため、商品名を変えないとおかし くなってしまう • 既存の定期購入に別の期間を追加す ることはできない
  78. 新しく作成する定期購入だけ新しい構成に則る 78 Base plan • 請求対象期間 : 月間 Subscription 定期購入1

    - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い Subscription 定期購入1 - 半年払い Base plan • 請求対象期間 : 半年 1つの定期購入に対して 1つの期間のみが 紐づくように作成する 自動移行された既存の定期購入
  79. 新しい定期購入の構成に則るのを諦める 79 Base plan • 請求対象期間 : 月間 Subscription 定期購入1

    - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い 自動移行された既存の定期購入 Subscription 定期購入2 - 月払い Base plan • 請求対象期間 : 月間 Subscription 定期購入2 - 年払い Base plan • 請求対象期間 : 年間 いままでどおり、一つの定期購入に対して一つの 期間(基本プラン)のみを追加する 商品の扱いが変わらないのでオファー以外の実装 は既存の実装がそのまま使える(はず)
  80. 全部新しい定期購入の構成に則るように頑張る ※これをやるのはおすすめしません 80 Base plan • 請求対象期間 : 月間 Subscription

    定期購入1 - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い 自動移行された既存の定期購入 Base plan • 請求対象期間 : 月間 Subscription 新しい定期購入1 Base plan • 請求対象期間 : 年間 案1. 新しい定期購入を作る 新規ユーザーはこちらを購入させ、 既存ユーザーはそのままにするか、切り替えを促す(なぜ 移行しないといけないのか、ユーザーへの説明が難しい)
  81. 全部新しい定期購入の構成に則るように頑張る ※これをやるのはおすすめしません 81 Base plan • 請求対象期間 : 月間 Subscription

    定期購入1 - 月払い Base plan • 請求対象期間 : 年間 Subscription 定期購入1 - 年払い 自動移行された既存の定期購入 案2. 既存の定期購入に基本プランを増やす 新規ユーザーは新しい基本プランを購入させ、 既存ユーザーはそのままにするか、別の定期購入を購入 しているユーザーには移行を促す 既存の定期購入の商品名も変える必要がある Base plan • 請求対象期間 : 月間
  82. 自動移行された既存の定期購入をどうするか • 新しく作成する定期購入だけ新しい構成に則る • 新しい定期購入の構成に則るのを諦める ◦ 新しい定期購入があまり増えないのであればこれが良さそうかも • 全部新しい定期購入の構成に則るように頑張る 新しい定期購入をどのような構成にするかは予めチームで相談し

    て方針を決めておくとよい 82
  83. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 83
  84. 既存の定期購入における新しいオファーの活用方法 • 既存の定期購入は新しい定期購入の理想の構成にはできな いものの、新しい構成に自動移行されているのでオファーの 活用はできる 84

  85. 85 Subscription • 商品名: 月額PROプラン Offer 提供の条件: 新規ユーザー 段階: •

    無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 既存の定期購入における新しいオファーの活用方法 通常の無料試用
  86. 86 Subscription • 商品名: 月額PROプラン Offer 提供の条件: 新規ユーザー 段階: •

    無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 既存の定期購入における新しいオファーの活用方法 Offer 提供の条件: デベロッパー指定 段階: • 1ヶ月 20%割引 通常の無料試用 季節限定セール
  87. 87 Subscription • 商品名: 月額PROプラン Offer 提供の条件: 新規ユーザー 段階: •

    無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 既存の定期購入における新しいオファーの活用方法 Offer 提供の条件: デベロッパー指定 段階: • 1ヶ月 20%割引 通常の無料試用 季節限定セール Offer 提供の条件: 新規ユーザー 段階: • 3ヶ月 価格を300円に固定 期間限定 新規ユーザー向けセール
  88. 88 Subscription • 商品名: 月額PROプラン Offer 提供の条件: 新規ユーザー 段階: •

    無料試用 30日間 Base plan • 更新の種類: 自動更新 • 請求対象期間 : 月別 • 価格: 500円 既存の定期購入における新しいオファーの活用方法 Offer 提供の条件: デベロッパー指定 段階: • 1ヶ月 20%割引 通常の無料試用 季節限定セール Offer 提供の条件: デベロッパー指定 段階: • 1ヶ月 100円割引 Offer 提供の条件: 新規ユーザー 段階: • 3ヶ月 価格を300円に固定 期間限定 新規ユーザー向けセール 過去に購読してくれていた ユーザーに対しての おかえりなさいオファー
  89. 既存の定期購入における新しいオファーの活用方法 • 既存の定期購入では1つの定期購入に対して1つの基本プランしか 紐付いていないため、別々の期間に同じオファーを提供したい場合 は、定期購入ごとにオファーを追加する必要がある • オファーが使えるように正しく実装していれば、リリースしなくてもオ ファーが提供できる場合がある • 下位互換性の無いオファーを使うにはPBLv5へのアップグレードか

    つ新しいオファーを利用するための実装が必要 89
  90. 既存の定期購入における新しいオファーの活用方法 • どのようなオファーを作成できるのかをチームに共有し、オ ファーの条件に応じて適切な実装をすることでオファーを活用 できるようにしておく 90

  91. Agenda 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library 5.0への移行

    Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07 91
  92. Play Billing Library 5.0への移行 92 • 新しい定期購入の仕組みの再構築に合わせて、Play Billing Library 5.0(PBLv5)もリリースされた

    • 新しい定期購入の仕組みや新しいオファーを利用するには PBLv5へのアップデートが必須になる dependencies { def billingVersion = "5.0.0" implementation "com.android.billingclient:billing:$billingVersion" }
  93. 2020年 2021年 2022年 2023年 2024年 PBL 3.0 PBL 3.0 リリース

    PBL 4.0 リリース PBL 5.0 リリース PBL 6.0 リリース PBL 7.0 リリース 93 PBL 4.0 PBL 5.0
  94. Play Billing Library 5.0への移行 94 • PBL 3.xのサポートは2022年11月に終了する • 2022年11月以降、PBLを利用しているアプリがアプリの更新

    を公開するにはPBL 4.x以上にアップグレードする必要がある • PBL 5.0は、新しい定期購入の機能へ順次対応できるように、 古いメソッドは非推奨の形で残っている
  95. Play Billing Library 5.0への移行 95 • 4.xへのアップグレードと5.xへのアップグレードの移行コスト はほとんど変わらないため、まだ 3.xを利用しているアプリは 一旦5.0にアップグレードするのがおすすめ

    • すでに4.xを利用しているアプリも、一旦5.0に上げておき、 5.0から追加された機能や非推奨のメソッドは画面ごとに置き 換えるなど、順次対応していくのがおすすめ
  96. PBLv3からv4へのアップグレードは必要があれば こちらの技術書を参考に… 96 https://techbooster.booth.pm/items/3101567

  97. Agenda 97 従来の定期購入とその課題について 再構築された新しい定期購入 新しい定期購入の作り方 自動移行された既存の定期購入 既存の定期購入における新しいオファーの活用方法 Play Billing Library

    5.0への移行 Play Billing Library 5.0の機能をサポートする 01 02 03 04 05 06 07
  98. Play Billing Library 5.0の変更サマリ 98 • SkuDetailsに置き換わる、ProductDetailsが新たに追加された ◦ ProductDetailsには、すべての基本プランとそれに紐づくすべ てのオファー情報が含まれる

    • Sku関連のメソッドなどの命名がProductになったものが追加され た • それに伴い、Sku関連のクラスやメソッドなどはすべて非推奨になっ た
  99. Play Billing Library 5.0の変更サマリ 99 • SkuDetailsによる購入フローでは、skuは商品自体に定期購 入の期間も含まれていたため、SkuDetailsを渡せば購入でき ていた •

    ProductDetailsによる新しい定期購入の購入フローでは、 ProductDetailsの中に複数の基本プラン(定期購入の期間) やオファーが含まれるため、基本プランやオファーを選択する 実装が必要になった
  100. 100 sku( pro_monthly )を元にSkuDetailsを取得 SkuDetailsを使って購入をリクエストする skuがpro_monthlyのSkuDetails • これまでは商品自体に購入期間も含まれていたため、 SkuDetailsだけで購入を開始できた Play

    Billing Library 4.xまでのSkuDetailsを使った購入までの処理の流れ
  101. 101 sku( pro_monthly )を元にSkuDetailsを取得 SkuDetailsを使って購入をリクエストする skuがpro_monthlyのSkuDetails • これまでは商品自体に購入期間も含まれていたため、 SkuDetailsだけで購入を開始できた Play

    Billing Library 4.xまでのSkuDetailsを使った購入までの処理の流れ
  102. 102 sku( pro_monthly )を元にSkuDetailsを取得 SkuDetailsを使って購入をリクエストする skuがpro_monthlyのSkuDetails • これまでは商品自体に購入期間も含まれていたため、 SkuDetailsだけで購入を開始できた Play

    Billing Library 4.xまでのSkuDetailsを使った購入までの処理の流れ
  103. 103 sku( pro_monthly )を元にSkuDetailsを取得 SkuDetailsを使って購入をリクエストする skuがpro_monthlyのSkuDetails • これまでは商品自体に購入期間も含まれていたため、 SkuDetailsだけで購入を開始できた Play

    Billing Library 4.xまでのSkuDetailsを使った購入までの処理の流れ
  104. 104 offerTag = p1m offerTag = free-trial offerTag = free-trial-60days

    Play Billing Library 5.0からのProductDetailsを使った購入までの処理の流れ
  105. 105 購入対象の基本プランとそれに紐づくすべてのオファーの中か ら、どのオファーを提供する(またはオファー無しで提供する)の かを何らかのロジックで決める ProductDetailsのSubscriptionOfferDetailsのリストから、 基本プランに設定したタグ( p1m)を元に基本プランとそれに紐 づくオファーを探す Product Id(

    pro_plan )を元にProductDetailsを取得 Product IdがproのProductDetails • ProductDetailsのSubscriptionOfferDetailsのリストに すべての基本プランやそれに紐づくすべてのオファー の情報が含まれる SubscriptionOfferDetailsのリスト • 購入対象の基本プランとそれに紐づくすべてのオファーが含ま れる SubscriptionOfferDetails • 購入対象の基本プランとオファーのセットが含まれる SubscriptionOfferDetailsに含まれるofferTokenを使って購入 をリクエストする Play Billing Library 5.0からのProductDetailsを使った購入までの処理の流れ
  106. 106 購入対象の基本プランとそれに紐づくすべてのオファーの中か ら、どのオファーを提供する(またはオファー無しで提供する)の かを何らかのロジックで決める ProductDetailsのSubscriptionOfferDetailsのリストから、 基本プランに設定したタグ( p1m)を元に基本プランとそれに紐 づくオファーを探す Product Id(

    pro_plan )を元にProductDetailsを取得 Product IdがproのProductDetails • ProductDetailsのSubscriptionOfferDetailsのリストに すべての基本プランやそれに紐づくすべてのオファー の情報が含まれる SubscriptionOfferDetailsのリスト • 購入対象の基本プランとそれに紐づくすべてのオファーが含ま れる SubscriptionOfferDetails • 購入対象の基本プランとオファーのセットが含まれる SubscriptionOfferDetailsに含まれるofferTokenを使って購入 をリクエストする Play Billing Library 5.0からのProductDetailsを使った購入までの処理の流れ
  107. 107 val product = QueryProductDetailsParams.Product .newBuilder() .setProductId("pro_plan") .setProductType(BillingClient.ProductType.SUBS) .build() val

    queryProductDetailsParams = QueryProductDetailsParams .newBuilder() .setProductList(listOf(product)) .build() ProductDetailsを取得するためのパラメータを生成する
  108. 108 val product = QueryProductDetailsParams.Product .newBuilder() .setProductId("pro_plan") .setProductType(BillingClient.ProductType.SUBS) .build() val

    queryProductDetailsParams = QueryProductDetailsParams .newBuilder() .setProductList(listOf(product)) .build() ProductDetailsを取得するためのパラメータを生成する
  109. 109 val product = QueryProductDetailsParams.Product .newBuilder() .setProductId("pro_plan") .setProductType(BillingClient.ProductType.SUBS) .build() val

    queryProductDetailsParams = QueryProductDetailsParams .newBuilder() .setProductList(listOf(product)) .build() ProductDetailsを取得するためのパラメータを生成する
  110. 110 val product = QueryProductDetailsParams.Product .newBuilder() .setProductId("pro_plan") .setProductType(BillingClient.ProductType.SUBS) .build() val

    queryProductDetailsParams = QueryProductDetailsParams .newBuilder() .setProductList(listOf(product)) .build() ProductDetailsを取得するためのパラメータを生成する
  111. 111 ProductDetailsを取得する billingClient.queryProductDetailsAsync( queryProductDetailsParams ) { billingResult: BillingResult, productDetailsList ->

    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList.isNotEmpty() ) { onResponse(productDetailsList) } else { // エラーハンドリング } }
  112. 112 ProductDetailsを取得する billingClient.queryProductDetailsAsync( queryProductDetailsParams ) { billingResult: BillingResult, productDetailsList ->

    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList.isNotEmpty() ) { onResponse(productDetailsList) } else { // エラーハンドリング } }
  113. 113 ProductDetailsを取得する billingClient.queryProductDetailsAsync( queryProductDetailsParams ) { billingResult: BillingResult, productDetailsList ->

    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList.isNotEmpty() ) { onResponse(productDetailsList) } else { // エラーハンドリング } }
  114. 114 ProductDetailsを取得する billingClient.queryProductDetailsAsync( queryProductDetailsParams ) { billingResult: BillingResult, productDetailsList ->

    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList.isNotEmpty() ) { onResponse(productDetailsList) } else { // エラーハンドリング } }
  115. 115 購入対象の基本プランとそれに紐づくすべてのオファーの中か ら、どのオファーを提供する(またはオファー無しで提供する)の かを何らかのロジックで決める ProductDetailsのSubscriptionOfferDetailsのリストから、 基本プランに設定したタグ( p1m)を元に基本プランとそれに紐 づくオファーを探す Product Id(

    pro_plan )を元にProductDetailsを取得 Product IdがproのProductDetails • ProductDetailsのSubscriptionOfferDetailsのリストに すべての基本プランやそれに紐づくすべてのオファー の情報が含まれる SubscriptionOfferDetailsのリスト • 購入対象の基本プランとそれに紐づくすべてのオファーが含ま れる SubscriptionOfferDetails • 購入対象の基本プランとオファーのセットが含まれる SubscriptionOfferDetailsに含まれるofferTokenを使って購入 をリクエストする Play Billing Library 5.0からの購入までの処理の流れ
  116. 116 基本プランとそれに紐づくオファーを探す // 基本プラン(オファー無し)と基本プランに紐づくオファーが取得できる val p1mOfferDetailsList = subscriptionOfferDetailsList .filter {

    subscriptionOfferDetails -> // 基本プランに設定したタグを探す // 基本プランに紐づくオファーには基本プランに設定したタグが継承されるため、 // タグが一意であれば基本プランとそれに紐づくオファーが取得できる subscriptionOfferDetails.offerTags.any { offerTag -> offerTag == "p1m" } }
  117. 117 基本プランとそれに紐づくオファーを探す // 基本プラン(オファー無し)と基本プランに紐づくオファーが取得できる val p1mOfferDetailsList = subscriptionOfferDetailsList .filter {

    subscriptionOfferDetails -> // 基本プランに設定したタグを探す // 基本プランに紐づくオファーには基本プランに設定したタグが継承されるため、 // タグが一意であれば基本プランとそれに紐づくオファーが取得できる subscriptionOfferDetails.offerTags.any { offerTag -> offerTag == "p1m" } }
  118. 118 基本プランとそれに紐づくオファーを探す // 基本プラン(オファー無し)と基本プランに紐づくオファーが取得できる val p1mOfferDetailsList = subscriptionOfferDetailsList .filter {

    subscriptionOfferDetails -> // 基本プランに設定したタグを探す // 基本プランに紐づくオファーには基本プランに設定したタグが継承されるため、 // タグが一意であれば基本プランとそれに紐づくオファーが取得できる subscriptionOfferDetails.offerTags.any { offerTag -> offerTag == "p1m" } }
  119. 無料期間30日のオファーのSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[0] offerDetails.offerTags = p1m, free-trial ━━━━━━━━━━━━━━━━━━━━

    offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=1 pricingPhase.billingPeriod=P4W2D pricingPhase.formattedPrice=無料 pricingPhase.priceAmountMicros=0 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=FINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [1] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ 119 基本プランとそれに紐づくオファーを探す 無料期間60日のオファーのSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[1] offerDetails.offerTags = p1m, free-trial-60days ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=1 pricingPhase.billingPeriod=P8W4D pricingPhase.formattedPrice=無料 pricingPhase.priceAmountMicros=0 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=FINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [1] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━
  120. 無料期間30日のオファーのSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[0] offerDetails.offerTags = p1m, free-trial ━━━━━━━━━━━━━━━━━━━━

    offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=1 pricingPhase.billingPeriod=P4W2D pricingPhase.formattedPrice=無料 pricingPhase.priceAmountMicros=0 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=FINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [1] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ 120 基本プランとそれに紐づくオファーを探す 無料期間60日のオファーのSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[1] offerDetails.offerTags = p1m, free-trial-60days ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=1 pricingPhase.billingPeriod=P8W4D pricingPhase.formattedPrice=無料 pricingPhase.priceAmountMicros=0 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=FINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [1] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━
  121. オファー無し(基本プラン) のSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[2] offerDetails.offerTags = p1m ━━━━━━━━━━━━━━━━━━━━

    offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING 121 基本プランとそれに紐づくオファーを探す
  122. オファー無し(基本プラン) のSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[2] offerDetails.offerTags = p1m ━━━━━━━━━━━━━━━━━━━━

    offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING 122 基本プランとそれに紐づくオファーを探す
  123. 123 購入対象の基本プランとそれに紐づくすべてのオファーの中か ら、どのオファーを提供する(またはオファー無しで提供する)の かを何らかのロジックで決める ProductDetailsのSubscriptionOfferDetailsのリストから、 基本プランに設定したタグ( p1m)を元に基本プランとそれに紐 づくオファーを探す Product Id(

    pro )を元にProductDetailsを取得 Product IdがproのProductDetails • ProductDetailsのSubscriptionOfferDetailsのリストに すべての基本プランやそれに紐づくすべてのオファー の情報が含まれる SubscriptionOfferDetailsのリスト • 購入対象の基本プランとそれに紐づくすべてのオファーが含ま れる SubscriptionOfferDetails • 購入対象の基本プランとオファーのセットが含まれる SubscriptionOfferDetailsに含まれるofferTokenを使って購入 をリクエストする Play Billing Library 5.0からの購入までの処理の流れ
  124. オファーの選択ロジックを考える 124 • 基本プランに複数のオファーが紐付いているとき、どのオ ファーをユーザーに購入させるのかはアプリが決めなければな らない • たとえば、オファーの中で一番低い価格のオファーを選択する ように実装しておけば、無料期間があれば無料期間のオファー が選択されるなど

  125. オファーの選択ロジックを考える 125 • また、たとえば次のようなケースではGoogle Playに判定を任 せることはできないので、タグを使ってどのオファーを優先させ るのかをアプリ側で判定する必要がある ◦ 季節限定でオファーを提供したい ◦

    アプリが持つユーザー情報を元に特定のユーザー向けに オファーを提供したい
  126. オファーの選択ロジックを考える 126 • 通常時は一番低い価格のオファーが選択されるようにしてお き、何らかの条件に一致したユーザーにはそのユーザーに対 して特別なオファーが適用されるようにするなど、どのようにオ ファーを選択するのかは予めチームで相談して決めておくとよ い

  127. オファーの選択ロジックを考える 127 • 今回は、Play Billing Libraryのサンプルにもある一番低い価 格のオファーを選択するロジックを例に説明する

  128. 128 オファーの中で一番低い価格のオファーを探して取得する // オファーの中で一番低い価格のオファーを探して取得する private fun List<ProductDetails.SubscriptionOfferDetails>.findOffer(): ProductDetails.SubscriptionOfferDetails? { var

    lowestPrice = Long.MAX_VALUE var selectedOfferDetails: ProductDetails.SubscriptionOfferDetails? = null this.forEach { offerDetails -> offerDetails.pricingPhases.pricingPhaseList.forEach { pricingPhase -> // 価格が一番低いオファーを探して保持する if (pricingPhase.priceAmountMicros < lowestPrice) { lowestPrice = pricingPhase.priceAmountMicros selectedOfferDetails = offerDetails } } } return selectedOfferDetails }
  129. 129 オファーの中で一番低い価格のオファーを探して取得する // オファーの中で一番低い価格のオファーを探して取得する private fun List<ProductDetails.SubscriptionOfferDetails>.findOffer(): ProductDetails.SubscriptionOfferDetails? { var

    lowestPrice = Long.MAX_VALUE var selectedOfferDetails: ProductDetails.SubscriptionOfferDetails? = null this.forEach { offerDetails -> offerDetails.pricingPhases.pricingPhaseList.forEach { pricingPhase -> // 価格が一番低いオファーを探して保持する if (pricingPhase.priceAmountMicros < lowestPrice) { lowestPrice = pricingPhase.priceAmountMicros selectedOfferDetails = offerDetails } } } return selectedOfferDetails }
  130. 130 オファーの中で一番低い価格のオファーを探して取得する // オファーの中で一番低い価格のオファーを探して取得する private fun List<ProductDetails.SubscriptionOfferDetails>.findOffer(): ProductDetails.SubscriptionOfferDetails? { var

    lowestPrice = Long.MAX_VALUE var selectedOfferDetails: ProductDetails.SubscriptionOfferDetails? = null this.forEach { offerDetails -> offerDetails.pricingPhases.pricingPhaseList.forEach { pricingPhase -> // 価格が一番低いオファーを探して保持する if (pricingPhase.priceAmountMicros < lowestPrice) { lowestPrice = pricingPhase.priceAmountMicros selectedOfferDetails = offerDetails } } } return selectedOfferDetails }
  131. 131 オファーの中で一番低い価格のオファーを探して取得する // オファーの中で一番低い価格のオファーを探して取得する private fun List<ProductDetails.SubscriptionOfferDetails>.findOffer(): ProductDetails.SubscriptionOfferDetails? { var

    lowestPrice = Long.MAX_VALUE var selectedOfferDetails: ProductDetails.SubscriptionOfferDetails? = null this.forEach { offerDetails -> offerDetails.pricingPhases.pricingPhaseList.forEach { pricingPhase -> // 価格が一番低いオファーを探して保持する if (pricingPhase.priceAmountMicros < lowestPrice) { lowestPrice = pricingPhase.priceAmountMicros selectedOfferDetails = offerDetails } } } return selectedOfferDetails }
  132. 132 オファーの中で一番低い価格のオファーを探して取得する // オファーの中で一番低い価格のオファーを探して取得する private fun List<ProductDetails.SubscriptionOfferDetails>.findOffer(): ProductDetails.SubscriptionOfferDetails? { var

    lowestPrice = Long.MAX_VALUE var selectedOfferDetails: ProductDetails.SubscriptionOfferDetails? = null this.forEach { offerDetails -> offerDetails.pricingPhases.pricingPhaseList.forEach { pricingPhase -> // 価格が一番低いオファーを探して保持する if (pricingPhase.priceAmountMicros < lowestPrice) { lowestPrice = pricingPhase.priceAmountMicros selectedOfferDetails = offerDetails } } } return selectedOfferDetails }
  133. 133 オファーの中で一番低い価格のオファーを探して取得する // p1mの基本プランの中の一番価格の低いオファーを取得する val p1mOfferDetails = subscriptionOfferDetailsList .filter {

    subscriptionOfferDetails -> subscriptionOfferDetails.offerTags.any { offerTag -> offerTag == "p1m" } } .findOffer()
  134. 134 オファーの中で一番低い価格のオファーを探して取得する // p1mの基本プランの中の一番価格の低いオファーを取得する val p1mOfferDetails = subscriptionOfferDetailsList .filter {

    subscriptionOfferDetails -> subscriptionOfferDetails.offerTags.any { offerTag -> offerTag == "p1m" } } .findOffer()
  135. 135 オファーの中で一番低い価格のオファーを探して取得する // p1mの基本プランの中の一番価格の低いオファーを取得する val p1mOfferDetails = subscriptionOfferDetailsList .filter {

    subscriptionOfferDetails -> subscriptionOfferDetails.offerTags.any { offerTag -> offerTag == "p1m" } } .findOffer()
  136. 無料期間30日のオファーのSubscriptionOfferDetails val offerDetails = p1mOfferDetailsList[0] offerDetails.offerTags = p1m, free-trial ━━━━━━━━━━━━━━━━━━━━

    offerDetails.pricingPhases.pricingPhaseList [0] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=1 pricingPhase.billingPeriod=P4W2D pricingPhase.formattedPrice=無料 pricingPhase.priceAmountMicros=0 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=FINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ offerDetails.pricingPhases.pricingPhaseList [1] ━━━━━━━━━━━━━━━━━━━━ pricingPhase.billingCycleCount=0 pricingPhase.billingPeriod=P1M pricingPhase.formattedPrice=¥500 pricingPhase.priceAmountMicros=500000000 pricingPhase.priceCurrencyCode=JPY pricingPhase.recurrenceMode=INFINITE_RECURRING ━━━━━━━━━━━━━━━━━━━━ 136 基本プランとそれに紐づくオファーを探す • 今回のロジックだと価格しか見ていな いので、価格が低いかつ一番最初に 見つかった30日のオファーが選択さ れる • 無料期間の長いものを選択するに は、これに追加してbillingPeriodが 長いものを優先するなど、提供するオ ファーに応じてユーザーに一番有利 なオファーが選択されるなどの工夫を したほうがよい
  137. 137 購入対象の基本プランとそれに紐づくすべてのオファーの中か ら、どのオファーを提供する(またはオファー無しで提供する)の かを何らかのロジックで決める ProductDetailsのSubscriptionOfferDetailsのリストから、 基本プランに設定したタグ( p1m)を元に基本プランとそれに紐 づくオファーを探す Product Id(

    pro_plan )を元にProductDetailsを取得 Product IdがproのProductDetails • ProductDetailsのSubscriptionOfferDetailsのリストに すべての基本プランやそれに紐づくすべてのオファー の情報が含まれる SubscriptionOfferDetailsのリスト • 購入対象の基本プランとそれに紐づくすべてのオファーが含ま れる SubscriptionOfferDetails • 購入対象の基本プランとオファーのセットが含まれる SubscriptionOfferDetailsに含まれるofferTokenを使って購入 をリクエストする Play Billing Library 5.0からの購入までの処理の流れ
  138. 138 購入をリクエストする // 購入をリクエストするオファートークンを取得 val offerToken = p1mOfferDetails?.offerToken.orEmpty() val productDetailsParams

    = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, flowParams)
  139. 139 購入をリクエストする // 購入をリクエストするオファートークンを取得 val offerToken = p1mOfferDetails?.offerToken.orEmpty() val productDetailsParams

    = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, flowParams)
  140. 140 購入をリクエストする // 購入をリクエストするオファートークンを取得 val offerToken = p1mOfferDetails?.offerToken.orEmpty() val productDetailsParams

    = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, flowParams) • 存在しないオファートークンを渡すとエラーになるが、空文字 を渡すとエラーにはならず、基本プランやオファーは自動で選 択される • テスト購入で試した感じ、おそらく基本プランの作成が早いも のが選択されている(確証はない) • ここではorEmptyしているが、意図しない商品の購入画面が 表示される恐れがあるため、空の場合は購入画面は表示せ ず、アプリ側でエラーとして扱ったほうがよいかもしれない
  141. 141 購入をリクエストする // 購入をリクエストするオファートークンを取得 val offerToken = p1mOfferDetails?.offerToken.orEmpty() val productDetailsParams

    = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, flowParams)
  142. 142 購入をリクエストする // 購入をリクエストするオファートークンを取得 val offerToken = p1mOfferDetails?.offerToken.orEmpty() val productDetailsParams

    = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, flowParams)
  143. 143 購入をリクエストする // 購入をリクエストするオファートークンを取得 val offerToken = p1mOfferDetails?.offerToken.orEmpty() val productDetailsParams

    = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, flowParams)
  144. 144 購入対象の基本プランとそれに紐づくすべてのオファーの中か ら、どのオファーを提供する(またはオファー無しで提供する)の かを何らかのロジックで決める ProductDetailsのSubscriptionOfferDetailsのリストから、 基本プランに設定したタグ( p1m)を元に基本プランとそれに紐 づくオファーを探す Product Id(

    pro_plan )を元にProductDetailsを取得 Product IdがproのProductDetails • ProductDetailsのSubscriptionOfferDetailsのリストに すべての基本プランやそれに紐づくすべてのオファー の情報が含まれる SubscriptionOfferDetailsのリスト • 購入対象の基本プランとそれに紐づくすべてのオファーが含ま れる SubscriptionOfferDetails • 購入対象の基本プランとオファーのセットが含まれる SubscriptionOfferDetailsに含まれるofferTokenを使って購入 をリクエストする Play Billing Library 5.0からの購入までの処理の流れ
  145. Play Billing Library 5.0の機能をサポートする 145 1. Play Consoleで基本プランやオファーにタグを設定する ◦ 基本プランが正しく判別できるように、基本プランのタグは必ず一意な

    タグを設定しておく 2. オファーの決定ロジックを決める ◦ 先に決めておかないとオファーを正しく活用できない ◦ たとえばオファーの中で一番低い価格のオファーを自動で選択するな どを予め決めておき、チーム内で合意をとっておく
  146. Play Billing Library 5.0の機能をサポートする 146 3. SkuDetailsを使った購入フローの実装から、ProductDetails を使った購入フローの実装に置き換える ◦ ProductDetailsから基本プランやオファーを取得し、オ

    ファーの選択ロジックなどを実装する 4. Skuと名のつく非推奨になったクラスやメソッドが残っていない か確認して、残っていればProductと名のつくものに置き換え る
  147. 参考資料 147 • Success on Google Play with new acquisition,

    engagement, and monetization tools ◦ 新しい定期購入にした背景や変更点の概要 ◦ https://youtu.be/7ky2PZl16i4 • Google Play Console での定期購入に関する最近の変更 - Play Console ヘルプ ◦ 下位互換性についての説明 ◦ https://support.google.com/googleplay/android-developer/answer/12124625 • 定期購入について理解する - Play Console ヘルプ ◦ 新しい定期購入の仕組みや機能などについての説明 ◦ https://support.google.com/googleplay/android-developer/answer/12154973 • May 2022 subscription changes guide | Google Play's billing system | Android Developers ◦ Backend APIを含めたサブスクリプションの変更に関する説明 ◦ https://developer.android.com/google/play/billing/compatibility?hl=ja
  148. 参考資料 148 • Google Play Billing Library 4 to 5

    Migration Guide ◦ PBL 4から5へのマイグレーション(課金アイテム取得方法の before/afterなど) ◦ https://developer.android.com/google/play/billing/migrate-gpblv5?hl=ja • Android Developers Blog: What's new in Google Play ◦ 課金含めたGoogle Play関連まとめ ◦ https://android-developers.googleblog.com/2022/05/whats-new-in-google-play.html • Play Billing Library 5 を使用してアプリ内で定期購入を販売する ◦ PBLv5のcodelab ◦ https://codelabs.developers.google.com/play-billing-codelab?hl=ja#0 • play-billing-samples/PlayBillingCodelab ◦ PBLv5のcodelabのサンプルコード ◦ https://github.com/android/play-billing-samples/tree/main/PlayBillingCodelab
  149. ありがとうございました Taichi Sato / @syarihu Giftmall, Inc. Android Engineer 149