5.0.0
RuStore allows you to integrate payments into your mobile app.
Implementation example
Get acquainted with the app example below to learn how to implement reviews and ratings SDK.
Prerequisites
- Kotlin
- Java
- The current version of RuStore is installed on the user's device.
- The user is authorized in RuStore.
- The user and the app should not be blocked in RuStore.
- In-app purchases should be enabled for the app in RuStore Console.
The service has some restrictions to work outside of Russia.
- The current version of RuStore is installed on the user's device.
- The user is authorized in RuStore.
- The user and the app should not be blocked in RuStore.
- In-app purchases should be enabled for the app in RuStore Console.
The service has some restrictions to work outside of Russia.
Getting started
- Kotlin
- Java
How to add a repository
Connect repository (see below)
repositories {
maven {
url = uri("https://artifactory-external.vkpartner.ru/artifactory/maven")
}
}
Connecting the dependency
Add the following code to your configuration file to inject the dependency:
dependencies {
implementation("ru.rustore.sdk:billingclient:5.0.0")
}
How to add a repository
Connect repository (see below)
repositories {
maven {
url "https://artifactory-external.vkpartner.ru/artifactory/maven"
}
}
Connecting the dependency
Add the following code to your configuration file to inject the dependency:
dependencies {
implementation 'ru.rustore.sdk:billingclient:5.0.0'
}
Deeplink processing
- Kotlin
- Java
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. Specify the intent-filter
in AndroidManifest.xml
with scheme
of your project (see below).
<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>
where yourappscheme
— your deeplink scheme, it can be changed to another one.
This scheme must match the deeplinkSheme value specified during the billing client library initialization.
Next, add the following code to the Activity
you need to return to after making the payment (your store page):
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 your app after deep linking, you need to add android:launchMode="singleTask"
to AndroidManifest.xml
.
<activity
android:name=".YourBillingActivity"
android:launchMode="singleTask"
android:exported="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
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. Specify the intent-filter
in AndroidManifest.xml
with scheme
of your project (see below).
<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>
where yourappscheme
— your deeplink scheme, it can be changed to another one.
This scheme must match the deeplinkSheme value specified during the billing client library initialization.
Next, add the following code to the Activity
you need to return to after making the payment (your store page):
public class YourBillingActivityextends AppCompatActivity {
// Previously created with RuStoreBillingClientFactory.create();
RuStoreBillingClient billingClient = YourDependencyInjection.getBillingClient();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
billingClient.onNewIntent(getIntent());
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
billingClient.onNewIntent(intent);
}
}
To restore your app after deep linking, you need to add android:launchMode="singleTask"
to 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.
- Kotlin
- Java
Craete RuStoreBillingClient
via RuStoreBillingClientFactory.create()
.
val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
// Optional parameters
themeProvider = null,
debugLogs = false,
externalPaymentLoggerFactory = null,
)
-
context
— Android context. Any context is allowed,applicationContext
is used in the release version. -
consoleApplicationId
— application code from RuStore Console (example:https://console.rustore.ru/apps/123456
).
The ApplicationId
specified in build.gradle
must match the applicationId
of the APK file you published to the RuStore Console.
-
deeplinkScheme
— deeplink scheme required to return to your app upon payment via a third-party application (for example, SberPay or SBP). SDK generates its host for this scheme.
yourappscheme
must match the one specified in AndroidManifest.xml
(see Deeplinks handling).-
themeProvider
— interface that providesBillingClientTheme
. There are 2 possible options ofBillingClientTheme
: light (Light
) and dark (Dark
).This interface is optional; the light team is set by default. -
externalPaymentLoggerFactory
— interface that provides access to an external event logging system. -
debugLogs
— flag that regulates logging (logs will be automatically disabled for Release builds).
The keystore
signature must be the same as the one used to sign the application published to the RuStore Console. Make sure that the buildType
(eg: debug
) uses the same signature as the published application (eg: release
).
Craete RuStoreBillingClient
via RuStoreBillingClientFactory.create()
.
final Context context = getContext();
final String consoleApplicationId = "111111";
final String deeplinkScheme = "yourappscheme";
// Опциональные параметры
final BillingClientThemeProvider themeProvider = null;
final boolean debugLogs = false;
final ExternalPaymentLoggerFactory externalPaymentLoggerFactory = null;
RuStoreBillingClient billingClient = RuStoreBillingClientFactory.INSTANCE.create(
context,
consoleApplicationId,
deeplinkScheme,
// Optional parameters
themeProvider,
debugLogs,
externalPaymentLoggerFactory
);
-
context
— Android context. Any context is allowed,applicationContext
is used in the release version. -
consoleApplicationId
— application code from RuStore Console (example:https://console.rustore.ru/apps/123456
).
The ApplicationId
specified in build.gradle
must match the applicationId
of the APK file you published to the RuStore Console.
-
deeplinkScheme
— deeplink scheme required to return to your app upon payment via a third-party application (for example, SberPay or SBP). SDK generates its host for this scheme.
yourappscheme
must match the one specified in AndroidManifest.xml
(see Deeplinks handling).-
themeProvider
— interface that providesBillingClientTheme
. There are 2 possible options ofBillingClientTheme
: light (Light
) and dark (Dark
).This interface is optional; the light team is set by default. -
externalPaymentLoggerFactory
— interface that provides access to an external event logging system. -
debugLogs
— flag that regulates logging (logs will be automatically disabled for Release builds).
The keystore
signature must be the same as the one used to sign the application published to the RuStore Console. Make sure that the buildType
(eg: debug
) uses the same signature as the published application (eg: release
).
How payments work
Checking purchases availability
- Kotlin
- Java
Please ensure compliance with the conditions below to check whether your app supports payment functions.
- The current version of RuStore is installed on the user's device.
- RuStore app supports payment functionality.
- The user is authorized in RuStore.
- The user and the app should not be blocked in RuStore.
- In-app purchases should be enabled for the app in RuStore Console.
checkPurchasesAvailability
method.
If all conditions are met, the method returns FeatureAvailabilityResult.Available
.
Otherwise, it returns FeatureAvailabilityResult.Unavailable(val cause: RuStoreException)
, where cause
indicates an unfulfilled condition.
All possible RuStoreException
errors are described in Error handling. Other errors are processed in onFailure
.
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.
Please ensure compliance with the conditions below to check whether your app supports payment functions.
- The current version of RuStore is installed on the user's device.
- RuStore app supports payment functionality.
- The user is authorized in RuStore.
- The user and the app should not be blocked in RuStore.
- In-app purchases should be enabled for the app in RuStore Console.
checkPurchasesAvailability
method.
If all conditions are met, the method returns FeatureAvailabilityResult.Available
.
Otherwise, it returns FeatureAvailabilityResult.Unavailable(val cause: RuStoreException)
, where cause
indicates an unfulfilled condition.
All possible RuStoreException
errors are described in Error handling. Other errors are processed in onFailure
.
RuStoreBillingClientExtKt.checkPurchasesAvailability(RuStoreBillingClient.Companion, getContext())
.addOnSuccessListener(result -> {
if (result instanceof FeatureAvailabilityResult.Available) {
// Hanlde purchases available
} else {
RuStoreException exception = ((FeatureAvailabilityResult.Unavailable) result).getCause();
// Hanlde purchases unavailable
}
})
.addOnFailureListener(error -> {
// Handle error
});
Working with SDK
Getting products list
- Kotlin
- Java
getProducts
method to get a list of products.
val productsUseCase: ProductsUseCase = billingClient.products
productsUseCase.getProducts(productIds = listOf("id1", "id2"))
.addOnSuccessListener { products: List<Product> ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}
productIds
— list of products IDs. Maximum length is 2083 symbols in a 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..productType
— product type..productStatus
— product status..priceLable
— formatted purchase price, including the currency symbol inlanguage
.price
— price in minor units (in kopecks)..currency
— ISO 4217 currency code.language
— language specified with the BCP 47 encoding..title
— product name inlanguage
.description
— product description inlanguage
.imageUrl
— link to an image..promoImageUrl
— promotional picture link..subscription
— subscription description, returned only for products withsubscription
.
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
— trial subscription period..gracePeriod
— grace period..introductoryPrice
— formatted introductory subscription price, including the currency symbol, inproduct:language
.introductoryPriceAmount
— introductory price in minor units of currency (in kopecks).introductoryPricePeriod
— calculated period of the introductory price..
Structure of the subscription period
data class SubscriptionPeriod(
val years: Int,
val months: Int,
val days: Int,
)
years
— number of years..months
— number of days..days
— number of days..
Use the getProducts
method to get a list of products.
ProductsUseCase productsUseCase = billingClient.getProducts();
productsUseCase.getProducts(Arrays.asList("id1", "id2"))
.addOnSuccessListener(products -> {
// Process success
}).addOnFailureListener(throwable ->
// Process error
);
productIds: List<String>
— list of products IDs.
Product structure
public Product(
String productId,
@Nullable
ProductType productType,
ProductStatus productStatus,
@Nullable
String priceLabel,
@Nullable
Integer price,
@Nullable
String currency,
@Nullable
String language,
@Nullable
String title,
@Nullable
String description,
@Nullable
Uri imageUrl,
@Nullable
Uri promoImageUrl,
@Nullable
ProductSubscription subscription
) {
this.productId = productId;
this.productType = productType;
this.productStatus = productStatus;
this.priceLabel = priceLabel;
this.price = price;
this.currency = currency;
this.language = language;
this.title = title;
this.description = description;
this.imageUrl = imageUrl;
this.promoImageUrl = promoImageUrl;
this.subscription = subscription;
}
productId
— product ID..productType
— product type..productStatus
— product status..priceLabel
— formatted purchase price, including the currency symbol inlanguage
.price
— price in minor units (in kopecks)..currency
— ISO 4217 currency code.language
— language specified with the BCP 47 encoding..title
— product name inlanguage
.description
— product description inlanguage
.imageUrl
— link to an image..promoimageurl
— promotional picture link..subscription
— subscription description, returned only for products withsubscription
.
Subscription structure
public ProductSubscription(
@Nullable
SubscriptionPeriod subscriptionPeriod,
@Nullable
SubscriptionPeriod freeTrialPeriod,
@Nullable
SubscriptionPeriod gracePeriod,
@Nullable
String introductoryPrice,
@Nullable
String introductoryPriceAmount,
@Nullable
SubscriptionPeriod introductoryPricePeriod
) {
this.subscriptionPeriod = subscriptionPeriod;
this.freeTrialPeriod = freeTrialPeriod;
this.gracePeriod = gracePeriod;
this.introductoryPrice = introductoryPrice;
this.introductoryPriceAmount = introductoryPriceAmount;
this.introductoryPricePeriod = introductoryPricePeriod;
}
subscriptionPeriod
— subscription period..freeTrialPeriod
— trial subscription period..gracePeriod
— grace period..introductoryPrice
— formatted introductory subscription price, including the currency symbol, inproduct:language
.introductoryPriceAmount
— introductory price in minor units of currency (in kopecks).introductoryPricePeriod
— calculated period of the introductory price..
Structure of the subscription period
public SubscriptionPeriod(
int years,
int months,
int days
) {
this.years = years;
this.months = months;
this.days = days;
}
years
— number of years..months
— number of days..days
— number of days..
Purchasing product
- Kotlin
- Java
Use thepurchaseProduct
method to call a product purchase.
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..orderId: String
— unique payment identifier generated by the application (uuid);.quantity: Int
— product quantity..developerPayload
— уline specified by the developer that contains additional information about the order..
Purchase result structure
public sealed interface PaymentResult {
public data class Success(
val orderId: String?,
val purchaseId: String,
val productId: String,
val invoiceId: String,
val subscriptionToken: String? = null,
) : PaymentResult
public data class Cancelled(
val purchaseId: String,
) : PaymentResult
public data class Failure(
val purchaseId: String?,
val invoiceId: String?,
val orderId: String?,
val quantity: Int?,
val productId: String?,
val errorCode: Int?,
) : PaymentResult
public object InvalidPaymentState : PaymentResult()
}
Success
- successful purchase result..Failure
- error occurred when sending a payment request or receiving a payment status, it is not possible to set the purchase status..Cancelled
— a purchase request has been sent, but the user has closed the "payment window" on their device, so the payment result is unknown..
.InvalidPaymentState
— Billing SDK error.. May occur in case of an incorrect reverse deeplink.
Use thepurchaseProduct
method to call a product purchase.
purchasesUseCase.purchaseProduct(productId, null, 1, developerPayload)
.addOnSuccessListener( result ->
// Process PaymentResult
)
.addOnFailureListener(throwable ->
// Process error
);
productId: String
— product ID..orderId: String
— unique payment identifier generated by the application (uuid);.quantity: Int
— product quantity..developerPayload
— уline specified by the developer that contains additional information about the order..
Purchase result structure
public interface PaymentResult {
class Success implements PaymentResult {
@Nullable
String orderId;
String purchaseId;
String productId;
String invoiceId;
@Nullable
String subscriptionToken;
public Success(@Nullable String orderId, String purchaseId, String productId, String invoiceId,
@Nullable String subscriptionToken) {
this.orderId = orderId;
this.purchaseId = purchaseId;
this.productId = productId;
this.invoiceId = invoiceId;
this.subscriptionToken = subscriptionToken;
}
}
class Failure implements PaymentResult {
@Nullable
String purchaseId;
@Nullable
String invoiceId;
@Nullable
String orderId;
@Nullable
Integer quantity;
@Nullable
String productId;
@Nullable
Integer errorCode;
public Failure(@Nullable String purchaseId, @Nullable String invoiceId, @Nullable String orderId,
@Nullable Integer quantity, @Nullable String productId, @Nullable Integer errorCode) {
this.purchaseId = purchaseId;
this.invoiceId = invoiceId;
this.orderId = orderId;
this.quantity = quantity;
this.productId = productId;
this.errorCode = errorCode;
}
}
class Cancelled implements PaymentResult {
String purchaseId;
public Cancelled(String purchaseId) {
this.purchaseId = purchaseId;
}
}
class InvalidPaymentState implements PaymentResult {}
}
Success
- successful purchase result..Failure
- error occurred when sending a payment request or receiving a payment status, it is not possible to set the purchase status..Cancelled
— a purchase request has been sent, but the user has closed the "payment window" on their device, so the payment result is unknown..
.InvalidPaymentState
— Billing SDK error.. May occur in case of an incorrect reverse deeplink.
Getting purchase info
- Kotlin
- Java
getPurchaseInfo
method to retrive purchase details.
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 description: 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
— purchase ID.. -
productId
— product ID.. -
description
— product description inlanguage
. -
invoiceId
— invoice ID.. -
language
— language specified with the BCP 47 encoding.. -
.purchaseTime
— time of purchase. -
orderId
— unique payment identifier generated by the application (uuid);. -
amountLable
— formatted purchase price, including the currency symbol inlanguage
. -
amount
— price in minor units of currency.. -
currency
— ISO 4217 currency code. -
quantity
— product quantity.. -
purchaseState
— purchase state.:
;CREATED
- purchase created.INVOICE_CREATED
— created, waiting for payment.;
;PAID
— consumable purchases only - intermediate status, funds reserved in buyer's account. Purchase pending confirmation from developer.CONFIRMED
— final status, purchase confirmed (for subscriptions and non-consumable items). Funds sent to the developer. Repeat item purchase is blocked by the store;
;CONSUMED
— for consumable items - final status, purchase consumption confirmed. You can re-purchase an item.CANCELLED
— purchase cancelled - payment has not been made or a refund has been made to the buyer (for subscriptions, once refunded, the purchase does not becomeCANCELLED
);
;PAUSED
- for subscriptions - subscription shifted to HOLD period.
.TERMINATED
— subscription closed.
-
developerPayload
— уline specified by the developer that contains additional information about the order.. -
subscriptionToken
— token for server validation..
Status model (purchaseState
)
A status-based consumables subscription (CONSUMABLES
)
![img](/help/en/assets/images/CONSUMABLES-v0-bf7aa17ffc46aac0531f9898aadeea55.png)
A status-based non-consumables subscription (NON-CONSUMABLES
):
![img](/help/en/assets/images/NON-CONSUMABLES-v0-a78cd9b635ef5c2d972c41265a1380df.png)
A status-based subscription purchase model (SUBSCRIPTIONS
)
![img](/help/en/assets/images/SUBSCRIPTIONS-v1-65f6f99a293b233891bbb285c1c18e08.png)
getPurchaseInfo
method to retrive purchase details.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.getPurchaseInfo("purchaseId")
.addOnSuccessListener(purchase -> {
// Process success
}).addOnFailureListener(throwable -> {
// Process error
});
Purchase Structure
public Purchase(
@Nullable
String purchaseId,
String productId,
@Nullable
ProductType productType,
@Nullable
String invoiceId,
@Nullable
String description,
@Nullable
String language,
@Nullable
Date purchaseTime,
@Nullable
String orderId,
@Nullable
String amountLabel,
@Nullable
Integer amount,
@Nullable
String currency,
@Nullable
Integer quantity,
@Nullable
PurchaseState purchaseState,
@Nullable
String developerPayload,
@Nullable
String subscriptionToken
) {
this.purchaseId = purchaseId;
this.productId = productId;
this.productType = productType;
this.invoiceId = invoiceId;
this.description = description;
this.language = language;
this.purchaseTime = purchaseTime;
this.orderId = orderId;
this.amountLabel = amountLabel;
this.amount = amount;
this.currency = currency;
this.quantity = quantity;
this.purchaseState = purchaseState;
this.developerPayload = developerPayload;
this.subscriptionToken = subscriptionToken;
}
-
purchaseId
— purchase ID.. -
productId
— product ID.. -
description
— product description inlanguage
. -
invoiceId
— invoice ID.. -
language
— language specified with the BCP 47 encoding.. -
.purchaseTime
— time of purchase. -
orderId
— unique payment identifier generated by the application (uuid);. -
amountLable
— formatted purchase price, including the currency symbol inlanguage
. -
amount
— price in minor units of currency.. -
currency
— ISO 4217 currency code. -
quantity
— product quantity.. -
purchaseState
— purchase state.:
;CREATED
- purchase created.INVOICE_CREATED
— created, waiting for payment.;
;PAID
— consumable purchases only - intermediate status, funds reserved in buyer's account. Purchase pending confirmation from developer.CONFIRMED
— final status, purchase confirmed (for subscriptions and non-consumable items). Funds sent to the developer. Repeat item purchase is blocked by the store;
;CONSUMED
— for consumable items - final status, purchase consumption confirmed. You can re-purchase an item.CANCELLED
— purchase cancelled - payment has not been made or a refund has been made to the buyer (for subscriptions, once refunded, the purchase does not becomeCANCELLED
);
.PAUSED
- for subscriptions - subscription shifted to HOLD period.
.TERMINATED
— subscription closed.
-
developerPayload
— уline specified by the developer that contains additional information about the order.. -
subscriptionToken
— token for server validation..
Status model (purchaseState
)
A status-based consumables subscription (CONSUMABLES
)
![img](/help/en/assets/images/CONSUMABLES-v0-bf7aa17ffc46aac0531f9898aadeea55.png)
A status-based non-consumables subscription (NON-CONSUMABLES
):
![img](/help/en/assets/images/NON-CONSUMABLES-v0-a78cd9b635ef5c2d972c41265a1380df.png)
A status-based subscription purchase model (SUBSCRIPTIONS
)
![img](/help/en/assets/images/SUBSCRIPTIONS-v1-65f6f99a293b233891bbb285c1c18e08.png)
Getting purchases list
- Kotlin
- Java
The method only returns purchases with statuses from the table below.
Type/Status | INVOICE_CREATED | CONFIRMED | PAID | PAUSED |
---|---|---|---|---|
consumable | + | + | ||
non-consumable | + | + | ||
subscription | + | + | + |
The method returns incomplete purchase and purchase consumable states that require processing. Apart from that, it shows confirmed purchases for subscriptions and non-consumable items - those that cannot be purchased again.
Use thegetPurchases
method to get the user's list of purchases.
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 description: 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
— purchase ID..productId
— product ID..productType
— product type..invoiceId
— invoice ID..description
— product description inlanguage
.language
— language specified with the BCP 47 encoding..
.purchaseTime
— time of purchase.orderId
— unique payment identifier generated by the application (uuid);.amountLable
— formatted purchase price, including the currency symbol inlanguage
.amount
— price in minor units of currency..currency
— ISO 4217 currency code.quantity
— product quantity..purchaseState
— purchase state..
Purchase request server response structure
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 request info..code
— error code..errorMessage
— error message for the user..errorDescription
— error message decoding..errors
— list of errors..purchases
— list of requested purchases..
The method only returns purchases with statuses from the table below.
Type/Status | INVOICE_CREATED | CONFIRMED | PAID | PAUSED |
---|---|---|---|---|
consumable | + | + | ||
non-consumable | + | + | ||
subscription | + | + | + |
The method returns incomplete purchase and purchase consumable states that require processing. Apart from that, it shows confirmed purchases for subscriptions and non-consumable items - those that cannot be purchased again.
Use thegetPurchases
method to get the user's list of purchases.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.getPurchases()
.addOnSuccessListener(purchases -> {
// Process PaymentResult
}).addOnFailureListener(throwable ->
// Process error
);
Purchase Structure
public Purchase(
@Nullable
String purchaseId,
String productId,
@Nullable
ProductType productType,
@Nullable
String invoiceId,
@Nullable
String description,
@Nullable
String language,
@Nullable
Date purchaseTime,
@Nullable
String orderId,
@Nullable
String amountLabel,
@Nullable
Integer amount,
@Nullable
String currency,
@Nullable
Integer quantity,
@Nullable
PurchaseState purchaseState,
@Nullable
String developerPayload,
@Nullable
String subscriptionToken
) {
this.purchaseId = purchaseId;
this.productId = productId;
this.productType = productType;
this.invoiceId = invoiceId;
this.description = description;
this.language = language;
this.purchaseTime = purchaseTime;
this.orderId = orderId;
this.amountLabel = amountLabel;
this.amount = amount;
this.currency = currency;
this.quantity = quantity;
this.purchaseState = purchaseState;
this.developerPayload = developerPayload;
this.subscriptionToken = subscriptionToken;
}
purchaseId
— purchase ID..productId
— product ID..productType
— product type..invoiceId
— invoice ID..description
— product description inlanguage
.language
— language specified with the BCP 47 encoding..
.purchaseTime
— time of purchase.orderId
— unique payment identifier generated by the application (uuid);.AmountLabel
— formatted purchase price, including the currency symbol inlanguage
.amount
— price in minor units of currency..currency
— ISO 4217 currency code.quantity
— product quantity..purchaseState
— purchase state..developerPayload
— уline specified by the developer that contains additional information about the order..subscriptionToken
— token for server validation..
Server validation
- Kotlin
- Java
For server purchase validation using API RuStore methods, you can use the subscriptionToken
, in the PurchaseResult
, returned by purchaseProduct
in case of a successful purchase.
SubscriptionToken
consists of invoiceId
of purchase and userId
, written with a dot: $invoiceId.$userId
.
You can also get a subscriptionToken
in the Purchase
entity.
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
in the Purchase
entity.
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)
}
For server purchase validation using API RuStore methods, you can use the subscriptionToken
, in the PurchaseResult
, returned by purchaseProduct
in case of a successful purchase.
SubscriptionToken
consists of invoiceId
of purchase and userId
, written with a dot: $invoiceId.$userId
.
You can also get a subscriptionToken
in the Purchase
entity.
Purchase
entity can be retrieved using the getPurchases()
method.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.purchaseProduct(productId).addOnSuccessListener(paymentResult -> {
if (paymentResult instanceof PaymentResult.Success) {
String subscriptionToken = ((PaymentResult.Success) paymentResult).getSubscriptionToken();
yourApi.validate(subscriptionToken);
}
});
You can also get a subscriptionToken
in the Purchase
entity.
Purchase
entity can be retrieved using the getPurchases()
method.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.getPurchases().addOnSuccessListener(purchases -> {
for (Purchase purchase : purchases) {
yourApi.validate(purchase.getSubscriptionToken());
}
});
Confirming purchase
Products that require confirmation
The RuStore application consists of the following types of products:
SUBSCRIPTION
— subscription (can be purchased for a period of time, such as a streaming service subscription)..NON_CONSUMABLE
— non-consumables (one-time purchases, such as disabling ads in an app).
.CONSUMABLE
— consumables (multiple-time purchases, such as crystals in the app);
Only CONSUMABLE
type products require confirmation if they are in PurchaseState.PAID
state.
Calling confirmation method
- Kotlin
- Java
Use theconfirmPurchase
method to call a product purchase. The release of the goods must be accompanied by a purchase confirmation request. Once the confirmation is called, the purchase will have a CONSUMED
status.
val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.confirmPurchase(purchaseId = "purchaseId", developerPayload = null)
.addOnSuccessListener {
// Process success
}.addOnFailureListener { throwable: Throwable ->
// Process error
}
purchaseId
— purchase ID..developerPayload
— уline specified by the developer that contains additional information about the order..
The RuStore application consists of the following types of products:
SUBSCRIPTION
— subscription (can be purchased for a period of time, such as a streaming service subscription)..NON_CONSUMABLE
— non-consumables (one-time purchases, such as disabling ads in an app).
.CONSUMABLE
— consumables (multiple-time purchases, such as crystals in the app);
Only CONSUMABLE
type products require confirmation if they are in PurchaseState.PAID
state.
Confirm Purchase request
Use theconfirmPurchase
method to call a product purchase. The release of the goods must be accompanied by a purchase confirmation request. Once the confirmation is called, the purchase will have a CONSUMED
status.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.confirmPurchase("purchaseId", "developerPayload")
.addOnSuccessListener(unit -> {
// Process success
}).addOnFailureListener(throwable -> {
// Process error
});
purchaseId
— purchase ID..developerPayload
— уline specified by the developer that contains additional information about the order..
Canceling purchase
- Kotlin
- Java
Use thedeletePurchase
method to cancel a purchase.
val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.deletePurchase(purchaseId = "purchaseId")
.addOnSuccessListener {
// Process success
}.addOnFailureListener { throwable: Throwable ->
// Process error
}
purchaseId
— purchase ID..
Note. 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
When calling the RuStoreBillingClient.purchases.purchaseProduct()
method, errors are handled automatically.
To display the error dialogue to the user, use the resolveForBilling
method (see below).
public fun RuStoreException.resolveForBilling(context: Context)
Use thedeletePurchase
method to cancel a purchase.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.deletePurchase("purchaseId")
.addOnSuccessListener(unit -> {
// Process success
})
}).addOnFailureListener(throwable -> {
// Process error
});
purchaseId
— purchase ID..
Note. 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.
BillingRuStoreExceptionExtKt.resolveForBilling(exception, getContext());
Processing unfinished payments
Uncompleted payments must be processed by the developer.
To confirm a CONSUMABLE
purchase type in PAID
status, you can call the confirm-purchase method (see Getting purchase info).
In the case of purchase cancellations, consider your internal process when using payment processing methods. As for some developers, it provides for pre-consumption checks or purchase cancellation. In this case, request the status of such purchase separately.
For example, if a user has paid for an item you can't supply to them for some reason, call the cancel purchase method on the PAID
status to cancel the purchase.
In cases where the receive shopping list method returns a purchase with INVOICE_CREATED
status, you can use the cancel purchase method. For example, if you don't want to see a purchase with these statuses in your shopping list. You don't have to do it yourself, as RuStore handles the cancellation of such purchases on its side.
In some cases, after paying through a banking app (SBP, SberPay, TinkoffPay, etc.), the purchase status may still return INVOICE_CREATED
, when you subsequently return to app. This is caused by the purchase processing time by the bank. Therefore, the developer needs to correctly link the shopping list obtaining function to the life cycle on the screen.
You acn also cancel a purchase in INVOICE_CREATED
status only through user interaction with the app. For example, create a separate button for this purpose.
Logging
- Kotlin
- Java
If you need to log payment library events, add the parameters externalPaymentLoggerFactory
and debugLogs
to RuStoreBillingClientFactory.create()
. 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)
}
}
Logging processing parameter:
externalPaymentLoggerFactory
— interface that allows you to create a logger that sends the library logs to the host application.;debugLogs
— enable logs (logs will be automatically disabled for Release builds).).
Where PaymentLogger
— example of payment event logging implementation.
If you need to log payment library events, add the parameters externalPaymentLoggerFactory
and debugLogs
to RuStoreBillingClientFactory.create()
. These parameters are optional.
final Context context = getContext();
final String consoleApplicationId = "111111";
final String deeplinkScheme = "yourappscheme";
final ExternalPaymentLoggerFactory externalPaymentLoggerFactory = (tag) -> new PaymentLogger(tag);
final boolean debugLogs = true;
RuStoreBillingClient billingClient = RuStoreBillingClientFactory.INSTANCE.create(
context,
consoleApplicationId,
deeplinkScheme,
externalPaymentLoggerFactory,
debugLogs
);
public class PaymentLogger implements ExternalPaymentLogger {
private final String tag;
public PaymentLogger(String tag) {
this.tag = tag;
}
@Override
public void d(@Nullable Throwable throwable, @NonNull Function0<String> function0) {
Log.d(tag, function0.invoke());
}
@Override
public void e(@Nullable Throwable throwable, @NonNull Function0<String> function0) {
Log.e(tag, function0.invoke());
}
@Override
public void i(@Nullable Throwable throwable, @NonNull Function0<String> function0) {
Log.i(tag, function0.invoke());
}
@Override
public void v(@Nullable Throwable throwable, @NonNull Function0<String> function0) {
Log.v(tag, function0.invoke());
}
@Override
public void w(@Nullable Throwable throwable, @NonNull Function0<String> function0) {
Log.w(tag, function0.invoke());
}
}
Logging processing parameter:
externalPaymentLoggerFactory
— interface that allows you to create a logger that sends the library logs to the host application.;debugLogs
— enable logs (logs will be automatically disabled for Release builds).).
Where PaymentLogger
— example of payment event logging implementation.
Changing UI theme
- Kotlin
- Java
The SDK supports dynamic theme changing via the BillingClientThemeProvider
provider interface:
val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
themeProvider? = BillingClientThemeProviderImpl(),
)
class BillingClientThemeProviderImpl: BillingClientThemeProvider {
override fun provide(): BillingClientTheme {
// // Logic for checking the installed theme should be placed here
val darkTheme = ....
if(darkTheme){
BillingClientTheme.Dark
} else {
BillingClientTheme.Light
}
}
}}
}
The SDK supports dynamic theme changing via the BillingClientThemeProvider
provider interface:
final Context context = getContext();
final String consoleApplicationId = "111111";
final String deeplinkScheme = "yourappscheme";
final BillingClientThemeProvider themeProvider = BillingClientThemeProviderImpl();
RuStoreBillingClient billingClient = RuStoreBillingClientFactory.INSTANCE.create(
context,
consoleApplicationId,
deeplinkScheme,
themeProvider
);
public class BillingClientThemeProviderImpl implements BillingClientThemeProvider {
@NonNull
@Override
public BillingClientTheme provide() {
// // Logic for checking the installed theme should be placed here
boolean darkTheme = ...;
if (darkTheme) {
return BillingClientTheme.Dark;
} else {
return BillingClientTheme.Light;
}
}
Errors processing
- Kotlin
- Java
Possible errors
RuStoreNotInstalledException
— RuStore not installed on user's device;RuStoreOutdatedException
— RuStore, installed on the user's device, does not support payment processing functions.;RuStoreUserUnauthorizedException
— user not authorized on RuStore;RuStoreRequestLimitReached
— not enough time has passed since the process was last shown.;RuStoreReviewExists
— this user has already rated your app.;RuStoreInvalidReviewInfo
— problems withReviewInfo
;RuStoreException
— basic RuStore error, from which all other errors are inherited..
RuStoreBillingClient.purchases.purchaseProduct()
method is called, errors are handled automatically.
To display the error dialogue to the user, use the resolveForBilling
method.
public fun RuStoreException.resolveForBilling(context: Context)
Possible errors
RuStoreNotInstalledException
— RuStore not installed on user's device;RuStoreOutdatedException
— RuStore, installed on the user's device, does not support payment processing functions.;RuStoreUserUnauthorizedException
— user not authorized on RuStore;RuStoreRequestLimitReached
— not enough time has passed since the process was last shown.;RuStoreReviewExists
— this user has already rated your app.;RuStoreInvalidReviewInfo
— problems withReviewInfo
;RuStoreException
— basic RuStore error, from which all other errors are inherited..
RuStoreBillingClient.purchases.purchaseProduct()
method is called, errors are handled automatically.
To display the error dialogue to the user, use the resolveForBilling
method.
BillingRuStoreExceptionExtKt.resolveForBilling(exception, getContext());
Error codes
The following is a description of possible errors in errorCode
.
HTTP code | Error code | Description |
---|---|---|
400 | 40001 | Incorrect request parameters: mandatory parameters are not filled in/incorrect parameters format |
400 | 40003 | No application found |
400 | 40004 | inactive application status |
400 | 40005 | Product not found |
400 | 40006 | inactive product status |
400 | 40007 | Invalid product type. Supported types: consumable , non-consumable , subscription . |
400 | 40008 | A purchase with this order_id already exists |
400 | 40009 | The current client has a purchase of this product with the status invoice_created . Your are required to offer the client to pay for/cancel the purchase |
400 | 40010 | For consumable product type. The current customer already has purchased this product with the status paid . First you need to confirm the purchase on the device, and then you can send the following purchase request for this product |
400 | 40011 | For non-consumable product type. The current client already has purchased this product with the status pre_confirmed /confirmed . Such product has already been purchased. This product cannot be sold more than once. |
400 | 40012 | Forsubscription product type. The current client already has purchased this product with the status pre_confirmed /confirmed . Such product has already been purchased. This product cannot be sold more than once. |
400 | 40013 | Forsubscription product type. When requesting the subscription service for a list of products GET/products (serviceId , user_id ) data were not received |
400 | 40014 | The required attribute(s) was not received in the request. |
400 | 40015 | Failed to change status when updating purchase (no transition allowed). |
400 | 40016 | When purchasing a subscription for a non-consumable product, the number quantity > 1 is specified |
400 | 40017 | Product removed, no new purchases available. |
400 | 40018 | You cannot consume type product . |
401 | 40101 | Invalid token. |
401 | 40102 | Token lifetime has expired. |
403 | 40301 | Access to the requested resource is denied (unauthorized). |
403 | 40302 | The current call is not authorized (method prohibited) for the token. |
403 | 40303 | The application ID in the request does not match the one specified in the token. |
403 | 40305 | Incorrect token type. |
404 | 40401 | Not found. |
408 | 40801 | The notification timeout period specified in the request has expired. |
500 | 50*** | Internal payment service error. |