diff --git a/.gitignore b/.gitignore index aa724b7..643d213 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,15 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore index 8675629..8524ff0 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,5 +1,5 @@ -# Default ignored files -/shelf/ -/workspace.xml -# CodeStream ignored files -/codestream.xml +# Default ignored files +/shelf/ +/workspace.xml +# CodeStream ignored files +/codestream.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b589d56..8fabff5 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 7d474e1..bec0c27 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/detekt.xml b/.idea/detekt.xml index ee7289c..735cde3 100644 --- a/.idea/detekt.xml +++ b/.idea/detekt.xml @@ -1,7 +1,7 @@ - - - - + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2504dc6..b4ae5ae 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,19 +1,19 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml index f8051a6..48052b2 100644 --- a/.idea/migrations.xml +++ b/.idea/migrations.xml @@ -1,10 +1,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index f3aad98..87d6e37 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,10 +1,10 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..9661ac7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt index d4ab5c2..3181b22 100644 --- a/MIT-LICENSE.txt +++ b/MIT-LICENSE.txt @@ -1,20 +1,20 @@ -Copyright «2023-2024» «VK» - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +Copyright «2023-2024» «VK» + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.MD b/README.MD index daa3f0f..346bdb2 100644 --- a/README.MD +++ b/README.MD @@ -1,190 +1,199 @@ -# Пример внедрения SDK платежей RuStore -## [Документация SDK платежей](https://www.rustore.ru/help/sdk/payments/kotlin-java/) - -### [English version of README.md](https://gitflic.ru/project/rustore/rustore-sdk-billing-example/blob?file=README_EN.MD&branch=master) - - -### Оглавление -- [Условия работы платежей](#Условия-работы-платежей) -- [Подготовка требуемых параметров](#Подготовка-требуемых-параметров) -- [Настройка примера приложения](#Настройка-примера-приложения) -- [Сценарий использования](#Сценарий-использования) -- [Обработка deeplink](#Обработка-deeplink) -- [Условия распространения](#Условия-распространения) -- [Техническая поддержка](#Техническая-поддержка) - - -### Условия работы платежей -Для работы проведения платежей необходимо соблюдение следующих условий. - -1. На устройстве пользователя установлено приложение RuStore. - -2. Пользователь и приложение не должны быть заблокированы в RuStore. - -3. Для приложения включена возможность покупок в системе [RuStore Консоль](https://console.rustore.ru/sign-in). - -> Сервис имеет некоторые ограничения на работу за пределами РФ. - - -### Подготовка требуемых параметров -Для корректной настройки примера приложения вам следует подготовить: - -1. `consoleApplicationId` - код приложения из консоли разработчика RuStore (пример: https://console.rustore.ru/apps/123456), тут `consoleApplicationId` = 123456 - -2. `applicationId` - из приложения, которое вы публиковали в консоль RuStore, находится в файле build.gradle вашего проекта - - ``` - android { - defaultConfig { - applicationId = "ru.rustore.sdk.billingexample" - } - } - ``` - -3. `availableProductIds` - [подписки](https://www.rustore.ru/help/developers/monetization/create-app-subscription/) и [разовые покупки](https://www.rustore.ru/help/developers/monetization/create-paid-product-in-application/) доступные в вашем приложении - -4. `release.keystore` - подпись, которой было подписано приложение, опубликованное в консоль RuStore. - -5. `release.properties` - в этом файле должны быть указаны параметры подписи, которой было подписано приложение, опубликованное в консоль RuStore. [Как работать с ключами подписи APK-файлов](https://www.rustore.ru/help/developers/publishing-and-verifying-apps/app-publication/apk-signature/) - - -### Настройка примера приложения -Для проверки работы приложения вы можете воспользоваться функционалом [тестовых платежей](https://www.rustore.ru/help/developers/monetization/sandbox). - -1. Указать `consoleApplicationId` своего приложения в `RuStoreBillingClientFactory.INSTANCE.create()`: - - ``` - private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( - app, - "2063535084", // consoleApplicationId - "ru.edudin.rustoreqrscanner", // deeplinkScheme - null, - null, - true - ); - ``` - -2. Замените `applicationId`, в файле build.gradle, на applicationId apk-файла, который вы публиковали в консоль RuStore: - - ``` - android { - defaultConfig { - applicationId = "ru.rustore.sdk.billingexample" - } - } - ``` - -3. В директории `cert` замените сертификат `release.keystore` - сертификатом своего приложения, так же в `release.properties` выполните настройку параметров `key_alias`, `key_password`, `store_password`. Подпись `release.keystore` должна совпадать с подписью, которой было подписано приложение, опубликованное в консоль RuStore. Убедитесь, что используемый `buildType` (пр. debug) использует такую же подпись, что и опубликованное приложение (пр. release). - -4. В `StartFragment` в методе `getProducts` перечислите подписки и разовые продукты доступные в вашем приложении: - - ``` - Arrays.asList( - "productId1", - "productId2", - "productId3" - ) - ``` - -5. Запустите проект и проверьте работу приложения - - -### Сценарий использования - -#### Проверка доступности работы с платежами -При открытия приложения в методе `onViewCreated` в `StartFragment` выполняется [проверка доступности платежей](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#проверка-доступности-работы-с-платежами), - - -#### Получение списка продуктов -Нажатие на кнопку 'getProducts()' выполняет получение и отображение [списка продуктов](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#получение-списка-продуктов) - - -#### Получение списка покупок -Нажатие на кнопку 'getPurchases()' выполняет получение и отображение [списка покупок](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#getpurchases) - - -#### Покупка продукта -Нажатие на айтем с названием продукта выполняет запуск сценария [покупки продукта](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#покупка-продукта) с отображением шторки выбора метода оплаты. -Далее если приходит ответ PaymentResult.Success -> выполняется [подтверждение покупки](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#подтверждение-покупки), если приходит ответ PaymentResult.Failure -> выполняется [отмена покупки](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#отмена-покупки). - - -### Обработка deeplink -Данный пункт не относится к настройке примера тестового приложения. Он информирует о том, как правильно настроить `deeplink` в вашем приложении - -`deeplinkScheme` — схема deeplink, необходимая для возврата в ваше приложение после оплаты через стороннее приложение (например, SberPay или СБП). SDK генерирует свой хост к данной схеме. - -Для корректной работы оплаты через сторонние приложения (СБП или SberPay), вам необходимо правильно реализовать обработку deeplink. Для этого необходимо указать в `AndroidManifest.xml` intent-filter с scheme вашего проекта. - -1. В файле `AndroidManifest.xml` в параметре data android:scheme укажите URL-адрес для использования deeplink (должен совпадать с параметром `deeplinkScheme` из пункта 1) - - ``` - - - - - // Заменить на свой deeplink - - - - ``` - -2. Указать `deeplinkScheme` своего приложения в `RuStoreBillingClientFactory.INSTANCE.create`: - - ``` - private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( - app, - "184050", - "rustoresdkexamplescheme", - null, - null, - null, - true - ); - ``` - -3. Далее в Activity, в которую необходимо вернуться после совершения оплаты (ваша страница магазина), нужно добавить: - - ``` - public class MainActivity extends AppCompatActivity { - - private static final RuStoreBillingClient billingClient = YourDependencyInjection.provideRuStorebillingClient(); - - public MainActivity() { - super(R.layout.activity_main); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState == null) { - billingClient.onNewIntent(getIntent()); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - billingClient.onNewIntent(intent); - } - } - ``` - -4. Для восстановления состояния вашего приложения при возврате с deeplink добавьте в `AndroidManifest.xml` android:launchMode="singleTask" - - ``` - - ``` - - -### Условия распространения -Данное программное обеспечение, включая исходные коды, бинарные библиотеки и другие файлы распространяется под лицензией MIT. Информация о лицензировании доступна в документе `MIT-LICENSE.txt` - - -### Техническая поддержка -Если появились вопросы по интеграции SDK платежей, обратитесь по [ссылке](https://www.rustore.ru/help/sdk/payments). +# Пример внедрения SDK платежей RuStore + +> ⚠️ **ВНИМАНИЕ: SDK BillingClient [DEPRECATED]** +> +> SDK BillingClient устарел и больше не будет развиваться. Новый функционал не будет добавляться, будут исправляться только критические ошибки. +> +> **Рекомендуется перейти на SDK Pay:** +> - [Документация SDK Pay](https://www.rustore.ru/help/sdk/pay/kotlin-java/) +> - [Руководство по миграции с BillingClient на SDK Pay](https://www.rustore.ru/help/sdk/pay/migration) + +## [Документация SDK платежей](https://www.rustore.ru/help/sdk/payments/kotlin-java/) + +### [English version of README.md](https://gitflic.ru/project/rustore/rustore-sdk-billing-example/blob?file=README_EN.MD&branch=master) + + +### Оглавление +- [Условия работы платежей](#Условия-работы-платежей) +- [Подготовка требуемых параметров](#Подготовка-требуемых-параметров) +- [Настройка примера приложения](#Настройка-примера-приложения) +- [Сценарий использования](#Сценарий-использования) +- [Обработка deeplink](#Обработка-deeplink) +- [Условия распространения](#Условия-распространения) +- [Техническая поддержка](#Техническая-поддержка) + + +### Условия работы платежей +Для работы проведения платежей необходимо соблюдение следующих условий. + +1. На устройстве пользователя установлено приложение RuStore. + +2. Пользователь и приложение не должны быть заблокированы в RuStore. + +3. Для приложения включена возможность покупок в системе [RuStore Консоль](https://console.rustore.ru/sign-in). + +> Сервис имеет некоторые ограничения на работу за пределами РФ. + + +### Подготовка требуемых параметров +Для корректной настройки примера приложения вам следует подготовить: + +1. `consoleApplicationId` - код приложения из консоли разработчика RuStore (пример: https://console.rustore.ru/apps/123456), тут `consoleApplicationId` = 123456 + +2. `applicationId` - из приложения, которое вы публиковали в консоль RuStore, находится в файле build.gradle вашего проекта + + ``` + android { + defaultConfig { + applicationId = "ru.rustore.sdk.billingexample" + } + } + ``` + +3. `availableProductIds` - [подписки](https://www.rustore.ru/help/developers/monetization/create-app-subscription/) и [разовые покупки](https://www.rustore.ru/help/developers/monetization/create-paid-product-in-application/) доступные в вашем приложении + +4. `release.keystore` - подпись, которой было подписано приложение, опубликованное в консоль RuStore. + +5. `release.properties` - в этом файле должны быть указаны параметры подписи, которой было подписано приложение, опубликованное в консоль RuStore. [Как работать с ключами подписи APK-файлов](https://www.rustore.ru/help/developers/publishing-and-verifying-apps/app-publication/apk-signature/) + + +### Настройка примера приложения +Для проверки работы приложения вы можете воспользоваться функционалом [тестовых платежей](https://www.rustore.ru/help/developers/monetization/sandbox). + +1. Указать `consoleApplicationId` своего приложения в `RuStoreBillingClientFactory.INSTANCE.create()`: + + ``` + private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( + app, + "2063535084", // consoleApplicationId + "ru.edudin.rustoreqrscanner", // deeplinkScheme + null, + null, + true + ); + ``` + +2. Замените `applicationId`, в файле build.gradle, на applicationId apk-файла, который вы публиковали в консоль RuStore: + + ``` + android { + defaultConfig { + applicationId = "ru.rustore.sdk.billingexample" + } + } + ``` + +3. В директории `cert` замените сертификат `release.keystore` - сертификатом своего приложения, так же в `release.properties` выполните настройку параметров `key_alias`, `key_password`, `store_password`. Подпись `release.keystore` должна совпадать с подписью, которой было подписано приложение, опубликованное в консоль RuStore. Убедитесь, что используемый `buildType` (пр. debug) использует такую же подпись, что и опубликованное приложение (пр. release). + +4. В `StartFragment` в методе `getProducts` перечислите подписки и разовые продукты доступные в вашем приложении: + + ``` + Arrays.asList( + "productId1", + "productId2", + "productId3" + ) + ``` + +5. Запустите проект и проверьте работу приложения + + +### Сценарий использования + +#### Проверка доступности работы с платежами +При открытия приложения в методе `onViewCreated` в `StartFragment` выполняется [проверка доступности платежей](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#проверка-доступности-работы-с-платежами), + + +#### Получение списка продуктов +Нажатие на кнопку 'getProducts()' выполняет получение и отображение [списка продуктов](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#получение-списка-продуктов) + + +#### Получение списка покупок +Нажатие на кнопку 'getPurchases()' выполняет получение и отображение [списка покупок](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#getpurchases) + + +#### Покупка продукта +Нажатие на айтем с названием продукта выполняет запуск сценария [покупки продукта](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#покупка-продукта) с отображением шторки выбора метода оплаты. +Далее если приходит ответ PaymentResult.Success -> выполняется [подтверждение покупки](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#подтверждение-покупки), если приходит ответ PaymentResult.Failure -> выполняется [отмена покупки](https://www.rustore.ru/help/sdk/payments/kotlin-java/6-0-0/#отмена-покупки). + + +### Обработка deeplink +Данный пункт не относится к настройке примера тестового приложения. Он информирует о том, как правильно настроить `deeplink` в вашем приложении + +`deeplinkScheme` — схема deeplink, необходимая для возврата в ваше приложение после оплаты через стороннее приложение (например, SberPay или СБП). SDK генерирует свой хост к данной схеме. + +Для корректной работы оплаты через сторонние приложения (СБП или SberPay), вам необходимо правильно реализовать обработку deeplink. Для этого необходимо указать в `AndroidManifest.xml` intent-filter с scheme вашего проекта. + +1. В файле `AndroidManifest.xml` в параметре data android:scheme укажите URL-адрес для использования deeplink (должен совпадать с параметром `deeplinkScheme` из пункта 1) + + ``` + + + + + // Заменить на свой deeplink + + + + ``` + +2. Указать `deeplinkScheme` своего приложения в `RuStoreBillingClientFactory.INSTANCE.create`: + + ``` + private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( + app, + "184050", + "rustoresdkexamplescheme", + null, + null, + null, + true + ); + ``` + +3. Далее в Activity, в которую необходимо вернуться после совершения оплаты (ваша страница магазина), нужно добавить: + + ``` + public class MainActivity extends AppCompatActivity { + + private static final RuStoreBillingClient billingClient = YourDependencyInjection.provideRuStorebillingClient(); + + public MainActivity() { + super(R.layout.activity_main); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + billingClient.onNewIntent(getIntent()); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + billingClient.onNewIntent(intent); + } + } + ``` + +4. Для восстановления состояния вашего приложения при возврате с deeplink добавьте в `AndroidManifest.xml` android:launchMode="singleTask" + + ``` + + ``` + + +### Условия распространения +Данное программное обеспечение, включая исходные коды, бинарные библиотеки и другие файлы распространяется под лицензией MIT. Информация о лицензировании доступна в документе `MIT-LICENSE.txt` + + +### Техническая поддержка +Если появились вопросы по интеграции SDK платежей, обратитесь по [ссылке](https://www.rustore.ru/help/sdk/payments). diff --git a/README_EN.md b/README_EN.md index c7cc971..f57275f 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,187 +1,196 @@ -# RuStore SDK Billing implementation example -## [SDK Billing documentation](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/) - -### Table of contents -- [Prerequisites](#Prerequisites) -- [Required parameters](#Required-parameters) -- [Configure example app](#Configure-example-app) -- [Use scenario](#Use-scenario) -- [Deeplink handling](#Deeplink-handling) -- [Distribution terms](#Distribution-terms) -- [Technical support](#Technical-support) - - -### Prerequisites -For in-app payments to work, the following requirements must be met. - -1. The RuStore app must be installed on the user's device. - -2. The user and the app are not banned in RuStore. - -3. In-app purchases for the app are enabled in [RuStore Console](https://console.rustore.ru/sign-in). - -> The service has some restrictions to work outside of Russia. - - -### Required parameters -To correctly configure the example app, you need to have the following parameters: - -1. `consoleApplicationId` - application code from RuStore Console (example: https://console.rustore.ru/apps/123456), in our example `consoleApplicationId` = 123456 - -2. `applicationId` - value from the app you published in the RuStore Console, it is in the build.gradle of your project - - ``` - android { - defaultConfig { - applicationId = "ru.rustore.sdk.billingexample" - } - } - ``` - -3. `availableProductIds` - [subscriptions](https://www.rustore.ru/help/en/developers/monetization/create-app-subscription/) and [single purchases ](https://www.rustore.ru/help/en/developers/monetization/create-paid-product-in-application/) available in your app - -4. `release.keystore` - signature that was used to sign your app in the RuStore Console. - -5. `release.properties` - this file must contain the parameters of the signature that was used to sign your app published in the RuStore Console. [Working with signature keys for APK files](https://www.rustore.ru/help/en/developers/publishing-and-verifying-apps/app-publication/apk-signature/) - - -### Configure example app -To test your app, you can use our [sandbox](https://www.rustore.ru/help/en/developers/monetization/sandbox). - -1. Specify `consoleApplicationId` of your app in `RuStoreBillingClientFactory.INSTANCE.create()`: - - ``` - private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( - app, - "2063535084", // consoleApplicationId - "ru.edudin.rustoreqrscanner", // deeplinkScheme - null, - null, - true - ); - ``` - -2. Replace `applicationId` in build.gradle, with applicationId of the APK file that you published in the RuStore Console: - - ``` - android { - defaultConfig { - applicationId = "ru.rustore.sdk.billingexample" - } - } - ``` - -3. In the `cert` folder replace the `release.keystore` certificate with the certificate of your app, also, in `release.properties` configure parameters `key_alias`, `key_password`, and `store_password`. The `release.keystore` signature must match the signature tha was used to sign the app published in the RuStore Console. Make sure that `buildType` used (example: debug) uses the same signature as the published app (example: release). - -4. In `StartFragment` of the `getProducts` method list subscriptions and consumable products available in your app: - - ``` - Arrays.asList( - "productId1", - "productId2", - "productId3" - ) - ``` - -5. Run your project and test your app performance - - -### Use scenario - -#### Payments availability check -On app start, in the `onViewCreated` method of `StartFragment`, [payments availability](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#checkpurchasesavailability) is checked. - - -#### Retrieving products list -Pressing the 'getProducts()' button triggers retrieving and displaying the [products list](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#getproducts) - - -#### Retrieve list of purchases -Pressing the 'getPurchases()' buttion triggers retrieving and displaying the [purchases list](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#getpurchases) - - -#### Product purchase -Pressing an item with a product name triggers the [product purchase](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#purchaseproduct) scenario with the payment method screen display. -Then, if PaymentResult.Success -> is returned, [purchase confirmation](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#confirm) is triggered. - - -### Deeplink handling -This section does not apply to the example app configuration. It contains the information on how to correctly configure `deeplink` in your app. - -`deeplinkScheme` — a deeplink scheme required to return the user to your app after making a payment in a third-party app (for example: in SberPay or SBP). SDK generates its host for this scheme. - -To redirect a user to your app after payment via third-party apps (the Faster Payments System (SBP), SberPay and others), you need to properly implement deep linking in your app. To achieve this, specify intent-filter with the scheme of your project in `AndroidManifest.xml`. - -1. In the data android:scheme parameter of your `AndroidManifest.xml` file specify the deeplink URL address (it must match `deeplinkScheme` from item 1) - - ``` - - - - - // Replace with your deeplink - - - - ``` - -2. Specify `deeplinkScheme` of your app in `RuStoreBillingClientFactory.INSTANCE.create`: - - ``` - private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( - app, - "184050", - "rustoresdkexamplescheme", - null, - null, - null, - true - ); - ``` - -3. Next, add the following code to the Activity you need to return to after making the payment (your store page): - - ``` - public class MainActivity extends AppCompatActivity { - - private static final RuStoreBillingClient billingClient = YourDependencyInjection.provideRuStorebillingClient(); - - public MainActivity() { - super(R.layout.activity_main); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState == null) { - billingClient.onNewIntent(getIntent()); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - billingClient.onNewIntent(intent); - } - } - ``` - -4. To restore your app state after returning from deeplink, add android:launchMode="singleTask" to your `AndroidManifest.xml` - - ``` - - ``` - - -### Distribution terms -This software, including its source code, binary libraries, and other files is distributed under the MIT license. The license information is available in the `MIT-LICENSE.txt` document - - -### Technical support -If you have any questions regarding SDK Billing, please, follow this [link](https://www.rustore.ru/help/en/sdk/payments). +# RuStore SDK Billing implementation example + +> ⚠️ **WARNING: SDK BillingClient [DEPRECATED]** +> +> SDK BillingClient is deprecated and will no longer be developed. New features will not be added, only critical bugs will be fixed. +> +> **We recommend migrating to SDK Pay:** +> - [SDK Pay documentation](https://www.rustore.ru/help/en/sdk/pay/kotlin-java/) +> - [Migration guide from BillingClient to SDK Pay](https://www.rustore.ru/help/en/sdk/pay/migration) + +## [SDK Billing documentation](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/) + +### Table of contents +- [Prerequisites](#Prerequisites) +- [Required parameters](#Required-parameters) +- [Configure example app](#Configure-example-app) +- [Use scenario](#Use-scenario) +- [Deeplink handling](#Deeplink-handling) +- [Distribution terms](#Distribution-terms) +- [Technical support](#Technical-support) + + +### Prerequisites +For in-app payments to work, the following requirements must be met. + +1. The RuStore app must be installed on the user's device. + +2. The user and the app are not banned in RuStore. + +3. In-app purchases for the app are enabled in [RuStore Console](https://console.rustore.ru/sign-in). + +> The service has some restrictions to work outside of Russia. + + +### Required parameters +To correctly configure the example app, you need to have the following parameters: + +1. `consoleApplicationId` - application code from RuStore Console (example: https://console.rustore.ru/apps/123456), in our example `consoleApplicationId` = 123456 + +2. `applicationId` - value from the app you published in the RuStore Console, it is in the build.gradle of your project + + ``` + android { + defaultConfig { + applicationId = "ru.rustore.sdk.billingexample" + } + } + ``` + +3. `availableProductIds` - [subscriptions](https://www.rustore.ru/help/en/developers/monetization/create-app-subscription/) and [single purchases ](https://www.rustore.ru/help/en/developers/monetization/create-paid-product-in-application/) available in your app + +4. `release.keystore` - signature that was used to sign your app in the RuStore Console. + +5. `release.properties` - this file must contain the parameters of the signature that was used to sign your app published in the RuStore Console. [Working with signature keys for APK files](https://www.rustore.ru/help/en/developers/publishing-and-verifying-apps/app-publication/apk-signature/) + + +### Configure example app +To test your app, you can use our [sandbox](https://www.rustore.ru/help/en/developers/monetization/sandbox). + +1. Specify `consoleApplicationId` of your app in `RuStoreBillingClientFactory.INSTANCE.create()`: + + ``` + private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( + app, + "2063535084", // consoleApplicationId + "ru.edudin.rustoreqrscanner", // deeplinkScheme + null, + null, + true + ); + ``` + +2. Replace `applicationId` in build.gradle, with applicationId of the APK file that you published in the RuStore Console: + + ``` + android { + defaultConfig { + applicationId = "ru.rustore.sdk.billingexample" + } + } + ``` + +3. In the `cert` folder replace the `release.keystore` certificate with the certificate of your app, also, in `release.properties` configure parameters `key_alias`, `key_password`, and `store_password`. The `release.keystore` signature must match the signature tha was used to sign the app published in the RuStore Console. Make sure that `buildType` used (example: debug) uses the same signature as the published app (example: release). + +4. In `StartFragment` of the `getProducts` method list subscriptions and consumable products available in your app: + + ``` + Arrays.asList( + "productId1", + "productId2", + "productId3" + ) + ``` + +5. Run your project and test your app performance + + +### Use scenario + +#### Payments availability check +On app start, in the `onViewCreated` method of `StartFragment`, [payments availability](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#checkpurchasesavailability) is checked. + + +#### Retrieving products list +Pressing the 'getProducts()' button triggers retrieving and displaying the [products list](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#getproducts) + + +#### Retrieve list of purchases +Pressing the 'getPurchases()' buttion triggers retrieving and displaying the [purchases list](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#getpurchases) + + +#### Product purchase +Pressing an item with a product name triggers the [product purchase](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#purchaseproduct) scenario with the payment method screen display. +Then, if PaymentResult.Success -> is returned, [purchase confirmation](https://www.rustore.ru/help/en/sdk/payments/kotlin-java/6-0-0#confirm) is triggered. + + +### Deeplink handling +This section does not apply to the example app configuration. It contains the information on how to correctly configure `deeplink` in your app. + +`deeplinkScheme` — a deeplink scheme required to return the user to your app after making a payment in a third-party app (for example: in SberPay or SBP). SDK generates its host for this scheme. + +To redirect a user to your app after payment via third-party apps (the Faster Payments System (SBP), SberPay and others), you need to properly implement deep linking in your app. To achieve this, specify intent-filter with the scheme of your project in `AndroidManifest.xml`. + +1. In the data android:scheme parameter of your `AndroidManifest.xml` file specify the deeplink URL address (it must match `deeplinkScheme` from item 1) + + ``` + + + + + // Replace with your deeplink + + + + ``` + +2. Specify `deeplinkScheme` of your app in `RuStoreBillingClientFactory.INSTANCE.create`: + + ``` + private static RuStoreBillingClient ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( + app, + "184050", + "rustoresdkexamplescheme", + null, + null, + null, + true + ); + ``` + +3. Next, add the following code to the Activity you need to return to after making the payment (your store page): + + ``` + public class MainActivity extends AppCompatActivity { + + private static final RuStoreBillingClient billingClient = YourDependencyInjection.provideRuStorebillingClient(); + + public MainActivity() { + super(R.layout.activity_main); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + billingClient.onNewIntent(getIntent()); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + billingClient.onNewIntent(intent); + } + } + ``` + +4. To restore your app state after returning from deeplink, add android:launchMode="singleTask" to your `AndroidManifest.xml` + + ``` + + ``` + + +### Distribution terms +This software, including its source code, binary libraries, and other files is distributed under the MIT license. The license information is available in the `MIT-LICENSE.txt` document + + +### Technical support +If you have any questions regarding SDK Billing, please, follow this [link](https://www.rustore.ru/help/en/sdk/payments). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d7eaf9..189ceb1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,65 +1,65 @@ -import com.android.build.api.dsl.ApkSigningConfig -import java.util.Properties - -plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") -} - -android { - namespace = "ru.rustore.example.rustorebillingsample" - compileSdk = 34 - - defaultConfig { - applicationId = "ru.rustore.example.rustorebillingsample" - minSdk = 24 - targetSdk = 34 - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - - signingConfigs { - // Замените на свою подпись! - val debugStoreFile = rootProject.file("cert/release.keystore") - val debugPropsFile = rootProject.file("cert/release.properties") - val debugProps = Properties() - debugPropsFile.inputStream().use(debugProps::load) - val debugSigningConfig = getByName("debug") { - storeFile = debugStoreFile - keyAlias = debugProps.getProperty("key_alias") - keyPassword = debugProps.getProperty("key_password") - storePassword = debugProps.getProperty("store_password") - } - signingConfig = debugSigningConfig - } - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - signingConfig = signingConfigs.getByName("debug") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} - -dependencies { - - implementation(libs.kotlin.stdlib) - implementation(libs.androidx.core) - implementation(libs.material) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - -// RuStore Implementation - implementation(platform(libs.rustore.sdk.bom)) - implementation(libs.rustore.sdk.billingclient) -} +import com.android.build.api.dsl.ApkSigningConfig +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "ru.rustore.example.rustorebillingsample" + compileSdk = 34 + + defaultConfig { + applicationId = "ru.rustore.example.rustorebillingsample" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + signingConfigs { + // Замените на свою подпись! + val debugStoreFile = rootProject.file("cert/release.keystore") + val debugPropsFile = rootProject.file("cert/release.properties") + val debugProps = Properties() + debugPropsFile.inputStream().use(debugProps::load) + val debugSigningConfig = getByName("debug") { + storeFile = debugStoreFile + keyAlias = debugProps.getProperty("key_alias") + keyPassword = debugProps.getProperty("key_password") + storePassword = debugProps.getProperty("store_password") + } + signingConfig = debugSigningConfig + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("debug") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation(libs.kotlin.stdlib) + implementation(libs.androidx.core) + implementation(libs.material) + implementation(libs.constraintlayout) + testImplementation(libs.junit) + +// RuStore Implementation + implementation(platform(libs.rustore.sdk.bom)) + implementation(libs.rustore.sdk.billingclient) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..64b4a05 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,21 +1,21 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. #-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c507d41..ea0dd35 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/App.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/App.java index 807d1e6..1b0febd 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/App.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/App.java @@ -1,14 +1,14 @@ -package ru.rustore.example.rustorebillingsample; - -import android.app.Application; - -import ru.rustore.example.rustorebillingsample.di.PaymentsModule; - -public class App extends Application { - @Override - public void onCreate() { - super.onCreate(); - - PaymentsModule.install(this); - } -} +package ru.rustore.example.rustorebillingsample; + +import android.app.Application; + +import ru.rustore.example.rustorebillingsample.di.PaymentsModule; + +public class App extends Application { + @Override + public void onCreate() { + super.onCreate(); + + PaymentsModule.install(this); + } +} diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/ItemClickSupport.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/ItemClickSupport.java index 9711af3..42c5898 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/ItemClickSupport.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/ItemClickSupport.java @@ -1,99 +1,99 @@ -package ru.rustore.example.rustorebillingsample; - -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -public class ItemClickSupport { - - private final RecyclerView mRecyclerView; - private OnItemClickListener mOnItemClickListener; - private OnItemLongClickListener mOnItemLongClickListener; - - private final View.OnClickListener mOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mOnItemClickListener != null) { - RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); - mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v); - } - } - }; - - private final View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mOnItemLongClickListener != null) { - RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); - return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v); - } - return false; - } - }; - - private final RecyclerView.OnChildAttachStateChangeListener mAttachListener - = new RecyclerView.OnChildAttachStateChangeListener() { - @Override - public void onChildViewAttachedToWindow(@NonNull View view) { - if (mOnItemClickListener != null) { - view.setOnClickListener(mOnClickListener); - } - if (mOnItemLongClickListener != null) { - view.setOnLongClickListener(mOnLongClickListener); - } - } - - @Override - public void onChildViewDetachedFromWindow(@NonNull View view) { - - } - }; - - private ItemClickSupport(RecyclerView recyclerView) { - mRecyclerView = recyclerView; - mRecyclerView.setTag(R.id.item_click_support, this); - mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener); - } - - public static ItemClickSupport addTo(RecyclerView view) { - ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support); - if (support == null) { - support = new ItemClickSupport(view); - } - return support; - } - - public static ItemClickSupport removeFrom(RecyclerView view) { - ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support); - if (support != null) { - support.detach(view); - } - return support; - } - - public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) { - mOnItemClickListener = listener; - return this; - } - - public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) { - mOnItemLongClickListener = listener; - return this; - } - - private void detach(RecyclerView view) { - view.removeOnChildAttachStateChangeListener(mAttachListener); - view.setTag(R.id.item_click_support, null); - } - - public interface OnItemClickListener { - - void onItemClicked(RecyclerView recyclerView, int position, View v); - } - - public interface OnItemLongClickListener { - - boolean onItemLongClicked(RecyclerView recyclerView, int position, View v); - } -} +package ru.rustore.example.rustorebillingsample; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +public class ItemClickSupport { + + private final RecyclerView mRecyclerView; + private OnItemClickListener mOnItemClickListener; + private OnItemLongClickListener mOnItemLongClickListener; + + private final View.OnClickListener mOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mOnItemClickListener != null) { + RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); + mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v); + } + } + }; + + private final View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (mOnItemLongClickListener != null) { + RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); + return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v); + } + return false; + } + }; + + private final RecyclerView.OnChildAttachStateChangeListener mAttachListener + = new RecyclerView.OnChildAttachStateChangeListener() { + @Override + public void onChildViewAttachedToWindow(@NonNull View view) { + if (mOnItemClickListener != null) { + view.setOnClickListener(mOnClickListener); + } + if (mOnItemLongClickListener != null) { + view.setOnLongClickListener(mOnLongClickListener); + } + } + + @Override + public void onChildViewDetachedFromWindow(@NonNull View view) { + + } + }; + + private ItemClickSupport(RecyclerView recyclerView) { + mRecyclerView = recyclerView; + mRecyclerView.setTag(R.id.item_click_support, this); + mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener); + } + + public static ItemClickSupport addTo(RecyclerView view) { + ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support); + if (support == null) { + support = new ItemClickSupport(view); + } + return support; + } + + public static ItemClickSupport removeFrom(RecyclerView view) { + ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support); + if (support != null) { + support.detach(view); + } + return support; + } + + public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) { + mOnItemClickListener = listener; + return this; + } + + public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) { + mOnItemLongClickListener = listener; + return this; + } + + private void detach(RecyclerView view) { + view.removeOnChildAttachStateChangeListener(mAttachListener); + view.setTag(R.id.item_click_support, null); + } + + public interface OnItemClickListener { + + void onItemClicked(RecyclerView recyclerView, int position, View v); + } + + public interface OnItemLongClickListener { + + boolean onItemLongClicked(RecyclerView recyclerView, int position, View v); + } +} diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/MainActivity.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/MainActivity.java index 3268968..2f91561 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/MainActivity.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/MainActivity.java @@ -1,32 +1,32 @@ -package ru.rustore.example.rustorebillingsample; - -import androidx.appcompat.app.AppCompatActivity; -import android.content.Intent; -import android.os.Bundle; -import ru.rustore.example.rustorebillingsample.di.PaymentsModule; -import ru.rustore.sdk.billingclient.RuStoreBillingClient; - -public class MainActivity extends AppCompatActivity { - - private static final RuStoreBillingClient billingClient = - PaymentsModule.provideRuStorebillingClient(); - - - public MainActivity() { - super(R.layout.activity_main); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState == null) { - billingClient.onNewIntent(getIntent()); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - billingClient.onNewIntent(intent); - } +package ru.rustore.example.rustorebillingsample; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Intent; +import android.os.Bundle; +import ru.rustore.example.rustorebillingsample.di.PaymentsModule; +import ru.rustore.sdk.billingclient.RuStoreBillingClient; + +public class MainActivity extends AppCompatActivity { + + private static final RuStoreBillingClient billingClient = + PaymentsModule.provideRuStorebillingClient(); + + + public MainActivity() { + super(R.layout.activity_main); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + billingClient.onNewIntent(getIntent()); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + billingClient.onNewIntent(intent); + } } \ No newline at end of file diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/ProductsAdapter.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/ProductsAdapter.java index 32a0526..5ba763d 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/ProductsAdapter.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/ProductsAdapter.java @@ -1,65 +1,65 @@ -package ru.rustore.example.rustorebillingsample; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import java.util.List; -import ru.rustore.sdk.billingclient.model.product.Product; - -public class ProductsAdapter extends RecyclerView.Adapter { - private final List mProducts; - - public ProductsAdapter(List products) { - mProducts = products; - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - - public TextView productIdTextView; - public TextView productNameTextView; - public TextView productPriceTextView; - - - - public ViewHolder(@NonNull View itemView) { - super(itemView); - - productNameTextView = itemView.findViewById(R.id.productName); - productIdTextView = itemView.findViewById(R.id.productId); - productPriceTextView = itemView.findViewById(R.id.productPrice); - } - } - - @NonNull - @Override - public ProductsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - Context context = parent.getContext(); - LayoutInflater inflater = LayoutInflater.from(context); - - View productView = inflater.inflate(R.layout.item_product, parent, false); - - return new ViewHolder(productView); - } - - @Override - public void onBindViewHolder(@NonNull ProductsAdapter.ViewHolder holder, int position) { - Product product = mProducts.get(position); - - TextView productName = holder.productNameTextView; - TextView productId = holder.productIdTextView; - TextView priceText = holder.productPriceTextView; - - productName.setText(product.getTitle()); - productId.setText(product.getProductId()); - priceText.setText(String.valueOf(product.getPrice())); - } - - @Override - public int getItemCount() { - return mProducts.size(); - } -} +package ru.rustore.example.rustorebillingsample; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; +import ru.rustore.sdk.billingclient.model.product.Product; + +public class ProductsAdapter extends RecyclerView.Adapter { + private final List mProducts; + + public ProductsAdapter(List products) { + mProducts = products; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + public TextView productIdTextView; + public TextView productNameTextView; + public TextView productPriceTextView; + + + + public ViewHolder(@NonNull View itemView) { + super(itemView); + + productNameTextView = itemView.findViewById(R.id.productName); + productIdTextView = itemView.findViewById(R.id.productId); + productPriceTextView = itemView.findViewById(R.id.productPrice); + } + } + + @NonNull + @Override + public ProductsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + Context context = parent.getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + + View productView = inflater.inflate(R.layout.item_product, parent, false); + + return new ViewHolder(productView); + } + + @Override + public void onBindViewHolder(@NonNull ProductsAdapter.ViewHolder holder, int position) { + Product product = mProducts.get(position); + + TextView productName = holder.productNameTextView; + TextView productId = holder.productIdTextView; + TextView priceText = holder.productPriceTextView; + + productName.setText(product.getTitle()); + productId.setText(product.getProductId()); + priceText.setText(String.valueOf(product.getPrice())); + } + + @Override + public int getItemCount() { + return mProducts.size(); + } +} diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/PurchaseAdapter.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/PurchaseAdapter.java index f0c79c9..e461a8b 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/PurchaseAdapter.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/PurchaseAdapter.java @@ -1,66 +1,66 @@ -package ru.rustore.example.rustorebillingsample; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import ru.rustore.sdk.billingclient.model.purchase.Purchase; - -public class PurchaseAdapter extends RecyclerView.Adapter { - - private final List mPurchases; - - public PurchaseAdapter(List purchases) { - mPurchases = purchases; - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - - public TextView purchaseId; - public TextView purchaseState; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - purchaseId = (TextView) itemView.findViewById(R.id.purchaseId); - purchaseState = (TextView) itemView.findViewById(R.id.purchaseState); - } - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - Context context = parent.getContext(); - LayoutInflater inflater = LayoutInflater.from(context); - - View purchase = inflater.inflate(R.layout.item_purchase, parent, false); - - return new ViewHolder(purchase); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - Purchase purchase = mPurchases.get(position); - - TextView purchaseId = holder.purchaseId; - TextView purchaseState = holder.purchaseState; - - purchaseId.setText(purchase.getPurchaseId()); - purchaseState.setText(String.valueOf(purchase.getPurchaseState())); - } - - - @Override - public int getItemCount() { - return mPurchases.size(); - } - - -} +package ru.rustore.example.rustorebillingsample; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import ru.rustore.sdk.billingclient.model.purchase.Purchase; + +public class PurchaseAdapter extends RecyclerView.Adapter { + + private final List mPurchases; + + public PurchaseAdapter(List purchases) { + mPurchases = purchases; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + public TextView purchaseId; + public TextView purchaseState; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + purchaseId = (TextView) itemView.findViewById(R.id.purchaseId); + purchaseState = (TextView) itemView.findViewById(R.id.purchaseState); + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + Context context = parent.getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + + View purchase = inflater.inflate(R.layout.item_purchase, parent, false); + + return new ViewHolder(purchase); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Purchase purchase = mPurchases.get(position); + + TextView purchaseId = holder.purchaseId; + TextView purchaseState = holder.purchaseState; + + purchaseId.setText(purchase.getPurchaseId()); + purchaseState.setText(String.valueOf(purchase.getPurchaseState())); + } + + + @Override + public int getItemCount() { + return mPurchases.size(); + } + + +} diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/StartFragment.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/StartFragment.java index 62fe2fa..56cfabd 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/StartFragment.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/StartFragment.java @@ -1,183 +1,183 @@ -package ru.rustore.example.rustorebillingsample; - -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.Arrays; -import java.util.List; - -import ru.rustore.example.rustorebillingsample.di.PaymentsModule; -import ru.rustore.sdk.billingclient.RuStoreBillingClient; -import ru.rustore.sdk.billingclient.model.purchase.PaymentResult; -import ru.rustore.sdk.billingclient.model.purchase.Purchase; -import ru.rustore.sdk.billingclient.model.purchase.PurchaseAvailabilityResult; -import ru.rustore.sdk.billingclient.model.purchase.PurchaseState; -import ru.rustore.sdk.billingclient.usecase.ProductsUseCase; -import ru.rustore.sdk.billingclient.usecase.PurchasesUseCase; -import ru.rustore.sdk.billingclient.utils.pub.RuStoreBillingClientExtKt; -import ru.rustore.sdk.core.exception.RuStoreException; - -public class StartFragment extends Fragment { - - Button productButton; - Button purchaseButton; - RecyclerView productsList; - RecyclerView purchasesList; - - private static final String TAG = "RuStoreBillingClient"; - - private static final RuStoreBillingClient billingClient = - PaymentsModule.provideRuStorebillingClient(); - - public StartFragment() { - super(R.layout.fragment_start); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_start, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - productButton = view.findViewById(R.id.productsButton); - purchaseButton = view.findViewById(R.id.purchasesButton); - - productsList = view.findViewById(R.id.productsList); - purchasesList = view.findViewById(R.id.purchasesList); - - checkPurchaseAvailability(); - - productButton.setOnClickListener(v -> getProducts()); - - purchaseButton.setOnClickListener(v -> getPurchases()); - } - - public void checkPurchaseAvailability() { - RuStoreBillingClientExtKt.checkPurchasesAvailability(RuStoreBillingClient.Companion) - .addOnSuccessListener(result -> { - if (result instanceof PurchaseAvailabilityResult.Available) { - Log.w(TAG, "Success calling checkPurchaseAvailability - Available: " + result); - } else if (result instanceof PurchaseAvailabilityResult.Unavailable) { - RuStoreException exception = ((PurchaseAvailabilityResult.Unavailable) result).getCause(); - Log.w(TAG, "Success calling checkPurchaseAvailability - Unavailable: " + exception); - } else if (result instanceof PurchaseAvailabilityResult.Unknown) { - Log.w(TAG, "Success calling checkPurchaseAvailability - Unknown: " + result); - } - }).addOnFailureListener(error -> Log.e(TAG, "Error calling checkPurchaseAvailability: " + error)); - } - - public void getProducts() { - ProductsUseCase productsUseCase = billingClient.getProducts(); - List productsId = Arrays.asList( - "productId1", - "productId2", - "productId3" - ); - - productsUseCase.getProducts(productsId) - .addOnSuccessListener(products -> { - ProductsAdapter productsAdapter = new ProductsAdapter(products); - - productsList.setAdapter(productsAdapter); - - productsList.setLayoutManager(new LinearLayoutManager(getContext())); - - ItemClickSupport.addTo(productsList).setOnItemClickListener((recyclerView, position, v) -> { - purchaseProduct(products.get(position).getProductId()); - Toast.makeText(getContext(), "Clicked: " + position, Toast.LENGTH_LONG).show(); - }); - }) - .addOnFailureListener(throwable -> Log.e(TAG, "Error calling getProducts cause: " + throwable)); - } - - public void getPurchases() { - PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); - - purchasesUseCase.getPurchases() - .addOnSuccessListener(purchases -> { - PurchaseAdapter purchaseAdapter = new PurchaseAdapter(purchases); - purchasesList.setAdapter(purchaseAdapter); - purchasesList.setLayoutManager(new LinearLayoutManager(getContext())); - - proceedUnfinishedPurchases(purchases); - }) - .addOnFailureListener(throwable -> Log.e("RuStoreBillingClient", "Error calling getPurchases cause: " + throwable)); - } - - public void proceedUnfinishedPurchases(List purchases) { - purchases.forEach(purchase -> { - String purchaseId = purchase.getPurchaseId(); - PurchaseState purchaseState = purchase.getPurchaseState(); - - if (purchaseId == null) { - return; - } else if (purchaseState == null) { - Log.e(TAG, "PurchaseState is null"); - return; - } - - if (purchaseState == PurchaseState.PAID) { - // If you can not give product to the customer use - // deletePurchase(purchaseId) - - confirmPurchase(purchaseId); - } - }); - } - - public void purchaseProduct(String productId) { - PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); - - String developerPayload = "your_developer_payload"; - - purchasesUseCase.purchaseProduct(productId, null, 1, developerPayload) - .addOnSuccessListener(this::handlePaymentResult) - .addOnFailureListener(throwable -> - Log.e("RuStoreBillingClient", "Error calling purchaseProduct cause: " + throwable) - ); - } - - private void handlePaymentResult(PaymentResult paymentResult) { - if (paymentResult instanceof PaymentResult.Success) { - confirmPurchase(((PaymentResult.Success) paymentResult).getPurchaseId()); - } - } - - public void confirmPurchase(String purchaseId) { - PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); - - purchasesUseCase.confirmPurchase(purchaseId) - .addOnSuccessListener(unit -> { - Toast.makeText(getContext(), "Purchase confirmed", Toast.LENGTH_LONG).show(); - }).addOnFailureListener(throwable -> { - Log.e("RuStoreBillingClient", "Error calling confirmPurchase cause: " + throwable); - }); - } - - @SuppressWarnings("unused") - public void deletePurchase(String purchaseId) { - PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); - - purchasesUseCase.deletePurchase(purchaseId) - .addOnSuccessListener(unit -> { - }) - .addOnFailureListener(throwable -> { - Log.e("RuStoreBillingClient", "Error calling deletePurchase cause: " + throwable); - }); - } -} +package ru.rustore.example.rustorebillingsample; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.Arrays; +import java.util.List; + +import ru.rustore.example.rustorebillingsample.di.PaymentsModule; +import ru.rustore.sdk.billingclient.RuStoreBillingClient; +import ru.rustore.sdk.billingclient.model.purchase.PaymentResult; +import ru.rustore.sdk.billingclient.model.purchase.Purchase; +import ru.rustore.sdk.billingclient.model.purchase.PurchaseAvailabilityResult; +import ru.rustore.sdk.billingclient.model.purchase.PurchaseState; +import ru.rustore.sdk.billingclient.usecase.ProductsUseCase; +import ru.rustore.sdk.billingclient.usecase.PurchasesUseCase; +import ru.rustore.sdk.billingclient.utils.pub.RuStoreBillingClientExtKt; +import ru.rustore.sdk.core.exception.RuStoreException; + +public class StartFragment extends Fragment { + + Button productButton; + Button purchaseButton; + RecyclerView productsList; + RecyclerView purchasesList; + + private static final String TAG = "RuStoreBillingClient"; + + private static final RuStoreBillingClient billingClient = + PaymentsModule.provideRuStorebillingClient(); + + public StartFragment() { + super(R.layout.fragment_start); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_start, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + productButton = view.findViewById(R.id.productsButton); + purchaseButton = view.findViewById(R.id.purchasesButton); + + productsList = view.findViewById(R.id.productsList); + purchasesList = view.findViewById(R.id.purchasesList); + + checkPurchaseAvailability(); + + productButton.setOnClickListener(v -> getProducts()); + + purchaseButton.setOnClickListener(v -> getPurchases()); + } + + public void checkPurchaseAvailability() { + RuStoreBillingClientExtKt.checkPurchasesAvailability(RuStoreBillingClient.Companion) + .addOnSuccessListener(result -> { + if (result instanceof PurchaseAvailabilityResult.Available) { + Log.w(TAG, "Success calling checkPurchaseAvailability - Available: " + result); + } else if (result instanceof PurchaseAvailabilityResult.Unavailable) { + RuStoreException exception = ((PurchaseAvailabilityResult.Unavailable) result).getCause(); + Log.w(TAG, "Success calling checkPurchaseAvailability - Unavailable: " + exception); + } else if (result instanceof PurchaseAvailabilityResult.Unknown) { + Log.w(TAG, "Success calling checkPurchaseAvailability - Unknown: " + result); + } + }).addOnFailureListener(error -> Log.e(TAG, "Error calling checkPurchaseAvailability: " + error)); + } + + public void getProducts() { + ProductsUseCase productsUseCase = billingClient.getProducts(); + List productsId = Arrays.asList( + "productId1", + "productId2", + "productId3" + ); + + productsUseCase.getProducts(productsId) + .addOnSuccessListener(products -> { + ProductsAdapter productsAdapter = new ProductsAdapter(products); + + productsList.setAdapter(productsAdapter); + + productsList.setLayoutManager(new LinearLayoutManager(getContext())); + + ItemClickSupport.addTo(productsList).setOnItemClickListener((recyclerView, position, v) -> { + purchaseProduct(products.get(position).getProductId()); + Toast.makeText(getContext(), "Clicked: " + position, Toast.LENGTH_LONG).show(); + }); + }) + .addOnFailureListener(throwable -> Log.e(TAG, "Error calling getProducts cause: " + throwable)); + } + + public void getPurchases() { + PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); + + purchasesUseCase.getPurchases() + .addOnSuccessListener(purchases -> { + PurchaseAdapter purchaseAdapter = new PurchaseAdapter(purchases); + purchasesList.setAdapter(purchaseAdapter); + purchasesList.setLayoutManager(new LinearLayoutManager(getContext())); + + proceedUnfinishedPurchases(purchases); + }) + .addOnFailureListener(throwable -> Log.e("RuStoreBillingClient", "Error calling getPurchases cause: " + throwable)); + } + + public void proceedUnfinishedPurchases(List purchases) { + purchases.forEach(purchase -> { + String purchaseId = purchase.getPurchaseId(); + PurchaseState purchaseState = purchase.getPurchaseState(); + + if (purchaseId == null) { + return; + } else if (purchaseState == null) { + Log.e(TAG, "PurchaseState is null"); + return; + } + + if (purchaseState == PurchaseState.PAID) { + // If you can not give product to the customer use + // deletePurchase(purchaseId) + + confirmPurchase(purchaseId); + } + }); + } + + public void purchaseProduct(String productId) { + PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); + + String developerPayload = "your_developer_payload"; + + purchasesUseCase.purchaseProduct(productId, null, 1, developerPayload) + .addOnSuccessListener(this::handlePaymentResult) + .addOnFailureListener(throwable -> + Log.e("RuStoreBillingClient", "Error calling purchaseProduct cause: " + throwable) + ); + } + + private void handlePaymentResult(PaymentResult paymentResult) { + if (paymentResult instanceof PaymentResult.Success) { + confirmPurchase(((PaymentResult.Success) paymentResult).getPurchaseId()); + } + } + + public void confirmPurchase(String purchaseId) { + PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); + + purchasesUseCase.confirmPurchase(purchaseId) + .addOnSuccessListener(unit -> { + Toast.makeText(getContext(), "Purchase confirmed", Toast.LENGTH_LONG).show(); + }).addOnFailureListener(throwable -> { + Log.e("RuStoreBillingClient", "Error calling confirmPurchase cause: " + throwable); + }); + } + + @SuppressWarnings("unused") + public void deletePurchase(String purchaseId) { + PurchasesUseCase purchasesUseCase = billingClient.getPurchases(); + + purchasesUseCase.deletePurchase(purchaseId) + .addOnSuccessListener(unit -> { + }) + .addOnFailureListener(throwable -> { + Log.e("RuStoreBillingClient", "Error calling deletePurchase cause: " + throwable); + }); + } +} diff --git a/app/src/main/java/ru/rustore/example/rustorebillingsample/di/PaymentsModule.java b/app/src/main/java/ru/rustore/example/rustorebillingsample/di/PaymentsModule.java index fbf423b..21cd3e2 100644 --- a/app/src/main/java/ru/rustore/example/rustorebillingsample/di/PaymentsModule.java +++ b/app/src/main/java/ru/rustore/example/rustorebillingsample/di/PaymentsModule.java @@ -1,26 +1,26 @@ -package ru.rustore.example.rustorebillingsample.di; - -import android.app.Application; -import ru.rustore.sdk.billingclient.RuStoreBillingClient; -import ru.rustore.sdk.billingclient.RuStoreBillingClientFactory; - -public class PaymentsModule { - - private static RuStoreBillingClient ruStoreBillingClient; - - public static void install(Application app) { - ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( - app, - "184050", - "rustoresdkexamplescheme", - null, - null, - true - ); - } - - public static RuStoreBillingClient provideRuStorebillingClient() { - return ruStoreBillingClient; - } - -} +package ru.rustore.example.rustorebillingsample.di; + +import android.app.Application; +import ru.rustore.sdk.billingclient.RuStoreBillingClient; +import ru.rustore.sdk.billingclient.RuStoreBillingClientFactory; + +public class PaymentsModule { + + private static RuStoreBillingClient ruStoreBillingClient; + + public static void install(Application app) { + ruStoreBillingClient = RuStoreBillingClientFactory.INSTANCE.create( + app, + "184050", + "rustoresdkexamplescheme", + null, + null, + true + ); + } + + public static RuStoreBillingClient provideRuStorebillingClient() { + return ruStoreBillingClient; + } + +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index ca3826a..953ca56 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,74 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 2b068d1..cc14f03 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,30 +1,30 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 55805a7..85559e9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_start.xml b/app/src/main/res/layout/fragment_start.xml index 888f0f8..1caf4a8 100644 --- a/app/src/main/res/layout/fragment_start.xml +++ b/app/src/main/res/layout/fragment_start.xml @@ -1,50 +1,50 @@ - - - -