Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
消費型プロダクトのGoogle Play Billing Library移行
Search
Yukio MURAKAMI
May 17, 2021
Programming
0
270
消費型プロダクトのGoogle Play Billing Library移行
集まれKotlin好き!Kotlin愛好会 vol.29 @オンライン
Yukio MURAKAMI
May 17, 2021
Tweet
Share
More Decks by Yukio MURAKAMI
See All by Yukio MURAKAMI
モーダル表示について
m_yukio
0
190
Kotlin初心者がGoogle Play Billing Libraryを使ってみた
m_yukio
0
310
Other Decks in Programming
See All in Programming
まだ間に合う!Claude Code元年をふりかえる
nogu66
5
900
GoLab2025 Recap
kuro_kurorrr
0
780
PostgreSQLで手軽にDuckDBを使う!DuckDB&pg_duckdb入門/osc25hi-duckdb
takahashiikki
0
200
令和最新版Android Studioで化石デバイス向けアプリを作る
arkw
0
460
Claude Codeの「Compacting Conversation」を体感50%減! CLAUDE.md + 8 Skills で挑むコンテキスト管理術
kmurahama
1
660
C-Shared Buildで突破するAI Agent バックテストの壁
po3rin
0
420
Kotlin Multiplatform Meetup - Compose Multiplatform 외부 의존성 아키텍처 설계부터 운영까지
wisemuji
0
130
AIエンジニアリングのご紹介 / Introduction to AI Engineering
rkaga
8
3.4k
公共交通オープンデータ × モバイルUX 複雑な運行情報を 『直感』に変換する技術
tinykitten
PRO
0
170
[AtCoder Conference 2025] LLMを使った業務AHCの上⼿な解き⽅
terryu16
6
850
TerraformとStrands AgentsでAmazon Bedrock AgentCoreのSSO認証付きエージェントを量産しよう!
neruneruo
4
1.9k
Pythonではじめるオープンデータ分析〜書籍の紹介と書籍で紹介しきれなかった事例の紹介〜
welliving
3
630
Featured
See All Featured
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.6k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
130
New Earth Scene 8
popppiees
0
1.2k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
0
1k
Claude Code のすすめ
schroneko
67
210k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
120
Crafting Experiences
bethany
0
22
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
57
41k
Highjacked: Video Game Concept Design
rkendrick25
PRO
0
250
Building the Perfect Custom Keyboard
takai
1
660
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
31
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
130
Transcript
Ϗοπ༗ݶձࣾଜ্༤ ফඅܕϓϩμΫτͷ (PPHMF1MBZ#JMMJOH-JCSBSZҠߦ *OBQQ#JMMJOH "*%- ͔ΒͷҠߦ
$PDPBTXJGU w ାίϫʔΩϯάεϖʔε0QFO0 ff i DF'03&45Ͱ։࠵ͯ͠·͢ɻ w NBD04J04XBUDI04UW04"OESPJEϓϩάϥϚʔͷͨΊͷษڧ ձͰ͢ɻ w
ϓϩάϥϜʹؔ͢ΔࣝܦݧΛڞ༗Λతͱ͍ͯ͠·͢ɻ w ձͷ׆ಈ༰ൃදࢿྉɺαϯϓϧίʔυެ։Λલఏͱ͍ͯ͠· ͢ɻ w ਃ͠ࠐΈɺDPOOQBTT͔Βɻ https://cocoa-kanto.connpass.com/
࡛ۄݝேբࢢͰιϑτϋεΛىۀɻ NBD04J04ɺ"OESPJEͷΞϓϦέʔγϣϯ։ൃΛओʹ͚ෛ͏ɻ ࣗࣾΞϓϦͷ࡞ɻ 5XJUUFS!N@ZVLJP 'BDFCPPLZVLJPNVSBLBNJ (JU)VCNVSBLBNJ IUUQXXXCJU[DPKQXFCMPH
w"OESPJEΠϯλʔϑΣʔεఆٛݴޠʢ"*%-ʣΛ͍ͬͯΔ*O BQQ#JMMJOH͔Β(PPHMF1MBZ#JMMJOH-JCSBSZͷҠߦ͕ඞਢͱ ͳ͍ͬͯ·͢ɻ w(PPHMF1MBZ#JMMJOH-JCSBSZରԠࡁΈͰɺWWͷ߹ ɺWͷҠߦ͕ඞਢͱͳ͍ͬͯ·͢ɻ w৽نΞϓϦ͔Βɻ wطଘΞϓϦ·Ͱʹɻ
*OBQQ#JMMJOH࣌ w ఏڙ͞ΕΔ"1*؆ૉͳ͍ͷͷɻ w αϯϓϧΛϦϑΝϨϯε࣮ͱͯ͠׆༻ɻ https://github.com/android/play-billing-samples/ w "1*ͦͷͷͷͰͳ͍͕՝͕͋ͬͨɻ w
"DUJWJUZ'SBHNFOU࠶ੜͷࡍʹɺྫ͑ϝϯόʔͱͯ͠ อ͍࣋ͯͨ͠՝ۚίʔυ͕ࡴ͞ΕɺͦΕʹΑͬͯΤϥʔ͕ൃ ੜ͢Δɻ w ྫ͑ը໘ճసɻΞϓϦ͕ը໘ճసΛ༗ޮʹ͍ͯͨ͘͠ ͯɺγεςϜͰը໘ճసΛ༗ޮʹ͍ͯ͠Δͱൃੜ͢Δ ύλʔϯ͕͋Δɻ
(PPHMF1MBZ#JMMJOH-JCSBSZͷҠߦ w ߴ͍ͷ"1* w *OBQQ#JMMJOH αϯϓϧͱ༷ʹࠩҟ͕ൃੜ͢Δɻ w อཹதͷऔҾͷରԠ͕ඞਢͱͳΔɻ w "1*ͦͷͷͷརͰͳ͍͕ศརͳϥΠϒϥϦ͕ར༻Ͱ͖Δɻ
w "DUJWJUZ'SBHNFOU࠶ੜͷରԠɻ w -JWF%BUB "DUJWJUZ'SBHNFOUͷΠϯελϯεͷอ͕࣋ආΒΕΔɻ w 7JFX.PEFM "DUJWJUZ'SBHNFOUͷण໋ͱಠཱͯ͠ଘࡏͰ͖Δɻ ϓϩηεͷ࠶ੜੑͷରԠͰ͖ͳ͍͕ɻ
ফඅܕϓϩμΫτͷ߹ͷରԠ߲ w ॳظԽͱଓ w ΞΠςϜͷใΛऔಘ͢Δ w ߪೖ͢Δ w ߪೖͷใΛऔಘ͢Δ w
ফඅ͢Δ
αϯϓϧ5SJWJBM%SJWF,PUMJOͷΫϥεߏ (BNF'SBHNFOU #JMMJOH7JFX.PEFM #JMMJOH3FQPTJUPSZ ࠷৽ͷαϯϓϧ,PUMJO'MPXΛͬͨ#JMMJOH%BUB4PVSDFΫϥεʹ՝ۚϩδοΫΛ࣋ͬͯߦ͍ͬͯ·͢ɻ
ॳظԽͱଓ 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) } } อཹதͷऔҾΛ༗ޮʹ͢Δ
ΞΠςϜͷใΛऔಘ͢Δ ͷ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 -> { // Τϥʔ } } } }
ߪೖ͢Δ 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 }
ߪೖͷใΛऔಘ͢Δ 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 : ະফඅʢফඅࡁΈʣϦετऔಘ }
https://androidpublisher.googleapis.com/androidpublisher/v3/applications/ {packageName}/purchases/products/{productId}/tokens/{token}:acknowledge { "developerPayload": string } .FUIPEQVSDIBTFTQSPEVDUTBDLOPXMFEHF IUUQTEFWFMPQFSTHPPHMFDPNBOESPJEQVCMJTIFSBQJSFGSFTUWQVSDIBTFTQSPEVDUTBDLOPXMFEHF IMFO 3FTQPOTFCPEZ
*GTVDDFTTGVM UIFSFTQPOTFCPEZJTFNQUZ
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 -> { /* ͦͷଞΤϥʔ */ } } } } ফඅ͢Δ
wߪೖͷߋ৽ใʹɺߪೖࡁΈͱอཹதͷ͕ଘࡏ͢Δɻ wߪೖॲཧͷظݶ͕ઃ͚ΒΕͨɻ wɻςετڥͰͩͬͨɻ wඇফඅܕϓϩμΫτͰɺظݶʹ #JMMJOH$MJFOUBDLOPXMFEHF1VSDIBTF ͕ݺΕͳ͍ͱฦۚͱ ͳΔɻ w(PPHMF1MBZ%FWFMPQFS"1*Λͬͨߪೖͷεςʔλεͷ֬ೝ͕ ॏཁͱͳΔɻ