6.1.1
With RuStore you can integrate payments in your mobile app.
If you are in doubt read the instruction in the usage scenarios.
Implementation example
Look at the example app to learn how to integrate our SDK.
Prerequisites
- 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.
Connect to project
- Installation with .unitypackage
- Installation with Package Manager
To connect, download RuStoreUnityBillingSDK-version.unitypackage
file and import it to the project (Assets > Import Package > Custom Package). The dependencies are connected automatically with External Dependency Manager (included in .unitypackage).
If you use macOS, change Archive Utility settings. Uncheck Keep expanding if possible. Otherwise, the project archive will not be downloaded correctly.
You can also clone the code using Git.
For proper SDK dependencies processing set the following settings.
-
Opt project settings: Edit > Project Settings > Player > Android Settings.
-
In the Publishing Settings section enable to following settings.
- Custom Main Manifest.
- Custom Main Gradle Template.
- Custom Gradle Properties Template.
-
In the Other Settings section configure:
- package name.
- Minimum API Level = 24.
- Target API Level = 34.
-
Open the External Dependency Manager settings: Assets > External Dependency Manager > Android Resolver > Settings and enable the following settings.
- Use Jetifier.
- Patch mainTemplate.gradle.
- Patch gradleTemplate.properties.
-
Update project dependencies: Assets > External Dependency Manager > Android Resolver > Force Resolve.
Application minification (ProGuard/R8) is not currently supported; it must be disabled in the project settings (File > Build Settings > Player Settings > Publishing Settings > Minify).
Update
Plugin version 6.1.0 and above contain modified directory structure. The modified structure allows to use the advantages of separate builds for project parts Assembly definitions.
Before installation, delete the following folders.
- Assets > RuStoreSDK > BillingClient > Editor.
- Assets > RuStoreSDK > Common > Editor.
After deletion, import new .unitypackage
in your project as during a usual installation (Assets > Import Package > Custom Package).
Download:
and import into project using Package Manager (Window > Package Manager > + > Add package from tarball...).
The dependencies are connected automatically with External Dependency Manager:
- Open package manager window (Window > Package Manager > + > Add package from git URL...).
- Use the https://github.com/googlesamples/unity-jar-resolver.git?path=/upm link to connect the package.
- To cure the
Google.IOSResolver.dll will not be loaded
error, install the iOS build module for your version of Unity (UnityHub > Installs > Your version of Unity > Add modules > iOS Build Support).
Assembly 'Packages/com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.182/Google.IOSResolver.dll' will not be loaded due to errors:
Unable to resolve reference 'UnityEditor.iOS.Extensions.Xcode'. Is the assembly missing or incompatible with the current platform?
Reference validation can be disabled in the Plugin Inspector.
If you use macOS, change Archive Utility settings. Uncheck Keep expanding if possible. Otherwise, the project archive will not be downloaded correctly.
For proper SDK dependencies processing set the following settings.
-
Opt project settings: Edit > Project Settings > Player > Android Settings.
-
In the Publishing Settings section enable to following settings.
- Custom Main Manifest.
- Custom Main Gradle Template.
- Custom Gradle Properties Template.
-
In the Other Settings section configure:
- package name.
- Minimum API Level = 24.
- Target API Level = 34.
-
Open the External Dependency Manager settings: Assets > External Dependency Manager > Android Resolver > Settings and enable the following settings.
- Use Jetifier.
- Patch mainTemplate.gradle.
- Patch gradleTemplate.properties.
-
Update project dependencies: Assets > External Dependency Manager > Android Resolver > Force Resolve.
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 = "123456" ,
deeplinkPrefix = "yourappscheme" ,
allowNativeErrorHandling = true,
enableLogs = true
};
RuStoreBillingClient.Instance.Init(config);
-
consoleApplicationId
— product ID form the RuStore Console.
Where are app IDs in the RuStore Console?
- 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
.
-
deeplinkPrefix
— deeplink URL address. You can use any unique name (for example:yourappscheme
). -
allowNativeErrorHandling
— allow handling errors using native SDK (see more in Error handling). -
enableLogs
— enable logging.
deeplinkPrefix
must match the scheme specified in AndroidManifest.xml
(see Deeplink processing).If you need to check that the library is initialized, use RuStoreBillingClient.isInitialized
, which returns true
if the library is initialized and false
if the init
function has not been called yet.
var isInitialized = RuStoreBillingClient.Instance.IsInitialized;
Deeplink handling
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="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>
Replace yourappscheme
in the example above with the name of your scheme. For example, ru.package.name.rustore.scheme
.
Then, extend the UnityPlayerActivity
class and add incoming intent
processing in 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);
}
}
Put the Java file with the UnityPlayerActivity
extension code in the project folder Assets
. If you already have your own UnityPlayerActivity
extension, include the code of onCreate
and onNewIntent
functions into it.
Before you start
Payments availability check
To check payments availability, use theCheckPurchasesAvailability
method.
If all above conditions are met, FeatureAvailabilityResult.isAvailable == true
is returned.
Otherwise, FeatureAvailabilityResult.isAvailable == false
is returned, where FeatureAvailabilityResult.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.Instance.CheckPurchasesAvailability(
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
if (response.isAvailable) {
// Process purchases available
} else {
// Process purchases unavailable
}
}
);
Working with SDK
Retrieving products list
You verified that payments are available and the users are able to make purchases. Now you can request products list. Use the GetProducts
method to request the information about products added to your app in RuStore Console.
RuStoreBillingClient.Instance.GetProducts(productsId,
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
// Process response
}
);
string[] 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 products list 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 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
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
— 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 class SubscriptionPeriod {
public int years;
public int months;
public int days;
}
years
— amount of years.months
— amount of months.days
— amount of days.
Getting products list
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.
RuStoreBillingClient.Instance.GetPurchases(
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
// Process response
}
);
The method returns List<Purchase> response
— purchases list.
Purchase structure
public class Purchase {
public enum PurchaseState
{
CREATED,
INVOICE_CREATED,
CONFIRMED,
PAID,
CANCELLED,
CONSUMED,
CLOSED,
PAUSED,
TERMINATED
}
public string purchaseId;
public string productId;
public Product.ProductType productType;
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
— product ID.productId
— product ID assigned to product in RuStore Console (mandatory).invoiceId
— invoice ID.language
— language specified with BCP 47 code.purchaseTime
— purchase time.orderId
— payment ID generated by the app (optional). If you specify this parameter in your system, you will receive it via our API. If not specified, will be generated automatically (uuid). 150 characters max.amountLable
— formatted purchase price, including currency symbol.amount
— price in minimum currency units.currency
— ISO 4217 currency code.quantity
— product amount (optional, value1
will be used if not specified).purchaseState
— purchase state:developerPayload
— string with additional order information, that you can specify on purchase initialization.subscriptionToken
— purchase token for server validation .
Getting purchase information
Go get purchase information, use thegetPurchaseInfo
method.
RuStoreBillingClient.Instance.GetPurchaseInfo(
purchaseId: "purchaseId",
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
// Process response
}
);
The method returns the Purchase
object with the purchase information.
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 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
— product ID.productId
— product ID assigned to product in RuStore Console (mandatory).invoiceId
— invoice ID.language
— language specified with BCP 47 code.purchaseTime
— purchase time.orderId
— payment ID generated by the app (optional). If you specify this parameter in your system, you will receive it via our API. If not specified, will be generated automatically (uuid). 150 characters max.amountLable
— formatted purchase price, including currency symbol.amount
— price in minimum currency units.currency
— ISO 4217 currency code.quantity
— product amount (optional, value1
will be used if not specified).purchaseState
— purchase state: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
Server validation
If you need to validate a successful app using API RuStore methods, you can use subscriptionToken
in PurchaseResult
returned by purchaseProduct
on successful purchase.
SubscriptionToken
consists of invoiceId
and userId
of the purchase separated by period: $invoiceId.$userId
.
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
from the Purchase
entity. The 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
To purchase product, use the PurchaseProduct
method.
RuStoreBillingClient.Instance.PurchaseProduct(
productId: productId,
quantity: 1,
developerPayload: "your payload",
onFailure: (error) => {
// process error
},
onSuccess: (result) => {
// process result
}
);
productId: String
— product ID assigned to product in RuStore Console (mandatory).orderId: String
— payment ID generated by the app (optional). If you specify this parameter in your system, you will receive it via our API. If not specified, will be generated automatically (uuid). 150 characters max.quantity: Int
— product amount (optional, value1
will be used if not specified).developerPayload
— string with additional order information, that you can specify on purchase initialization.
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 bool sandbox;
}
public class PaymentCancelled : PaymentResult {
public string purchaseId;
public bool sandbox;
}
public class PaymentFailure : PaymentResult {
public string purchaseId;
public string invoiceId;
public string orderId;
public int quantity;
public string productId;
public int errorCode;
public bool sandbox;
}
public class 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.
PaymentSuccess
- successful purchase result.PaymentFailure
- there was a problem during sending payment request or receiving payment status, purchase status unknown.PaymentCancelled
— 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.
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 theirs. In this case, if a purchase is canceled from the PAID
status the refund is made, not reverse. The developer's fee will not be returned. At the same time, to complete the purchase you still have to confirm purhchase — see the table below.
Payment method | Payment type | Payment in the PAID status |
---|---|---|
| Two-stage |
|
| One-stage |
|
ConfirmPurchase request
Us theconfirmPurchase
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
.
RuStoreBillingClient.Instance.ConfirmPurchase(
purchaseId: "purchaseId" ,
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);
purchaseId
— product ID.
Purchase cancellation
To cancel a purchase, use the DeletePurchase
method.
RuStoreBillingClient.Instance.DeletePurchase(
purchaseId: "purchaseId" ,
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);
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.
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.
In some cases, after paying with a bank app (SBP, SberPay, T-Pay, etc.) and returning to your app the purchase status may still be INVOICE_CREATED
while payment status shows that the purchase failed. This is caused by the purchase processing time by the bank. In this case, you need to adapt your screen lifecycle to the product list retrieval logic.
Alternatively, you can implement cancellation of purchases in the INVOICE_CREATED
state only through user action in your app. For example, create a dedicated button for this purpose.
Changing interface theme
SDK supports dynamic theme changing via the BillingClientThemeProvider
provider.
The current theme can be retrieved with the GetTheme()
method.
RuStoreBillingClient.Instance.GetTheme()
You can change the current theme using the SetTheme(BillingClientTheme theme)
method.
RuStoreBillingClient.Instance.SetTheme(BillingClientTheme.Dark)
Available themes are listed in BillingClientTheme
.
public enum BillingClientTheme {
Dark,
Light,
}
Error handling
Possible errors
RuStoreNotInstalledException
— RuStore is not installed on the user's device;RuStoreOutdatedException
— RuStore version installed on the user's device does not support this SDK;RuStoreUserUnauthorizedException
— user is not authorized in RuStore;RuStoreRequestLimitReached
— not enough time has passed since the process was last shown;RuStoreReviewExists
— this user has already rated your app;RuStoreInvalidReviewInfo
— problems withReviewInfo
;RuStoreException
— basic RuStore error from which other errors are inherited.
Errors that may occur are processed by the onFailure
handler.
Error structure
public class RuStoreError {
public string name;
public string description;
}
name
– error name.description
– error description.
Automatic error handling
On calling thePurchaseProduct
method, errors are handled automatically.
If allowNativeErrorHandling == true
is passed the during SDK initialization, both the Failure
handler is called and the user is shown the error dialog.
public fun RuStoreException.resolveForBilling(context: Context)
You can change this behavior after the initialization by setting the AllowNativeErrorHandling
property.
RuStoreBillingClient.Instance.AllowNativeErrorHandling = false;