From 88797d9b280f5f1f3f063c34a0bd197a02fdffce Mon Sep 17 00:00:00 2001 From: Michael Totschnig Date: Wed, 13 Nov 2024 08:51:45 +0100 Subject: [PATCH] Updated helpers for updating subscriptions in Google Play --- PlayStoreApi/build.gradle | 10 +- .../playstoreapi/AndroidPublisherHelper.kt | 41 +----- .../java/org/totschnig/playstoreapi/Main.kt | 107 +++++++++++---- doc/pro.xsl | 128 +++++++++--------- 4 files changed, 155 insertions(+), 131 deletions(-) diff --git a/PlayStoreApi/build.gradle b/PlayStoreApi/build.gradle index 3ea7b85b95..defc04f8a9 100644 --- a/PlayStoreApi/build.gradle +++ b/PlayStoreApi/build.gradle @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } application { @@ -14,7 +14,7 @@ application { dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'com.google.apis:google-api-services-androidpublisher:v3-rev20220704-2.0.0' - implementation 'com.google.http-client:google-http-client-gson:1.42.2' + implementation "org.jetbrains.kotlin:kotlin-stdlib:2.0.21" + implementation 'com.google.apis:google-api-services-androidpublisher:v3-rev20241016-2.0.0' + implementation 'com.google.http-client:google-http-client-gson:1.44.1' } \ No newline at end of file diff --git a/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/AndroidPublisherHelper.kt b/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/AndroidPublisherHelper.kt index 577414d4f3..f2342178f2 100644 --- a/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/AndroidPublisherHelper.kt +++ b/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/AndroidPublisherHelper.kt @@ -15,53 +15,21 @@ */ package org.totschnig.playstoreapi -import com.google.api.client.auth.oauth2.Credential -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport import com.google.api.client.http.HttpTransport import com.google.api.client.json.JsonFactory import com.google.api.client.json.gson.GsonFactory import com.google.api.services.androidpublisher.AndroidPublisher -import com.google.api.services.androidpublisher.AndroidPublisherScopes -import org.apache.commons.logging.LogFactory +import com.google.auth.http.HttpCredentialsAdapter +import com.google.auth.oauth2.ServiceAccountCredentials import java.io.IOException import java.security.GeneralSecurityException -/** - * Helper class to initialize the publisher APIs client library. - * - * - * Before making any calls to the API through the client library you need to - * call the [.init] method. This will run - * all precondition checks for for client id and secret setup properly in - * resources/client_secrets.json and authorize this client against the API. - * - */ object AndroidPublisherHelper { - private val log = LogFactory.getLog(AndroidPublisherHelper::class.java) - - /** Path to the private key file (only used for Service Account auth). */ - private const val SRC_RESOURCES_KEY_P12 = "/api-5950718857839288276-239934-55c93989bd9c.p12" - /** Global instance of the JSON factory. */ private val JSON_FACTORY: JsonFactory = GsonFactory.getDefaultInstance() - /** Global instance of the HTTP transport. */ private var HTTP_TRANSPORT: HttpTransport? = null - @Throws(GeneralSecurityException::class, IOException::class) - private fun authorizeWithServiceAccount(): Credential { - val serviceAccountEmail = Main.SERVICE_ACCOUNT_EMAIL - log.info(String.format("Authorizing using Service Account: %s", serviceAccountEmail)) - - // Build service account credential. - return GoogleCredential.Builder() - .setTransport(HTTP_TRANSPORT) - .setJsonFactory(JSON_FACTORY) - .setServiceAccountId(serviceAccountEmail) - .setServiceAccountScopes(setOf(AndroidPublisherScopes.ANDROIDPUBLISHER)) - .setServiceAccountPrivateKeyFromP12File(Main::class.java.getResourceAsStream(SRC_RESOURCES_KEY_P12)) - .build() - } /** * Performs all necessary setup steps for running requests against the API. @@ -72,11 +40,10 @@ object AndroidPublisherHelper { internal fun init(): AndroidPublisher { // Authorization. newTrustedTransport() - val credential = authorizeWithServiceAccount() + val credential = ServiceAccountCredentials.getApplicationDefault().createScoped("https://www.googleapis.com/auth/androidpublisher") - // Set up and return API client. return AndroidPublisher.Builder( - HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName(Main.APPLICATION_NAME) + HTTP_TRANSPORT, JSON_FACTORY, HttpCredentialsAdapter(credential) ).setApplicationName(Main.APPLICATION_NAME) .build() } diff --git a/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/Main.kt b/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/Main.kt index 8b61ce2721..11b8510afd 100644 --- a/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/Main.kt +++ b/PlayStoreApi/src/main/java/org/totschnig/playstoreapi/Main.kt @@ -3,10 +3,11 @@ package org.totschnig.playstoreapi import com.google.api.services.androidpublisher.AndroidPublisher import com.google.api.services.androidpublisher.model.InAppProduct import com.google.api.services.androidpublisher.model.InAppProductListing +import com.google.api.services.androidpublisher.model.Subscription +import com.google.api.services.androidpublisher.model.SubscriptionListing import org.apache.commons.logging.Log import org.apache.commons.logging.LogFactory -import java.io.IOException -import java.security.GeneralSecurityException +import kotlin.text.get class Main { companion object { @@ -173,37 +174,89 @@ class Main { private val log: Log = LogFactory.getLog(Main::class.java) + private val proSubscription = listOf( + SubscriptionListing().setLanguageCode("ar").setTitle("المفتاح شامل").setBenefits(listOf("دعم البريد الإلكتروني","حق التصويت على مخططات التنمية","فتح جميع الميزات المتميزة")), + SubscriptionListing().setLanguageCode("bg").setTitle("Професионален ключ").setBenefits(listOf("Отключете всички премиум възможности","Поддръжка по имейл","Право да гласувате на карта за развитие")), + SubscriptionListing().setLanguageCode("ca").setTitle("Clau professional").setBenefits(listOf("Desbloqueja totes les funcions prèmium","Suport per correu electrònic","Full de ruta de desenvolupament")), + SubscriptionListing().setLanguageCode("cs-CZ").setTitle("Profesionální klíč").setBenefits(listOf("Emailová podpora","Odemkněte všechny prémiové funkce","Hlasovací právo o plánu rozvoje")), + SubscriptionListing().setLanguageCode("da-DK").setTitle("Professionel Nøgle").setBenefits(listOf("Lås op for alle premiumfunktioner","Stemmeret på udviklingsplan","E-mail support")), + SubscriptionListing().setLanguageCode("de-DE").setTitle("Professioneller Schlüssel").setBenefits(listOf("Schalte alle Premium-Funktionen frei","E-Mail-Support","Stimmrecht zum Entwicklungsfahrplan")), + SubscriptionListing().setLanguageCode("el-GR").setTitle("Επαγγελματικό κλειδί").setBenefits(listOf("Υποστήριξη ηλεκτρονικού ταχυδρομείου","Δικαίωμα ψήφου για τον χάρτη πορείας","Ξεκλειδώστε όλες τις premium λειτουργίες")), + SubscriptionListing().setLanguageCode("es-ES").setTitle("Llave Profesional").setBenefits(listOf("Soporte por correo electrónico","Participar en la ruta de desarrollo","Desbloquea todas las funciones premium")), + SubscriptionListing().setLanguageCode("eu-ES").setTitle("Gako profesionala").setBenefits(listOf("E-mail bidezko laguntza","Garapen ibilbidean bozkatzeko aukera","Desblokeatu premium eginbide guztiak")), + SubscriptionListing().setLanguageCode("fr-FR").setTitle("Clé professionnelle").setBenefits(listOf("Débloquez les fonctionnalités premium","Assistance par e-mail","Voter sur la feuille de route")), + SubscriptionListing().setLanguageCode("hr").setTitle("Profesionalni ključ").setBenefits(listOf("Otključaj sve premium funkcije","E-mail podrška","Pravo glasa o planu razvoja")), + SubscriptionListing().setLanguageCode("hu-HU").setTitle("Szakmai kulcs").setBenefits(listOf("Nyissa ki az összes prémium funkciót","E-mailes támogatás","Szavazás a fejlesztési ütemtervről")), + SubscriptionListing().setLanguageCode("it-IT").setTitle("Licenza Professionale").setBenefits(listOf("Supporto email","Votare sulla roadmap di sviluppo","Sblocca tutte le funzionalità premium")), + SubscriptionListing().setLanguageCode("iw-IL").setTitle("מפתח מקצועי").setBenefits(listOf("תמיכה דרך דוא״ל","זכות הצבעה על מפת דרכים לפיתוח היישומון","פתיחת כל יכולות הפרימיום")), + SubscriptionListing().setLanguageCode("ja-JP").setTitle("プロフェッショナルキー").setBenefits(listOf("メールサポート","開発ロードマップ の投票権","すべてのプレミアム機能のロックを解除する")), + SubscriptionListing().setLanguageCode("kn-IN").setTitle("ವೃತ್ತಿಪರ ಕೀ").setBenefits(listOf("ಮೇಲ್ ಬೆಂಬಲ","ಅಭಿವೃದ್ಧಿ ಮಾರ್ಗಸೂಚಿಯಲ್ಲಿ ಮತದಾನ","ಪ್ರೀಮಿಯಂ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಿ")), + SubscriptionListing().setLanguageCode("ko-KR").setTitle("프로페셔널 키").setBenefits(listOf("이메일 지원","개발 로드맵에 바로 투표","모든 프리미엄 기능 잠금 해제")), + SubscriptionListing().setLanguageCode("km-KH").setTitle("គន្លឹះវិជ្ជាជីវៈ").setBenefits(listOf("ផែនទីអភិវឌ្ឍន៍","ដោះសោមុខងារពិសេសទាំងអស់។","ការគាំទ្រអ៊ីម៉ែល")), + SubscriptionListing().setLanguageCode("ms").setTitle("Kunci profesional").setBenefits(listOf("Sokongan emel","Hak mengundi dalam peta jalan","Buka kunci semua ciri premium")), + SubscriptionListing().setLanguageCode("nl-NL").setTitle("Professionele Sleutel").setBenefits(listOf("Ontgrendel alle premiumfuncties","E-mail ondersteuning","Stemrecht op ontwikkelingsroutekaart")), + SubscriptionListing().setLanguageCode("pl-PL").setTitle("Profesjonalny Klucz").setBenefits(listOf("Wysyłaj pocztę e-mail","Głosowanie na mapie drogowej rozwoju","Odblokuj wszystkie funkcje premium")), + SubscriptionListing().setLanguageCode("pt-PT").setTitle("Chave Profissional").setBenefits(listOf("Desbloqueia todos os recursos premium","Suporte por e-mail","Voto no roteiro de desenvolvimento")), + SubscriptionListing().setLanguageCode("ro").setTitle("Cheie profesională").setBenefits(listOf("Deblocați toate funcțiile premium","Asistență prin e-mail","Foaia de parcurs de dezvoltare")), + SubscriptionListing().setLanguageCode("ru-RU").setTitle("Professional-ключ").setBenefits(listOf("E-mail поддержка","Право голоса на дорожной карте","Разблокировать все премиум-функции")), + SubscriptionListing().setLanguageCode("si-LK").setTitle("වෘත්තීය යතුර").setBenefits(listOf("සංවර්ධන මාර්ග සිතියම මත ඡන්ද අයිතිය","විද්යුත් තැපැල් සහාය","සියලුම වාරික විශේෂාංග අගුළු හරින්න")), + SubscriptionListing().setLanguageCode("ta-IN").setTitle("நிபுணத்துவ திறவுகோல்").setBenefits(listOf("ஞ்சல் ஆதரவு","வளர்ச்சித் திட்டத்தில் வாக்களியுங்கள்","பிரீமியம் அம்சங்களைத் திறக்கவும்")), + SubscriptionListing().setLanguageCode("te-IN").setTitle("ప్రొఫెషనల్ కీ").setBenefits(listOf("అన్ని ప్రీమియం ఫీచర్లను అన్ లాక్ చేయండి","ఇమెయిల్ మద్దతు","అభివృద్ధి రోడ్ మ్యాప్ పై ఓటు హక్కు")), + SubscriptionListing().setLanguageCode("tr-TR").setTitle("Profesyonel Anahtar").setBenefits(listOf("E-posta desteği","Geliştirme yol haritasına oy verin","Tüm ücretli özelliklerin kilidini açın")), + SubscriptionListing().setLanguageCode("vi").setTitle("Chìa khóa chuyên nghiệp").setBenefits(listOf("Hỗ trợ qua email","Bỏ phiếu về phát triển ứng dụng","Mở khóa tất cả các tính năng cao cấp")), + SubscriptionListing().setLanguageCode("zh-CN").setTitle("专业版密钥(Professional Key)").setBenefits(listOf("对程序开发者的未来蓝图进行投票","解锁所有高级功能","电子邮件支持")), + SubscriptionListing().setLanguageCode("zh-TW").setTitle("專業版金鑰 (Professional Key)").setBenefits(listOf("解鎖所有進階功能","電子郵件支援","對開發路線圖的投票權")), + SubscriptionListing().setLanguageCode("en-US").setTitle("Professional Key").setBenefits(listOf("Unlock all premium features","Email support","Voting right on development roadmap")) + ) + @JvmStatic fun main(args: Array) { - val sku = SKU_accounts_unlimited + subscriptions() + } + + + fun subscriptions() { + val service: AndroidPublisher = AndroidPublisherHelper.init() + val subs = service.monetization().subscriptions() + val sku = "sku_professional_yearly" + try { - // Create the API service. - val service: AndroidPublisher = AndroidPublisherHelper.init() - val inappproducts = service.inappproducts() - - val content = InAppProduct() - .setPackageName(PACKAGE_NAME) - .setSku(sku.first) - .setPurchaseType("managedUser") - .setListings( - buildMap { - sku.second.forEach { - put( - it.key, - InAppProductListing().setTitle(it.value.first) - .setDescription(it.value.second) - ) - } - } + subs.patch(PACKAGE_NAME, sku, + Subscription().setListings( + proSubscription ) + ).also { + it.updateMask = "listings" + it.regionsVersionVersion = "2022/02" + }.execute() + println("Subscription $sku updated successfully.") + } catch (e: Exception) { + println("Error updating subscription ${sku}: ${e.message}") + } + } - inappproducts.patch(PACKAGE_NAME, sku.first, content).execute() - } catch (ex: IOException) { - log.error("Exception was thrown while updating listing", ex) - } catch (ex: GeneralSecurityException) { - log.error("Exception was thrown while updating listing", ex) - } + fun inAppProducts() { + val service: AndroidPublisher = AndroidPublisherHelper.init() + val inappproducts = service.inappproducts() + val sku = SKU_accounts_unlimited + val content = InAppProduct() + .setPackageName(PACKAGE_NAME) + .setSku(sku.first) + .setPurchaseType("managedUser") + .setListings( + buildMap { + sku.second.forEach { + put( + it.key, + InAppProductListing().setTitle(it.value.first) + .setDescription(it.value.second) + ) + } + } + ) + + inappproducts.patch(PACKAGE_NAME, sku.first, content).execute() } } } \ No newline at end of file diff --git a/doc/pro.xsl b/doc/pro.xsl index 1a6f033af8..b53f02d0c3 100644 --- a/doc/pro.xsl +++ b/doc/pro.xsl @@ -1,66 +1,70 @@ - - - - + + + + - - - - - - - + + + + + + + - - - - ../myExpenses/src/main/res/values - - - - /strings.xml - - - ../../Documents/MyExpenses.business/AddOns/strings - - - - .xml - - " - - - - " to InAppProductListing() - .setTitle(" - - Missing key professional_key for lang - - - ") - .setDescription(" - - ") - - - .setBenefits(listOf( - - " - - Benefit for lang must not exceed 40 characters. + + + + ../myExpenses/src/main/res/values + + + + /strings.xml + + + ../business/ProductCatalog/values + + + + /strings.xml + + SubscriptionListing().setLanguageCode(" + + + + ").setTitle(" + + Missing key professional_key for lang + + - - " - , - - )) - - , - - - + + ") + + .setBenefits(listOf( + + " + + Benefit for lang + + must not exceed 40 characters. + + + + " + + , + + + )) + + + , + + + +