6.0.0
With RuStore you can integrate payments in your mobile app.
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
- Kotlin
- Java
- App uploaded to RuStore Console.
- App passed moderation (you don't have to publish the app).
- 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.
The service has some restrictions to work outside of Russia.
- App uploaded to RuStore Console.
- App passed moderation (you don't have to publish the app).
- 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.
The service has some restrictions to work outside of Russia.
Getting started
- Kotlin
- Java
Adding repository
Add our repository as shown in the example below.
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.
- BOM
- Direct connection
Advantages of using a BOM file for configuration.
-
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
andru.rustore.sdk:pushclient
, you can use BOM to make sure that they are all compatible with each other.
-
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.
-
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.
dependencies {
implementation(platform("ru.rustore.sdk:bom:6.0.0"))
implementation("ru.rustore.sdk:billingclient")
}
dependencies {
implementation("ru.rustore.sdk:billingclient:6.0.0")
}
Adding repository
Add our repository as shown in the example below.
repositories {
maven {
url "https://artifactory-external.vkpartner.ru/artifactory/maven"
}
}
Connecting the dependency
Add the following code to your configuration file to add the dependency.
- BOM
- Direct connection
Advantages of using a BOM file for configuration.
-
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
andru.rustore.sdk:pushclient
, you can use BOM to make sure that they are all compatible with each other.
-
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.
-
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.
dependencies {
implementation platform("ru.rustore.sdk:bom:6.0.0")
implementation "ru.rustore.sdk:billingclient"
}
dependencies {
implementation "ru.rustore.sdk:billingclient:6.0.0"
}
Deeplink handling
- Kotlin
- Java
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.
<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
.
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
.
<activity
android:name=".YourBillingActivity"
android:launchMode="singleTask"
android:exported="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
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.
<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
.
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).
public class YourBillingActivity extends 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 the state of your app after returning from deeplink, 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
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.infoThere are several types of context in Android:
App context — a singleton instance available via
getApplicationContext()
.Activity
context is available inActivity
and is bound to its lifecycle.ContentProvider
context is available via thegetContext()
method and analogous to the application context.
To create
RuStoreBillingClient
, it is preferable to useapplicationContext
. -
consoleApplicationId
— идентификатор приложения из консоли RuStore.
Где в RuStore Консоль отображаются идентификаторы приложений?
- Navigate to the Applications tab and selected the needed app.
- 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 addresshttps://console.rustore.ru/apps/123456/versions
the app ID is123456
.
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.
yourappscheme
must match the scheme specified in AndroidManifest.xml
(see Deeplink processing).-
themeProvider
— interface that provides theBillingClientTheme
theme. There are 2 possible implementations of theBillingClientTheme
theme:Light
andDark
. Optional, by default the light theme is used.noteThe
Light
theme is used by default but you can configure theDark
theme as well. -
externalPaymentLoggerFactory
— interface that allows logging.tipEvent logging might be useful for versions that are not yet published for users. It can help you to track errors.
-
debugLogs
— флаг, регулирующий ведение журнала событий. Set it totrue
, if you want events to be logged. Otherwise, set it tofalse
.
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
).
Create RuStoreBillingClient
using RuStoreBillingClientFactory.create()
.
final Context context = getContext();
final String consoleApplicationId = "111111";
final String deeplinkScheme = "yourappscheme";
// Optional parameters
final BillingClientThemeProvider themeProvider = null;
final boolean debugLogs = false;
final ExternalPaymentLoggerFactory externalPaymentLoggerFactory = null;
final SuperAppTokenProvider superAppTokenProvider = null;
RuStoreBillingClient billingClient = RuStoreBillingClientFactory.INSTANCE.create(
context,
consoleApplicationId,
deeplinkScheme,
// Optional parameters
themeProvider,
superAppTokenProvider,
externalPaymentLoggerFactory
debugLogs,
);
-
context
— key element that provides information about current object state.infoThere are several types of context in Android:
App context — a singleton instance available via
getApplicationContext()
.Activity
context is available inActivity
and is bound to its lifecycle.ContentProvider
context is available via thegetContext()
method and analogous to the application context.
To create
RuStoreBillingClient
, it is preferable to useapplicationContext
. -
consoleApplicationId
— идентификатор приложения из консоли RuStore.
Где в RuStore Консоль отображаются идентификаторы приложений?
- Navigate to the Applications tab and selected the needed app.
- 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 addresshttps://console.rustore.ru/apps/123456/versions
the app ID is123456
.
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.
yourappscheme
must match the scheme specified in AndroidManifest.xml
(see Deeplink processing).-
themeProvider
— interface that provides theBillingClientTheme
theme. There are 2 possible implementations of theBillingClientTheme
theme:Light
andDark
. Optional, by default the light theme is used.noteThe
Light
theme is used by default but you can configure theDark
theme as well. -
externalPaymentLoggerFactory
— interface that allows logging.tipEvent logging might be useful for versions that are not yet published for users. It can help you to track errors.
-
debugLogs
— флаг, регулирующий ведение журнала событий. Set it totrue
, if you want events to be logged. Otherwise, set it tofalse
.
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
Payment flow example
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.
Payments availability check
- Kotlin
- Java
To check payments availability, use the checkPurchasesAvailability
method.
Purchases availability check depends on whether RuStore is installed on the user's device or not. Starting from version 6.0.0, is RuStore is not installed on the user's app, the method will return FeatureAvailabilityResult.Available
. This is possible because [payments can be accepted without RuStore installed].(/developers/monetization/without-rustore-app).
Depending on whether RuStore is installed on the user's device or not, the following conditions are checked:
RuStore not installed
If RuStore is not installed on the device, the method will return FeatureAvailabilityResult.Available
.
RuStore installed
- 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 the above conditions are met, the method will return FeatureAvailabilityResult.Available
.
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).
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.
To check payments availability, use the checkPurchasesAvailability
method.
Purchases availability check depends on whether RuStore is installed on the user's device or not. Starting from version 6.0.0, is RuStore is not installed on the user's app, the method will return FeatureAvailabilityResult.Available
. This is possible because [payments can be accepted without RuStore installed].(/developers/monetization/without-rustore-app).
Depending on whether RuStore is installed on the user's device or not, the following conditions are checked:
RuStore not installed
If RuStore is not installed on the device, the method will return FeatureAvailabilityResult.Available
.
RuStore installed
- 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 the above conditions are met, the method will return FeatureAvailabilityResult.Available
.
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).
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
});
In this SDK version the method will return payment availability message.
Working with SDK
Retrieving products list
- Kotlin
- Java
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.
- Open RuStore Console.
- Navigate to the Applications tab.
- Select the necessary app.
- In the left side menu select Monetization.
- Select product type: Subscriptions or In-App purchases.
- Copy the IDs of the required products. These are product
id
s.
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 inlanguage
.price
— price in minimum currency units.currency
— ISO 4217 currency code.language
— language specified with BCP 47 code.title
— product name inlanguage
.description
— descriptions inlanguage
.imageUrl
— image URL.promoImageUrl
— promo image URL.subscription
— subscription description, returns only forsubscription
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 theproduct: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.
getProducts
method to request the information about products added to your app in RuStore Console.
ProductsUseCase productsUseCase = billingClient.getProducts();
productsUseCase.getProducts(Arrays.asList("id1", "id2"))
.addOnSuccessListener(products ->
// Process success
)
.addOnFailureListener(throwable ->
// Process error
);
productIds: List<String>
— the list of product IDs. Must not exceed 100 entries.
To specify id
of the products needed for the method, do the following.
- Open RuStore Console.
- Navigate to the Applications tab.
- Select the necessary app.
- In the left side menu select Monetization.
- Select product type: Subscriptions or In-App purchases.
- Copy the IDs of the required products. These are product
id
s.
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 assigned to product in RuStore Console (mandatory).productType
— product type:CONSUMABLE
/NON-CONSUMABE
/SUBSCRIPTION
.productStatus
— product status.priceLabel
— formatted product price, including currency symbol inlanguage
.price
— price in minimum currency units.currency
— ISO 4217 currency code.language
— language specified with BCP 47 code.title
— product name inlanguage
.description
— descriptions inlanguage
.imageUrl
— image URL.promoimageurl
— promo image URL.subscription
— subscription description, returns only forsubscription
products.
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
— subscription trial period.gracePeriod
— subscription grace period.introductoryPrice
— formated introductory price with the currency symbol in theproduct:language
language.introductoryPriceAmount
— introductory price in minimum currency units.introductoryPricePeriod
— introductory price invoice period.
Structure of the subscription period
public SubscriptionPeriod(
int years,
int months,
int days
) {
this.years = years;
this.months = months;
this.days = days;
}
years
— amount of years.months
— amount of months.days
— amount of days.
Purchasing product
- Kotlin
- Java
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. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.quantity: Int
— количество продукта (необязательный параметр — если не указывать, будет подставлено значение1
).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()
}
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.
To purchase product, use the purchaseProduct
method.
purchasesUseCase.purchaseProduct(productId, null, 1, developerPayload)
.addOnSuccessListener(result ->
// Process PaymentResult
)
.addOnFailureListener(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. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.quantity: Int
— количество продукта (необязательный параметр — если не указывать, будет подставлено значение1
).developerPayload
— string with additional order information, that you can specify on purchase initialization.
Purchase result structure
public interface PaymentResult {
class Success implements PaymentResult {
@Nullable
String orderId;
String purchaseId;
String productId;
String invoiceId;
boolean sandbox;
@Nullable
String subscriptionToken;
public Success(@Nullable String orderId, String purchaseId, String productId, String invoiceId,
boolean sandbox, @Nullable String subscriptionToken) {
this.orderId = orderId;
this.purchaseId = purchaseId;
this.productId = productId;
this.invoiceId = invoiceId;
this.sandbox = sandbox;
this.subscriptionToken = subscriptionToken;
}
}
class Failure implements PaymentResult {
@Nullable
String purchaseId;
@Nullable
String invoiceId;
@Nullable
String orderId;
@Nullable
Integer quantity;
@Nullable
String productId;
boolean sandbox;
@Nullable
Integer errorCode;
public Failure(@Nullable String purchaseId, @Nullable String invoiceId, @Nullable String orderId,
@Nullable Integer quantity, @Nullable String productId, boolean sandbox, @Nullable Integer errorCode) {
this.purchaseId = purchaseId;
this.invoiceId = invoiceId;
this.orderId = orderId;
this.quantity = quantity;
this.productId = productId;
this.sandbox = sandbox;
this.errorCode = errorCode;
}
}
class Cancelled implements PaymentResult {
String purchaseId;
boolean sandbox;
public Cancelled(String purchaseId, boolean sandbox) {
this.purchaseId = purchaseId;
this.sandbox = sandbox;
}
}
class InvalidPaymentState implements PaymentResult {}
}
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
- Kotlin
- Java
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. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов. -
amountLable
— formatted purchase price, including currency symbol. -
amount
— price in minimum currency units. -
currency
— ISO 4217 currency code. -
quantity
— количество продукта (необязательный параметр — если не указывать, будет подставлено значение1
). -
purchaseState
— purchase state:CREATED
— purchase created;INVOICE_CREATED
— purchase invoiced 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 toCANCELLED
);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
NON-CONSUMABLES
- purchase state model
(SUBSCRIPTIONS
) - purchase state model
getPurchaseInfo
method.
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 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.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
— 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. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов. -
amountLable
— formatted purchase price, including currency symbol. -
amount
— price in minimum currency units. -
currency
— ISO 4217 currency code. -
quantity
— количество продукта (необязательный параметр — если не указывать, будет подставлено значение1
). -
purchaseState
— purchase state:CREATED
— purchase created;INVOICE_CREATED
— purchase invoiced 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 toCANCELLED
);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
NON-CONSUMABLES
- purchase state model
(SUBSCRIPTIONS
) - purchase state model
Getting products list
- Kotlin
- Java
This method returns purchases with the following statuses. For more informations on possible purchase states see Getting purchase info.
Type/Status | INVOICE_CREATED | CONFIRMED | PAID | PAUSED |
---|---|---|---|---|
CONSUMABLE | + | + | ||
NON-CONSUMABLE | + | + | ||
SUBSCRIPTION | + | + | + |
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. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.amountLable
— formatted purchase price, including currency symbol.amount
— price in minimum currency units.currency
— ISO 4217 currency code.quantity
— количество продукта (необязательный параметр — если не указывать, будет подставлено значение1
).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.
This method returns purchases with the following statuses. For more informations on possible purchase states see Getting purchase info.
Type/Status | INVOICE_CREATED | CONFIRMED | PAID | PAUSED |
---|---|---|---|---|
CONSUMABLE | + | + | ||
NON-CONSUMABLE | + | + | ||
SUBSCRIPTION | + | + | + |
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.
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 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.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
— 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. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.AmountLabel
— formatted purchase price, including currency symbol.amount
— price in minimum currency units.currency
— ISO 4217 currency code.quantity
— количество продукта (необязательный параметр — если не указывать, будет подставлено значение1
).purchaseState
— purchase state.developerPayload
— string with additional order information, that you can specify on purchase initialization.subscriptionToken
— purchase token for server validation .
Server validation
- Kotlin
- Java
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)
}
}
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.
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
from the Purchase
entity. The 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());
}
});
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 CONFIRMED
status.
Use the addOnSuccessListener
callback of the confirmPurchase
method to deliver products.
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 their's. 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 method | Payment type | Payment in the PAID status |
---|---|---|
| Two-stage |
|
| One-stage |
|
ConfirmPurchase request
- Kotlin
- Java
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.
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
.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.confirmPurchase("purchaseId", "developerPayload")
.addOnSuccessListener(unit ->
// Process success
)
.addOnFailureListener(throwable ->
// Process error
);
purchaseId
— product ID.developerPayload
— string with additional order information, that you can specify on purchase initialization.
Purchase cancellation
- Kotlin
- Java
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.
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)
To cancel a purchase, use the deletePurchase
method.
PurchasesUseCase purchasesUseCase = billingClient.getPurchases();
purchasesUseCase.deletePurchase("purchaseId")
.addOnSuccessListener(unit ->
// Process success
)
.addOnFailureListener(throwable ->
// Process error
);
purchaseId
— product ID.
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).
BillingRuStoreExceptionExtKt.resolveForBilling(exception, getContext());
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.
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.
Sometimes, after paying with a bank app (SBP, SberPay, T-Pay etc.) and returning to the app, the status is still INVOICE_CREATED
, and the payment status is unsuccessful. 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
- Kotlin
- Java
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.
If you need to log the billing library events, add the externalPaymentLoggerFactory
and debugLogs
parameters to the RuStoreBillingClientFactory.create()
call. 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());
}
}
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
- Kotlin
- Java
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
}
}
}
SDK supports dynamic theme changing via the BillingClientThemeProvider
provider.
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() {
// This is where the validation logic for the installed theme should be placed
boolean darkTheme = ...;
if (darkTheme) {
return BillingClientTheme.Dark;
} else {
return BillingClientTheme.Light;
}
}
}
Error handling
- Kotlin
- Java
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 withReviewInfo
;RuStoreException
— basic RuStore error from which other errors are inherited.
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)
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 withReviewInfo
;RuStoreException
— basic RuStore error from which other errors are inherited.
RuStoreBillingClient.purchases.purchaseProduct()
method, errors are handled automatically.
Use the resolveForBilling
method to display the error dialog to the user.
BillingRuStoreExceptionExtKt.resolveForBilling(exception, getContext());
Error codes
Below is the list of possible errors that can be received errorCode
field.
HTTP code | Error code | Description |
---|---|---|
400 | 40001 | Incorrect request parameters: mandatory parameters are not filled in/incorrect parameters format. |
400 | 40003 | App not found. |
400 | 40004 | App status: inactive . |
400 | 40005 | Product not found. |
400 | 40006 | Product status: inactive . |
400 | 40007 | Invalid product type. Supported types: consumable , non-consumable , subscription . |
400 | 40008 | An order with this order_id already exists. |
400 | 40009 | This 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. |
400 | 40010 | For 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. |
400 | 40011 | For 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. |
400 | 40012 | For 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. |
400 | 40013 | For subscription products. The data was not received in the GET/products (serviceId , user_id ) response. |
400 | 40014 | One or more mandatory attribute was not received in the request. |
400 | 40015 | Failed to change the order status during the purchase update (not allowed). |
400 | 40016 | quantity > 1 is specified for a non-consumable product purchase. |
400 | 40017 | Product deleted, new purchases not available. |
400 | 40018 | Cannot consume products with product type . |
401 | 40101 | Invalid token. |
401 | 40102 | Token lifetime has expired. |
403 | 40301 | Access to the requested resource is denied (unauthorized). |
403 | 40302 | This call is not authorized for the current token (method not allowed). |
403 | 40303 | The app ID in the request does not match 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*** | Payment service internal error. |