Skip to main content

6.1.0

With RuStore you can integrate payments in your mobile app.

tip

If you are in doubt read the instruction in the usage scenarios.

Implementation example

Please, look at the application example to learn how to integrate payments in your app.

Read more about accepting payments without RuStore installation [here].(/developers/monetization/without-rustore-app).

Prerequisites

  • App uploaded to RuStore Console.
  • App passed moderation (you don't have to publish the app).
Important
  • Test build signature (for example: debug) of the app must match the signature of the app build that was uploaded to the console and passed moderation (for example, release).
  • The user and the app are not banned in RuStore.
  • In-app purchases for the app are enabled in RuStore Console.
caution

The service has some restrictions to work outside of Russia.

Getting started

Adding repository

Add our repository as shown in the example below.

build.gradle
repositories {
maven {
url = uri("https://artifactory-external.vkpartner.ru/artifactory/maven")
}
}

Connecting the dependency

Add the following code to your configuration file to add the dependency.

Advantages of using a BOM file for configuration.

  1. Unified version management:

    • With BOM you can manage versions of all dependencies from a single file. This is especially useful when you use several libraries that must be compatible with each other.
    • For example, if you have several RuStore libraries such as ru.rustore.sdk:billingclient and ru.rustore.sdk:pushclient, you can use BOM to make sure that they are all compatible with each other.
  2. Easier updates:

    • Updating dependencies is easier as you only have to change the version number in one place — in the BOM file. This lowers the risk of missing a dependency update and helps to avoid version conflicts.
    • For example, if a new version of the BOM file contains updated versions of all libraries, you only have to update the BOM file, not each dependency.
  3. Increased compatibility:

    • Using BOM allows to avoid version conflicts between different libraries. This is especially important when libraries have mutual dependencies.
    • For example, if two libraries depend on different versions of the same library, this can cause conflicts. BOM helps to avoid this making sure that all dependencies are compatible.
build.gradle
dependencies {
implementation(platform("ru.rustore.sdk:bom:6.1.0"))
implementation("ru.rustore.sdk:billingclient")
}

RuStore SDK uses deeplink to handle third-party payment applications. This makes it easier to pay with third-party apps and return to your app.

To configure deeplinks functionality in your app and RuStore SDK, define deeplinkScheme in your AndroidManifest file and redefine the onNewIntent method of your activity.

AndroidManifest.xml
<activity
android:name=".YourBillingActivity">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourappscheme" />
</intent-filter>

</activity>

Replace yourappscheme in the example above with the name of your scheme. For example, ru.package.name.rustore.scheme.

info

The scheme defined in the Androidmanifest file must match the scheme you specify in the create method of the RuStore SDK billing.

Then, add the following code to the Activity the user will return after making their payment (the page in tour store).

class YourBillingActivity: AppCompatActivity() {

// Previously created with RuStoreBillingClientFactory.create()
private val billingClient: RuStoreBillingClient = YourDependencyInjection.getBillingClient()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
billingClient.onNewIntent(intent)
}
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
billingClient.onNewIntent(intent)
}
}

To restore the state of your app after returning from deeplink, add android:launchMode="singleTask" to AndroidManifest.xml.

AndroidManifest.xml
<activity
android:name=".YourBillingActivity"
android:launchMode="singleTask"
android:exported="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">

Initialization

Initialize the library before calling its methods.

Create RuStoreBillingClient using RuStoreBillingClientFactory.create().

val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
// Optional parameters
themeProvider = null,
debugLogs = false,
externalPaymentLoggerFactory = null,
)
  • context — key element that provides information about current object state.
    info

    There are several types of context in Android:

    App context — a singleton instance available via getApplicationContext().

    • Activity context is available in Activity and is bound to its lifecycle.
    • ContentProvider context is available via the getContext() method and analogous to the application context.

    To create RuStoreBillingClient, it is preferable to use applicationContext.

  • consoleApplicationId — product ID form the RuStore Console.

Where are app IDs in the RuStore Console?
  1. Navigate to the Applications tab and selected the needed app.
  2. Copy the ID from the URL address of the app page — it is a set of numbers between apps/ and /versions. FOr example, for URL address https://console.rustore.ru/apps/123456/versions the app ID is 123456.

note

applicationId specified in build.gradle must match applicationId of the APK file you published in the RuStore Console.

  • deeplinkScheme — deeplink scheme that i sused to return to your app after paying with a third party payment app (for example, SberPay or SBP). SDK generates its own host to this scheme.
note
Tye deeplink scheme that is passed in yourappscheme must match the scheme specified in AndroidManifest.xml (see Deeplink processing).
  • themeProvider — interface that provides the BillingClientTheme theme. There are 2 possible implementations of the BillingClientTheme theme: Light and Dark. Optional, by default the light theme is used.

    img img
    note

    The Light theme is used by default but you can configure the Dark theme as well.

  • externalPaymentLoggerFactory — interface that allows logging.
    tip

    Event logging might be useful for versions that are not yet published for users. It can help you to track errors.

  • debugLogs — flag that enables/disables logging. Set it to true, if you want events to be logged. Otherwise, set it to false.
note

The keystore signature must match the signature that 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).

Before you start

caution

The scheme below shows an approximate algorithm that you can use an example of how to configure and connect payments. Please, keep in mind the special aspects of your project while configuring payments.

Payment appRuStore Server RuStore_Billing_ClientYour serverYour appUserPayment appRuStore Server RuStore_Billing_ClientYour serverYour appUserPayments availability check[Optional]Server validation[Mandatory]Purchasing consumable product[Mandatory]Deeplink processing for paying with SBP, SberPay, etc.Product purchaseStarts your app checkPurchasesAvailabilityResultgetProductsProducts list of your appDisplaying list of available purchasesPurchasing product purchaseProductRequest payment methodPayment methodMaking paymentPayment resultPayment callbackServer validation (public API)Full purchase informationDeliver product to the user confirmPurchaseConsumption resultDeliver product to the userpurchaseProductRequest payment methodSpecified SBP/SberPay/T-PayStart payment processPayment scenarioPurchase paymentReturn to app OnNewIntentPayment resultDisplaying payment screen with the result

Payments availability check

To check payments availability, use the checkPurchasesAvailability method.

info

Starting from 6.0.0 the payment availability check procedure depends on whether RuStore is installed on the user's device.

For details, see below.

 RuStore is installedCheck procedure and result
Not installed

The method will retun FeatureAvailabilityResult.Available — payments available. This is possible because [payments can be accepted without RuStore installed].(/developers/monetization/without-rustore-app).

Installed

The following requirements are checked.

  • The current version of RuStore is installed on the user's device.
  • RuStore app supports payments.
  • User is authorized in RuStore.
  • The user and the app are not banned in RuStore.
  • In-app purchases for the app are enabled in RuStore Console.
If all above conditions are met, FeatureAvailabilityResult.Available is returned.

Otherwise, FeatureAvailabilityResult.Unavailable(val cause: RuStoreException) is returned, where cause is an error of a failed condition.

All possible errors RuStoreException are described in Error handling. Other errors are returned in onFailure. (See Task API).

caution

Thus, a positive response will be returned in the following cases:

  • if RuStore is not installed;
  • if RuStore is installed and the installed RuStore instance meets the requirements.

The checkPurchasesAvailability method itself does not show whether RuStore is installed on the user's device. You can check whether RuStore is installed on the user's device using the isRuStoreInstalled method.

Below is an example of using checkPurchasesAvailability.

RuStoreBillingClient.checkPurchasesAvailability(context)
.addOnSuccessListener { result ->
when (result) {
FeatureAvailabilityResult.Available -> {
// Process purchases available
}

is FeatureAvailabilityResult.Unavailable -> {
// Process purchases unavailable
}
}
}
.addOnFailureListener { throwable ->
// Process unknown error
}

context — Android context.

In this SDK version the method will return payment availability message.

Working with SDK

Check whether RuStore is installed on the device

The isRuStoreInstalled methods is used to check whether RuStore is installed on the user's device. This check is needed to correctly implement handling of products and purchase. The below SDK methods require user authorization:

If RuStore is not installed on the user's device, each time the authorization screen will be displayed that might have a negative effect on user experience. Thus, if the check shows that RuStore is not installed on the user's device, it makes sense to limit the number of requests that require authorization (for more details see section Receiving payments without RuStore app).

public fun isRuStoreInstalled(context: Context): Boolean

Retrieving products list

You verified that payments are available and the users are able to make purchases. Now you can request products list. Use the getProducts method to request the information about products added to your app in RuStore Console.
val productsUseCase: ProductsUseCase = billingClient.products
productsUseCase.getProducts(productIds = listOf("id1", "id2"))
.addOnSuccessListener { products: List<Product> ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}

productIds — the list of product IDs. Must not exceed 100 entries.

To specify id of the products needed for the method, do the following.

  1. Open RuStore Console.
  2. Navigate to the Applications tab.
  3. Select the necessary app.
  4. In the left side menu select Monetization.
  5. Select product type: Subscriptions or In-App purchases.
  6. Copy the IDs of the required products. These are product ids.

Maximum length — 2083 characters in the list.

The method returns

data class Product(
val productId: String,
val productType: ProductType?,
val productStatus: ProductStatus,
val priceLabel: String?,
val price: Int?,
val currency: String?,
val language: String?,
val title: String?,
val description: String?,
val imageUrl: Uri?,
val promoImageUrl: Uri?,
val subscription: ProductSubscription?,
)

Product structure

  • productId — product ID assigned to product in RuStore Console (mandatory).
  • productType — product type: CONSUMABLE/NON-CONSUMABE/SUBSCRIPTION.
  • productStatus — product status.
  • priceLable — formatted product price, including currency symbol in language.
  • price — price in minimum currency units.
  • currency — ISO 4217 currency code.
  • language — language specified with BCP 47 code.
  • title — product name in language.
  • description — descriptions in language.
  • imageUrl — image URL.
  • promoImageUrl — promo image URL.
  • subscription — subscription description, returns only for subscription products.

Subscription structure

data class ProductSubscription(
val subscriptionPeriod: SubscriptionPeriod?,
val freeTrialPeriod: SubscriptionPeriod?,
val gracePeriod: SubscriptionPeriod?,
val introductoryPrice: String?,
val introductoryPriceAmount: String?,
val introductoryPricePeriod: SubscriptionPeriod?
)
  • subscriptionPeriod — subscription period.
  • freeTrialPeriod — subscription trial period.
  • gracePeriod — subscription grace period.
  • introductoryPrice — formated introductory price with the currency symbol in the product:language language.
  • introductoryPriceAmount — introductory price in minimum currency units.
  • introductoryPricePeriod — introductory price invoice period.

Structure of the subscription period

data class SubscriptionPeriod(
val years: Int,
val months: Int,
val days: Int,
)
  • years — amount of years.
  • months — amount of months.
  • days — amount of days.

Purchasing product

To purchase product, use the purchaseProduct method.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.purchaseProduct(
productId = productId,
orderId = UUID.randomUUID().toString(),
quantity = 1,
developerPayload = null,
).addOnSuccessListener { paymentResult: PaymentResult ->
when (paymentResult) {
// Process PaymentResult
}
}.addOnFailureListener { throwable: Throwable ->
// Process error
}
  • productId: String — product ID assigned to product in RuStore Console (mandatory).
  • orderId: String — payment ID generated by the app (optional). If you specify this parameter in your system, you will receive it via our API. If not specified, will be generated automatically (uuid). 150 characters max.
  • quantity: Int — product amount (optional, value 1 will be used if not specified).
  • developerPayload — string with additional order information, that you can specify on purchase initialization.

Purchase result structure

public sealed interface PaymentResult {     

public data class Success(
val orderId: String?,
val purchaseId: String,
val productId: String,
val invoiceId: String,
val sandbox: Boolean,
val subscriptionToken: String? = null,
) : PaymentResult

public data class Cancelled(
val purchaseId: String,
val sandbox: Boolean,
) : PaymentResult

public data class Failure(
val purchaseId: String?,
val invoiceId: String?,
val orderId: String?,
val quantity: Int?,
val productId: String?,
val sandbox: Boolean,
val errorCode: Int?,
) : PaymentResult

public object InvalidPaymentState : PaymentResult()
}
info

The sandbox parameter defines whether a payment is a test payment. Available values: true or false, where true means a test payment and false — an actual payment.

  • Success - successful purchase result.
  • Failure - there was a problem during sending payment request or receiving payment status, purchase status unknown.
  • Cancelled — payment request sent, although, the user closed the payment screen on their app, thus, the payment result is unknown.
  • InvalidPaymentState — SDK payments error. May occur due to an incorrect return deeplink.

Getting purchase information

Go get purchase information, use the getPurchaseInfo method.
val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.getPurchaseInfo("purchaseId")
.addOnSuccessListener { purchase: Purchase ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}

Purchase structure

data class Purchase(
val purchaseId: String?,
val productId: String,
val productType: ProductType?,
val invoiceId: String?,
val language: String?,
val purchaseTime: Date?,
val orderId: String?,
val amountLabel: String?,
val amount: Int?,
val currency: String?,
val quantity: Int?,
val purchaseState: PurchaseState?,
val developerPayload: String?,
val subscriptionToken: String?
)
  • purchaseId — product ID.

  • productId — product ID assigned to product in RuStore Console (mandatory).

  • invoiceId — invoice ID.

  • language — language specified with BCP 47 code.

  • purchaseTime — purchase time.

  • orderId — payment ID generated by the app (optional). If you specify this parameter in your system, you will receive it via our API. If not specified, will be generated automatically (uuid). 150 characters max.

  • amountLable — formatted purchase price, including currency symbol.

  • amount — price in minimum currency units.

  • currency — ISO 4217 currency code.

  • quantity — product amount (optional, value 1 will be used if not specified).

  • purchaseState — purchase state:

    • CREATED — purchase created;
    • INVOICE_CREATED — purchase invoice is created and awaiting payment;
    • PAID — only for consumable products — intermediate status, the funds on the user's account are put on hold. The purchase is awaiting confirmation from the developer;
    • CONFIRMED — payment for non-consumable product successful;
    • CONSUMED — payment for consumable product successful;
    • CANCELLED — purchase canceled — there was no payment or the payment was refunded (if a purchase is a subscription it doesn't change its state to CANCELLED);
    • PAUSED — for subscriptions — the purchase is in HOLD period;
    • TERMINATED — subscription terminated.
  • developerPayload — string with additional order information, that you can specify on purchase initialization.

  • subscriptionToken — purchase token for server validation .

State model (purchaseState)

CONSUMABLES - purchase state model

Purchase created, however, the invoice was not createdThere was no payment. This purchase can be ignored.The user has an unfinished purchase. Invoice was created but not paid. You need to inform the user.Purchase canceledFor consumable products. Purchase paid, but not consumed. Any App needs to send an immediate request for purchase confirmation.For consumable products. Consumption was confirmed.CREATEDINVOICE_CREATEDCANCELLEDInvoice createdInvoice paidConsumptionPAIDCONSUMEDInvoice canceledPayment returnPurchase canceled / was not consumed within 72 horsPurchase canceled

NON-CONSUMABLES - purchase state model

Purchase created, however, the invoice was not created. There was no payment. This purchase can be ignored.The user has an unfinished purchase. Invoice was created but not paid. You need to inform the user.Purchase canceledPayment confirmation status for non-consumable or subscription products. This status means that the product is purchased, further purchase attempts will lead errors.CREATEDINVOICE_CREATEDCANCELLEDInvoice creationPurchase confirmedCONFIRMEDInvoice canceledPayment returnPurchase cancellation

(SUBSCRIPTIONS) - purchase state model

Purchase created, however, the invoice was not created. There was no payment. This purchase can be ignored.Subscription is on HOLD. Another purchase attempt will lead to an error.The user has an unfinished purchase. Invoice was created but not paid. You need to inform the user.Purchase canceledSubscription is active or in GRACE period. Another purchase attempt will lead to an error.Subscription expired. This product can be purchased again.CREATEDINVOICE_CREATEDCANCELLEDPAUSEDInvoice creationPurchase confirmedSubscription closedCONFIRMEDTERMINATEDInvoice canceledRecurring subscription payment errorSubscription paidSubscription closedPurchase cancellation

Getting products list

This method returns purchases with the following statuses. For more informations on possible purchase states see Getting purchase info.

Type/StatusINVOICE_CREATEDCONFIRMEDPAIDPAUSED
CONSUMABLE++
NON-CONSUMABLE++
SUBSCRIPTION+++
note

This method returns incomplete purchases that require attention. Also, it shows confirmed subscriptions and consumable products that cannot be purchased more than once.

Go get the user's purchases list, use the getPurchases method.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.getPurchases()
.addOnSuccessListener { purchases: List<Purchase> ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}

Purchase structure

data class Purchase(
val purchaseId: String?,
val productId: String,
val productType: ProductType?,
val invoiceId: String?,
val language: String?,
val purchaseTime: Date?,
val orderId: String?,
val amountLabel: String?,
val amount: Int?,
val currency: String?,
val quantity: Int?,
val purchaseState: PurchaseState?,
val developerPayload: String?,
val subscriptionToken: String?
)
  • purchaseId — product ID.
  • productId — product ID assigned to product in RuStore Console (mandatory).
  • productType — product type: CONSUMABLE/NON-CONSUMABE/SUBSCRIPTION.
  • invoiceId — invoice ID.
  • language — language specified with BCP 47 code.
  • purchaseTime — purchase time.
  • orderId — payment ID generated by the app (optional). If you specify this parameter in your system, you will receive it via our API. If not specified, will be generated automatically (uuid). 150 characters max.
  • amountLable — formatted purchase price, including currency symbol.
  • amount — price in minimum currency units.
  • currency — ISO 4217 currency code.
  • quantity — product amount (optional, value 1 will be used if not specified).
  • purchaseState — purchase state.

Status model of the response to purchase requests

data class PurchasesResponse(
override val meta: RequestMeta?,
override val code: Int,
override val errorMessage: String?,
override val errorDescription: String?,
override val errors: List<DigitalShopGeneralError>?,
val purchases: List<Purchase>?,
) : ResponseWithCode
  • meta — additional information about request.
  • code — response code.
  • errorMessage — error message for the user.
  • errorDescription — detailed error message.
  • errors — errors list for requested products.
  • purchases — list of requested purchases.

Server validation

If you need to validate a successful app using API RuStore methods, you can use subscriptionToken in PurchaseResult returned by purchaseProduct on successful purchase.

SubscriptionToken consists of invoiceId and userId of the purchase separated by period: $invoiceId.$userId.

You can also get a subscriptionToken from the Purchase entity. The Purchase entity can be retrieved using the getPurchases method.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.purchaseProduct(productId).addOnSuccessListener { paymentResult ->
if (paymentResult is PaymentResult.Success) {
val subscriptionToken = paymentResult.subscriptionToken
yourApi.validate(subscriptionToken)
}
}

You can also get a subscriptionToken from the Purchase entity. The Purchase entity can be retrieved using the getPurchases method.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.getPurchases().addOnSuccessListener { purchases ->
purchases.forEach { purchase ->
yourApi.validate(purchase.subscriptionToken)
}
}

Consume (confirm) purchase

Products that require confirmation

Please, keep in mind the purchase type. Confirmation is only needed if your product is CONSUMABLE and can be purchased more than once.

To correctly deliver such products, confirm purchases with the confirmPurchase method. On delivering a product, use server validation. Deliver your product only after the payment (invoice) is in the CONFIRMED status. Use the addOnSuccessListener callback of the confirmPurchase method to deliver products.

Attention!

The PAID status is intermediate and meas that the user's money is put on hold on their card, so you need to confirm the purchase.

The exceptions are SBP or mobile payment — see the explanation below.

When paying for CONSUMABLE products with SBP or mobile payment, one-stage payment is used, although the payment model is as in two-stage payments. It means that what a payment is in the PAID status, when paying with SBP or mobile payment, the money is withdrawn from the buyer's account, as well as the developer's fee is withdrawn from theirs. In this case, if a purchase is canceled from the PAID status the refund is made, not reverse. The developer's fee will not be returned. At the same time, to complete the purchase you still have to confirm purhchase — see the table below.

Payment methodPayment typePayment in the PAID status
  • bank cards;
  • Sber ID;
  • SberPay;
  • T-Pay;
  • VK Pay.
Two-stage
  • Money is on hold on the buyer's account.
  • Developer's fee is not applied.
  • Payment cancellation (reverse) is available.
  • SBP;
  • mobile payment.
One-stage
  • Money is withdrawn from the buyer's account.
  • The developer's fee is applied.
  • If a purchase is canceled form the PAID status the transaction is refunded, not reverse reversed. The developer's fee will not be returned.

ConfirmPurchase request

Us the confirmPurchase method to confirm a purchase. Purchase confirmation request must be accompanied by the delivery of the product. After calling the confirmation method the purchase changes its state to CONSUMED.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.confirmPurchase(purchaseId = "purchaseId", developerPayload = null)
.addOnSuccessListener {
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}
  • purchaseId — product ID.
  • developerPayload — string with additional order information, that you can specify on purchase initialization.

Purchase cancellation

To cancel a purchase, use the deletePurchase method.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.deletePurchase(purchaseId = "purchaseId")
.addOnSuccessListener {
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}
  • purchaseId — product ID.
info

Use this method if your app logic is related to purchase cancellation. The purchase is canceled automatically after a 20-min timeout, or upon a second purchase from the same customer.

Error handling

On calling the RuStoreBillingClient.purchases.purchaseProduct() method, errors are handled automatically.

Use the resolveForBilling method to display the error dialog to the user (see below).

public fun RuStoreException.resolveForBilling(context: Context)

Handling pending payments

Handling of unfinished payments is done by the developer.

To confirm a purchase of CONSUMABLE product in the PAID state, call the [purchase confirmation method].(#confirm) (see Retrieve purchase details).

When dealing with purchase cancellation using payment processing methods take into account your business logic. Some developers implement additional checks before purchase confirmation or cancellation. In that case, make a separate purchase state request.

tip

For example, if the user paid for the product that you cannot deliver for some reason, call the payment cancellation method for the purchase in the PAID status to cancel the purchase.

If the get purchases list method returns a purchase in the INVOICE_CREATED state, you can us the purchase cancellation method. For instance, if you don't want to see a purchase in that state in the purchase list. This is optional as RuStore handles cancellation of such purchases on its side.

info

In some cases, after paying with a bank app (SBP, SberPay, T-Pay, etc.) and returning to your app the purchase status may still be INVOICE_CREATED while payment status shows that the purchase failed. This is caused by the purchase processing time by the bank. In this case, you need to adapt your screen lifecycle to the product list retrieval logic.

Alternatively, you can implement cancellation of purchases in the INVOICE_CREATED state only through user action in your app. For example, create a dedicated button for this purpose.

Event logging

If you need to log the billing library events, add the externalPaymentLoggerFactory and debugLogs parameters to the RuStoreBillingClientFactory.create() call. These parameters are optional.

val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
externalPaymentLoggerFactory = { tag -> PaymentLogger(tag) },
debugLogs = true
)

class PaymentLogger(private val tag: String) : ExternalPaymentLogger {
override fun d(e: Throwable?, message: () -> String) {
Log.d(tag, message.invoke(), e)
}

override fun e(e: Throwable?, message: () -> String) {
Log.e(tag, message.invoke(), e)
}

override fun i(e: Throwable?, message: () -> String) {
Log.i(tag, message.invoke(), e)
}

override fun v(e: Throwable?, message: () -> String) {
Log.v(tag, message.invoke(), e)
}

override fun w(e: Throwable?, message: () -> String) {
Log.w(tag, message.invoke(), e)
}
}

Below are the logging parameters.

  • externalPaymentLoggerFactory — interface that allows to create a logger that passes library log entries to the host app;
  • debugLogs — enable logging (logging will be automatically disabled for Release builds).

PaymentLogger — logging implementation example.

Changing interface theme

SDK supports dynamic theme changing via the BillingClientThemeProvider provider.

val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
themeProvider? = BillingClientThemeProviderImpl(),
)

class BillingClientThemeProviderImpl: BillingClientThemeProvider {

override fun provide(): BillingClientTheme {
// This is where the validation logic for the installed theme should be placed
val darkTheme = ....
if(darkTheme){
BillingClientTheme.Dark
} else {
BillingClientTheme.Light
}
}
}

Error handling

Possible errors

  • RuStoreNotInstalledException — RuStore is not installed on the user's device;
  • RuStoreOutdatedException — RuStore version installed on the user's device does not support this SDK;
  • RuStoreUserUnauthorizedException — user is not authorized in RuStore;
  • RuStoreRequestLimitReached — not enough time has passed since the process was last shown;
  • RuStoreReviewExists — this user has already rated your app;
  • RuStoreInvalidReviewInfo — problems with ReviewInfo;
  • RuStoreException — basic RuStore error from which other errors are inherited.
On calling the RuStoreBillingClient.purchases.purchaseProduct() method, errors are handled automatically.

Use the resolveForBilling method to display the error dialog to the user.

public fun RuStoreException.resolveForBilling(context: Context)

Error codes

Below is the list of possible errors that can be received errorCode field.

HTTP codeError codeDescription
40040001Incorrect request parameters: mandatory parameters are not filled in/incorrect parameters format.
40040003App not found.
40040004App status: inactive.
40040005Product not found.
40040006Product status: inactive.
40040007Invalid product type. Supported types: consumable, non-consumable, subscription.
40040008An order with this order_id already exists.
40040009This client already has an active order for this product in the invoice_created state. Prompt the client to either complete their payment or cancel the purchase.
40040010For consumable products. This customer has and order for this product in the paid state. First, the order needs to be consumed (confirmed) in the app, then, you can send another purchase request.
40040011For non-consumable products. This client already has an order of this product in the pre_confirmed/confirmed state. Such product has already been purchased. This product cannot be sold more than once.
40040012For subscription products. This client already has an order of this product in the pre_confirmed/confirmed state. Such product has already been purchased. This product cannot be sold more than once.
40040013For subscription products. The data was not received in the GET/products (serviceId, user_id) response.
40040014One or more mandatory attribute was not received in the request.
40040015Failed to change the order status during the purchase update (not allowed).
40040016quantity > 1 is specified for a non-consumable product purchase.
40040017Product deleted, new purchases not available.
40040018Cannot consume products with product type.
40140101Invalid token.
40140102Token lifetime has expired.
40340301Access to the requested resource is denied (unauthorized).
40340302This call is not authorized for the current token (method not allowed).
40340303The app ID in the request does not match the token.
40340305Incorrect token type.
40440401Not found.
40840801The notification timeout period specified in the request has expired.
50050***Payment service internal error.