带你玩转俄罗斯 rustore支付接入 横空出世

53 阅读6分钟

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 支付的核心流程。关键点在于:

  1. 初始化:确保 SDK 正确初始化。
  2. 支付流程:查询 -> 支付 -> 消耗。
  3. 异常处理:务必实现 queryInventoryAsync 补单机制,防止用户支付后未发货。

希望这篇教程能帮助大家快速接入 RuStore 支付!