Firebase Cloud Function 如何检查应用程序外部的 IAP 订阅更改?

How Firebase Cloudfunction check IAP subscribe changes outside from App?

我从 CodeLab his project with all steps are here Github 开始学习教程。 Codelab 对我帮助很大,谢谢! 我执行了这些步骤并从教程中删除了所有 IAP 产品并仅添加了可订阅产品。我在 Appstoreconnect 的同一个系列中有两个购买产品“Normal”和“Ultimate”。它运行良好,但我发现了一个问题:

情况A:

当用户从他们那里订阅一个时,它工作正常,但是当用户想在他的验证活动时间订阅另一个,比如从“普通”到“终极”或“终极”到“普通”,然后Firebase Cloud 不更新他的购买(产品 ID 和订单 ID 仍然是旧 ID)。当 Firebase 不更新时,立即让用户不升级到其他订阅。一年后他升级到另一个购买。

情况B:

同样的问题,但在应用程序之外。 用户订阅了一种产品,然后他从 Appstore 设置中的应用程序走出去并更改他的订阅产品。 Firebase 从 Apple 获取信息,但 Firebase Cloud 不更新用户的订阅信息。 你能解决这个问题吗?

我对 Codelab 的更改 ->

Constant.dart

const cloudRegion = 'europe-west1';

const subscriptionList = ["kunde_1_fahrzeug", "kunde_3_fahrzeug"];

//storeKeySubscription
const subscription_kunde_1_fahrzeug = 'kunde_1_fahrzeug';
const subscription_kunde_3_fahrzeug = 'kunde_3_fahrzeug';

IAPRepo.dart


  void updatePurchases() {
 // omitted

      // hasActiveSubscription = purchases.any((element) => element.productId == subscription_kunde_1_fahrzeug && element.status != Status.expired);
      //hasActiveSubscription = purchases.any((element) => element.productId == subscriptionList && element.status != Status.expired);
      hasActiveSubscription = purchases.any((element) => subscriptionList.any((x) => x == element.productId)  && element.status != Status.expired);
      for(PastPurchase x  in purchases){
        print("Gelb hasActiveSubscription IAP-REPO : ${x.productId} - ${x.status}");
      };

      hasUpgrade = purchases.any(
            (element) => subscriptionList.any((x) => x == element.productId),
      );
/*
      hasUpgrade = purchases.any(
            (element) => element.productId == storeKeyUpgrade,
      );
*/
      notifyListeners();

 // omitted
}

XXX_Purchase


  void purchasesUpdate() {
 // omitted

    if (products.isNotEmpty) {
      // subscriptions = products .where((element) => element.productDetails.id == subscription_kunde_1_fahrzeug) .toList();

      subscriptions = products
          .where((element) => subscriptionList.any((x) => x == element.productDetails.id))
          .toList();

      upgrades = products
          .where((element) => subscriptionList.any((x) => x == element.productDetails.id))
          .toList();

    }

 // omitted
}

  Future<void> loadPurchases() async {
 // omitted

    const ids = <String>{
      subscription_kunde_1_fahrzeug,
      subscription_kunde_3_fahrzeug,
      //storeKeyUpgrade,
    };

 // omitted
}

  Future<void> buy(PurchasableProduct product) async {
 // omitted

    // case storeKeyConsumable:
    // await iapConnection.buyConsumable(purchaseParam: purchaseParam);
    //  break;
      case subscription_kunde_1_fahrzeug:
        await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
        break;
      case subscription_kunde_3_fahrzeug:
      //case storeKeyUpgrade:
        await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
        break;
 // omitted
}

  Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
 // omitted

      if (validPurchase) {
        // Apply changes locally
        switch (purchaseDetails.productID) {
          case subscription_kunde_1_fahrzeug:
            print("Orange:  ID Produkt:  ${purchaseDetails.productID},  ${purchaseDetails.transactionDate},  ${purchaseDetails.verificationData},  ${purchaseDetails.status},  ${purchaseDetails.purchaseID},  ${purchaseDetails.pendingCompletePurchase}, switch (purchaseDetails.productID)  case: subscription_kunde_1_fahrzeug");
            counter.applyPaidMultiplier_kunde_1_fahrzeug();
            break;
          case subscription_kunde_3_fahrzeug:
            print("Orange: ID Produkt: ${purchaseDetails.productID},  ${purchaseDetails.transactionDate},  ${purchaseDetails.verificationData},  ${purchaseDetails.status},  ${purchaseDetails.purchaseID},  ${purchaseDetails.pendingCompletePurchase},  switch (purchaseDetails.productID)  case: subscription_kunde_3_fahrzeug");
            counter.applyPaidMultiplier_kunde_3_fahrzeug();
            break;
        //   case storeKeyConsumable:
        //    counter.addBoughtDashes(2000);
        //     break;
        /* case storeKeyUpgrade:
            _beautifiedDashUpgrade = true;
            break;

          */
 // omitted
}

PastPurchase


@immutable
class PastPurchase {
 // omitted

  String get title {
    switch (productId) {
      case subscription_kunde_1_fahrzeug:
        return 'Subscription';
      case subscription_kunde_3_fahrzeug:
        return 'Subscription';
      default:
        return productId;
    }
  }
 // omitted
}

Firebase Backend

export interface ProductData {
  productId: string;
  type: "SUBSCRIPTION" | "NON_SUBSCRIPTION";
}
export const productDataMap: { [productId: string]: ProductData } = {
  "kunde_1_fahrzeug": {
    productId: "kunde_1_fahrzeug",
    type: "SUBSCRIPTION",
  },
  "kunde_3_fahrzeug": {
    productId: "kunde_3_fahrzeug",
    type: "SUBSCRIPTION",
  },
};

问题是 Codelab 与订阅相关的问题简单得不切实际,并且依赖于 node.js 包,该包也不处理 in-family 订阅更改。 Apple 的订阅更改不提供新的 product_id,它们提供新的订阅 ID 作为 auto_renew_product_id,并且 product_id 与原始交易保持不变。转为 verbose: true 以在 运行 你的函数时看到它。

因此,要修复,您需要第三个函数来更改 in-app 订阅,您无法从 apple-receipt-verify 验证和 return 正确,因为该包没有不要提供您要切换到的 auto_renew_product_id。所以您需要一种新的方法来验证收据。

对于应用程序外部的更改,您需要修复 handleServerEvent,因为这对从应用程序外部更改订阅不起作用。

我建议 RevenueCat。成本极低,而且您将拥有一家对更新 API 有既得利益的公司。

你的收入基础需要稳固,虽然 Google Play Store 性能包和 server-side 代码对我来说一尘不染......他们与 Apple 的互动对最简单的场景。

编辑:有几点需要澄清,为未来的读者详述。

  1. 截至 2022 年 5 月,代码实验室中存在错字。您需要使用 Apple 的 Type 1 Notifications。
  2. Apple 自行处理 in-group 订阅更改。它将在一个组内退订和订阅。您还需要对您的 in-group 订阅与苹果进行排名。 Android 立即更改。
  3. Apple 以不同方式处理 down-grade、cross-grade 和升级。所有降级必须在更改前完成其任期。这样苹果就不用退款了。所以当你降级时,term 必须完成,然后 apple 会改变 sub 并触发服务器事件。
  4. 代码实验室逻辑有 Android creating/canceling 个子项,并在 firestore 中留下购买文档的子历史记录...这是因为每个子项更改都是取消和创建。然而,Apple 在内部更改了 subs,然后向服务器发送通知,告诉你发生了什么变化。所以代码实验室没有详细说明任何订阅历史的文档跟踪..每个子始终是一个文档...只是更改产品 ID 和状态。

简而言之:进行 Codelab 并达到应用的功能级别肯定会让您很好地了解每个商店如何处理订阅...以及他们处理订阅的方式有何不同。因为它与 in_app_purchase plug-in、服务器端验证和性能有关...虽然做得很好,但它是代码实验室和程序包提供的最低限度指导。