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

消費型プロダクトのGoogle Play Billing Library移行

消費型プロダクトのGoogle Play Billing Library移行

集まれKotlin好き!Kotlin愛好会 vol.29 @オンライン

Yukio MURAKAMI

May 17, 2021
Tweet

More Decks by Yukio MURAKAMI

Other Decks in Programming

Transcript

  1. $PDPBTXJGU w ஑ାίϫʔΩϯάεϖʔε0QFO0 ff i DF'03&45Ͱ։࠵ͯ͠·͢ɻ w NBD04J04XBUDI04UW04"OESPJEϓϩάϥϚʔͷͨΊͷษڧ ձͰ͢ɻ w

    ϓϩάϥϜʹؔ͢Δ஌ࣝ΍ܦݧΛڞ༗Λ໨తͱ͍ͯ͠·͢ɻ w ձͷ׆ಈ಺༰΍ൃදࢿྉɺαϯϓϧίʔυ͸ެ։Λલఏͱ͍ͯ͠· ͢ɻ w ਃ͠ࠐΈ͸ɺDPOOQBTT͔Βɻ 
 https://cocoa-kanto.connpass.com/
  2. *OBQQ#JMMJOH࣌୅ w ఏڙ͞ΕΔ"1*͸؆ૉͳ௿͍૚ͷ΋ͷɻ w αϯϓϧΛϦϑΝϨϯε࣮૷ͱͯ͠׆༻ɻ 
 https://github.com/android/play-billing-samples/ w "1*ͦͷ΋ͷͷ໰୊Ͱ͸ͳ͍͕՝୊͕͋ͬͨɻ w

    "DUJWJUZ'SBHNFOU࠶ੜ੒ͷࡍʹɺྫ͑͹ϝϯόʔͱͯ͠ อ͍࣋ͯͨ͠՝ۚίʔυ͕ࡴ͞ΕɺͦΕʹΑͬͯΤϥʔ͕ൃ ੜ͢Δɻ w ྫ͑͹ը໘ճసɻΞϓϦ͕ը໘ճసΛ༗ޮʹ͍ͯͨ͘͠ ͯ΋ɺγεςϜͰը໘ճసΛ༗ޮʹ͍ͯ͠Δͱൃੜ͢Δ ύλʔϯ͕͋Δɻ
  3. (PPHMF1MBZ#JMMJOH-JCSBSZ΁ͷҠߦ w ߴ͍૚ͷ"1* w *OBQQ#JMMJOH αϯϓϧͱ࢓༷ʹࠩҟ͕ൃੜ͢Δɻ w อཹத΁ͷऔҾ΁ͷରԠ͕ඞਢͱͳΔɻ w "1*ͦͷ΋ͷͷར఺Ͱ͸ͳ͍͕ศརͳϥΠϒϥϦ͕ར༻Ͱ͖Δɻ

    w "DUJWJUZ'SBHNFOU࠶ੜ੒΁ͷରԠɻ w -JWF%BUB 
 "DUJWJUZ'SBHNFOUͷΠϯελϯεͷอ͕࣋ආΒΕΔɻ w 7JFX.PEFM 
 "DUJWJUZ'SBHNFOUͷण໋ͱ͸ಠཱͯ͠ଘࡏͰ͖Δɻ 
 ϓϩηεͷ࠶ੜੑ΁ͷରԠͰ͖ͳ͍͕ɻ
  4. ॳظԽͱ઀ଓ class BillingRepository private constructor( private val application: Application, private

    val coroutineScope: CoroutineScope) : PurchasesUpdatedListener, BillingClientStateListener { lateinit private var billingClient: BillingClient fun startDataSourceConnections() { billingClient = BillingClient.newBuilder(application.applicationContext) .setListener(this) .enablePendingPurchases() .build() connectToPlayBillingService() } private fun connectToPlayBillingService() { billingClient.startConnection(this) } } อཹதͷऔҾΛ༗ޮʹ͢Δ
  5. ΞΠςϜͷ৘ใΛऔಘ͢Δ ঎඼ͷ4,6ͷϦετ ఆظߪೖͰͳ͍ fun querySkuDetailsAsync(skuList: List<String>) { val skuType: String

    = BillingClient.SkuType.INAPP val params = SkuDetailsParams.newBuilder().setSkusList(skuList).setType(skuType).build() billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList -> when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { if (skuDetailsList.orEmpty().isNotEmpty()) { // ߪೖՄೳͳΞΠςϜҰཡΛऔಘ } else { // ੒ޭ͕ۭͨͩͬͨ͠ } } else -> { // Τϥʔ } } } }
  6. ߪೖ͢Δ fun launchBillingFlow(activity: Activity, sku: String) = coroutineScope.launch { val

    skuType: String = BillingClient.SkuType.INAPP val params = SkuDetailsParams.newBuilder().setSkusList(listOf(sku)).setType(skuType).build() billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList -> when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { if (skuDetailsList.orEmpty().isNotEmpty()) { val purchaseParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList!!.first()).build() val result = billingClient.launchBillingFlow(activity, purchaseParams) if (result.responseCode != BillingClient.BillingResponseCode.OK) { /* Google Play੥ٻαʔϏεͷߪೖؔ਺͕ΤϥʔΛฦͨ͠ */ } } else { /* ࢦఆ͞Εͨ঎඼ΞΠςϜIDͷৄࡉ͕ଘࡏ͠ͳ͔ͬͨ */ } } else -> { /* ࢦఆ͞Εͨ঎඼ΞΠςϜIDͷৄࡉͷऔಘ͕ࣦഊͨ͠ */ } } } } override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) { when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { if (purchases != null) { if (isPending(purchases)) { /* อཹதͷऔҾ */ } else { /* ߪೖ੒ޭ */ } } else { /* ੒ޭ͕ฦ͞Ε͕ͨɺߪೖͨ͠঎඼Ϧετ͕nullͩͬͨɻ */ } } BillingClient.BillingResponseCode.USER_CANCELED -> { /* Ωϟϯηϧ͞Εͨ */ } BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> { /* ߪೖ͚ͨ͠Ͳফඅ͞Ε͍ͯͳ͍ */ } BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> { /* ࠶઀ଓΛࢼΈΔ */ connectToPlayBillingService() } else -> { /* ͦͷଞΤϥʔ */ } } } private fun isPending(purchases: List<Purchase>): Boolean { purchases.forEach { if (it.getPurchaseState() != Purchase.PurchaseState.PURCHASED) { return true } } return false }
  7. ߪೖͷ৘ใΛऔಘ͢Δ fun queryPurchasesAsync() = coroutineScope.launch { val skuType: String =

    BillingClient.SkuType.INAPP val purchasesResult = billingClient.queryPurchases(skuType) val validPurchases = mutableListOf<Purchase>() purchasesResult?.purchasesList?.let { it.forEach { purchase -> if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { validPurchases.add(purchase) } else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) { // อཹதͷऔҾ } } } // validPurchases : ະফඅʢফඅࡁΈʣϦετऔಘ }
  8. fun handleConsumablePurchasesAsync(purchaseToken: String) = coroutineScope.launch { val skuType: String =

    BillingClient.SkuType.INAPP var purchase: Purchase? = null val purchasesResult = billingClient.queryPurchases(skuType) purchasesResult?.purchasesList?.let { it.forEach { if ((it.purchaseToken == purchaseToken) && (it.getPurchaseState() == Purchase.PurchaseState.PURCHASED)) { purchase = it return@forEach } } } if (purchase == null) { return@launch } val params = ConsumeParams.newBuilder().setPurchaseToken(purchase!!.purchaseToken).build() billingClient.consumeAsync(params) { billingResult, purchaseToken -> when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { /* ফඅ੒ޭ */ } else -> { /* ͦͷଞΤϥʔ */ } } } } ফඅ͢Δ