RuStore 支付接入实战指南
前言导读
各位同学大家好,有段时间没有见面了。应业务需求,最近整理了一份 RuStore(俄罗斯应用商店)支付接入的完整教程。本文将带大家一步步完成支付功能的集成,废话不多说,我们正式开始!
具体实现
0. 支付流程总览
在开始编写代码之前,我们先通过流程图梳理一下 RuStore 支付的完整交互逻辑。
graph TD
Start[开始] --> Init[1. 初始化 SDK]
Init --> Auth{2. 检查用户授权}
Auth -- 未授权 --> Login[引导用户登录]
Auth -- 已授权 --> CheckAvail{3. 检查支付可用性}
CheckAvail -- 不可用 --> Error[提示错误/结束]
CheckAvail -- 可用 --> QueryProd[4. 查询商品详情]
QueryProd --> CheckProd{商品有效?}
CheckProd -- 无效 --> Error
CheckProd -- 有效 --> Pay[5. 发起支付 launchBillingFlow]
Pay --> PayResult{支付结果}
PayResult -- 失败/取消 --> HandleFail[处理失败]
PayResult -- 成功 --> LocalRecord[6. 本地记录订单]
LocalRecord --> Consume[7. 验证并消耗商品 consumeAsync]
Consume --> ConsumeResult{消耗成功?}
ConsumeResult -- 是 --> Deliver[发放道具/服务]
ConsumeResult -- 否 --> Retry[保留状态等待补单]
Deliver --> End[结束]
subgraph 补单流程
Boot[启动/检查] --> QueryInv[8. 查询未消耗订单 queryInventoryAsync]
QueryInv --> HasInv{有未消耗订单?}
HasInv -- 是 --> Consume
HasInv -- 否 --> End
end
1. 添加依赖
在模块级的 build.gradle.kts (或 build.gradle) 文件中添加 RuStore SDK 的依赖。推荐使用 BOM (Bill of Materials) 来管理版本。
dependencies {
// 使用 BOM 管理版本,确保各模块版本一致
implementation(platform("ru.rustore.sdk:bom:2025.08.01"))
implementation("ru.rustore.sdk:pay")
}
2. 配置 AndroidManifest
在 AndroidManifest.xml 的 <application> 标签内添加以下配置。
1. 添加元数据 (Meta-data) 这些元数据用于标识应用 Scheme 和 RuStore 控制台的应用 ID。
<application ...>
<!-- 替换为你的 App Scheme -->
<meta-data
android:name="sdk_pay_scheme_value"
android:value="yourappscheme" />
<!-- 替换为 RuStore 控制台的应用 ID -->
<meta-data
android:name="console_app_id_value"
android:value="xxxxxx" />
<!-- 下面配置 Activity -->
</application>
2. 配置 Intent-Filter
在处理支付回调的 Activity(通常是发起支付的 Activity)中配置 intent-filter,以便接收支付结果的回调。请确保 android:scheme 的值与上面 meta-data 中的 sdk_pay_scheme_value 保持一致。
<activity android:name=".YourPaymentActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 必须与 sdk_pay_scheme_value 一致 -->
<data android:scheme="yourappscheme" />
</intent-filter>
</activity>
3. 初始化 SDK 与生命周期处理
在 Activity 或 Application 中初始化 RuStorePayClient,并处理生命周期回调(用于接收支付结果)。
private RuStorePayClient ruStorePayClient;
private IntentInteractor intentInteractor;
private Activity mActivity;
public void init(Activity activity) {
this.mActivity = activity;
// 获取 RuStorePayClient 实例
ruStorePayClient = RuStorePayClient.Companion.getInstance();
// 初始化后建议进行一些预检查
checkUserAuthorization();
}
/**
* 在 Activity 的 onCreate 中调用
* @param activity
* @param savedInstanceState
*/
public void onCreate(Activity activity, Bundle savedInstanceState) {
// 必须调用 super
super.onCreate(activity, savedInstanceState);
if (savedInstanceState == null) {
intentInteractor = RuStorePayClient.Companion.getInstance().getIntentInteractor();
intentInteractor.proceedIntent(activity.getIntent());
}
}
/**
* 在 Activity 的 onNewIntent 中调用
* @param intent
*/
public void onNewIntent(Intent intent) {
// 必须调用 super
super.onNewIntent(intent);
if (intentInteractor != null) {
intentInteractor.proceedIntent(intent);
}
}
4. 检查用户授权状态
在发起支付前,检查用户是否已登录 RuStore 账号。
private void checkUserAuthorization() {
if (ruStorePayClient == null) return;
ruStorePayClient.getUserInteractor().getUserAuthorizationStatus()
.addOnSuccessListener(new OnSuccessListener<UserAuthorizationStatus>() {
@Override
public void onSuccess(UserAuthorizationStatus status) {
switch (status) {
case AUTHORIZED:
Log.d("RuStoreSdk", "用户已授权");
break;
case UNAUTHORIZED:
Log.w("RuStoreSdk", "用户未授权,请引导用户登录");
// TODO: 处理未授权逻辑,例如提示用户登录
break;
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Throwable throwable) {
Log.e("RuStoreSdk", "检查授权失败: " + throwable.getMessage());
}
});
}
5. 检查支付服务可用性
确认用户的设备环境是否支持支付功能。
private void checkPurchaseAvailability(String userId, String productId, String cpOrderId) {
if (ruStorePayClient == null) return;
ruStorePayClient.getPurchaseInteractor().getPurchaseAvailability()
.addOnSuccessListener(result -> {
if (result instanceof PurchaseAvailabilityResult.Available) {
// 支付服务可用,继续查询商品详情
checkProductIdToPay(mActivity, userId, productId, cpOrderId);
} else if (result instanceof PurchaseAvailabilityResult.Unavailable) {
// 支付服务不可用
PurchaseAvailabilityResult.Unavailable unavailable = (PurchaseAvailabilityResult.Unavailable) result;
Log.e("RuStoreSdk", "支付不可用: " + unavailable.getCause().getMessage());
afterPaySDK(mActivity, false, 1001, "Payment Unavailable");
}
})
.addOnFailureListener(throwable -> {
Log.e("RuStoreSdk", "检查支付可用性出错", throwable);
afterPaySDK(mActivity, false, 1001, "Check Availability Failed");
});
}
6. 查询商品详情
在发起支付前,验证商品 ID 是否有效,并获取商品信息。
public void checkProductIdToPay(Activity activity, final String userId,
final String productIdStr, final String cpOrderId) {
List<ProductId> productIds = new ArrayList<>();
try {
productIds.add(new ProductId(productIdStr));
ruStorePayClient.getProductInteractor().getProducts(productIds)
.addOnSuccessListener(products -> {
if (products == null || products.isEmpty()) {
Log.e("RuStoreSdk", "未查询到商品信息: " + productIdStr);
return;
}
for (Product product : products) {
Log.d("RuStoreSdk", "查询到商品: " + product.getProductId().getValue());
if (productIdStr.equals(product.getProductId().getValue())) {
// 商品有效,发起支付
launchBillingFlow(activity, userId, productIdStr, cpOrderId);
}
}
})
.addOnFailureListener(throwable -> {
Log.e("RuStoreSdk", "查询商品失败", throwable);
});
} catch (Exception e) {
Log.e("RuStoreSdk", "查询商品异常", e);
}
}
7. 调起支付页面
构建支付参数并拉起 RuStore 收银台。
/**
* 调起支付
* @param activity 上下文
* @param userId 用户ID
* @param productIdStr 商品ID
* @param cpOrderId 订单号
*/
public void launchBillingFlow(Activity activity, final String userId,
final String productIdStr, final String cpOrderId) {
try {
// 构建支付参数
// ProductId: 商品ID
// Quantity: 数量 (默认1)
// OrderId: 订单号 (UUID)
// DeveloperPayload: 透传参数
// AppUserId: 应用用户ID
ProductPurchaseParams params = new ProductPurchaseParams(
new ProductId(productIdStr),
new Quantity(1),
new OrderId(cpOrderId),
new DeveloperPayload(cpOrderId),
new AppUserId(userId),
null
);
ruStorePayClient.getPurchaseInteractor().purchase(params, PreferredPurchaseType.ONE_STEP)
.addOnSuccessListener(new OnSuccessListener<ProductPurchaseResult>() {
@Override
public void onSuccess(ProductPurchaseResult paymentResult) {
handlePaymentSuccess(paymentResult, cpOrderId);
}
})
.addOnFailureListener(throwable -> {
Log.e("RuStoreSdk", "支付失败", throwable);
// TODO: 处理支付失败逻辑
})
.addOnCompletionListener(new OnCompletionListener() {
@Override
public void onComplete(Throwable throwable) {
// 支付失败或取消等完成状态
Log.v("RuStoreSdk", "addOnCompletionListener");
}
});
} catch (Exception e) {
Log.e("RuStoreSdk", "调起支付异常", e);
}
}
// 处理支付成功回调
private void handlePaymentSuccess(ProductPurchaseResult paymentResult, String cpOrderId) {
if (paymentResult instanceof ProductPurchaseResult.Success) {
// 注意:新版 SDK 可能返回不同的 Result 类型,请根据实际 SDK 版本调整
ProductPurchaseResult.Success successResult = (ProductPurchaseResult.Success) paymentResult;
// 如果需要从 Success 对象中获取更多特定信息
}
// 获取支付详情
PurchaseId purchaseId = paymentResult.getPurchaseId(); // 购买标识符。
ProductId productId = paymentResult.getProductId(); // 购买产品的标识符。
InvoiceId invoiceId = paymentResult.getInvoiceId(); // 发票标识符。
OrderId orderId = paymentResult.getOrderId(); // 唯一的付款标识符。
PurchaseType purchaseType = paymentResult.getPurchaseType(); // 购买类型。
Quantity quantity = paymentResult.getQuantity(); // 产品数量。
boolean sandbox = paymentResult.getSandbox(); // 沙盒标志。
Log.v("RuStoreSdk", "支付成功 - invoiceId:" + invoiceId);
Log.v("RuStoreSdk", "支付成功 - productId:" + productId);
Log.v("RuStoreSdk", "支付成功 - orderId:" + orderId);
Log.v("RuStoreSdk", "支付成功 - sandbox:" + sandbox);
Log.v("RuStoreSdk", "支付成功 - purchaseId:" + purchaseId);
// 1. 本地数据上报/记录 (模拟业务逻辑)
// insertOrUpdateSssDataByOriginalJson(...);
// 2. 验证票据并消耗商品 (关键步骤)
consumeAsync(mActivity, orderId.getValue(), purchaseId.getValue(),
productId.getValue(), invoiceId.getValue(), sandbox, cpOrderId);
}
8. 确认交易 / 消耗商品 (Consume)
RuStore 支付通常采用“两步支付”模式或需要“消耗”商品。对于消耗型商品(如游戏币),支付成功后必须调用确认/消耗接口,否则无法再次购买。
/**
* 消耗/确认订单
* @param activity 上下文
* @param orderId 订单ID
* @param purchaseId 购买ID
* @param productId 商品ID
* @param invoiceId 发票ID
* @param isSandbox 是否沙盒
* @param cpOrderId 开发者订单号
*/
private void consumeAsync(Activity activity, String orderId, String purchaseId,
String productId, String invoiceId, boolean isSandbox, String cpOrderId) {
try {
Log.d("RuStoreSdk", "开始消耗订单: " + purchaseId);
if (ruStorePayClient != null) {
ruStorePayClient.getPurchaseInteractor().confirmTwoStepPurchase(
new PurchaseId(purchaseId),
new DeveloperPayload(cpOrderId)
).addOnSuccessListener(unit -> {
Log.d("RuStoreSdk", "订单消耗/确认成功: " + purchaseId);
// TODO: 1. 发放道具给用户
// TODO: 2. 通知服务端订单完成
}).addOnFailureListener(throwable -> {
Log.e("RuStoreSdk", "订单消耗/确认失败: " + throwable.getMessage());
// TODO: 处理消耗失败,可能需要重试或记录日志
});
}
} catch (Throwable throwable) {
Log.e("RuStoreSdk", "consumeAsync 异常", throwable);
}
}
9. 补单机制:查询未消耗订单
在应用启动或特定时机,检查是否有已支付但未消耗的订单(例如支付后网络断开导致未回调),并进行补单处理。
/**
* 查询是否存在未消耗商品
*/
public void queryInventoryAsync() {
Log.d("RuStoreSdk", "开始查询未消耗订单...");
try {
if (ruStorePayClient == null) return;
// 查询状态为 PAID (已支付) 的 CONSUMABLE (消耗型) 商品
ruStorePayClient.getPurchaseInteractor().getPurchases(ProductType.CONSUMABLE_PRODUCT, ProductPurchaseStatus.PAID)
.addOnSuccessListener(purchases -> {
if (purchases != null && !purchases.isEmpty()) {
Log.d("RuStoreSdk", "发现未消耗订单数: " + purchases.size());
for (Purchase purchase : purchases) {
// 处理每一笔掉单
handleUnconsumedPurchase(purchase);
}
} else {
Log.d("RuStoreSdk", "无未消耗订单");
}
})
.addOnFailureListener(error -> {
Log.e("RuStoreSdk", "查询未消耗订单失败", error);
});
} catch (Exception e) {
Log.e("RuStoreSdk", "queryInventoryAsync 异常", e);
}
}
/**
* 处理单笔未消耗订单
*/
public void handleUnconsumedPurchase(Purchase purchase) {
String orderId = purchase.getOrderId().getValue();
String purchaseId = purchase.getPurchaseId().getValue();
String productId = purchase.getProductId().getValue();
String invoiceId = purchase.getInvoiceId().getValue();
boolean isSandbox = purchase.getSandbox();
Log.d("RuStoreSdk", "处理补单 OrderId: " + orderId);
// 重新执行消耗/验证流程
// 注意:这里的最后一个参数传 orderId 作为 cpOrderId,具体视业务逻辑而定
consumeAsync(mActivity, orderId, purchaseId, productId, invoiceId, isSandbox, orderId);
}
总结
以上就是接入 RuStore 支付的核心流程。关键点在于:
- 初始化:确保 SDK 正确初始化。
- 支付流程:查询 -> 支付 -> 消耗。
- 异常处理:务必实现
queryInventoryAsync补单机制,防止用户支付后未发货。
希望这篇教程能帮助大家快速接入 RuStore 支付!