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
250
消費型プロダクトの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
300
Other Decks in Programming
See All in Programming
速いWebフレームワークを作る
yusukebe
5
1.7k
デザイナーが Androidエンジニアに 挑戦してみた
874wokiite
0
550
もうちょっといいRubyプロファイラを作りたい (2025)
osyoyu
1
460
Reading Rails 1.0 Source Code
okuramasafumi
0
250
Putting The Genie in the Bottle - A Crash Course on running LLMs on Android
iurysza
0
140
🔨 小さなビルドシステムを作る
momeemt
4
690
複雑なフォームに立ち向かう Next.js の技術選定
macchiitaka
2
240
AIを活用し、今後に備えるための技術知識 / Basic Knowledge to Utilize AI
kishida
22
5.9k
RDoc meets YARD
okuramasafumi
4
170
請來的 AI Agent 同事們在寫程式時,怎麼用 pytest 去除各種幻想與盲點
keitheis
0
130
AI Coding Agentのセキュリティリスク:PRの自己承認とメルカリの対策
s3h
0
240
250830 IaCの選定~AWS SAMのLambdaをECSに乗り換えたときの備忘録~
east_takumi
0
400
Featured
See All Featured
It's Worth the Effort
3n
187
28k
Bash Introduction
62gerente
615
210k
Code Reviewing Like a Champion
maltzj
525
40k
Site-Speed That Sticks
csswizardry
10
820
Gamification - CAS2011
davidbonilla
81
5.4k
YesSQL, Process and Tooling at Scale
rocio
173
14k
RailsConf 2023
tenderlove
30
1.2k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
9
810
GitHub's CSS Performance
jonrohan
1032
460k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.9k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
48
9.7k
Visualization
eitanlees
148
16k
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*Λͬͨߪೖͷεςʔλεͷ֬ೝ͕ ॏཁͱͳΔɻ