React-native-iap: IOS 检查自动续订订阅是否仍然有效

创建于 2018-09-27  ·  61评论  ·  资料来源: dooboolab/react-native-iap

react-native-iap 的版本

2.2.2

您遇到错误的平台(IOS 或 Android 或两者?)

IOS

预期行为

使用 RNIAP api 我们可以检查自动续订订阅是否仍然有效

实际行为

在 android 上,我这样做是为了检查:

export const isUserSubscriptionActive = async (subscriptionId) =>{
    // Get all the items that the user has
    const availablePurchases = await getAvailablePurchases();
    if(availablePurchases !== null && availablePurchases.length > 0){
        const subscription = availablePurchases.find((element)=>{
            return subscriptionId === element.productId;
        });
        if(subscription){
             // check for the autoRenewingAndroid flag. If it is false the sub period is over
              return subscription["autoRenewingAndroid"] == true;
            }
        }else{
            return false;
        }
    }
}

在 ios 上没有标志可以检查,并且 getAvailablePurchases 方法返回所有购买,即使是目前未激活的订阅。

有没有办法检查这个?

问候,
马科斯

📱 iOS 🚶🏻 stale

最有用的评论

这是我在 Android 和 iOS 上使用的功能,在 iOS 上正确排序以确保我们获得最新的收据数据:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

所有61条评论

我也在努力解决这个问题。 我还没有测试过它,但从我正在阅读的内容来看,我正在收集可以通过在 iTunes Connect 中创建一个“共享秘密”并将其传递给带有密钥“密码”的 validateReceiptIos 来实现。 我相信这将返回一个 JSON 对象,该对象将包含一个代码,指示订阅的验证状态和密钥 latest_receipt、latest_receipt_info 和 latest_expired_receipt 信息等,您可以使用它们来确定订阅状态。 我只是想弄清楚这一点,所以我还没有测试它。 这是我从问题和 Apple 的文档中总结出来的。 如果这有效,它真的应该在文档中非常清楚,而不是被埋没在问题中。
我相信以下链接是相关的:
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

203#237

编辑:我可以确认我上面提到的过程有效。 我认为我们应该努力在文档中获得对此的完整解释。 我会在应用程序启动时实施类似的操作,以确定订阅是否已过期:

RNIap.getPurchaseHistory()
        .then(purchases => {
                RNIap.validateReceiptIos({
                   //Get receipt for the latest purchase
                    'receipt-data': purchases[purchases.length - 1].transactionReceipt,
                    'password': 'whateveryourpasswordis'
                }, __DEV__)
                    .then(receipt => {
                       //latest_receipt_info returns an array of objects each representing a renewal of the most 
                       //recently purchased item. Kinda confusing terminology
                        const renewalHistory = receipt.latest_receipt_info
                       //This returns the expiration date of the latest renewal of the latest purchase
                        const expiration = renewalHistory[renewalHistory.length - 1].expires_date_ms
                       //Boolean for whether it has expired. Can use in your app to enable/disable subscription
                        console.log(expiration > Date.now())
                    })
                    .catch(error => console.log(`Error`))
        })

@kevinEsherick

杰出的!

只是一些对你的代码来说不是那么琐碎的事情;

  1. 它应该是

const 过期 =更新历史[renewalHistory.length - 1].expires_date_ms

而不是(可能只是一个错字)

const expires =

2) 对于那些不知道什么是“密码”的人:“whateveryourpasswordis”:

您可以为 In App paments 创建共享密钥并在您的应用程序中使用它

步骤在这里: https :

+1 更新文档,也许@doobolab您可以将 IOS 和 Android 中的用例分开,似乎有一些不同之处。 如果你愿意,我可以帮你。

问候

@kevinEsherick

自动续订订阅会发生什么?

好像expires_date_ms是第一个过期日期,没有更新

我已经测试过这个:

  1. 订阅订阅项目
  2. 检查到期日期(如 5 分钟后)
  3. 等待 10 分钟
  4. 订阅仍然有效似乎是自动续订订阅
  5. 如果我检查上次续订收据到期仍然与第 2 点相同

有任何想法吗?

我的糟糕,到期日期似乎更新了,只是需要一段时间。

如果有人遇到同样的情况,我会在这里留下这个评论。

问候

  1. 它应该是

const 过期 =更新历史[renewalHistory.length - 1].expires_date_ms

而不是(可能只是一个错字)

const expires =

哎呀,是的,你是对的,那是一个错字。 我更改了名称以使其含义更明显,但忘记更改所有出现的内容。 我已经编辑了,谢谢。

而且我还没有确切的到期日期情况,但是有些东西根本不会自动续订,尽管它以某种方式自行解决了(这让我担心,但我无法重现,所以我不知道还能做什么)。 你遇到的问题是一个相当普遍的问题。 这是 Apple 端的预期行为,因此您应该在取消订阅用户之前为订阅的续订留出缓冲期。 SO上的这个问题更详细地解释了: https :

谢谢! 很好的信息,可能是原因

关于这个问题,我认为@kevinEsherick代码应该在文档上👍 这是检查订阅者的好方法!

是的。 如果你们中的任何人要求PR我将不胜感激readme的整洁文档。 谢谢。

嗨,大家好。 我在自述文件中添加了问答部分。 希望你们中的任何人都可以为此问题给我们一个PR

我会考虑在接下来的几天内做一个 PR :)

我还希望将此库专门用于订阅。 iOS 和安卓都可以。 记录确定用户是否具有有效订阅的正确方法将不胜感激。 😄

@curiousdustin Apple 在这方面的实现真的很糟糕,如果你指的是medium 。 由于您需要在后端处理此类事情,因此我们无法在我们的模块中完全支持这一点。 但是,您可以使用我们的方法getAvailablePurchases在 android 中获取availablePurchases该方法不适用于 ios 订阅产品。

那么您是说@marcosmartinez7@kevinEsherick在此线程中研究的解决方案不是解决方案,并且需要 Apple 以外的后端服务器来确认 iOS 订阅的状态?

通过阅读您与 Apple 文档一起发布的 Medium 文章,似乎首选使用服务器,但仅使用设备确定订阅状态并非不可能。

抱歉,我在写作中遗漏了一些信息。 我现在会处理这个。
Apple建议将receipt在您自己的服务器中以防止中间攻击。 因此,您可以稍后检查该收据以了解收据是否有效或订阅是否仍然可用。

然而,仍然尝试使用react-native-iap并非不可能,因为我们将验证收据的直接提取提供给自己的 Apple 服务器(沙箱和生产),幸好@marcosmartinez7@kevinEsherick解决了这个问题。

我认为目前这是检查ios订阅的解决方案。

嘿伙计们,抱歉已经超过几天了,我忙于工作,但我仍会提交该 PR 以更新自述文件。 @curiousdustin我上面写的方法肯定有效,我会将其添加到文档中。 我今晚或明天提交 PR :)

感谢更新。

我从你的例子中注意到的另一件事。

'收据数据':购买[purchases.length - 1].transactionReceipt,
...
const 过期 = 更新历史[renewalHistory.length - 1].expires_date_ms

在我的测试中,购买和续订历史不一定按任何特定顺序排列。 我们不需要对它们进行排序或其他东西来验证我们正在使用我们打算使用的吗?

同样在这里。 购买和续订历史肯定不是有序的,所以我们必须先排序。 在开发中,这最终会导致很多迭代。

这是我在 Android 和 iOS 上使用的功能,在 iOS 上正确排序以确保我们获得最新的收据数据:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

@andrewze

在 android 场景中,如果订阅是自动更新的,它将在 availablePurchases 上返回

@curiousdustin @andrewzey你说得对,没有订购商品。 然而,renewalHistory/latest_receipt_info 总是为我订购。 我记得我意识到购买不是订购的,但我认为我找到了一些原因,这实际上并不值得关注。 等我回家后,我会在几个小时内调查此事。 我不想提交 PR,直到我们弄清楚了这一点。
编辑:所以我认为您不需要对购买进行排序的原因是因为 expires_date_ms 返回相同的值,无论您查询了哪种购买。 这对你们不一样吗? 或者您是否需要一些需要排序的信息? 让我知道你的想法。 我确实同意文档仍应明确说明购买不是按时间顺序排列的,但据我所知,不需要排序来获得到期日期。

@kevinEsherick谢谢! 就我而言,既没有订购购买也没有订购续订历史。

@kevinEsherick @andrewzey你们能在这个解决方案上给出PR吗? 我们想与其他受苦的人分享。

当然,我会在我们的公共应用发布完成后立即准备它 =)。

我正在等待关于我上一条评论的更多对话。 我提出了一些尚未解决的问题。 请参阅上文以供参考。 一旦我们解决了这个问题,我们中的一个人就可以制作 PR。

@kevinEsherick啊对不起,我错过了。

expires_date_ms在每张续订收据上对我来说绝对是独一无二的。 或许我理解错了? 我注意到,无论选择哪个收据,附加到每个解码收据的续订收据数组都是相同的,因此我可以看到您可能是对的(从我的示例中)不需要:

const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );

但无论如何,我这样做是为了努力做到准确,并补偿 Apple 文档中实际 API 响应中可能存在的差异。 考虑到您可能会遇到自动续订订阅的收据总数,我认为排序操作不会非常昂贵。

PR 上有 ETA 吗? 我正在考虑从这个包中转移,因为它根本不处理这种情况。

@schmannd鉴于 PR 仅用于更新文档,您现在可以转到react-native-iap

我已经确认我的代码片段在使用我们最近推出的应用程序的生产中确实没有问题: https :

编辑我肯定会做公关(我在我们的 Trello 的“回馈 OSS”列表中有它),但在黑色星期五周末疯狂之后😄

@schmannd我支持@andrewzey所说的。 PR 会说的一切都包含在这篇文章中。 我一直想解决这个问题,但我们花了一段时间才弄清楚到底需要做什么,再加上旅行和忙碌的启动时间意味着我还没有时间公关它。 我仍然计划很快,但如果他们愿意,其他任何人都可以在此期间加强。 和@andrewzey 祝贺男人! 看起来不错,我要下载!

我不相信这个线程准确地记录了在 iOS 中管理自动续订订阅的正确方法,而是根据这个库公开的 API 描述了一个“解决方法”工作流程。 然而,这完全是我的推测,我很清楚我可能是错的,所以我想对我在下面概述的观察结果提供反馈。

数周以来,我一直在研究在 React Native 中管理自动更新订阅的正确方法,但无济于事,所以我换了个方向,开始研究如何在 Objective-C / Swift 中本地管理它们。 这为我提供了一个参考点,可以将本机 StoreKit API 和记录使用情况与该库中已实现的内容进行比较。

这篇文章是迄今为止我找到的关于原生 iOS 应用程序如何处理订阅购买、验证和恢复的最佳解释: https : react-native-iap ,但它们正在实现不同的工作流程。

我看到的一个主要区别是appStoreReceiptUrl

StoreKit 提供了appStoreReceiptUrl ,根据我的理解,这是加载 iOS 应用程序的当前购买/收据信息的正确方法。 虽然我看到react-native-iap代码中使用和引用了它,但它似乎与引用文章中记录的工作流处于不同的上下文中。

我不确定这种实现差异是否/如何有问题,但react-native-iap API 似乎对 StoreKit 的restoreCompletedTransactions产生了不必要的依赖,这最终被getPurchaseHistory调用

在我看来, react-native-iap API 应该支持以下 iOS 工作流程:

  • 尝试在应用程序启动时加载当前的应用程序收据(通过appStoreReceiptUrl
  • 提供验证收据的机会
  • 如果appStoreReceiptUrl为空,则在当前设备上启动“恢复”,然后设置appStoreReceiptUrl的值,并且可以重试验证逻辑

想法?

同样,我可能会误解此库的实现或引用的本机实现。

@sellmedog

我认为您链接的文章中也提到的一个关键点是:

注意:项目应用程序运行 SelfieService,它接受收据并将它们直接上传到 Apple 的收据验证服务。 这是一个“欺骗”,让教程专注于设置订阅。 在真正的应用程序中,您应该为此使用远程服务器——而不是应用程序。

我花了一些时间来弄清楚整个自动续订订阅的事情,但据我所知,处理它的最佳方法是使用您自己的后端作为唯一的事实来源:

  • 立即将所有交易数据发送到您的后端
  • 提供一些在传输出现问题时重新发送的方法(例如恢复购买)
  • 定期(例如每天一次)验证您后端存储的所有收据
  • 应用程序查询后端订阅状态,例如从后台返回。

在我看来,这似乎是处理订阅的唯一明智方法。 一段时间以来,我还与其他使用商业自动更新订阅的开发人员验证了这个过程。

所以我对 OP 问题的回答是:在后端而不是前端定期检查订阅

@舒曼德

我不确定我们是否理解或谈论同一个问题。

收据验证应在服务器上完成。 根据 Apple 自己的文档

不要从您的应用程序调用 App Store 服务器/verifyReceipt端点。

为简洁起见,我将引用文章中的“作弊”具体解释为示例应用程序直接从应用程序直接调用/verifyReceipt ,而不是委托给后端服务器。

在我看来,Apple/App Store 是而且应该是唯一的事实来源。 Apple 最终会处理购买、续订、取消等,并相应地生成收据,并通过StoreKit API 提供这些数据。

react-native-iap毫无问题地处理购买和恢复购买,但我仍然认为在访问 App Store 收据数据方面存在差距,该数据在appStoreReceiptUrl本地提供,这也是Apple 记录的访问收据的方式数据

为了取回收据数据,使用appStoreReceiptURL的方法NSBundle查找应用程序的收据...

我通过阅读 Apple 文档和引用原生 Object-C / Swift 实现所理解的工作流程如下:

  • 加载要购买的产品
  • 购买产品(消耗品、非消耗品、订阅,无所谓)
  • 该购买的收据由StoreKit在后台存储在本地appStoreReceiptUrl
  • 应用程序应根据需要将收据数据发送到后端服务器(而不是直接发送到 App Store /verifyReceipt )进行验证
  • 每次取消和/或续订时, appStoreReceiptUrl都会更新StoreKit (假设正确注册了正确的StoreKit代表)
  • appStoreReceiptUrl为空时,因为用户在新设备上等,使用恢复购买工作流程将从 Apple / AppStore 检索当前收据数据, StoreKit将在appStoreReceiptUrl本地保存此数据

同样, react-native-iap处理所有这些,除了从appStoreReceiptUrl加载收据数据。 我认为查询本地收据的getReceipt方法将减轻管理订阅和/或任何其他购买的负担。

苹果让这件事变得比它应该的更难推理。

每当取消和/或续订发生时, appStoreReceiptUrl会更新StoreKit (假设正确注册了正确的 StoreKit 委托)

这描述了我使用这种方法遇到的问题:如果用户之后没有通过有效的互联网连接打开您的应用程序,您将不会收到任何续订/取消/到期等通知。

除此之外,它似乎是我描述的方法的一个很好的替代方案。 我想知道哪一个最常用于生产应用程序。

@sellmedog我认为这与我在此处发布的想法相同:#356
Apple 建议,在本地验证收据是一种方法。 这完全独立于任何服务器/网络请求。

是的。 “没有网络连接 - 没有验证”有缺点。
但是有一些用例。
我希望在每次应用启动时快速验证收据,并且不想通过UserDefaultsKeychain自行跟踪应用内购买。 StoreKit已经提供了存储机制。

你应该如何检查 IOS 用户是否续订或取消了他们的订阅,而不提示他们通过 getAvailablePurchases 之类的东西登录到 iTunes 帐户?

例如:
首次购买订阅时,我们会验证收据并将到期日期保存到他们的 Firebase 配置文件中。 我们使用该 firebase 节点来检查启动时它们是否有活动订阅。 当到期日期已过并且新的计费周期开始时,问题就会出现。 我们如何检查它们是否自动续订或取消? 如前所述,我们使用了 getAvailablePurchases,但这会像恢复按钮一样提示 iTunes 登录——这是糟糕的用户界面

我也想知道同样的事情。

在我们的应用程序中,我们不需要这个,因为我们有一个服务器端 API,可以直接与 Apple 通信以检查状态。 原始购买后,收据数据存储在我们的服务器上,然后用于查询更新状态。

但是,我相信这在设备方面仍然是可能的。 我不是这方面的专家,但根据我的理解,只要发生续订,应用程序就会从操作系统收到更新的交易/收据。 我怀疑这个插件在应用程序启动时处理这些收据。 (我从 Xcode 运行时看到一堆日志,它们似乎正在处理多个事务)但是,据我所知,无法利用 JS 代码中的这些事件。

我们能得到更多关于这方面的信息吗?

@csumrell @curiousdustin你们在生产环境中确认过这种行为吗? 我想知道它是否可能只是因为沙箱而发生,沙箱帐户需要登录,因为您的设备上可能有另一个非沙箱帐户,而在生产中,用户可能会登录到他们的正常帐户,这模块可能能够在不提示登录的情况下获取购买。

抱歉,我还没有确认getAvailablePurchases()在生产中会导致密码提示。

我只是假设会这样,因为文档中谈到了这种方法用于通常称为恢复购买的功能,根据我的经验,这至少在 iOS 上涉及身份验证。

@hyochan @JJMoon我们能否也获得更多信息? 具体来说,有没有一种方法可以在不触发操作系统尝试身份验证的情况下可靠地获得可用或历史购买? 另请参阅我上面的问题 2 评论,关于对应用启动时检测到和处理的购买做出反应的能力。

@curiousdustin是的,所以当我退出我的普通 iTunes 帐户并进入沙箱帐户时,它不再提示我在调用此方法时登录。 所以我的猜测/希望是,在生产中,用户没有使用沙箱帐户,问题不应该出现。 Beta 测试应该以与生产相同的方式工作,因为用户没有在设备上设置沙箱帐户,这只是开发设备的问题。 所以我明天将与 beta 用户一起测试这个,让你知道我的发现。
编辑:我可以确认这发生在非沙盒设备上。 这是糟糕的用户体验,而且是相当有问题的。 任何人,任何想法?

因此,当您通过从您的应用程序调用 App Store 服务器 /verifyReceipt 端点在您自己的服务器上进行验证时,它将始终返回该用户的最新更新日期信息,仅包含原始的第一张购买收据。

有谁知道使用本地验证方法是否也将始终返回最新信息? 还是严格针对您在本地验证的收据?

另外,是否有人建议您设置自己的服务器以与 Apple 进行验证? 我没有这方面的经验

@csumrell您可以通过调用 getPurchaseHistory 并对其进行排序来获取最新购买的收据。 上面的评论详细说明了如何做到这一点。 这应该有最新的信息。

我即将探索在服务器上进行验证的选项,以便我们可以互相帮助。 很想听听其他人是怎么做的。

@kevinEsherick是的,但 getPurchaseHistory 将触发 Itunes 登录弹出窗口。 我说的是当用户第一次购买时将原始收据保存到本地存储。 然后不是调用 getPurchaseHistory 来检查下一个帐单,而是调用本地验证的 validateReceiptIos(originalReceipt)。 但是,我不确定 validateReceiptIos 是否返回像苹果服务器 /verifyReceipt 这样的最新交易

@csumrell 陷阱。 嗯,这是快速且容易测试的。 你没试过吗? 如果没有,我今天晚些时候会检查一下。
编辑: @csumrell validateReceiptIos 实际上会跟踪该购买的最新自动续订,因此这是一种有效的方法。 我会说这比上面提出的检查订阅的方法要好,因为它避免了 iTunes 登录提示(除非有人能想出解决办法)。 缺点是您必须在本地存储收据。 这应该不会太麻烦。 此外,出于安全原因或它们很大(约 60k 个字符)的事实,您可能不希望这些收据进入您的后端,因此这意味着如果订阅的用户退出或开始使用新设备,那么您需要提示他们登录以获取最新的购买收据。 您可以在内部存储他们的订阅状态,以便您只提示上次检查时订阅的用户。

@hyochan已为此问题提供了 20.00 美元的资金。 在 IssueHunt 上查看

@hyochan你资助的具体解决方案是什么? 我们在此线程中提供了一些,我认为我的最新评论提供了最强大的解决方案,但问题最少。 您是否资助某人将这些解决方案收集到 README 的简明 PR 中? 或者这是您仍然希望看到解决的实际代码问题吗?

@kevinEsherick

您是否资助某人将这些解决方案收集到 README 的简明 PR 中?

是的。 这是绝对正确的。 另外,我只是想测试一下issue hunt是如何开箱即用的。 我的下一个计划是资助制作测试用例。

@kevinEsherick你是对的,使用validateReceiptIos进行本地验证实际上只使用原始的第一个事务ID(存储在redux状态)返回有关订阅的最新信息

我将使用这种方法来避免必须设置我自己的服务器,并在检查订阅是否处于活动状态时避免 iTunes 登录弹出窗口。

对于那些考虑使用@csumrell和我已经列出的验证流程的人,请注意:我发现在开发中,iTunes 登录提示仍在首次启动时弹出。 这在生产中不会发生。 只要您按照我们列出的步骤进行操作,一切都应该没问题。 至于为什么这样做,也许当 iOS 看到 IAP 正在使用/是开发中的模块映射的一部分时,它会自动提示登录。我不太确定,但它似乎只是沙盒测试的一个功能。

您好,最近好像没有关于这个问题的活动。 问题已经解决了,还是仍然需要社区的关注? 如果没有进一步的活动发生,这个问题可能会被关闭。 您也可以将此问题标记为“供讨论”或“好的第一期”,我将保持开放。 感谢你的贡献。

在长时间不活动后关闭此问题。 如果此问题仍然存在于最新版本中,请随时创建具有最新信息的新问题。

也许整个流程需要在文档中澄清?

尤其是在自动续订订阅方面? 对于新人来说,您是否能够检查expires_date_ms以了解订阅是否仍然有效,这一点非常不清楚?

我建议最好的地方是在更完整的工作示例中?

人们会对此感兴趣吗? 如果我有时间,我可以做这个吗?

@alexpchin是的,这肯定会节省大量的开发时间,你的业力也会变得清晰:D

同意! 有人会对#856感兴趣吗?

你好,

@andrewzey的方法对我们很有用,直到昨天我们突然不再验证订阅的任何收据时 - 我们在调用 validateReceiptIos() 时从 iOS 收到一个 JSON 解析错误。 我认为没有其他人遇到过这个......? 自昨天以来所做的唯一更改是在 ITC 上添加了促销代码,我们已将其删除以帮助消除变量。 是否有任何原因导致收据无法通过 JSON 解析? 对于返回的每个收据,它都会失败 - 不仅仅是 sortedAvailablePurchases 索引 0 处的收据。 代码与andrewzey 的示例基本相同——我在validateReceiptIos 之后省略了所有内容,因为错误是在那里抛出的。

我们正在运行 RN 0.61.4、RNIap:4.4.2 和 iOS 13.3.1

我们尝试过:

  • 重新安装应用程序
  • 新用户帐户
  • 使用新的应用机密
  • 测试每个用户的所有收据 - 没有一个收据无法解析

我们想知道我们在购买沙箱时是否没有正确使用finishTransactionIOS?

真正奇怪的是,当我们使用这个在线工具验证收据时,一切看起来都很正常,我们可以看到所有的收据元数据。

  isSubscriptionActive = async () => {
      const availablePurchases = await RNIap.getAvailablePurchases();
      const sortedAvailablePurchases = availablePurchases.sort(
        (a, b) => b.transactionDate - a.transactionDate
      );
      const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;
      const isTestEnvironment = __DEV__;

      try {
        const decodedReceipt = await RNIap.validateReceiptIos(
          {
            'receipt-data': latestAvailableReceipt,
            password: Config.IN_APP_PURCHASE_SECRET,
          },
          isTestEnvironment
        );
        console.log('response!', decodedReceipt)
      } catch (error) {
        console.warn('Error validating receipt', error) // JSON PARSE ERROR HERE
      }
...

关于@andrewzey放置的代码的问题。 Date.now()不会是当前设备时间,用户可能会更改它并无限订阅吗? 🤔

此外,是否不鼓励从应用程序本身验证收据?

@doteric你能指出我在哪里提到他们不鼓励吗? 我知道您可以设置服务器端通知,但从我的角度来看,在客户端而不是服务器上处理此验证似乎要容易得多。
我正在努力从苹果或其他可靠的本地资源中找到正确的文档。

@doteric是 Date.now() 是设备时间,因此可以解决。 然而,用户这样做的机会微乎其微,甚至可以使用其他方法来解决无休止的订阅。 例如,如果使用简单的服务器端验证,他们可以在打开应用程序之前进入飞行模式,以防止应用程序意识到订阅已过期。 当然,您还可以采取其他保护措施,但我的观点是,使用 Date.now() 的客户端肯定可以正常工作。 @captaincole我手头没有文档,但我可以确认我也读过不鼓励客户端验证。 我在我相信的 Apple 文档中阅读了它。 话虽如此,我认为客户端完成了工作。

你好,

我正在尝试使用 validateReceiptIos 和 latest_receipt_data (将 expires_date_ms 与实际日期进行比较)中讨论的方法之一。 它在 Xcode 中开发时效果很好。 但是,当我在 Testflight 中对其进行测试时,它不再起作用。 它很难调试,所以我无法确定确切的问题,它似乎没有获取收据数据。 有没有人对 testflight 有类似的问题?

提前致谢!

你好,

我正在尝试使用 validateReceiptIos 和 latest_receipt_data (将 expires_date_ms 与实际日期进行比较)中讨论的方法之一。 它在 Xcode 中开发时效果很好。 但是,当我在 Testflight 中对其进行测试时,它不再起作用。 它很难调试,所以我无法确定确切的问题,它似乎没有获取收据数据。 有没有人对 testflight 有类似的问题?

提前致谢!

+1

@asobralr @Somnus007我可能错了,但我认为您不能使用 testflight 测试 IAP,因为用户无法通过 testflight 进行购买。 Apple 没有提供很好的机会在类似生产的环境中测试购买

@asobralr @Somnus007我可能错了,但我认为您不能使用 testflight 测试 IAP,因为用户无法通过 testflight 进行购买。 Apple 没有提供很好的机会在类似生产的环境中测试购买

正确的。 你甚至不能在沙箱中模拟失败场景,这是一种耻辱。 😞

@kevinEsherick ,感谢您的回复。 你可能错了。 用户可以通过tesflight进行购买。 在 testflight 上,似乎用户必须登录一个与真实苹果帐户具有相同电子邮件地址的沙盒帐户。 然后一切正常。 顺便说一句, getPurchaseHistory会在生产

看起来 ios 14 打破了这个解决方案,因为在调用 requestSubscription() 之前调用 getAvailablePurchases() 时会出现问题。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

rossbulat picture rossbulat  ·  50评论

Swyanmitra picture Swyanmitra  ·  38评论

mtakac picture mtakac  ·  31评论

umutpiri picture umutpiri  ·  45评论

the-dut picture the-dut  ·  57评论