跳到主要内容
已弃用

BillingClient SDK 的支持将于 2026 年 8 月 1 日停止。 2026 年 8 月 1 日之后,所有购买项(包括订阅)将停止处理付款。

在此日期之前,BillingClient SDK 将继续运行,但修复影响付款功能的故障可能需要更多时间。 将不再添加新功能。

建议在项目中使用 Pay SDK
请参考迁移指南以迁移至 Pay SDK。

适用于 Unreal Engine 的应用内支付和订阅 SDK(版本 10.3.0

RuStore 支持在移动应用程序中集成支付功能。

提示

如果您不知道从哪里开始,请阅读使用场景中的指南。

准备工作

  1. 前往 GitFlic 上项目仓库的“发布 (Releases)”部分

  2. 从所选的发布版本中下载以下产物:

    • RuStoreBilling.zip
    • RuStoreCore.zip
  3. 将下载的压缩包解压到 Unreal 项目根目录下的 Plugins 文件夹中,以形成以下结构:

📁 your_project
└─ 📁 Plugins
├─ 📁 RuStoreBilling
│ ├─ 📁 Binaries
│ ├─ 📁 Content
│ ├─ 📁 Intermediate
│ ├─ 📁 Resources
│ ├─ 📁 Source
│ └─ RuStoreBilling.uplugin
└─ 📁 RuStoreCore
├─ 📁 Binaries
├─ 📁 Content
├─ 📁 Intermediate
├─ 📁 Resources
├─ 📁 Source
└─ RuStoreCore.uplugin
  1. 重启 Unreal Engine。

  2. 在插件列表 (Edit → Plugins → Project → Mobile) 中勾选 RuStoreBillingRuStoreCore 插件。

  3. YourProject.Build.cs 文件的 PublicDependencyModuleNames 列表中连接 RuStoreCoreRuStoreBilling 模块。

  4. 在项目设置 (Edit → Project Settings → Android) 中,将 Minimum SDK Version 参数设置为不低于 24,将 Target SDK Version 参数设置为不低于 31

为了确保通过第三方应用程序(СБП、SberPay 等)进行支付时能正常运行,必须正确实现 deeplink 处理。

RuStore Pay 插件会自动修改 AndroidManifest.xml

  1. 删除 GameActivitySplashActivity 的启动 intent-filter
  2. 添加一个额外的 RuStorePayIntentFilterActivity activity,其中包含用于启动应用程序和处理 deeplink 的 intent-filter

您可以在 RuStoreBilling_UPL_Android.xml 文件中修改此行为。

AndroidManifest.xml
<!-- Deeplink activity -->
<activity android:name="com.Plugins.RuStoreBilling.RuStoreBillingIntentFilterActivity"
android:theme="@android:style/Theme.NoDisplay"
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" />

<!-- Deeplink scheme -->
<data android:scheme="@string/rustore_app_scheme" />
</intent-filter>
</activity>
RuStorePayIntentFilterActivity 的实现
package com.Plugins.RuStoreBilling;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import ru.rustore.unrealsdk.billingclient.RuStoreUnrealBillingClient;

public class RuStoreBillingIntentFilterActivity extends Activity {

private String[] ueActivitieNames = {
"com.epicgames.ue4.SplashActivity",
"com.epicgames.ue4.GameActivity",
"com.epicgames.unreal.SplashActivity",
"com.epicgames.unreal.GameActivity"
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (savedInstanceState == null) {
RuStoreUnrealBillingClient.onNewIntent(getIntent());
}

if (!isTaskRoot()) {
finish();
return;
}

startUEActivity(ueActivitieNames);
finish();
}

@Override
public void onNewIntent(Intent newIntent) {
super.onNewIntent(newIntent);

RuStoreUnrealBillingClient.onNewIntent(newIntent);
}

private void startUEActivity(String[] ueActivitieNames) {
String ueActivityClassName = findActivityClassName(ueActivitieNames);
Class<?> ueActivityClass = getActivityClass(ueActivityClassName);

if (ueActivityClass != null) {
Intent intent = new Intent(this, ueActivityClass);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
}

private String findActivityClassName(String[] findActivitieNames) {
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(
getPackageName(), PackageManager.GET_ACTIVITIES);

for (ActivityInfo activityInfo : packageInfo.activities) {
String activityInfoName = activityInfo.name;

for (String findActivityName : findActivitieNames) {
if (activityInfoName.equals(findActivityName)) {
return activityInfoName;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}

private Class<?> getActivityClass(String activityClassName) {
try {
return Class.forName(activityClassName);
} catch(ClassNotFoundException ex) {
return null;
}
}
}
警告

您的项目需要实现 @string/rustore_app_scheme 字符串资源。

若要将字符串资源文件包含在项目的 Android 构建中,需要:

  1. 创建一个包含所需值的字符串资源文件。 例如,rustore_billing_values.xml
rustore_billing_values.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="rustore_app_scheme">yourscheme</string>
</resources>
  1. 在项目的 UPL 脚本中添加文件复制操作。 文件将复制到 Android 构建资源中:
YourProjectName_UPL_Android.xml
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:android="http://schemas.android.com/apk/res/android">
<resourceCopies>
<copyFile src="$S(PluginDir)/rustore_billing_values.xml" dst="$S(BuildDir)/res/values/rustore_billing_values.xml" />
</resourceCopies>
</root>

在上面的示例中,文件从 Source/YOUR_PROJECT_NAME 目录复制。

  1. @string/rustore_app_scheme 的值是您的 deeplink 方案 (scheme)。 该方案必须与初始化 billing-client 时指定的方案一致。

初始化

在调用库方法之前,需要对其进行初始化。

调用 Init 方法
FURuStoreBillingClientConfig config;
config.consoleApplicationId = "123456";
config.deeplinkScheme = "yourscheme";
config.enableLogs = false;

URuStoreBillingClient::Instance()->Init(config);

所有客户端操作也可以通过 Blueprints 实现。 以下是初始化示例。

img
在 RuStore 控制台的哪里可以查看应用程序 ID?
  1. 转到应用程序选项卡并选择所需的应用程序。
  2. 从应用程序页面的 URL 地址中复制 ID —— 即 apps//versions 之间的那一组数字。 例如,对于 URL 地址 https://console.rustore.ru/apps/123456/versions,应用程序 ID 为 123456

  • deeplinkSheme — 用于使用深度链接的 URL 地址。 作为名称,可以使用任何唯一的名称(例如:yourappscheme)。
  • enableLogs — 启用事件日志记录。

调用 Init() 会将对象绑定到场景根节点。如果之后不再计划使用该对象,则必须调用 Dispose() 方法以释放内存。

反初始化

调用 Dispose 方法
URuStoreBillingClient::Instance()->Dispose();
img

检查初始化状态

如果您需要检查库是否已初始化,请使用 GetIsInitialized() 方法。 如果库已初始化,该方法将返回 true;如果尚未调用 Init,则返回 false

调用 GetIsIninialized 方法
bool isInitialized = URuStoreBillingClient::Instance()->GetIsIninialized();
img

支付工作流程

Payment appRuStore Server RuStore_Billing_ClientYour serverYour appUserPayment appRuStore Server RuStore_Billing_ClientYour serverYour appUserPayments availability check[Optional]Server validation[Mandatory]Purchasing consumable product[Mandatory]Deeplink processing for paying with SBP, SberPay, etc.Product purchaseStarts your app checkPurchasesAvailabilityResult getProductsProducts list of your appDisplaying list of available purchasesPurchasing product purchaseProductRequest payment methodMaking paymentPayment methodPayment resultPayment infoServer validation (public API)Reliable purchase informationDeliver product to the userValidation result confirmPurchaseConsumption result purchaseProductRequest payment methodSpecified SBP/SberPay/T-PayStart payment processPayment scenarioPurchase paymentReturn to app OnNewIntentPayment resultDisplaying payment screen with the result

检查支付功能可用性

RuStore 可用性检查流程与结果
未 安装

该方法将返回以下参数:

  • FURuStorePurchaseAvailabilityResult::isAvailable == false.
  • FURuStorePurchaseAvailabilityResult::cause.name == "Error".
  • FURuStorePurchaseAvailabilityResult::cause.description == "Unknown response type".

由于支持在未安装 RuStore 的情况下接收付款,因此可以进行支付。

已安装

系统将检查以下条件。

  • 用户设备上已安装最新版本的 RuStore
  • RuStore 应用程序支持支付功能
  • 用户已在 RuStore 中登录
  • 用户和应用在 RuStore 中均未被封禁。

  • 已在 RuStore 控制台 为应用程序开启购买功能

如果上述所有条件均满足,则在 onSuccess 事件中返回 FURuStorePurchaseAvailabilityResult::isAvailable == true

否则,将返回 FURuStorePurchaseAvailabilityResult::isAvailable == false,且 FURuStorePurchaseAvailabilityResult::cause 为未满足条件的错误。

其他错误将在 onFailure 事件中返回。 所有可能的 RuStoreException 错误均在错误处理章节中描述。

已弃用

该方法已弃用,不建议使用。

请使用以下方法检查支付可用性: CheckPurchasesAvailability.

每个异步请求在单次应用程序启动过程中都会获得一个唯一的 requestId。 每个事件都会返回触发该事件的请求的 requestId

调用 CheckPurchasesAvailability 方法
long requestId = URuStoreBillingClient::Instance()->CheckPurchasesAvailability( 
[]( long requestId, TSharedPtr<FURuStorePurchaseAvailabilityResult, ESPMode::ThreadSafe> response) {
// Process response
},
[]( long requestId, TSharedPtr<FURuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img

Success 回调通知将在 Response 参数中返回 FURuStorePurchaseAvailabilityResult 结构体(见下文)。

USTRUCT(BlueprintType)
struct RUSTORECORE_API FURuStorePurchaseAvailabilityResult
{
GENERATED_USTRUCT_BODY()

FURuStorePurchaseAvailabilityResult()
{
isAvailable = false;
}

UPROPERTY(BlueprintReadWrite)
bool isAvailable;

UPROPERTY(BlueprintReadWrite)
FURuStoreError cause;
};
  • isAvailable — 支付执行条件的满足情况 (true/false)。
  • cause — 错误信息。

所有可能的 cause 错误都在错误处理章节中进行了描述。 其他错误将在 Failure 中返回。

Failure 回调通知在 Error 参数中返回包含错误信息的 FURuStoreError 结构。 FURuStoreError 错误结构体在错误处理章节中有所描述。

检查 RuStore 应用是否安装

若要检查用户设备上是否安装了 RuStore,需要调用 IsRuStoreInstalled 方法。

bool bIsRuStoreInstalled = URuStoreBillingClient::Instance()->IsRuStoreInstalled();
img
  • true – 已安装 RuStore。

  • false – 未安装 RuStore。

SDK 方法

确定用户是否已授权

BillingClient 可以确定用户是否已通过身份验证。 要确定用户是否已通过身份验证,需要调用 GetAuthorizationStatus 方法。

调用 GetAuthorizationStatus 方法
long requestId = URuStoreBillingClient::Instance()->GetAuthorizationStatus(
[](long requestId, TSharedPtr<FURuStoreUserAuthorizationStatus, ESPMode::ThreadSafe> response) {
// Process response
},
[](long requestId, TSharedPtr<FURuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img

Success 回调通知在 Response 参数中返回 FURuStoreUserAuthorizationStatus 结构(见下文)。

USTRUCT(BlueprintType)
struct FURuStoreUserAuthorizationStatus
{
GENERATED_USTRUCT_BODY()

FURuStoreUserAuthorizationStatus()
{
authorized = false;
}

UPROPERTY(BlueprintReadOnly)
bool authorized;
};

authorized — 用户的身份验证状态值。 如果为 true,则用户已在 RuStore 中通过身份验证。 如果为 false,则用户未通过身份验证。

重要

如果在 RuStore 之外使用 SDK,且用户在支付过程中通过 VK ID 进行了身份验证,并且距离验证时间不足 15 分钟,则结果也可能返回 true

Failure 回调通知在 Error 参数中返回包含错误信息的 FURuStoreError 结构。

FURuStoreError 错误结构体在错误处理章节中有所描述。

获取产品列表

您已确认支付功能可用,且用户可以完成购买。 现在可以获取产品列表。 使用 GetProducts() 方法获取通过 RuStore 控制台添加到您应用中的产品信息。

调用 GetProducts 方法
long requestId = URuStoreBillingClient::Instance()->GetProducts(
productsId,
[]( long requestId, TSharedPtr<FURuStoreProductsResponse, ESPMode::ThreadSafe> response) {
// Process response
},
[]( long requestId, TSharedPtr<FURuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img

TArray<FString> productIds — 产品标识符列表。 其中不得超过 100 个项目。

要指定该方法运行所需的产品 id,请执行以下操作。

  1. 打开 RuStore 控制台
  2. 转到应用程序选项卡。
  3. 选择所需的应用。
  4. 在左侧边栏菜单中选择货币化部分。
  5. 选择商品类型:订阅一次性购买
  6. 复制所需商品的 ID。 这些就是产品 id

Success 回调通知在 Response 参数中返回 FURuStoreProductsResponse 结构(见下文)。

GetProducts 的响应

USTRUCT(BlueprintType)
struct FURuStoreProductsResponse
{
GENERATED_USTRUCT_BODY()

UPROPERTY(BlueprintReadOnly)
TArray<FURuStoreProduct> products;
};

products — 产品列表

产品结构

USTRUCT(BlueprintType)
struct FURuStoreProduct
{
GENERATED_USTRUCT_BODY()

FURuStoreProduct()
{
productId = "";
productType = EURuStoreProductType::NON_CONSUMABLE;
productStatus = EURuStoreProductStatus::INACTIVE;
priceLabel = "";
price = 0;
currency = "";
language = "";
title = "";
description = "";
imageUrl = "";
promoImageUrl = "";
}

UPROPERTY(BlueprintReadOnly)
FString productId;

UPROPERTY(BlueprintReadOnly)
EURuStoreProductType productType;

UPROPERTY(BlueprintReadOnly)
EURuStoreProductStatus productStatus;

UPROPERTY(BlueprintReadOnly)
FString priceLabel;

UPROPERTY(BlueprintReadOnly)
int price;

UPROPERTY(BlueprintReadOnly)
FString currency;

UPROPERTY(BlueprintReadOnly)
FString language;

UPROPERTY(BlueprintReadOnly)
FString title;

UPROPERTY(BlueprintReadOnly)
FString description;

UPROPERTY(BlueprintReadOnly)
FString imageUrl;

UPROPERTY(BlueprintReadOnly)
FString promoImageUrl;

UPROPERTY(BlueprintReadOnly)
FURuStoreProductSubscription subscription;
};
  • productId — 在 RuStore 控制台中为产品分配的产品标识符(必填参数)
  • productType — 产品类型(消耗性 / 非消耗性 / 订阅):CONSUMABLE/NON-CONSUMABE/SUBSCRIPTION
  • productStatus — 产品状态
  • priceLable — 格式化后的商品价格,包含 language 语言的货币符号
  • price — 以最小单位(分)表示的价格
  • currency — ISO 4217 货币代码
  • language — 使用 BCP 47 编码指定的语言
  • title — 使用 language 语言显示的产品名称
  • description — 使用 language 语言的描述
  • imageUrl — 图片链接
  • promoImageUrl — 促销图片的链接
  • subscription — 订阅描述,仅针对类型为 subscription 的产品返回

产品类型

UENUM(BlueprintType)
enum class EURuStoreProductType : uint8
{
NON_CONSUMABLE UMETA(DisplayName = "NON_CONSUMABLE"),
CONSUMABLE UMETA(DisplayName = "CONSUMABLE"),
SUBSCRIPTION UMETA(DisplayName = "SUBSCRIPTION")
};

产品状态

UENUM(BlueprintType)
enum class EURuStoreProductStatus : uint8
{
ACTIVE UMETA(DisplayName = "ACTIVE"),
INACTIVE UMETA(DisplayName = "INACTIVE")
};

订阅结构

USTRUCT(BlueprintType)
struct FURuStoreProductSubscription
{
GENERATED_USTRUCT_BODY()

FURuStoreProductSubscription()
{
introductoryPrice = "";
introductoryPriceAmount = "";
}

UPROPERTY(BlueprintReadOnly)
FURuStoreSubscriptionPeriod subscriptionPeriod;

UPROPERTY(BlueprintReadOnly)
FURuStoreSubscriptionPeriod freeTrialPeriod;

UPROPERTY(BlueprintReadOnly)
FURuStoreSubscriptionPeriod gracePeriod;

UPROPERTY(BlueprintReadOnly)
FString introductoryPrice;

UPROPERTY(BlueprintReadOnly)
FString introductoryPriceAmount;

UPROPERTY(BlueprintReadOnly)
FURuStoreSubscriptionPeriod introductoryPricePeriod;
};
  • subscriptionPeriod — 订阅周期
  • freeTrialPeriod — 订阅试用期
  • gracePeriod — 订阅宽限期
  • introductoryPrice — 格式化后的订阅入门价格,包含货币符号,使用 product:language 语言
  • introductoryPriceAmount — 以货币最小单位(分)表示的入门价格
  • introductoryPricePeriod — 入门价格的计费周期

订阅周期时长结构

USTRUCT(BlueprintType)
struct FURuStoreSubscriptionPeriod
{
GENERATED_USTRUCT_BODY()

FURuStoreSubscriptionPeriod()
{
years = 1970;
months = 1;
days = 1;
}

UPROPERTY(BlueprintReadOnly)
int years;

UPROPERTY(BlueprintReadOnly)
int months;

UPROPERTY(BlueprintReadOnly)
int days;
};
  • years — 年数
  • months — 月数
  • days — 天数

Failure 回调通知在 Error 参数中返回包含错误信息的 FURuStoreError 结构。

FURuStoreError 错误结构体在错误处理章节中有所描述。

购买产品

要调用产品购买,请使用 PurchaseProduct() 方法。

调用购买产品的方法

调用 PurchaseProduct 方法
long requestId = URuStoreBillingClient::Instance()->PurchaseProduct(
productId,
orderId,
quantity,
developerPayload,
[]( long requestId, TShardPtr<FURuStorePaymentResult, ESPMode::ThreadSafe> response) {
// Process response
},
[]( long requestId, TSharedPtr<FURuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img
  • productId — 在 RuStore 控制台中为产品分配的产品标识符(必填参数)
  • orderId: String — 由应用程序生成的唯一支付标识符(可选参数)。 如果您在系统中指定此参数,将在 API 响应中收到该参数。 如果不指定,则会自动生成 (uuid)。 最大长度为 150 个字符
  • quantity — 产品数量。 可选参数,默认值为 1。 仅适用于购买消耗性商品
  • developerPayload — 包含订单附加信息的字符串,您可以在确认购买时设置该信息。 此字符串将覆盖初始化时设置的值。 最大长度为 250 个字符。 字符不进行转义(使用引号时需要转义)

Success 回调通知在 Response 参数中返回一个指向 FURuStorePaymentResult 结构体派生类对象的智能线程安全 (ESPMode::ThreadSafe) 指针。

可以使用 GetTypeName() 方法获取派生类对象的类型。 可以通过 StaticCastSharedPtr<> 执行类型转换。

调用 GetTypeName 和 StaticCastSharedPtr 方法
// TShardPtr<FURuStorePaymentResult, ESPMode::ThreadSafe> response
FString typeName = response->GetTypeName();
if (typeName == "FURuStoreSuccess")
{
auto success = *StaticCastSharedPtr<FURuStoreSuccess>(response);
}

可能的类型值:

  • FURuStoreSuccess — 数字商品购买成功的结果
  • FURuStoreFailure — 在发送支付请求或获取支付状态时出现问题,无法确定购买状态
  • FURuStoreCancelled — 购买请求已发送,但用户关闭了设备上的“支付面板”,支付结果未知
  • FURuStoreInvalidPaymentState — 支付 SDK 运行错误。 在返回的 deeplink 不正确的情况下可能会出现

在 Blueprint 实现中,Success 回调通知在 Response 参数中返回一个 URuStorePaymentResultClass 类的派生类对象。 可以通过 Cast To 调用链执行向派生类型的转换。

img

购买结果结构

UCLASS(BlueprintType)
class RUSTOREBILLING_API URuStorePaymentResultClass : public UObject
{
GENERATED_BODY()
};

USTRUCT(BlueprintType)
struct RUSTOREBILLING_API FURuStorePaymentResult
{
GENERATED_USTRUCT_BODY()

virtual ~FURuStorePaymentResult() {}

virtual FString GetTypeName() { return "FURuStorePaymentResult"; }
};

Success 购买结果的结构

UCLASS(BlueprintType)
class RUSTOREBILLING_API URuStoreSuccess : public URuStorePaymentResultClass
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintReadOnly)
FURuStoreSuccess value;
};

USTRUCT(BlueprintType)
struct RUSTOREBILLING_API FURuStoreSuccess : public FURuStorePaymentResult
{
GENERATED_USTRUCT_BODY()

FURuStoreSuccess()
{
orderId = "";
purchaseId = "";
productId = "";
invoiceId = "";
subscriptionToken = "";
sandbox = false;
}

UPROPERTY(BlueprintReadOnly)
FString orderId;

UPROPERTY(BlueprintReadOnly)
FString purchaseId;

UPROPERTY(BlueprintReadOnly)
FString productId;

UPROPERTY(BlueprintReadOnly)
FString invoiceId;

UPROPERTY(BlueprintReadOnly)
FString subscriptionToken;

UPROPERTY(BlueprintReadOnly)
bool sandbox;

virtual FString GetTypeName() override { return "FURuStoreSuccess"; }
};

Cancelled 购买结果的结构

UCLASS(BlueprintType)
class RUSTOREBILLING_API URuStoreCancelled : public URuStorePaymentResultClass
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintReadOnly)
FURuStoreCancelled value;
};

USTRUCT(BlueprintType)
struct RUSTOREBILLING_API FURuStoreCancelled : public FURuStorePaymentResult
{
GENERATED_USTRUCT_BODY()

FURuStoreCancelled()
{
purchaseId = "";
sandbox = false;
}

UPROPERTY(BlueprintReadOnly)
FString purchaseId;

UPROPERTY(BlueprintReadOnly)
bool sandbox;

virtual FString GetTypeName() override { return "FURuStoreCancelled"; }
};

Failure 购买结果的结构

UCLASS(BlueprintType)
class RUSTOREBILLING_API URuStoreFailure : public URuStorePaymentResultClass
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintReadOnly)
FURuStoreFailure value;
};

USTRUCT(BlueprintType)
struct RUSTOREBILLING_API FURuStoreFailure : public FURuStorePaymentResult
{
GENERATED_USTRUCT_BODY()

public:
FURuStoreFailure()
{
purchaseId = "";
invoiceId = "";
orderId = "";
quantity = 0;
productId = "";
errorCode = 0;
sandbox = false;
}

UPROPERTY(BlueprintReadOnly)
FString purchaseId;

UPROPERTY(BlueprintReadOnly)
FString invoiceId;

UPROPERTY(BlueprintReadOnly)
FString orderId;

UPROPERTY(BlueprintReadOnly)
int quantity;

UPROPERTY(BlueprintReadOnly)
FString productId;

UPROPERTY(BlueprintReadOnly)
int errorCode;

UPROPERTY(BlueprintReadOnly)
bool sandbox;

virtual FString GetTypeName() override { return "FURuStoreFailure"; }
};
信息

sandbox 参数用于确定该笔付款是否为测试付款。 取值可以是 truefalse, 其中 true 表示测试付款,false 表示真实付款。

InvalidPaymentState 购买结果的结构

UCLASS(BlueprintType)
class RUSTOREBILLING_API URuStoreInvalidPaymentState : public URuStorePaymentResultBase
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintReadOnly)
FURuStoreInvalidPaymentState value;
};

USTRUCT(BlueprintType)
struct RUSTOREBILLING_API FURuStoreInvalidPaymentState : public FURuStorePaymentResult
{
GENERATED_USTRUCT_BODY()

virtual FString GetTypeName() override { return "FURuStoreInvalidPaymentState"; }
};

Failure 回调通知在 Error 参数中返回包含错误信息的 FURuStoreError 结构。 FURuStoreError 错误结构体在错误处理章节中有所描述。

获取购买列表

该方法仅返回下表中具有相应状态的购买记录。 有关购买的其他可能状态的详细信息,请参阅获取购买信息部分。

类型/状态INVOICE_CREATEDCONFIRMEDPAID
CONSUMABLE++
NON-CONSUMABLE++
SUBSCRIPTION++
备注

该方法返回未完成的购买状态以及需要处理的可消耗商品购买记录。 此外,它还会显示订阅和不可消耗商品(即无法重复购买的商品)的已确认购买记录。

要获取用户的购买列表,请使用 GetPurchases() 方法。

调用 GetPurchases 方法
long requestId = URuStoreBillingClient::Instance()->GetPurchases(
[]( long requestId, TSharedPtr<FURuStorePurchasesResponse, ESPMode::ThreadSafe> response) {
// Process response
},
[]( long requestId, TSharedPtr<FURuStoreRuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img

Success 回调通知在 Response 参数中返回一个 FURuStorePurchasesResponse 结构体(见下文)。

GetPurchases 响应

USTRUCT(BlueprintType)
struct FURuStorePurchasesResponse: public FURuStoreResponseWithCode
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly)
TArray<FURuStorePurchase> purchases;
};

purchases — 请求的购买列表

购买结构

USTRUCT(BlueprintType)
struct FURuStorePurchase
{
GENERATED_USTRUCT_BODY()

FURuStorePurchase()
{
purchaseId = "";
productId = "";
invoiceId = "";
language = "";
purchaseTime = FDateTime(0);
orderId = "";
amountLabel = "";
amount = 0;
currency = "";
quantity = 0;
purchaseState = EURuStorePurchaseState::CANCELLED;
developerPayload = "";
subscriptionToken = "";
}

UPROPERTY(BlueprintReadOnly)
FString purchaseId;

UPROPERTY(BlueprintReadOnly)
FString productId;

UPROPERTY(BlueprintReadOnly)
FString invoiceId;

UPROPERTY(BlueprintReadOnly)
FString language;

UPROPERTY(BlueprintReadOnly)
FDateTime purchaseTime;

UPROPERTY(BlueprintReadOnly)
FString purchaseTimeLabel;

UPROPERTY(BlueprintReadOnly)
FString orderId;

UPROPERTY(BlueprintReadOnly)
FString amountLabel;

UPROPERTY(BlueprintReadOnly)
int amount;

UPROPERTY(BlueprintReadOnly)
FString currency;

UPROPERTY(BlueprintReadOnly)
int quantity;

UPROPERTY(BlueprintReadOnly)
EURuStorePurchaseState purchaseState;

UPROPERTY(BlueprintReadOnly)
FString developerPayload;

UPROPERTY(BlueprintReadOnly)
FString subscriptionToken;
};

购买状态

UENUM(BlueprintType)
enum class EURuStorePurchaseState : uint8
{
CREATED UMETA(DisplayName = "CREATED"),
INVOICE_CREATED UMETA(DisplayName = "INVOICE_CREATED"),
CONFIRMED UMETA(DisplayName = "CONFIRMED"),
PAID UMETA(DisplayName = "PAID UMETA"),
CANCELLED UMETA(DisplayName = "CANCELLED"),
CONSUMED UMETA(DisplayName = "CONSUMED"),
PAUSED UMETA(DisplayName = "PAUSED"),
CLOSED UMETA(DisplayName = "CLOSED")
};
  • purchaseId — 购买 ID

  • productId — 在 RuStore 控制台中为产品分配的产品标识符(必填参数)

  • invoiceId — 账单标识符

  • language — 使用 BCP 47 编码指定的语言

  • purchaseTime — 购买时间

  • orderId — 由应用程序生成的唯一支付标识符(可选参数)。 如果您在系统中指定此参数,将在 API 响应中收到该参数。 如果不指定,则会自动生成 (uuid)。 最大长度为 150 个字符

  • amountLable — 格式化后的购买价格,包含货币符号

  • amount — 以货币最小单位计算的价格

  • currency — ISO 4217 货币代码

  • quantity — 产品数量。 可选参数,默认值为 1。 仅适用于购买消耗性商品

  • purchaseState — 购买状态

    • CREATED — 购买已创建
    • INVOICE_CREATED — 已创建付款账单,购买等待支付
    • PAID — 仅用于购买消耗性商品 — 中间状态,买家账户资金已预留。 购买等待开发者确认
    • CONFIRMED — 非消耗性商品的支付已成功完成
    • CONSUMED — 消耗性商品的支付已成功完成
    • CANCELLED — 购买已取消 — 未完成支付或已向买家退款(对于订阅,退款后购买状态不会转为 CANCELLED
    • PAUSED — 适用于订阅 — 订阅已进入 HOLD 期间
    • TERMINATED — 订阅已关闭
  • developerPayload — 包含订单附加信息的字符串,您可以在确认购买时设置该信息。 此字符串将覆盖初始化时设置的值。 最大长度为 250 个字符。 字符不进行转义(使用引号时需要转义)

  • subscriptionToken — 用于在服务器端验证购买的令牌

Failure 回调函数会返回一个 FURuStoreError 结构体,其 Error 参数中包含错误信息。

FURuStoreError 错误结构体在错误处理章节中有所描述。

获取购买详情

要获取购买信息,请使用 GetPurchaseInfo 方法。
long requestId = URuStoreBillingClient::Instance()->GetPurchaseInfo(
purchaseId,
[]( long requestId, TSharedPtr<FURuStorePurchaseInfoResponse, ESPMode::ThreadSafe> response) {
// Process response
},
[]( long requestId, TSharedPtr<FURuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img

purchaseId — 购买 ID

Success 回调通知会在 Response 参数中返回 FURuStorePurchase 结构。

Failure 回调通知会返回包含错误信息的 FURuStoreError 结构。 FURuStoreError 错误结构体在错误处理章节中有所描述。

状态模型 (purchaseState)

消费型商品购买的状态模型 (CONSUMABLES)

购买非消耗性商品的状态模型 (NON-CONSUMABLES)

订阅购买的状态模型 (SUBSCRIPTIONS)

img

确认(消耗)购买

需要确认(消耗)的产品

考虑购买类型。 确认方法(消耗)仅在您拥有可多次购买的可消耗商品(CONSUMABLE)时才是必需的。

要使此类商品能够无误地发放给用户,请使用 confirmPurchase 方法确认(消耗)产品。 在您的应用中发放商品时,请使用服务器端支付验证。 仅在付款(账单)进入最终状态 CONFIRMED 时才发放商品。 在 confirmPurchase 方法的 addOnSuccessListener 回调中向用户发放商品。

请注意!

状态 PAID 是中间状态,表示用户的资金已被冻结在卡上,您需要确认购买。

例外情况 —— 通过 СБП 或手机账户付款:这些付款方式使用单阶段付款,但账单模型仍为双阶段。 有关详细信息,请参阅下文说明。

通过 СБП 或手机账户支付消耗性 (CONSUMABLE) 商品时,使用单阶段付款,但账单模型仍为双阶段。 这意味着,当通过 СБП 或手机账户付款且账单进入 PAID 状态时,资金已从买家账户扣除,且已从开发者处扣除佣金。 在这种情况下,当取消处于 PAID 状态的购买时,会执行退款(refund),而不是取消预授权——reverse。 扣除的佣金不会退还给开发者。 即便如此,为了完成购买,仍然需要执行确认(消耗)方法 —— 另请参阅下表。

付款方式支付类型付款状态为 PAID
  • 银行卡;
  • 储蓄帐户ID;
  • SberPay;
  • T-Pay;
  • VK Pay.
两阶段
  • 资金已在买家账户中冻结。
  • 开发者佣金未扣除。
  • 支付可能会被取消。
  • SBP公司;
  • 从移动电话号码付款。
单阶段
  • 资金已从买家账户中扣除。
  • 已扣除开发者佣金。
  • 取消处于 PAID 状态的购买时,会执行退款(refund),而不是撤销预授权(reverse)。 扣除的佣金不会退还给开发者。

调用确认(消耗)方法

要确认(消耗)购买,请使用 ConfirmPurchase 方法。 购买确认(消费)请求必须伴随商品交付。 调用确认后,购买项将进入 CONSUMED 状态。

调用 ConfirmPurchase 方法
long requestId = RuStoreBillingClient::Instance()->ConfirmPurchase(
purchaseId,
[]( long requestId, TSharedPtr<FURuStoreError, ESPMode::ThreadSafe> error) {
// Process error
},
[]( long requestId, TSharedPtr<FURuStoreConfirmPurchaseResponse, ESPMode::ThreadSafe> response) {
// Process response
}
);
img
  • purchaseId — 购买 ID

Failure 回调函数会返回一个 FURuStoreError 结构体,其 Error 参数中包含错误信息。 FURuStoreError 错误结构体在错误处理章节中有所描述。

取消购买

要取消购买,请使用 DeletePurchase 方法。

调用取消购买方法

调用 DeletePurchase 方法
long requestId = URuStoreBillingClient::Instance()->DeletePurchase(
purchaseId,
[]( long requestId, TSharedPtr<FURuStoreDeletePurchaseResponse, ESPMode::ThreadSafe> response) {
// Process response
},
[]( long requestId, TSharedPtr<FURuStoreRuStoreError, ESPMode::ThreadSafe> error) {
// Process error
}
);
img
  • purchaseId — 购买 ID
信息

请谨慎使用取消购买方法,且仅在异常情况下使用,即购买状态仍为 PAID 但您无法向用户交付商品时。 在其他情况下,不建议手动取消购买,因为 RuStore 会自动处理未完成的购买(例如 20 分钟超时或同一客户端的重复购买)。

当通过 SBP 或从移动电话号码账户支付购买时,取消状态为 PAID 的购买不会解除资金冻结,而是会导致退款(refund)。 在此情况下,将向开发者收取不可退还的佣金。

Failure 回调函数会返回一个 FURuStoreError 结构体,其 Error 参数中包含错误信息。 FURuStoreError 错误结构体在错误处理章节中有所描述。

处理未完成的支付

未完成付款的处理由开发者执行。

要确认类型为 CONSUMABLE 且状态为 PAID 的产品购买(参见获取购买信息),请调用购买确认(消耗)方法。 如果没有确认,RuStore 将不会把资金转入开发者账户。 此操作对于完成购买流程并向用户发放商品是必需的。

如果您因任何原因无法提供已付款的商品,建议您自行取消购买。 如果您不执行此操作,RuStore 将在一定时间后自动取消该笔购买。

请谨慎使用购买取消方法,仅在极端情况下使用,例如用户已支付商品(购买状态为 PAID),但您无法提供该商品。 在其他情况下,不建议手动取消购买——RuStore 会自动处理状态为 INVOICE_CREATED 及其他中间状态的购买。

特别注意通过 SBP 或从移动电话号码账户支付时:

  • 当取消状态为 PAID 的购买时,资金不会被解除锁定(reverse),而是退还给用户(refund),因为上述支付方式不支持两阶段支付技术。 在这种情况下,将向开发者收取不可退还的佣金。 因此,在取消之前,请确认这确实是必要的。

如果您使用自定义的支付处理逻辑,建议在取消前始终额外检查当前购买状态。

您也可以通过 RuStore API 来确认或取消购买:

提示

如果用户已支付商品,而您因某些原因无法向其提供该商品,请调用处于 PAID 状态的购买取消方法以取消该购买。

记录事件日志

如果您需要记录支付库的事件,请在调用 Init 方法时,将 FURuStoreBillingClientConfig 结构体中的 enableLogs 参数设置为 true

调用 Init 方法
FURuStoreBillingClientConfig config;
config.consoleApplicationId = "123456";
config.deeplinkScheme = "yourscheme";
config.enableLogs = true;

URuStoreBillingClient::Instance()->Init(config);
img

此时将使用实现 Logcat 消息输出的 BillingClientLogger 对象。

class BillingClientLogger(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)
}
}

更改界面主题

要动态切换主题,请使用 SetTheme 方法。

调用 SetTheme 方法
EURuStoreTheme theme = EURuStoreTheme::DARK;
URuStoreBillingClient::Instance()->SetTheme(theme);
img

theme — 来自 EURuStoreTheme 枚举的主题类型。

主题类型

UENUM(BlueprintType)
enum class EURuStoreTheme : uint8
{
DARK UMETA(DisplayName = "DARK"),
LIGHT UMETA(DisplayName = "LIGHT")
};
  • DARK — 深色主题。
  • LIGHT — 浅色主题。

可以使用 GetTheme 方法获取已设置的主题信息。

调用 GetTheme 方法
EURuStoreTheme theme = URuStoreBillingClient::Instance()->GetTheme();
img

错误处理

可能的错误

  • RuStoreNotInstalledException — 用户设备上未安装 RuStore
  • RuStoreOutdatedException — 用户设备上安装的 RuStore 版本不支持此 SDK
  • RuStoreUserUnauthorizedException — 用户未在 RuStore 中登录
  • RuStoreRequestLimitReached — 自上次显示流程以来经过的时间太短
  • RuStoreReviewExists — 该用户已经评价了您的应用程序
  • RuStoreInvalidReviewInfoReviewInfo 出现问题
  • RuStoreException — RuStore 基础错误,其他错误均继承自此类

错误结构

USTRUCT(BlueprintType)
struct RUSTORECORE_API FURuStoreRuStoreError
{
GENERATED_USTRUCT_BODY()
FURuStoreRuStoreError()
{
name = "" ;
description = "" ;
}

UPROPERTY(BlueprintReadOnly)
FString name;

UPROPERTY(BlueprintReadOnly)
FString description;
};
  • name – 错误名称
  • description – 错误描述

错误代码

以下是 errorCode 字段中可能出现的错误说明。

HTTP 状态码错误代码描述
40040001请求参数无效——未填写必填参数或参数格式不正确。
40040003未找到应用。
40040004应用状态为 inactive
40040005未找到产品。
40040006产品状态为 inactive
40040007无效的产品类型。 支持的类型:consumable(消耗型)、non-consumable(非消耗型)、subscription(订阅型)。
40040008具有此 order_id 的购买已存在。
40040009当前客户端已找到该产品的购买记录,状态为 invoice_created。 必须向客户提出支付或取消购买的要求。
40040010适用于 consumable 产品类型。 当前客户端已找到该产品的购买记录,状态为 paid。 首先需要在设备上确认购买消费,然后才能发送对该产品的下一次购买请求。
40040011适用于 non-consumable 产品类型。 当前客户端已找到该产品的购买记录,状态为 pre_confirmed/confirmed。 该产品已被购买。 该产品不能购买多次。
40040012适用于 subscription 产品类型。 当前客户端已找到该产品的购买记录,状态为 pre_confirmed/confirmed。 该产品已被购买。 该产品不能购买多次。
40040013适用于 subscription 产品类型。 在调用订阅服务获取产品列表 GET/products (serviceId, user_id) 时,未收到数据。
40040014请求中缺少必需的属性。
40040015更新购买项时无法更改状态(禁止跳转)。
400�C1购买非消耗性产品订阅时,quantity 值大于 1。
40040017产品已删除,无法进行新购买。
40040018无法确认类型为 产品类型 的产品。
40140101无效的令牌。
40140102令牌已过期。
40340301禁止访问请求的资源(未授权)。
40340302当前令牌未授权本次调用(方法被禁止)。
40340303请求中的应用标识符与令牌不匹配。
40340305令牌类型不正确。
40440401未找到。
40840801请求中指定的通知等待时间已过期。
50050***支付服务内部错误。