Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[DroidKaigi 2020] Re:ゼロから始める Play Billing Library / Re: Zero - starting uses of Play Billing Library

syarihu
February 21, 2020

[DroidKaigi 2020] Re:ゼロから始める Play Billing Library / Re: Zero - starting uses of Play Billing Library

syarihu

February 21, 2020
Tweet

More Decks by syarihu

Other Decks in Programming

Transcript

  1. アジェンダ • Play Billing Library概要 • Play Billing Library 2.0新機能

    • Purchase Anywhere • AACを利用したPlay Billing Libraryによる課金実装 • 年額課金を実装したときに得ら れた知見
  2. 通常の支払いフロー ユーザー アプリ サーバー 支払いを行う レシート アイテムを渡す Google Play レシートを送信

    レシート検証 購入情報を登録 購入結果 Google Playでの購入は正常にできているのに サービスは利用できない状態になってしまう
  3. acknowledgeに対応した支払いフロー ユーザー アプリ サーバー 支払いを行う レシート アイテムを渡す Google Play レシートを送信

    レシート検証 購入情報を登録 購入結果 承認リクエスト 承認結果 購入情報の登録に失敗しても 3日以内に承認されない場合は払い戻されるので安心
  4. acknowledgeに対応した支払いフロー(アプリ) ユーザー アプリ サーバー 支払いを行う レシート 承認リクエスト 承認結果 アイテムを渡す Google

    Play レシートを送信 レシート検証 購入情報を登録 購入結果 アプリで通信失敗したときなどの考慮は必要
  5. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  6. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  7. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  8. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  9. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  10. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  11. Google Playへの支払いリクエスト ユーザー アプリ サーバー 支払いリクエストを行う 支払いを促す Google Play 支払いコード

    レシート送信 レシート検証 購入情報を登録 購入結果 レシート(支払い保留状態)
  12. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  13. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  14. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  15. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  16. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  17. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  18. コンビニへの支払い コンビニ ユーザー アプリ サーバー 支払いを行う 支払い完了通知 承認リクエスト 承認結果 アイテムを渡す

    Google Play 支払い完了通知 購入履歴をリクエスト レシート検証 購入情報を登録 購入結果 アプリを起動 購入履歴 レシートを送信
  19. • Promo Codes • Gift Cards • Google Play Points

    • Subscribe and Install アプリ外購入
  20. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { private val billingClient:

    BillingClient = BillingClient.newBuilder(application) .enablePendingPurchases() .setListener(this /* PurchasesUpdatedListener */) .build() } BillingViewModel.kt
  21. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { private val billingClient:

    BillingClient = BillingClient.newBuilder(application) .enablePendingPurchases() .setListener(this /* PurchasesUpdatedListener */) .build() } BillingViewModel.kt
  22. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { private val billingClient:

    BillingClient = BillingClient.newBuilder(application) .enablePendingPurchases() .setListener(this /* PurchasesUpdatedListener */) .build() } BillingViewModel.kt
  23. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { private val billingClient:

    BillingClient = BillingClient.newBuilder(application) .enablePendingPurchases() .setListener(this /* PurchasesUpdatedListener */) .build() } BillingViewModel.kt
  24. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { private val billingClient:

    BillingClient = BillingClient.newBuilder(application) .enablePendingPurchases() .setListener(this /* PurchasesUpdatedListener */) .build() } BillingViewModel.kt
  25. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { ...省略... /** 購入完了、購入キャンセルなどのコールバック

    */ override fun onPurchasesUpdated( billingResult: BillingResult, purchases: MutableList<Purchase>? ) { } } BillingViewModel.kt
  26. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { private val billingClient:

    BillingClient = BillingClient.newBuilder(application) .enablePendingPurchases() .setListener(this /* PurchasesUpdatedListener */) .build() ...省略... } BillingViewModel.kt
  27. sealed class BillingStatus { object SetupSuccess : BillingStatus() object BillingFlow

    : BillingStatus() object PendingPurchase : BillingStatus() object AcknowledgeSuccess : BillingStatus() object ServiceDisconnected : BillingStatus() object NoPreviousPlanPurchaseHistory : BillingStatus() data class Error(val billingResult: BillingResult) : BillingStatus() } BillingStatus.kt
  28. sealed class BillingStatus { object SetupSuccess : BillingStatus() object BillingFlow

    : BillingStatus() object PendingPurchase : BillingStatus() object AcknowledgeSuccess : BillingStatus() object ServiceDisconnected : BillingStatus() object NoPreviousPlanPurchaseHistory : BillingStatus() data class Error(val billingResult: BillingResult) : BillingStatus() } BillingStatus.kt
  29. sealed class BillingStatus { object SetupSuccess : BillingStatus() object BillingFlow

    : BillingStatus() object PendingPurchase : BillingStatus() object AcknowledgeSuccess : BillingStatus() object ServiceDisconnected : BillingStatus() object NoPreviousPlanPurchaseHistory : BillingStatus() data class Error(val billingResult: BillingResult) : BillingStatus() } BillingStatus.kt
  30. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { ...省略... /** 購入完了、購入キャンセルなどのコールバック

    */ override fun onPurchasesUpdated( billingResult: BillingResult, purchases: MutableList<Purchase>? ) { } } BillingViewModel.kt
  31. sealed class BillingStatus { object SetupSuccess : BillingStatus() object BillingFlow

    : BillingStatus() object PendingPurchase : BillingStatus() object AcknowledgeSuccess : BillingStatus() object ServiceDisconnected : BillingStatus() object NoPreviousPlanPurchaseHistory : BillingStatus() data class Error(val billingResult: BillingResult) : BillingStatus() } BillingStatus.kt
  32. sealed class BillingStatus { object SetupSuccess : BillingStatus() object BillingFlow

    : BillingStatus() object PendingPurchase : BillingStatus() object AcknowledgeSuccess : BillingStatus() object ServiceDisconnected : BillingStatus() object NoPreviousPlanPurchaseHistory : BillingStatus() data class Error(val billingResult: BillingResult) : BillingStatus() } BillingStatus.kt
  33. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { ...省略... private val

    _billingStatus = MutableLiveData<BillingStatus>() val billingStatus: LiveData<BillingStatus> = _billingStatus } BillingViewModel.kt
  34. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... private

    fun startConnection() { billingClient.startConnection(this /* BillingClientStateListener */) } } BillingViewModel.kt
  35. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... private

    fun startConnection() { billingClient.startConnection(this /* BillingClientStateListener */) } } BillingViewModel.kt
  36. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... private

    fun startConnection() { billingClient.startConnection(this /* BillingClientStateListener */) } } BillingViewModel.kt
  37. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... override

    fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) _billingStatus.postValue(BillingStatus.SetupSuccess) else _billingStatus.postValue(BillingStatus.Error(billingResult)) } override fun onBillingServiceDisconnected() = Unit } BillingViewModel.kt
  38. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... override

    fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) _billingStatus.postValue(BillingStatus.SetupSuccess) else _billingStatus.postValue(BillingStatus.Error(billingResult)) } override fun onBillingServiceDisconnected() = Unit } BillingViewModel.kt
  39. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... override

    fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) _billingStatus.postValue(BillingStatus.SetupSuccess) else _billingStatus.postValue(BillingStatus.Error(billingResult)) } override fun onBillingServiceDisconnected() = Unit } BillingViewModel.kt
  40. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... override

    fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) _billingStatus.postValue(BillingStatus.SetupSuccess) else _billingStatus.postValue(BillingStatus.Error(billingResult)) } override fun onBillingServiceDisconnected() = Unit } BillingViewModel.kt
  41. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... override

    fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) _billingStatus.postValue(BillingStatus.SetupSuccess) else _billingStatus.postValue(BillingStatus.Error(billingResult)) } override fun onBillingServiceDisconnected() = Unit } BillingViewModel.kt
  42. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener { ...省略... override

    fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) _billingStatus.postValue(BillingStatus.SetupSuccess) else _billingStatus.postValue(BillingStatus.Error(billingResult)) } override fun onBillingServiceDisconnected() = Unit } BillingViewModel.kt
  43. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener, LifecycleObserver { ...省略...

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun onCreate() { startConnection() } } BillingViewModel.kt
  44. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener, LifecycleObserver { ...省略...

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun onCreate() { startConnection() } } BillingViewModel.kt
  45. enum class BillingSkuType( @BillingClient.SkuType val skuType: String ) { /**

    定期購読 */ Subscription(BillingClient.SkuType.SUBS), /** 消費不可のアプリ内アイテム */ InAppNonConsumable(BillingClient.SkuType.INAPP), /** 消費可能なアプリ内アイテム */ InAppConsumable(BillingClient.SkuType.INAPP), } BillingSkuType.kt
  46. enum class BillingItem(val sku: String, val billingSkuType: BillingSkuType) { MonthlyPlan(

    "monthly_plan", BillingSkuType.Subscription), MonthlyPremiumPlan( "monthly_premium_plan", BillingSkuType.Subscription), NonConsumableItem( "non_consumable_item", BillingSkuType.InAppNonConsumable), ConsumableItem( "consumable_item", BillingSkuType.InAppConsumable), AndroidTestPurchased( "android.test.purchased", BillingSkuType.InAppNonConsumable); } BillingSkuType.kt
  47. enum class BillingItem(val sku: String,val billingSkuType: BillingSkuType) { ...省略... fun

    skusList(): List<String> = listOf(sku) companion object { fun findBySku(sku: String): BillingItem? = values().find { it.sku == sku } } } BillingSkuType.kt
  48. private fun getSkuDetails( billingItem: BillingItem, onResponse: (skuDetails: SkuDetails) -> Unit

    ) { val skuDetailsParams = SkuDetailsParams.newBuilder() .setSkusList(billingItem.skusList()) .setType(billingItem.billingSkuType.skuType) .build() ...省略... BillingViewModel.kt
  49. billingClient.querySkuDetailsAsync(skuDetailsParams ) { billingResult: BillingResult, skuDetailList: List<SkuDetails> -> if (billingResult.responseCode

    == BillingClient.BillingResponseCode.OK && skuDetailList.isNotEmpty() ) { onResponse(skuDetailList[0]) } else { _billingStatus.postValue(BillingStatus.Error(billingResult)) } } } BillingViewModel.kt
  50. billingClient.querySkuDetailsAsync(skuDetailsParams ) { billingResult: BillingResult, skuDetailList: List<SkuDetails> -> if (billingResult.responseCode

    == BillingClient.BillingResponseCode.OK && skuDetailList.isNotEmpty() ) { onResponse(skuDetailList[0]) } else { _billingStatus.postValue(BillingStatus.Error(billingResult)) } } } BillingViewModel.kt
  51. billingClient.querySkuDetailsAsync(skuDetailsParams ) { billingResult: BillingResult, skuDetailList: List<SkuDetails> -> if (billingResult.responseCode

    == BillingClient.BillingResponseCode.OK && skuDetailList.isNotEmpty() ) { onResponse(skuDetailList[0]) } else { _billingStatus.postValue(BillingStatus.Error(billingResult)) } } } BillingViewModel.kt
  52. billingClient.querySkuDetailsAsync(skuDetailsParams ) { billingResult: BillingResult, skuDetailList: List<SkuDetails> -> if (billingResult.responseCode

    == BillingClient.BillingResponseCode.OK && skuDetailList.isNotEmpty() ) { onResponse(skuDetailList[0]) } else { _billingStatus.postValue(BillingStatus.Error(billingResult)) } } } BillingViewModel.kt
  53. fun purchase(activity: Activity, billingItem: BillingItem) { getSkuDetails(billingItem) { skuDetails ->

    val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .build() _billingStatus.postValue(BillingStatus.BillingFlow) billingClient.launchBillingFlow(activity, flowParams) } } BillingViewModel.kt
  54. class BillingFragment : Fragment(), View.OnClickListener { ...省略... override fun onClick(v:

    View?) { billingViewModel.purchase( requireActivity(), BillingItem.NonConsumableItem ) } } BillingFragment.kt
  55. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { ...省略... /** 購入完了、購入キャンセルなどのコールバック

    */ override fun onPurchasesUpdated( billingResult: BillingResult, purchases: MutableList<Purchase>? ) { } } BillingViewModel.kt
  56. class BillingViewModel(application: Application) : ViewModel(), PurchasesUpdatedListener { ...省略... /** 購入完了、購入キャンセルなどのコールバック

    */ override fun onPurchasesUpdated( billingResult: BillingResult, purchases: MutableList<Purchase>? ) { } } BillingViewModel.kt
  57. class BillingFragment : Fragment(), View.OnClickListener { ...省略... override fun onClick(v:

    View?) { billingViewModel.purchase( requireActivity(), BillingItem.MonthlyPlan ) } } BillingFragment.kt
  58. class BillingFragment : Fragment(), View.OnClickListener { ...省略... override fun onClick(v:

    View?) { billingViewModel.purchase( requireActivity(), BillingItem.MonthlyPlan ) } } BillingFragment.kt
  59. fun changePlan( activity: Activity, billingItem: BillingItem, oldBillingItem: BillingItem ) {

    billingClient.queryPurchaseHistoryAsync( BillingClient.SkuType.SUBS) { _, purchases -> val purchase: PurchaseHistoryRecord? = purchases.find { it.sku == oldBillingItem.sku } if (purchase == null) { _billingStatus.postValue( BillingStatus.NoPreviousPlanPurchaseHistory) return@queryPurchaseHistoryAsync } BillingViewModel.kt
  60. fun changePlan( activity: Activity, billingItem: BillingItem, oldBillingItem: BillingItem ) {

    billingClient.queryPurchaseHistoryAsync( BillingClient.SkuType.SUBS) { _, purchases -> val purchase: PurchaseHistoryRecord? = purchases.find { it.sku == oldBillingItem.sku } if (purchase == null) { _billingStatus.postValue( BillingStatus.NoPreviousPlanPurchaseHistory) return@queryPurchaseHistoryAsync } BillingViewModel.kt
  61. fun changePlan( activity: Activity, billingItem: BillingItem, oldBillingItem: BillingItem ) {

    billingClient.queryPurchaseHistoryAsync( BillingClient.SkuType.SUBS) { _, purchases -> val purchase: PurchaseHistoryRecord? = purchases.find { it.sku == oldBillingItem.sku } if (purchase == null) { _billingStatus.postValue( BillingStatus.NoPreviousPlanPurchaseHistory) return@queryPurchaseHistoryAsync } BillingViewModel.kt
  62. fun changePlan( activity: Activity, billingItem: BillingItem, oldBillingItem: BillingItem ) {

    billingClient.queryPurchaseHistoryAsync( BillingClient.SkuType.SUBS) { _, purchases -> val purchase: PurchaseHistoryRecord? = purchases.find { it.sku == oldBillingItem.sku } if (purchase == null) { _billingStatus.postValue( BillingStatus.NoPreviousPlanPurchaseHistory) return@queryPurchaseHistoryAsync } BillingViewModel.kt
  63. billingClient.queryPurchaseHistoryAsync( BillingClient.SkuType.SUBS) { _, purchases -> ...省略... getSkuDetails(billingItem) { skuDetails

    -> val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .setOldSku(purchase.sku, purchase.purchaseToken) .build() _billingStatus.postValue(BillingStatus.BillingFlow) billingClient.launchBillingFlow(activity, flowParams) } BillingViewModel.kt
  64. billingClient.queryPurchaseHistoryAsync( BillingClient.SkuType.SUBS) { _, purchases -> ...省略... getSkuDetails(billingItem) { skuDetails

    -> val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .setOldSku(purchase.sku, purchase.purchaseToken) .build() _billingStatus.postValue(BillingStatus.BillingFlow) billingClient.launchBillingFlow(activity, flowParams) } BillingViewModel.kt
  65. class BillingFragment : Fragment(), View.OnClickListener { ...省略... override fun onClick(v:

    View?) { billingViewModel.changePlan( requireActivity(), billingItem = BillingItem.MonthlyPremiumPlan, oldBillingItem = BillingItem.MonthlyPlan ) } } BillingFragment.kt
  66. private fun acknowledgePurchase(purchase: Purchase) { if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED &&

    purchase.isAcknowledged.not()) { val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) .build() billingClient.acknowledgePurchase( acknowledgePurchaseParams, this /* AcknowledgePurchaseResponseListener */ ) } BillingViewModel.kt
  67. private fun acknowledgePurchase(purchase: Purchase) { if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED &&

    purchase.isAcknowledged.not()) { val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) .build() billingClient.acknowledgePurchase( acknowledgePurchaseParams, this /* AcknowledgePurchaseResponseListener */ ) } BillingViewModel.kt
  68. private fun acknowledgePurchase(purchase: Purchase) { if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED &&

    purchase.isAcknowledged.not()) { val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) .build() billingClient.acknowledgePurchase( acknowledgePurchaseParams, this /* AcknowledgePurchaseResponseListener */ ) } BillingViewModel.kt
  69. private fun acknowledgePurchase(purchase: Purchase) { if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED &&

    purchase.isAcknowledged.not()) { val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) .build() billingClient.acknowledgePurchase( acknowledgePurchaseParams, this /* AcknowledgePurchaseResponseListener */ ) } BillingViewModel.kt
  70. class BillingViewModel(application: Application) : ViewModel(), BillingClientStateListener, PurchasesUpdatedListener, LifecycleObserver, AcknowledgePurchaseResponseListener {

    override fun onAcknowledgePurchaseResponse(billingResult: BillingResult) { if (billingResult.responseCode() == BillingClient.BillingResponseCode.OK) { _billingStatus.postValue(BillingStatus.AcknowledgeSuccess) } else { _billingStatus.postValue(BillingStatus.Error(billingResult)) } } } BillingViewModel.kt
  71. プラン変更後3日以内に承認しなかった場合 ユーザー アプリ Google Play 支払いを行う レシート プランを利用可能にする 以前のプランを解 約

    新しいプランを契 約 3日以内に承認 されていない 払い戻し ユーザーが新たにプランを契約し、 その契約が承認されない状態でプランを変更した場合は?
  72. private fun acknowledgePurchase(purchase: Purchase) { if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED &&

    purchase.isAcknowledged.not()) { ...省略... } else if ( purchase.purchaseState == Purchase.PurchaseState.PENDING ) { _billingStatus.postValue(BillingStatus.PendingPurchase) } } BillingViewModel.kt
  73. class BillingFragment : Fragment(), View.OnClickListener { override fun onViewCreated(view: View,

    savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) billingViewModel.billingStatus.observe( viewLifecycleOwner, Observer { billingStatus -> when (billingStatus) { BillingStatus.SetupSuccess -> { // 購入履歴を取得して承認する } } }) } BillingFragment.kt
  74. 1. aidlファイルを削除 2. Play Billing Libraryを導入 3. Play Billing Libraryによる課金実装

    4. 古い定形コードを削除 AIDLからPBLに置き換えた話
  75. 月額プラン 500円 / 月 プラン変更 2019年04月15日 年額プラン 5,500円 / 年

    支払い日 2019年04月01日 500円 / 30日 = 16.66円 / 日 250円分使用
  76. 月額プラン 500円 / 月 プラン変更 2019年04月15日 年額プラン 5,500円 / 年

    支払い日 2019年04月01日 500円 / 30日 = 16.66円 / 日 250円分使用 前プランの残り = 250円
  77. 月額プラン 500円 / 月 プラン変更 2019年04月15日 年額プラン 5,500円 / 年

    支払い日 2019年04月01日 500円 / 30日 = 16.66円 / 日 250円分使用 前プランの残り = 250円 5,500円 / 365日 = 15.07円 / 日
  78. 月額プラン 500円 / 月 プラン変更 2019年04月15日 年額プラン 5,500円 / 年

    支払い日 2019年04月01日 500円 / 30日 = 16.66円 / 日 250円分使用 前プランの残り = 250円 5,500円 / 365日 = 15.07円 / 日 250円 / 15.07円 = 約16日分使用可能
  79. 月額プラン 500円 / 月 プラン変更 2019年04月15日 年額プラン 5,500円 / 年

    支払い日 2019年04月01日 500円 / 30日 = 16.66円 / 日 250円分使用 前プランの残り = 250円 5,500円 / 365日 = 15.07円 / 日 250円 / 15.07円 = 約16日分使用可能 新プラン支払い開始 2019年05月02日
  80. • The “Purchase Anywhere” Paradigm Shift (Android Dev Summit '19)

    ◦ https://youtu.be/L2aiHRD6Pfk • Required Updates and New Functionality (Android Dev Summit '19) ◦ https://youtu.be/Cj5vq1AOJeQ • What's New with Google Play Billing (Google I/O'19) ◦ https://youtu.be/N4004Set4F8 参考資料