Skip to main content

In-app Payments SDK for Flutter (version 10.0.0)

With RuStore you can integrate payments in your mobile app.

tip

Getting Started

To connect payments to your project, execute the following command.

flutter pub add flutter_rustore_billing

Handling deeplinks in the RuStore SDK allows for effective interaction with third-party applications when making payments through banking applications (FPS, SberPay, T-Pay, etc.). This allows you to transfer the user to the payment screen, and after the transaction is completed, return them to your application.

To configure deeplink handling in your application and the Pay SDK, specify deeplinkScheme using sdk_pay_scheme_value in the AndroidManifest.xml file.

Attention
  • When using deeplinks, specifying the scheme is mandatory. Attempting a payment without specifying the scheme will result in an error.
  • Only ASCII characters are allowed. The format must comply with RFC 3986.

Specifying deeplinkScheme

AndroidManifest.xml
<activity
android:name=".sample.MainActivity">
<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="@string/SDK_PAY_SCHEME" /> />
</intent-filter>

<meta-data
android:name="sdk_pay_scheme_value"
android:value="@string/SDK_PAY_SCHEME" />

</activity>
tip

Don't forget to specify the string with your SDK_PAY_SCHEME in the strings.xml file.

To restore the state of your application when returning from a deeplink, add the android:launchMode="singleTop" attribute to AndroidManifest.xml.

Specifying launchMode

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

SDK Initialization

Initialize the library before calling its methods. The initialization itself is done automatically, however, for your SDK to work, in your AndroidManifest.xml file define console_app_id_value.

AndroidManifest.xml
<meta-data
android:name="console_app_id_value"
android:value="@string/CONSOLE_APPLICATION_ID" />

In strings.xml, replace the string with your app_id.

strings.xml
<string name="CONSOLE_APPLICATION_ID">your_app_id</string>

CONSOLE_APPLICATION_ID — product ID form the RuStore Console.

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

Important
  • The ApplicationId specified in build.gradle must match the applicationId of the APK file you published in the RuStore Console.
  • 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).

Information

For security purposes, the SDK sets android:usesCleartextTraffic="false" by default to prevent data transmission over unsecured HTTP and protect against "Man-in-the-Middle" attacks. If your application requires the use of HTTP, you can change this attribute to true, but do so at your own risk, as it increases the chance of data interception and tampering. We recommend allowing unsecured traffic only in exceptional cases and for trusted domains, preferring HTTPS for all network interactions.

SDK Methods

Available public interactors:

  • PurchaseInteractor – an interactor that lets you work with payments and provides several public methods.

    • getPurchase(purchaseId: PurchaseId): Task<Purchase> – returns information about a purchase by its ID.
    • getPurchases(productType: ProductType? = null, purchaseStatus: PurchaseStatus? = null): Task<List<Purchase>> — returns the user’s purchases. This method supports optional filtering by product type (consumable, non-consumable products or subscriptions) and by purchase status (supported statuses are PAID, CONFIRMED, ACTIVE, and PAUSED). By default, filters are disabled and all user purchases (regardless of product type) in the PAID, CONFIRMED, ACTIVE, and PAUSED statuses are returned.
    • getPurchaseAvailability(): Task<PurchaseAvailabilityResult> – returns the result of checking whether payments are available.
    • purchase(params: ProductPurchaseParams, preferredPurchaseType: PreferredPurchaseType = PreferredPurchaseType.ONE_STEP,sdkTheme: SdkTheme = SdkTheme.LIGHT): Task<ProductPurchaseResult> – makes a product purchase with the specified payment type: one-step (ONE_STEP) or two-step (TWO_STEP). For this method, all available payment methods are shown on the payment screen. If the parameter is not specified, a one-step payment flow is used by default.
    Important

    If the TWO_STEP payment type is specified, the SDK will attempt to start a two-step payment flow, but the final result will directly depend on which payment method (card, SBP, etc.) the user selects.

    Note that the TWO_STEP payment type is not available:

    • When SBP is selected as the payment method.
    • When purchasing subscriptions.

    Two-step payment is available only for a specific set of payment methods (at the moment — cards and SberPay only). If the selected payment method does not support authorization hold, the purchase will fall back to a one-step payment flow.

    • purchaseTwoStep(params: ProductPurchaseParams,sdkTheme: SdkTheme = SdkTheme.LIGHT): Task<ProductPurchaseResult> – starts a guaranteed two-step purchase flow. When this method is used, only payment methods that support two-step payment are available to the user on the payment screen. During the payment flow, the customer’s funds are first put on hold and are charged only after the purchase is confirmed with the confirmTwoStepPurchase method.
    • confirmTwoStepPurchase(purchaseId: PurchaseId, developerPayload: DeveloperPayload? = null) – confirms a purchase made with a two-step payment flow.
    • cancelTwoStepPurchase(purchaseId: PurchaseId) – cancels a purchase made with a two-step payment flow.
  • ProductInteractor – an interactor that lets you work with products:

    • getProducts(productsId: List<ProductId>): Task<List<Product>> – returns information about active products published in the RuStore Console.
      Important

      This method returns no more than 1000 products and works without user authorization or RuStore being installed on the user’s device.

  • UserInteractor – an interactor that lets you get the user’s authorization status (UserAuthorizationStatus). This model has two possible states:

    • Authorized – the user is authorized in RuStore.
    • Unauthorized – the user is not authorized in RuStore.
  • IntentInteractor – an interactor that lets you handle intents and deeplinks. It is required to correctly return from the banking app back to your app and restore the state of the payment screen.

    • proceedIntent(intent: Intent?, sdkTheme: SdkTheme = SdkTheme.LIGHT) – a method for handling deeplinks and restoring the state of the payment screen when returning to your app from a banking app. You must call this method to correctly display the payment screen when returning to the app from a banking app.
  • The RuStoreUtils block – a set of public helper methods, such as:

    • isRuStoreInstalled – checks whether the RuStore app is installed on the user’s device.
    • openRuStoreDownloadInstruction – opens the web page for downloading the RuStore app.
    • openRuStore – launches the RuStore app.
    • openRuStoreAuthorization – launches the RuStore app for user authorization. After the user is successfully authorized, the RuStore app closes automatically.

Purchase types

In the SDK, there is a base Purchase interface that unifies the common fields for all purchase types. It has two implementations:

  • ProductPurchase — for consumable and non-consumable purchases.
  • SubscriptionPurchase — for subscriptions.

This separation allows each purchase type to expose its own specific properties and behavior.

Purchase Interface
abstract class Purchase {
String get purchaseId;
String get invoiceId;
String? get orderId;
PurchaseType get purchaseType;
PurchaseStatus get status;
String get description;
String? get purchaseTime; // ISO-8601
int get price;
String get amountLabel;
String get currency;
String? get developerPayload;
bool get sandbox;
}

Retrieving purchase information

Go get purchase information, use the getPurchase method.
RuStorePayClient.instance.purchaseInteractor.getPurchase("PURCHASE_ID").then((purchase) {
// To get the information about specific purchase type
if (purchase is ProductPurchase) {
print('Product ID: ${purchase.productId}, Status: ${purchase.status}');
} else if (purchase is SubscriptionPurchase) {
print('Product ID: ${purchase.productId}, Status: ${purchase.status}');
}
}).catchError((error) {
// Handling errors
});

The method returns information about a specific purchase in any status. The purchase model is specified in the purchase types section.

Retrieving purchase list

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

Calling the retrieving purchases list method
RuStorePayClient.instance.purchaseInteractor.getPurchases().then((purchases) {
for (final purchase in purchases) {
// To retrieve the specific type purchase
if (purchase is ProductPurchase) {
print('Product ID: ${purchase.productId}, Status: ${purchase.status}');
} else if (purchase is SubscriptionPurchase) {
print('Product ID: ${purchase.productId}, Status: ${purchase.status}');
}
}
}).catchError((error) {
// Handling errors
});

This method lets you filter purchases by product type and purchase status.

Product types:

  • Consumable products - ProductType.CONSUMABLE_PRODUCT
  • Non-consumable products - ProductType.NON_CONSUMABLE_PRODUCT
  • ПSubscriptions - ProductType.SUBSCRIPTION

Purchase statuses:

  • For products:

    • PAID: Funds are successfully held; the purchase awaits developer confirmation.
    • CONFIRMED: The purchase is confirmed; funds have been captured.
  • For subscriptions:

    • ACTIVE: The subscription is active.
    • PAUSED: The subscription is in a hold/grace period (for example, due to insufficient funds); retries continue as per the subscription tariff settings.

By default, filters are disabled. If values are not provided, the method returns all user purchases in thePAID, CONFIRMED, ACTIVE и PAUSED statuses, regardless of product type.

Calling retrieving purchases list method with filtration
RuStorePayClient.instance.purchaseInteractor.getPurchases().then((purchases) { 
// To retrieve the specific type purchase
for (final purchase in purchases) {
if (purchase is ProductPurchase) {
print('Product ID: ${purchase.productId}, Status: ${purchase.status}');
} else if (purchase is SubscriptionPurchase) {
print('Product ID: ${purchase.productId}, Status: ${purchase.status}');
}
}
}).catchError((error) {
// Handling errors
});

Retrieving product list

To retrieve the products added to your application via the RuStore Console, you must use the method:s getProducts.

RuStorePayClient.instance.productInteractor.getProducts(ids).then((products) {
for (final product in products) {
print(product.productId);
}
}, onError: (err) {
print("products err: $err");
});

ids: List<String> — the list of product IDs that are set when products are created in the RuStore Console. The list is limited by 1000 items.

Where are product IDs in the RuStore Console?
  1. Navigate to the Applications tab and selected the needed app.
  2. Select Monetization in the left menu.
  3. Select product type: Subscriptions or In-App purchases.
  4. Copy the IDs of the required products.

Method returns product list. Product model is below.

class Product {
final String productId;
final ProductType type;
final String amountLabel;
final int? price; // nullable, if there's no price
final String currency;
final String imageUrl;
final String title;
final String? description;
final SubscriptionInfo? subscriptionInfo;

const Product({
required this.productId,
required this.type,
required this.amountLabel,
this.price,
required this.currency,
required this.imageUrl,
required this.title,
this.description,
this.subscriptionInfo,
});
}

The model is shown below.

  • productId — product ID assigned to product in RuStore Console (mandatory).
  • type — product type. CONSUMABLE/NON-CONSUMABLE/SUBSCRIPTION (consumable/non-consumable/subscription).
  • amountLabel — formatted purchase price, including currency symbol.
  • price — price in minimum currency units.
  • currency — ISO 4217 currency code.
  • title — product name in language.
  • description — descriptions in language.
  • imageUrl — image URL.

ProductType structure

enum ProductType {
consumable,
nonConsumable,
subscription,
undefined
}

Subscription information structure

final class SubscriptionInfo {
final List<SubscriptionPeriod> periods;
}

Subscription period information structure (sealed class)

sealed class SubscriptionPeriod {
String get duration;
}

Subscription period types

  1. TrialPeriod (free trial period)

    • String duration – period duration in ISO 8601 format (for example, P7D – 7 days, P1M – 1 month)
    • String currency – ISO 4217 currency code
    • int price – price in the smallest currency units
  2. PromoPeriod (promotional period)

    • String duration – period duration in ISO 8601 format
    • String currency – ISO 4217 currency code
    • int price – price in the smallest currency units
  3. MainPeriod (main billing period)

    • String duration – period duration in ISO 8601 format
    • String currency – ISO 4217 currency code
    • int price – price in the smallest currency units
  4. GracePeriod (grace period)

    • String duration – period duration in ISO 8601 format
  5. HoldPeriod (pause / suspension period)

    • String duration – period duration in ISO 8601 format

Example of handling subscription periods

final products = await RuStorePayClient.instance.productInteractor.getProducts(ids);

for (final product in products) {
if (product.type == ProductType.subscription && product.subscriptionInfo != null) {
final subscriptionInfo = product.subscriptionInfo!;

for (final period in subscriptionInfo.periods) {
switch (period) {
case TrialPeriod trial:
print('Free period: ${trial.duration}');
break;
case PromoPeriod promo:
print('Trial period: ${promo.duration} за ${promo.price} ${promo.currency}');
break;
case MainPeriod main:
print('Main periodд: ${main.duration} за ${main.price} ${main.currency}');
break;
case GracePeriod grace:
print('Grace period: ${grace.duration}');
break;
case HoldPeriod hold:
print('Hold period: ${hold.duration}');
break;
}
}
}
}

Example of product response containing subscription info

Product(
productId: "premium_subscription",
type: ProductType.subscription,
amountLabel: "199 rub",
price: 19900,
currency: "RUB",
imageUrl: "https://example.com/image.png",
title: "Premium subscription",
description: "1 month access to all functions",
subscriptionInfo: SubscriptionInfo(
periods: [
TrialPeriod(
duration: "P7D",
currency: "RUB",
price: 0
),
MainPeriod(
duration: "P1M",
currency: "RUB",
price: 19900
)
]
)
)

Response examples

Consumable product model example
Product(
productId = ProductId("conProduct1"),
type = ProductType.CONSUMABLE_PRODUCT,
amountLabel = AmountLabel("100.00 rub"),
price = Price(10000),
currency = Currency("RUB"),
imageUrl = Url("https://your_image_consumable_product.png"),
title = Title("Consumable product name"),
description = Description("Consumable product description"),
)
Non-consumable product model example
Product(
productId = ProductId("nonConProduct1"),
type = ProductType.NON_CONSUMABLE_PRODUCT,
amountLabel = AmountLabel("200.00 rub"),
price = Price(20000),
currency = Currency("RUB"),
imageUrl = Url("https://your_image_non_consumable_product.png"),
title = Title("Non-consumable product name"),
description = Description("Non-consumable product description"),
)
Subscription model example
Product(
productId = ProductId("sub_1"),
type = ProductType.SUBSCRIPTION,
amountLabel = AmountLabel("300.00 rub"),
price = Price(30000),
currency = Currency("RUB"),
imageUrl = Url("https://your_image_subscription.png"),
title = Title("Subscription name"),
description = Description("Subscription description"),
)

Determining user authorization status

To check the user's authorization status call the method getUserAuthorizationStatus у UserInteractor. Результатом выполнения метода является класс UserAuthorizationStatus. Доступно 2 значения:

  • AUTHORIZED – the user is authorized in RuStore or via VK ID on the payment sheet.
  • UNAUTHORIZED – the user is not authorized. This value is also returned if RuStore is not installed on the user’s device.
RuStorePayClient.instance.userInteractor.getUserAuthorizationStatus().then((value) {
if (value case Authorized _) {
// Process authorized
} else if (value case Unauthorized _) {
// Process unauthorized
}
});

Checking payment availability

To check purchase availability, call the getPurchaseAvailability method. On calling, the following conditions are checked.

  • Purchases must be enabled for the application in the RuStore Console.
  • The user and the application must not be banned in RuStore.
If all above conditions are met, PurchaseAvailabilityResponse.Available is returned. Otherwise, PurchaseAvailabilityResponse.Unavailable(val cause: Throwable) is returned, where cause is an error of a failed condition.
Calling the getPurchaseAvailability method
RuStorePayClient.instance.purchaseInteractor.getPurchaseAvailability().then((value) {
if (value case Available _) {
// Process purchases available
} else if (value case Unavailable unavailable) {
// Process purchases unavailable
}
});

One-time purchase model ProductPurchase

Payment with a chosen purchase type

To initiate a purchase and choose the payment type, use purchase method.

Calling product purchase method
    class ProductPurchaseParams {
final String productId;
final int? quantity;
final String? orderId;
final String? developerPayload;
final String? appUserId;
final String? appUserEmail;

const ProductPurchaseParams({
required this.productId,
this.quantity,
this.orderId,
this.developerPayload,
this.appUserId,
this.appUserEmail,
});
}

Future<void> purchaseProduct() async {
final params = ProductPurchaseParams(
productId: 'productId',
orderId: null,
quantity: null,
developerPayload: null,
appUserId: null,
appUserEmail: null,
);

try {
final result = await RuStorePayClient.instance.purchaseInteractor.purchase(
params.productId,
quantity: params.quantity ?? 1,
orderId: params.orderId,
developerPayload: params.developerPayload,
appUserId: params.appUserId,
appUserEmail: params.appUserEmail,
preferredPurchaseType: PreferredPurchaseType.oneStep,
sdkTheme: SdkTheme.LIGHT, // или SdkTheme.DARK,
);
// Logic for processing a successful purchase result
} on RuStoreProductPurchaseException {
// Handling a product purchase error
} on RuStorePurchaseCancelledException {
// Handling a product purchase cancel
} catch (_) {
// Handling error
}
}
  • preferredPurchaseType — the desired purchase type: single-stage (ONE_STEP) or two-stage (TWO_STEP).
warning

This method is launched by default using the single-stage payment scenario (preferredPurchaseType = PreferredPurchaseType.ONE_STEP), i.e., without funds being held.

For two-stage payment, you need to specify preferredPurchaseType = PreferredPurchaseType.TWO_STEP. Two-stage payment (i.e., payment with funds being held) is not guaranteed for this method and directly depends on the payment method (card, SPB, etc.) selected by the user.

When launching this method (with the preferred preferredPurchaseType = twoStep), until the user selects a payment method, the purchase stage will be UNDEFINED. Please take this behavior into account when handling purchase cancellation results (ProductPurchaseCancelled) or purchase errors (ProductPurchaseException).

Two-step payment (with authorization hold)

To initiate a product purchase using the two-step flow, use the purchaseTwoStep method.

Payment method limitations for subscriptions

At the moment, purchasing a subscription (SubscriptionPurchase) is only possible with a one-step payment (PurchaseType.ONE_STEP).

Calling product purchase method
class ProductPurchaseParams {
final String productId;
final int? quantity;
final String? orderId;
final String? developerPayload;
final String? appUserId;
final String? appUserEmail;

const ProductPurchaseParams({
required this.productId,
this.quantity,
this.orderId,
this.developerPayload,
this.appUserId,
this.appUserEmail,
});
}

Future<void> purchaseTwoStep() async {
final params = ProductPurchaseParams(
productId: 'productId',
orderId: null,
quantity: null,
developerPayload: null,
appUserId: null,
appUserEmail: null,
);

try {
final result = await RuStorePayClient.instance.purchaseInteractor.purchaseTwoStep(
params.productId,
quantity: params.quantity ?? 1,
orderId: params.orderId,
developerPayload: params.developerPayload,
appUserId: params.appUserId,
appUserEmail: params.appUserEmail,
sdkTheme: SdkTheme.LIGHT, // or SdkTheme.DARK
);
// Logic for processing a successful purchase result
} on RuStoreProductPurchaseException {
// Handling a product purchase error
} on RuStorePurchaseCancelledException {
// Handling a product purchase cancel
} catch (_) {
// Handling error
}
}

Purchase parameters structure

Purchase parameters structure
class ProductPurchaseParams {
final String productId;
final int? quantity;
final String? orderId;
final String? developerPayload;
final String? appUserId;
final String? appUserEmail;

const ProductPurchaseParams({
required this.productId,
this.quantity,
this.orderId,
this.developerPayload,
this.appUserId,
this.appUserEmail,
});
}
  • productId — product ID assigned to product in RuStore Console (mandatory).
  • quantity — product amount (optional, value 1 will be used if not specified).
  • 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.
  • developerPayload — a string with additional order information that you can set when confirming the purchase. This string overrides the value set during initialization. Maximum length is 250 characters.
  • appUserId — the internal user ID in your application (optional parameter). A string with a maximum length of 128 characters.
    tip

    For example, this parameter can be used to detect cases of fraud in your application, which will help improve its security.

  • appUserEmail — this is an optional parameter that allows you to specify the user's email address in your application. If the buyer's email address was provided during registration in the app, it can be passed for automatic filling of the email field when sending a receipt — both for payments outside RuStore and in cases where the user is not authorized in RuStore. This saves the user from having to manually enter their email, shortens the purchase flow, and helps increase conversion.

Purchase status model

One-stage payment status model.



Two-stage payment status model.



Subscription model SubscriptionPurchase

Модель подписки SubscriptionPurchase
class SubscriptionPurchase implements Purchase {
final String purchaseId;
final String invoiceId;
final String? orderId;
final PurchaseType purchaseType;
final SubscriptionPurchaseStatus status;
final String description;
final String? purchaseTime; // ISO-8601
final int price;
final String amountLabel;
final String currency;
final String? developerPayload;
final bool sandbox;

final String productId;
final String expirationDate; // ISO-8601
final bool gracePeriodEnabled;

const SubscriptionPurchase({
required this.purchaseId,
required this.invoiceId,
this.orderId,
required this.purchaseType,
required this.status,
required this.description,
this.purchaseTime,
required this.price,
required this.amountLabel,
required this.currency,
this.developerPayload,
required this.sandbox,
required this.productId,
required this.expirationDate,
required this.gracePeriodEnabled,
});
}
  • purchaseId — product ID — the purchase identifier. Used to retrieve purchase details in the SDK via the purchase info method.

  • invoiceId — invoice ID — the invoice identifier. Used for server-side payment validation, for searching payments in the Developer Console, and is shown to the buyer in their payment history.

  • orderId — a unique payment identifier provided by the developer or generated automatically (UUID).

  • PurchaseType — purchase type:

    • ONE_STEP - one-stage payment;
    • TWO_STEP - two-stage payment;
    • UNDEFINED — number of payment stages is undefined.
  • status — subscription flow status:

    • INVOICE_CREATED — an invoice has been created; the subscription is waiting for payment.
    • CANCELLED — the subscription invoice was canceled.
    • EXPIRED — the time to pay the initial invoice has expired; no subscription was created.
    • PROCESSING — the first subscription payment is being processed.
    • REJECTED — the first subscription payment was rejected. The subscription was not created.
    • ACTIVE — the subscription is active.
    • PAUSED — the subscription is paused due to payment issues.
    • TERMINATED — all retry attempts for the subscription failed. The subscription was automatically closed due to payment issues.
    • CLOSED — the subscription was canceled by the user or the developer. After the paid period ended, the subscription was closed.
  • description — purchase description.

  • purchaseTime — purchase time.

  • price — price in minimum currency units.

  • amountLabel — formatted purchase price, including currency symbol.

  • currency — ISO 4217 currency code.

  • developerPayload — a string with additional order information that you can set when confirming the purchase. This string overrides the value set during initialization.

  • — test payment flag. true — test payment, false — actual payment.

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

  • expirationDate — the subscription end date.

  • gracePeriodEnabled — a flag indicating whether the grace period is enabled for the subscription.

Subscription status model

Purchasing product

Explanations on working with one-stage and two-stage payments
  • When using a single-stage payment, the purchase does not require confirmation; the funds are immediately debited from the buyer’s account, and a commission is charged to the developer. In this case, if a refund to the customer is required (for example, if the product cannot be delivered for some reason), a refund can only be processed via the RuStore Console, and the funds will be returned to the buyer within a few days. The full purchase amount is refunded, but the commission previously withheld from the developer is not reimbursed.
  • In the case of a two-stage payment, the funds are first held (authorized) on the buyer’s account. No commission is charged at this stage. After the hold, the purchase requires either confirmation or cancellation. The commission is charged to the developer upon purchase confirmation. Cancelling the purchase releases the hold, and the funds instantly become available to the buyer again.
Payment type restrictions for subscriptions

At this time, a subscription purchase (SubscriptionPurchase) can only be made using the one-step payment flow (PurchaseType.ONE_STEP).

warning

Two-stage payment is available only for a specific set of payment methods (currently — only for cards). SBP technologies do not support two-stage payment. If a payment method that does not support holding funds is selected, the purchase will be processed using the single-stage scenario.

Payment with a chosen purchase type

To initiate a purchase and choose the payment type, use purchase method.

Calling product purchase method
    class ProductPurchaseParams {
final String productId;
final int? quantity;
final String? orderId;
final String? developerPayload;
final String? appUserId;
final String? appUserEmail;

const ProductPurchaseParams({
required this.productId,
this.quantity,
this.orderId,
this.developerPayload,
this.appUserId,
this.appUserEmail,
});
}

Future<void> purchaseProduct() async {
final params = ProductPurchaseParams(
productId: 'productId',
orderId: null,
quantity: null,
developerPayload: null,
appUserId: null,
appUserEmail: null,
);

try {
final result = await RuStorePayClient.instance.purchaseInteractor.purchase(
params.productId,
quantity: params.quantity ?? 1,
orderId: params.orderId,
developerPayload: params.developerPayload,
appUserId: params.appUserId,
appUserEmail: params.appUserEmail,
preferredPurchaseType: PreferredPurchaseType.oneStep,
sdkTheme: SdkTheme.LIGHT, // или SdkTheme.DARK,
);
// Logic for processing a successful purchase result
} on RuStoreProductPurchaseException {
// Handling a product purchase error
} on RuStorePurchaseCancelledException {
// Handling a product purchase cancel
} catch (_) {
// Handling error
}
}
  • preferredPurchaseType — the desired purchase type: single-stage (ONE_STEP) or two-stage (TWO_STEP).
warning

This method is launched by default using the single-stage payment scenario (preferredPurchaseType = PreferredPurchaseType.ONE_STEP), i.e., without funds being held.

For two-stage payment, you need to specify preferredPurchaseType = PreferredPurchaseType.TWO_STEP. Two-stage payment (i.e., payment with funds being held) is not guaranteed for this method and directly depends on the payment method (card, SPB, etc.) selected by the user.

When launching this method (with the preferred preferredPurchaseType = twoStep), until the user selects a payment method, the purchase stage will be UNDEFINED. Please take this behavior into account when handling purchase cancellation results (ProductPurchaseCancelled) or purchase errors (ProductPurchaseException).

Two-step payment (with authorization hold)

To initiate a product purchase using the two-step flow, use the purchaseTwoStep method.

Payment method limitations for subscriptions

At the moment, purchasing a subscription (SubscriptionPurchase) is only possible with a one-step payment (PurchaseType.ONE_STEP).

Calling product purchase method
class ProductPurchaseParams {
final String productId;
final int? quantity;
final String? orderId;
final String? developerPayload;
final String? appUserId;
final String? appUserEmail;

const ProductPurchaseParams({
required this.productId,
this.quantity,
this.orderId,
this.developerPayload,
this.appUserId,
this.appUserEmail,
});
}

Future<void> purchaseTwoStep() async {
final params = ProductPurchaseParams(
productId: 'productId',
orderId: null,
quantity: null,
developerPayload: null,
appUserId: null,
appUserEmail: null,
);

try {
final result = await RuStorePayClient.instance.purchaseInteractor.purchaseTwoStep(
params.productId,
quantity: params.quantity ?? 1,
orderId: params.orderId,
developerPayload: params.developerPayload,
appUserId: params.appUserId,
appUserEmail: params.appUserEmail,
sdkTheme: SdkTheme.LIGHT, // or SdkTheme.DARK
);
// Logic for processing a successful purchase result
} on RuStoreProductPurchaseException {
// Handling a product purchase error
} on RuStorePurchaseCancelledException {
// Handling a product purchase cancel
} catch (_) {
// Handling error
}
}

Purchase parameters structure

Purchase parameters structure
class ProductPurchaseParams {
final String productId;
final int? quantity;
final String? orderId;
final String? developerPayload;
final String? appUserId;
final String? appUserEmail;

const ProductPurchaseParams({
required this.productId,
this.quantity,
this.orderId,
this.developerPayload,
this.appUserId,
this.appUserEmail,
});
}
  • productId — product ID assigned to product in RuStore Console (mandatory).
  • quantity — product amount (optional, value 1 will be used if not specified).
  • 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.
  • developerPayload — a string with additional order information that you can set when confirming the purchase. This string overrides the value set during initialization. Maximum length is 250 characters.
  • appUserId — the internal user ID in your application (optional parameter). A string with a maximum length of 128 characters.
    tip

    For example, this parameter can be used to detect cases of fraud in your application, which will help improve its security.

  • appUserEmail — this is an optional parameter that allows you to specify the user's email address in your application. If the buyer's email address was provided during registration in the app, it can be passed for automatic filling of the email field when sending a receipt — both for payments outside RuStore and in cases where the user is not authorized in RuStore. This saves the user from having to manually enter their email, shortens the purchase flow, and helps increase conversion.

Confirming the purchase

Only purchases initiated using the two-stage payment scenario (i.e., with funds being held) require confirmation. Such purchases, after successful holding, will have the status PurchaseStatus.paid.

To debit funds from the buyer's card, the purchase must be confirmed. For this, you should use the confirmTwoStepPurchase method.

RuStorePayClient.instance.purchaseInteractor.confirmTwoStepPurchase(id).then((value) {
// Process success
});
  • id — product ID.
  • developerPayload — a string with additional order information that you can set when confirming the purchase. This string overrides the value set during initialization

Canceling the purchase

With our SDK you can cancel only the purchases that undergo a two-stage payment process, i.e. when the user's money is put on hold. After a successful hold, such purchases are in the PurchaseStatus.PAID status. If a purchase is canceled it has the PurchaseStatus.REVERSED status.

tip

Cancel purchases if you cannot deliver your product after payment is made (when the user's money is put on hold).

To cancel a purchase (put the user's money off hold), use the cancelTwoStepPurchase method.

void cancelTwoStepPurchaseExample() {
RuStorePayClient.instance.purchaseInteractor
.cancelTwoStepPurchase('purchaseId')
.then((_) {
// Process success
})
.catchError((error) {
// Process error
});
}
  • purchaseId — product ID.

Purchase result structure

ProductPurchaseResult - the result of a successful payment for a digital product, subscription (for one-stage payment) or successful holding of funds (for two-stage payment).

class ProductPurchaseResult {
final String? orderId;
final String purchaseId;
final String productId;
final String invoiceId;
final PurchaseType purchaseType;
final ProductType productType;
final int quantity;
final bool sandbox;

const ProductPurchaseResult({
this.orderId,
required this.purchaseId,
required this.productId,
required this.invoiceId,
required this.purchaseType,
required this.productType,
required this.quantity,
required this.sandbox,
});
}
  • ProductPurchaseResult — the result of a successful digital product payment (for one-step payments) or a successful fund hold (for two-step payments).
  • purchaseId — purchase identifier. Used to get purchase information in the SDK using the get purchase information method.
  • productId — identifier of the purchased product, specified when creating it in the RuStore developer console.
  • invoiceId — invoice identifier. Used for server-side payment validation, searching for payments in the developer console, and is also displayed to the buyer in the payment history in the RuStore mobile app.
  • orderId — unique payment identifier, specified by the developer or generated automatically (uuid).
  • purchaseType — purchase type (ONE_STEP/TWO_STEP/UNDEFINED — one-step/two-step/undefined).
  • productType — type of product (NON_CONSUMABLE_PRODUCT — non-consumable product, CONSUMABLE_PRODUCT — consumable product, SUBSCRIPTION — subscription).
  • quantity — product quantity - optional. If not specified, the value will be set to 1. Applicable only for purchasing consumable products.
  • sandbox - a flag indicating a test payment in the sandbox. If TRUE - the purchase was made in test mode.

Error handling

If an error occurs during the payment process or the user cancels the purchase, the execution of the payment method (both with the choice of purchase type and the two-step method) terminates with an error:

  • ProductPurchaseException — product purchase error.
  • ProductPurchaseCancelled — an error caused by cancelling a product purchase (the user closed the payment sheet) before receiving the purchase result. In this case, it is recommended to additionally check the purchase status using the get purchase information method.

Error and purchase cancellation structure

class ProductPurchaseException implements Exception {
final String? orderId;
final String? purchaseId;
final String? productId;
final String? invoiceId;
final int? quantity;
final PurchaseType? purchaseType;
final ProductType? productType;
final bool? sandbox;
final Object cause;

const ProductPurchaseException({
this.orderId,
this.purchaseId,
this.productId,
this.invoiceId,
this.quantity,
this.purchaseType,
this.productType,
this.sandbox,
required this.cause,
});


String toString() => 'Error purchase product: $cause';
}

class ProductPurchaseCancelled implements Exception {
final String? purchaseId;
final PurchaseType? purchaseType;
final ProductType? productType;

const ProductPurchaseCancelled({
this.purchaseId,
this.purchaseType,
this.productType,
});


String toString() => 'Purchasing of the product is cancelled';
}
  • purchaseId — purchase identifier. Used to get purchase information in the SDK using the get purchase information method.
  • productId — identifier of the purchased product, specified when creating it in the RuStore developer console.
  • invoiceId — invoice identifier. Used for server-side payment validation, searching for payments in the developer console, and is also displayed to the buyer in the payment history in the RuStore mobile app.
  • orderId — unique payment identifier, specified by the developer or generated automatically (uuid).
  • purchaseType — purchase type (ONE_STEP/TWO_STEP/UNDEFINED — one-step/two-step/undefined).
  • productType — type of product (NON_CONSUMABLE_PRODUCT — non-consumable product, CONSUMABLE_PRODUCT — consumable product, SUBSCRIPTION — subscription).
  • quantity — product quantity - optional. If not specified, the value will be set to 1. Applicable only for purchasing consumable products.

Server-side purchase validation

If you need to validate a purchase on the RuStore server, you can use subscriptionToken in ProductPurchaseResult.SuccessProductPurchaseResult, that is returned on successful purchase.

If you need to validate a successful purchase in RuStore, you can use the public validation APIs (https://www.rustore.ru/help/work-with-rustore-api/api-subscription-payment/v2-purchase-invoiceid). Different endpoints are used for products and subscriptions:

  • To validate a product purchase, use the invoiceId from the ProductPurchaseResult model returned after the purchase completes.
  • To validate a subscription purchase, use the purchaseId from the ProductPurchaseResult model returned after the purchase completes.

You can determine the type of the purchased item from the data in the ProductPurchaseResult response.

You can also obtain a subscriptionToken from the Purchase entity. Retrieve the Purchase entity using the getPurchases() method.

Retrieving invoiceId from the purchase result
class ProductPurchaseParams {
final String productId;
const ProductPurchaseParams(this.productId);
}

Future<void> exampleInvoiceFromResult() async {
final params = ProductPurchaseParams('productId');

final result = await RuStorePayClient.instance.purchaseInteractor.purchase(
params.productId,
preferredPurchaseType: PreferredPurchaseType.twoStep,
);

switch (result.productType) {
case ProductType.consumable:
case ProductType.nonConsumable:
final invoiceId = result.invoiceId;
yourApi.validateProduct(invoiceId);
break;
case ProductType.subscription:
final purchaseId = result.purchaseId;
yourApi.validateSubscription(purchaseId);
break;
default:
// Optional handling of an unknown type
break;
}
}
Retrieving subscriptionToken from the purchase result
Future<void> exampleSubscriptionTokenFromPurchases() async {
final purchases =
await RuStorePayClient.instance.purchaseInteractor.getPurchases();

for (final purchase in purchases) {
if (purchase is SubscriptionPurchase) {
final purchaseId = purchase.purchaseId;
yourApi.validateSubscription(purchaseId);
} else {
final invoiceId = purchase.invoiceId;
yourApi.validateProduct(invoiceId);
}
}
}

RuStoreUtils

RuStoreUtils is a set of public methods that let your Flutter app interact with the RuStore app installed on the user’s device. You can access these utilities via RuStorePayClient.instance.ruStoreUtils.

The IsRuStoreInstalled method checks whether the RuStore app is installed on the user’s device. Example: RuStorePayClient.instance.ruStoreUtils.isRuStoreInstalled().then((isInstalled) { ... }); It returns a boolean value isInstalled.

The openRuStoreDownloadInstruction method opens a web page where the user can download the RuStore mobile app. Example: RuStorePayClient.instance.ruStoreUtils.openRuStoreDownloadInstruction();

The openRuStore method launches the RuStore mobile app. If the RuStore app is not installed, a toast notification with the message Не удалось открыть приложение (“Failed to open the app”) is shown. Example: RuStorePayClient.instance.ruStoreUtils.openRuStore();

The openRuStoreAuthorization method launches the RuStore mobile app for user authorization. After the user successfully signs in, the RuStore app closes automatically. If the RuStore app is not installed, a toast notification with the message Не удалось открыть приложение (“Failed to open the app”) is shown. Example: RuStorePayClient.instance.ruStoreUtils.openRuStoreAuthorization();

Using RuStoreUtils to test payment scenarios

A usage scenario for RuStoreUtils to check whether RuStore is installed on the device and to work with user authorization is described in the article “Accepting payments without the RuStore app installed” (/developers/monetization/without-rustore-app).

The scenario covers:

  • Purchase flows when RuStore is not installed on the device
  • Examples of sequentially calling installation and authorization checks via RuStoreUtils
  • SDK behavior specifics in different conditions (RuStore installed/not installed, user authorization state, etc.)

Error list

RuStorePaymentNetworkException - SDK network interaction error. The error model returns an error code (the code field), which can be used to determine the cause of the error. A table with error codes is available in the error codes section.

The message field contains a description of the error's cause.

class RuStorePaymentNetworkException implements Exception {
final String? code;
final String id;
final String message;
final Object? cause;

const RuStorePaymentNetworkException({
this.code,
required this.id,
required this.message,
this.cause,
});


String toString() => message;
}
  • RuStorePaymentNetworkException — SDK network communication error;
  • RuStorePaymentCommonException — general SDK error;
  • RuStorePayClientAlreadyExist — SDK re-initialization error;
  • RuStorePayClientNotCreated — attempt to access public SDK interfaces before initialization;
  • RuStorePayInvalidActivePurchase — payment initiated for unknown product type;
  • RuStorePayInvalidConsoleAppId — the required parameter console_application_id for SDK initialization is not specified;
  • RuStorePaySignatureException — invalid response signature. Occurs when attempting fraudulent actions;
  • EmptyPaymentTokenException — error obtaining payment token;
  • InvalidCardBindingIdException — error when paying with a saved card;
  • ApplicationSchemeWasNotProvided — scheme for the return deeplink is not specified;
  • ProductPurchaseException — product purchase error. The model structure is described in the section purchase result structure;
  • ProductPurchaseCancelled — product purchase was cancelled (the user closed the payment sheet). The model structure is described in the section purchase result structure;
  • ProductPurchaseException — product purchase error;
  • RuStoreNotInstalledException — RuStore is not installed on the user's device;
  • RuStoreOutdatedException — the installed version of RuStore on the device does not support payments;
  • RuStoreUserUnauthorizedException — the user is not authorized in RuStore;
  • RuStoreApplicationBannedException — the application is banned in RuStore;
  • RuStoreUserBannedException — the user is banned in RuStore.

Error Codes

Error CodeDescription
4000001The request is malformed: a required parameter is missing or incorrectly filled, or the data format is invalid.
4000002, 4000016, 4040005Application not found.
4000003Application is banned.
4000004Application signature does not match the registered one.
4000005Company not found.
4000006Company is banned.
4000007Company monetization is disabled or inactive.
4000014Product not found.
4000015Product not published.
4000017Invalid quantity parameter.
4000018Purchase limit exceeded.
4000020Product already purchased.
4000021Unfinished product purchase.
4000022Purchase not found.
4000025No suitable payment method found.
4000026Invalid purchase type for confirmation (should be two-stage payment).
4000027Invalid purchase status for confirmation.
4000028Invalid purchase type for cancellation (should be two-stage payment).
4000029Invalid purchase status for cancellation.
4000030The issued token does not match the purchased product.
4000041An active subscription already exists for this product code.
4000045Maximum size limit exceeded.
4010001Access to the requested resource is forbidden (unauthorized).
4010002Token lifetime has expired.
4010003Payment token is invalid.
4030001Payment token not provided.
4030002User is blocked due to security requirements.
4040002, 4040003, 4040004Payment system error.
5000***Internal error.