Skip to main content

5.0.0

RuStore allows you to integrate payments into your mobile app.

Implementation example

Please have a thorough look at the application example to learn how to integrate payments correctly.

Prerequisites

  • 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.
caution

The service has some restrictions to work outside of Russia.

Getting started

To get started, you need to download RuStore Billing SDK and import it into your project (Assets > Import Package > Custom Package). Dependencies are included automatically using External Dependency Manager (included in the SDK).

tip

If you are using macOS, change the settings of the archive utility. In the Archive Utility settings, uncheck Keep expanding if possible. Otherwise the project archive will not be downloaded correctly.

Apart from that, you can clone the code using Git.

To correctly handle SDK dependencies, you must set the following settings:

  • Edit > Project Settings > Player Settings > Publishing Settings, enable Custom Main Gradle Template and Custom Gradle Properties Template.
  • Assets > External Dependencies Manager > Android Resolver > Settings, enable Use Jetifier, Patch mainTemplate.gradle, Patch gradleTemplate.properties.

After setting up, be sure complete Assets > External Dependencies Manager > Android Resolver > Force Resolve.

Minimum API level must be set to at least 24. Application minification (ProGuard/R8) is not currently supported; it must be disabled in the project settings (File > Build Settings > Player Settings > Publishing Settings > Minify).

Initialization

Initialize the library before calling its methods.

In the editor menu select Window > RuStoreSDK > Settings > Billing Client..

RuStoreBillingClient.Instance.Init();

Once you’re required other settings, you can pass them directly from the code:

var config =  new RuStoreBillingClientConfig() {
consoleApplicationId = "11111" ,
deeplinkPrefix = "yourappscheme" ,
allowNativeErrorHandling = true,
enableLogs = true
};
RuStoreBillingClient.Instance.Init(config);
  • consoleApplicationId — application code from RuStore Console (example: https://console.rustore.ru/apps/123456).
  • deeplinkPrefix — an url used for deeplink. Make sure your use a unique name, for exampl: yourappscheme).

  • allowNativeErrorHandling — allow error handling (see in Error handling).
  • enableLogs — enable event logging.
note
The deeplink scheme passed in deeplinkPrefix must match the one specified in AndroidManifest.xml (see Deeplinks handling).

If you need to check that the library is initialized, use RuStoreBillingClient.Instance.isInitialized which returns true, if the library is initialized and false if the Init function has not been called yet.

var isInitialized = RuStoreBillingClient.Instance.IsIninialized;

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 schemeof your project (see below).

AndroidManifest.xml
<activity
android:name="ru.rustore.unitysdk.RuStoreUnityActivity" android:theme ="@style/UnityThemeSelector" android:exported ="true">
<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.

Next, extend the UnityPlayerActivity class and add incoming intent processing to onNewIntent.

package ru.rustore.unitysdk;import android.os.Bundle;import android.content.Intent;import ru.rustore.unitysdk.billingclient.RuStoreUnityBillingClient;import com.unity3d.player.UnityPlayerActivity;public class RuStoreUnityActivity extends UnityPlayerActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
if (savedInstanceState == null ) {
RuStoreUnityBillingClient.onNewIntent(getIntent());
}
}
@Override protected void onNewIntent(Intent intent) {
super .onNewIntent(intent);
RuStoreUnityBillingClient.onNewIntent(intent);
}}

The Java file with UnityPlayerActivity extension code must be placed in the Assets folder of the project. If you already have your own UnityPlayerActivity extension, you need to transfer the code of the onCreate and onNewIntent functions to it.

How payments work

Checking purchases availability

To check whether your app supports payment functions, call the CheckPurchasesAvailabilitymethod. If all conditions are met, the method returns FeatureAvailabilityResult.isAvailable == true. Otherwise, it returns FeatureAvailabilityResult.isAvailable == false, where FeatureAvailabilityResult.cause indicates an unfulfilled condition.

All possible RuStoreExceptionerrors are described in Error handling. Other errors are processed in onFailure.

RuStoreBillingClient.Instance.CheckPurchasesAvailability(
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
if (response.isAvailable) {
// Process purchases available
} else {
// Process purchases unavailable
}
});

Working with SDK

Getting products list

Use the GetProducts method to get a list of products.

RuStoreBillingClient.Instance.GetProducts(productIds,
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
// Process response
});

string[] productIds — list of products IDs.

This method returns List<Product>.

Product structure

public class Product {
public enum ProductStatus {
ACTIVE,
INACTIVE
}
public enum ProductType {
NON_CONSUMABLE,
CONSUMABLE,
SUBSCRIPTION
}
public string productId;
public ProductType productType;
public ProductStatus productStatus;
public string priceLabel;
public int price;
public string currency;
public string language;
public string title;
public string description;
public string imageUrl;
public string promoImageUrl;
public ProductSubscription subscription;
}
  • productId — product ID..
  • productType — product type..
  • productStatus — product status..
  • priceLable — formatted purchase price, including the currency symbol in language.
  • price — price in minor units (in kopecks)..
  • currency — ISO 4217 currency code.
  • language — language specified with the BCP 47 encoding..
  • title — product name in language.
  • description — product description in language.
  • imageUrl — link to an image..
  • promoImageUrl — promotional picture link..
  • subscription — subscription description, returned only for products with subscription.

Subscription structure

public class ProductSubscription {
public SubscriptionPeriod subscriptionPeriod;
public SubscriptionPeriod freeTrialPeriod;
public SubscriptionPeriod gracePeriod;
public string introductoryPrice;
public string introductoryPriceAmount;
public SubscriptionPeriod introductoryPricePeriod;
}
  • subscriptionPeriod — subscription period..
  • freeTrialPeriod — trial subscription period..
  • gracePeriod — grace period..
  • introductoryPrice — formatted introductory subscription price, including the currency symbol, in product:language.
  • introductoryPriceAmount — introductory price in minor units of currency (in kopecks).
  • introductoryPricePeriod — calculated period of the introductory price..

Structure of the subscription period

public class SubscriptionPeriod {
public int years;
public int months;
public int days;
}
  • years — number of years..
  • months — number of days..
  • days — number of days..

Getting purchases list

The method only returns purchases with statuses from the table below.

Type/StatusINVOICE_CREATEDCONFIRMEDPAIDPAUSED
consumable++
non-consumable++
subscription+++
note

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 theGetPurchasesmethod to get the user's list of purchases.

RuStoreBillingClient.Instance.GetPurchases(
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
// Process response
});

This method returns List<Purchase>response— list of products.

Purchase Structure

public class Purchase {
public enum PurchaseState
{
CREATED,
INVOICE_CREATED,
CONFIRMED,
PAID,
CANCELLED,
CONSUMED,
CLOSED
}
public string purchaseId;
public string productId;
public Product.ProductType productType;
public string description;
public string invoiceId;
public string language;
public DateTime purchaseTime;
public string orderId;
public string amountLabel;
public int amount;
public string currency;
public int quantity;
public PurchaseState purchaseState;
public string developerPayload;
public string subscriptionToken;
}
  • purchaseId — purchase ID..
  • productId — product ID..
  • description — product description in language.
  • 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 in language.
  • 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..

Getting purchase info

Use the getPurchaseInfo method to retrive purchase details.
RuStoreBillingClient.Instance.GetPurchaseInfo(
purchaseId: "purchaseId",
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
// Process response
}
);

This method returns Purchase response — purchase.

Purchase Structure

public class Purchase {
public enum PurchaseState
{
CREATED,
INVOICE_CREATED,
CONFIRMED,
PAID,
CANCELLED,
CONSUMED,
CLOSED
}
public string purchaseId;
public string productId;
public Product.ProductType productType;
public string description;
public string invoiceId;
public string language;
public DateTime purchaseTime;
public string orderId;
public string amountLabel;
public int amount;
public string currency;
public int quantity;
public PurchaseState purchaseState;
public string developerPayload;
public string subscriptionToken;
}
  • purchaseId — purchase ID..
  • productId — product ID..
  • description — product description in language.
  • 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 in language.
  • 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

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.

RuStoreBillingClient.Instance.PurchaseProduct(
productId: productId,
developerPayload: "test payload",
onFailure: (error) => {
// process error
},
onSuccess: (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.

RuStoreBillingClient.Instance.GetPurchases(
onFailure: (error) => {
// process error
},
onSuccess: (purchaseList) => {
foreach (var purchase in purchaseList) {
var subscriptionToken = purchase.subscriptionToken
yourApi.validate(subscriptionToken)
}
});

Purchasing product

Use thePurchaseProductmethod to call a product purchase.

RuStoreBillingClient.Instance.PurchaseProduct(
productId: productId,
quantity: 1,
developerPayload: "your payload",
onFailure: (error) => {
// process error
},
onSuccess: (result) => {
// process result
});
  • 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 class PaymentResult {
}

public class PaymentSuccess : PaymentResult {

public string orderId;
public string purchaseId;
public string productId;
public string invoiceId;
public string subscriptionToken;
}

public class PaymentCancelled : PaymentResult {

public string purchaseId;
}

public class PaymentFailure : PaymentResult {

public string purchaseId;
public string invoiceId;
public string orderId;
public int quantity;
public string productId;
public int errorCode;
}

public class InvalidPaymentState : PaymentResult {
}
  • PaymentSuccess - successful purchase result..
  • PaymentFailure - error occurred when sending a payment request or receiving a payment status, it is not possible to set the purchase status..
  • PaymentCancelled — 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.

    .

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

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.

RuStoreBillingClient.Instance.ConfirmPurchase(
purchaseId: "purchaseId" ,
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);
  • purchaseId — purchase ID..

Canceling purchase

Use theDeletePurchase method to cancel a purchase.

RuStoreBillingClient.Instance.DeletePurchase(
purchaseId: "purchaseId" ,
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);
  • purchaseId — purchase ID..
info

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.

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.

tip

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.

info

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.

Changing UI theme

The SDK supports dynamic theme changing via the BillingClientThemeProvider provider interface:

To retrieve the information about the set theme, use the GetTheme method.

RuStoreBillingClient.Instance.GetTheme()

You can change the current theme via SetTheme(BillingClientTheme theme) method.

RuStoreBillingClient.Instance.SetTheme(BillingClientTheme.Dark)

BillingClientTheme.

public enum BillingClientTheme {
Dark,
Light,
}

Errors processing

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 with ReviewInfo;
  • RuStoreException — basic RuStore error, from which all other errors are inherited..

All the errors that may occur are processed by onFailure.

Error structure

public class RuStoreError {
public string name;
public string description;
}
  • name – error name..
  • description – error description..

Auto error handling

When the PurchaseProduct method is called, errors are handled automatically.

If the allowNativeErrorHandling == true parameter was passed during SDK initialisation, an error dialog will be displayed to the user when an error occurs, apart from calling the appropriate Failure handler.

public fun RuStoreException.resolveForBilling(context: Context)

You can change this behavior after the initialization by setting AllowNativeErrorHandling property:

RuStoreBillingClient.Instance.AllowNativeErrorHandling = false ;