【问题标题】:How to check if subscription is active in iOS on app restart or reinstall如何在应用重启或重新安装时检查订阅是否在 iOS 中处于活动状态
【发布时间】:2020-12-01 08:58:42
【问题描述】:

我有一个带有订阅(非续订)的 iOS 应用(目标 C)。 当用户重新启动应用时,如何检查它是否仍然处于活动状态?

我已经阅读了很多关于此的内容,但答案似乎不清楚如何正确地做到这一点。

我目前拥有的是当应用程序启动时我注册了 TransactionObserver,

IAPManager* iapManager = [[IAPManager alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver: iapManager];

那么当用户购买我拥有的商品时,

- (void) paymentQueue: (SKPaymentQueue *)queue updatedTransactions: (NSArray *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                [self showTransactionAsInProgress:transaction deferred:NO];
                break;
            case SKPaymentTransactionStateDeferred:
                [self showTransactionAsInProgress:transaction deferred:YES];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                [queue finishTransaction: transaction];
                break;
            case SKPaymentTransactionStatePurchased:
                [self persistPurchase: transaction];
                [queue finishTransaction: transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                [queue finishTransaction: transaction];stopBusy];
                break;
            default:
                break;
        }
    }
}

因此,当用户首次订阅时,这可以正常工作。 但我对您应该如何存储/跟踪此购买感到困惑。我存储在用户订阅的静态变量中以启用应用程序功能,但是当用户重新启动应用程序时,我应该如何检查他们是否有活动订阅?

我使用的代码是将订阅存储在 iCloud 或 NSUserDefaults 中。 存储在 iCloud 中根本不起作用,当他们重新启动应用程序时,他们失去了订阅。存储在 NSUserDefaults 中有效,但订阅最终会过期,并且可以退款或取消。如何检查它是否处于活动状态?我可以存储订阅日期并假设持续时间并尝试检查自己,但这似乎非常错误。

如果用户卸载应用程序并重新安装,或者得到一部新手机/等,该怎么办。

- (void) persistPurchase: (SKPaymentTransaction*) transaction {
#if USE_ICLOUD_STORAGE
    NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
    NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
    if ([transaction.payment.productIdentifier isEqualToString: SUBSCRIPTION]) {
        [storage setBool: true forKey: SUBSCRIPTION];
        [IAPManager upgrade];
    }
    [storage synchronize];
    [self unlockPurchase: transaction];
}

为此,我认为我需要调用 restoreCompletedTransactions。我认为不将订阅存储在 NSUserDefaults 中,而是在每次应用启动时调用 restoreCompletedTransactions 是有意义的。

但从我读到的内容来看,Apple 似乎希望您有一个“恢复购买”按钮来执行此操作?每次应用程序启动时调用它是否有意义? 这将在您处理过的每笔付款(我认为??)中调用这些付款中的每笔付款如何知道它们是否仍然有效?

我可以获得交易日期,但这并不能告诉我付款是否过期,除非我假设它没有被取消并假设持续时间并自己检查日期?还是 Apple 只为您提供来自 restoreCompletedTransactions 的有效订阅/付款?

我还需要再次调用 finishTransaction 进行恢复吗?

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

【问题讨论】:

  • Sebastians 和我的回答都不正确有什么原因吗?这两个答案都直接来自 Apple 文档。

标签: ios objective-c in-app-purchase in-app-subscription


【解决方案1】:

有一个用于应用内购买的轻量级 iOS 库,称为 RMStore。 RMStore 支持事务持久化并提供参考实现。您可以在下面的链接中查看更多详细信息。

https://github.com/robotmedia/RMStore

【讨论】:

    【解决方案2】:

    如何在用户重新启动时检查它是否仍然处于活动状态 应用?

    https://developer.apple.com/documentation/storekit/in-app_purchase/persisting_a_purchase

    对于非续订订阅,请使用 iCloud 或您自己的服务器来保留 永久记录。

    我认为最好使用服务器(这样您可以收到退款通知),而不是 iCloud(您不会收到通知)。

    我猜你的意思是你想检查一下:

    • 不可续订的订阅未退款
    • 订阅尚未过期(订阅期尚未结束)。

    我假设您使用收据。这很重要,因为如果用户选择在到期前续订非续订订阅,您将需要它来计算订阅的持续时间。

    因此,当用户首次订阅时,这可以正常工作。但我很困惑 关于您应该如何存储/跟踪此购买。我存储在静态 用户订阅以启用应用程序功能的变量,但是 当用户重新启动应用程序时,我应该如何检查他们是否有 有效订阅?

    在我看来,正确的方法是检查存储在您服务器上的记录。这是因为,Apple 会向您在服务器上配置的 URL 发送退款通知。

    此链接说明了您的服务器将如何收到退款通知。 https://developer.apple.com/documentation/storekit/in-app_purchase/handling_refund_notifications

    由于苹果不会直接通知您的应用退款,您必须与您的服务器核实是否已发出退款。您与服务器的通信方式取决于您(定期轮询、推送通知等)。关键是,您应该在服务器上存储一条记录,并告知您的应用程序已退款,以便您决定是否撤销该功能。 由于您需要在服务器上存储记录以检查退款,因此您不妨询问自己的服务器订阅是否已过期。为此,您只需在您的应用程序(NSUserDefaults 等)中存储一个标识符,以便您可以询问您的服务器是否已过期。

    这比简单地将订阅的结束日期存储在您的应用中要好,因为如果您以不安全的方式存储结束日期,用户可以简单地编辑文件并继续延长结束日期。苹果在以下link 中声明:

    存储收据需要更多的应用程序逻辑,但会阻止 永久记录不被篡改。 由于您需要实现服务器来检查退款,因此使用服务器来检查订阅是否已过期是很简单的。

    存储在 NSUserDefaults 中有效,但订阅最终会 已过期,可以退款或取消。如何检查是否是 积极的?我可以存储订阅日期并假设持续时间 并尝试检查自己,但这似乎非常错误。

    .....

    是的,我认为将订阅日期存储在您的设备上并不是最好的方法,原因如下:

    1. 用户可以更改其设备上的时间来欺骗您的应用,并继续使用您的订阅。完成后,他可以将时间重置为正确的时间。
    2. 如果您以不安全的方式存储订阅到期日期并且他的手机已越狱,他可以自己编辑日期。

    正确的方法是查询你的服务器。这样他就不能在他的设备上编辑时间来欺骗你的应用程序延长他的订阅。如果您不安全地存储订阅日期,他也无法“编辑”订阅日期。该应用程序应该简单地向您的服务器发送一个标识符,并接收关于订阅是否处于活动状态的是或否响应。以下link 描述了如何执行此操作:

    将收据副本连同凭据或 标识符,以便您可以跟踪哪些收据属于 特定用户。例如,让用户向您的 带有用户名和密码的服务器。不要使用 identifierForVendor UIDevice 的属性。不同的设备对此有不同的价值 财产,所以你不能用它来识别和恢复购买 由同一用户在不同设备上进行。

    ..

    为此,我认为我需要调用 restoreCompletedTransactions。我想 不将订阅存储在 NSUserDefaults 中是有意义的, 而是在每次应用程序时调用 restoreCompletedTransactions 开始。

    但是从我读到的苹果似乎希望你有一个“恢复 购买”按钮,这样做是否有意义? 每次应用程序启动时?这将调用每个回调 您曾经为这些付款中的每一项处理过的付款(我认为??) 知道它们是否仍然活跃?

    不,每次启动应用时都调用恢复购买不是一个好主意。Apple 明确表示不要:

    https://developer.apple.com/documentation/storekit/in-app_purchase/restoring_purchased_products

    重要不要自动恢复购买,尤其是当您的 应用程序启动。恢复用户 App Store 的购买提示 凭据,这会中断您的应用程序的流程。

    它还说:

    但是,在给定的情况下,应用可能需要替代方法 情况:... 您的应用使用非续订订阅 - 您的应用负责 恢复过程。

    在我看来,每次应用启动时都没有必要恢复购买 - 恢复购买的目的是为了在新设备上重新安装/安装。它也不会告诉您订阅是否已退款或仍然有效,因为如果退款已发出,Apple 只会通知您的服务器 URL。 每次应用启动时你应该做的是查询你的服务器。 Apple 本身表示要在您的服务器上更新用户帐户的余额,以识别退款滥用情况。

    减少滥用退款并通过以下方式识别重复退款的购买 将退款通知映射到服务器上的玩家帐户。 监控和分析您的数据以识别可疑的退款活动。 如果您跨多个平台提供内容,请保持平衡 在您的服务器上更新的用户帐户。使用 App Store 服务器 通知以获取近乎实时的状态更新 影响您的客户的交易。

    您应该实现“恢复购买”按钮,以处理用户卸载/重新安装应用程序/在不同设备上安装的情况。当用户恢复购买时,您应该能够计算出您需要与服务器交谈以检查订阅是否仍然有效的标识符(您可能只使用原始事务 ID) 您的收据将包含原始购买日期和产品 ID,并且每次购买非续订订阅时都会更新。您可以通过调用刷新收据 (SKReceiptRefreshRequest) 来访问此数据,以便任何设备都有收据,并且您可以计算订阅期。

    我可以获得交易日期,但这并不能告诉我是否 付款是否过期,除非我认为它没有被取消并且 假设持续时间并自己检查日期?还是苹果只给 您通过 restoreCompletedTransactions 进行了有效订阅/付款?

    当您实现订阅行为时,首次启动订阅时,您应该将到期日期存储在您的服务器上。如前所述,Apple 会将退款通知发送到您的服务器,因此您应该让您的应用程序与您的服务器检查订阅是否已退款或过期。还请记住 - 用户可以更改他的设备时间/日期以绕过您在设备上存储到期日期。但是如果你用你自己的服务器检查他就不能,因为他不能篡改你服务器上的时间。

    我还需要再次调用 finishTransaction 进行恢复吗?

    我使用 swift,在 swift 中您只需调用 restoreCompletedTransactions()。很容易实现。

    见:How to restore in-app purchases correctly?

    此时,您可能想知道,如果收据包含每次非续订订阅购买的记录,那我为什么需要服务器来检查订阅是否有效?

    • 因为(对于非续订订阅)退款通知只会发送到您的服务器。收据上未提及。
    • 您可以使用收据来计算订阅的结束日期,但它不会告诉您是否退款。
    • 您仍想与您的服务器核对,以便用户无法简单地更改此设备上的时间/日期来绕过您的订阅期限。

    记住:

    每次您的用户购买非续订订阅时,您都应确保服务器上的逻辑计算正确的结束日期。例如,如果他选择在当前订阅结束之前购买非续订订阅,您应该正确计算结束日期,让下一个订阅仅在当前订阅结束时开始。

    【讨论】:

    • 感谢您的详细回复,但如果您的应用没有服务器?当 Apple 已经拥有所有这些信息时,您需要自己的服务器来完成所有这些工作似乎很奇怪。对于 Android,您只需查询 Google Play 即可查看订阅状态,很奇怪 Apple 不提供此基本功能??
    • @James 看起来就是这样——我的回答是基于我在 Apple 网站上阅读的信息。但是在 AWS 之类的东西上实现服务器,以及使用 lambda 进行繁重的工作并不是什么大不了的事。如果您真的觉得一定有更好的方法,只需使用 Apple 的技术支持事件来验证这一点。
    • @James 如果您不使用服务器,您将无法了解退款情况。此外,很难确保用户不会更改手机上的时间/日期,假装他的订阅处于活动状态。最后,我要说的是,考虑一下实现服务器的成本 - 如果它少于您这样做/节省的钱,那么您应该实现服务器。否则不要打扰。
    【解决方案3】:

    The documentation explicitly states 表示要坚持购买非续订订阅,“使用 iCloud 或您自己的服务器来保持持久记录”。因此,基本上 Apple 并没有提供内置的方法来跟踪用户/安装的应用购买情况,您必须自己动手。

    如您所见,在设备上存储(例如使用 NUserDefaults)并不是很有用,因为同一用户在同一设备或不同设备上重新安装后,它无法生存。

    如果您已经拥有可以存储和检索用户应用内购买数据的身份提供者后端,您可能想要使用它。对于每次成功购买,将 SKU、到期日期和收据与用户相关联(此时您的服务器应检查收据是否有效以避免欺诈)。当您的应用启动或恢复时,通过您的后端进行身份验证并检索用户应有权访问的 SKU,并在应用内交付内容。

    如果您没有身份提供者后端,您可能需要使用iCloud key-value store。然后,您可以将应用内购买关联到 Apple ID,而不是每次安装。请注意,使用 iCloud 有一些副作用,例如 you may not be able to ever transfer your app to another organisation。

    【讨论】:

    • 我尝试使用 iCloud,但它不起作用,上面的代码“NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];”,用户报告应用程序不记得购买,我的猜测是用户是否有多个帐户这可能会返回错误的
    • 返回的 NSUbiquitousKeyValueStore 与设备上登录的 Apple ID(设置/iCloud)相关联,因此那里只能有一个帐户,即用于进行应用内购买的帐户。除非用户使用多个帐户登录和注销 - 在这种情况下,您可能会争辩说购买的用户与使用该应用程序的用户不同。请注意,如果您使用的是 iCloud KVS,则需要在启动应用程序时尽早注册以进行更改 (developer.apple.com/documentation/foundation/…)
    猜你喜欢
    • 2020-07-10
    • 2020-07-26
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 2020-09-29
    • 1970-01-01
    • 1970-01-01
    • 2018-01-24
    相关资源
    最近更新 更多